Conversation
📝 WalkthroughWalkthrough이 변경사항은 홈 화면에 새로운 '찌르기' 기능을 구현합니다. 네트워크 계층에서 PokeService, 데이터 계층에서 PokeRepository를 추가하고, 도메인 계층에 PokeResult 모델을 정의했습니다. HomeViewModel에서 찌르기 의도를 처리하며, HomeScreen과 하위 컴포넌트들은 onPokeGoal 콜백을 전파합니다. UI에는 GoalVerifications 컴포넌트에서 파트너 상태에 찌르기 버튼을 추가했고, 아이콘 및 문자열 리소스도 업데이트했습니다. Estimated code review effort🎯 4 (Complex) | ⏱️ ~45분 상세 평가 및 개선 제안✅ 잘 구조화된 계층 분리네트워크 → 데이터 → 도메인 → 기능 계층에 걸쳐 일관된 패턴으로 구현되었습니다. 특히 PokeService, DefaultPokeRepository, PokeResult 간의 책임 분리가 명확하며, 의존성 주입도 적절히 구성되었습니다. 🔍 검토 포인트1. 파트너 문자열 리소스 변경
2. AppRoundButton 테두리 두께 변경
3. GoalVerifications 컴포넌트의 UI 추가
4. 에러 처리 일관성
5. 아이콘 리소스 업데이트
💬 토론이 필요한 부분
🎯 개선 제안// HomeViewModel에서 - 로딩 상태 추적
private fun pokeGoal(goalId: Long) {
launchResult(
block = { pokeRepository.pokeGoal(goalId) },
onSuccess = { result ->
_sideEffect.emit(HomeSideEffect.ShowPokeToast(result.message))
// 버튼 상태 업데이트: 사용자 입력 방지
},
onError = { error ->
// 에러 타입에 따른 세분화된 메시지
val message = when (error) {
is NetworkException -> "네트워크 연결을 확인해주세요"
else -> "찌르기에 실패했습니다"
}
_sideEffect.emit(HomeSideEffect.ShowErrorToast(message))
}
)
}전반적으로 기능 구현이 깔끔하고 계층 분리가 잘 되어 있습니다. 위의 검토 포인트들을 함께 논의하면 더욱 견고한 구현이 될 것으로 예상됩니다. 좋은 작업입니다! 🎉 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
feature/main/src/main/java/com/twix/home/HomeScreen.kt (1)
159-174:⚠️ Potential issue | 🟡 Minor
@PreviewComposable이 제공되지 않았습니다.
HomeScreen과GoalList는 공개된 Composable 함수임에도 불구하고@Preview가 없습니다.onPokeGoal파라미터가 새롭게 추가된 만큼, 이번 기회에 프리뷰를 함께 추가하면 디자인 검증과 협업에 도움이 됩니다.// 예시 `@Preview`(showBackground = true) `@Composable` private fun HomeScreenPreview() { HomeScreen( uiState = HomeUiState(/* ... */), // ... onPokeGoal = {}, ) }As per coding guidelines: "Preview Composable이 제공되는가?"
Also applies to: 237-244
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@feature/main/src/main/java/com/twix/home/HomeScreen.kt` around lines 159 - 174, Add `@Preview` Composable wrappers for the public composables HomeScreen and GoalList so design can be validated; create private preview functions (e.g., HomeScreenPreview and GoalListPreview) that call HomeScreen and GoalList with a representative HomeUiState and stub lambda implementations (including the new onPokeGoal: (Long) -> Unit) to satisfy all parameters and use `@Preview`(showBackground = true) and `@Composable` annotations; ensure these preview functions are in the same file and construct minimal dummy data for any models referenced by HomeUiState and GoalList so previews render without external dependencies.
🧹 Nitpick comments (4)
feature/main/src/main/java/com/twix/home/HomeViewModel.kt (1)
127-133:pokeGoal구현 — 전반적으로 깔끔합니다! 한 가지 확인 포인트
launchResult의 람다 시그니처에 맞게onSuccess에서tryEmitSideEffect(non-suspend),onError에서emitSideEffect(suspend)를 올바르게 사용하고 있습니다. 기존 패턴인fetchGoalList의onError도emitSideEffect를 사용하므로 일관성 있습니다.다만
onSuccess에서it.message(서버에서 내려온 원시 문자열)를ShowPokeToast에 그대로 전달하는 부분은HomeSideEffect.kt의ShowPokeToast설계와 함께 검토가 필요합니다 (해당 파일 코멘트 참조).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@feature/main/src/main/java/com/twix/home/HomeViewModel.kt` around lines 127 - 133, pokeGoal currently forwards the raw server string (it.message) into HomeSideEffect.ShowPokeToast, which may not match the expected payload type in HomeSideEffect.kt; inspect the ShowPokeToast constructor/type and change this call in pokeGoal to supply the correct type (e.g., a `@StringRes` resource id like R.string.toast_poke_goal_success, or wrap the server text in the expected ToastMessage/UiText type), provide a safe fallback when the server message is null/empty, and keep using tryEmitSideEffect in onSuccess while preserving pokeRepository.pokeGoal as the block.feature/main/src/main/java/com/twix/home/component/GoalVerifications.kt (1)
39-39:onPokeGoal파라미터에 기본값 제공을 고려해 보세요다른 콜백 파라미터(
onMyClick,onPartnerClick)는null기본값이 있는 반면,onPokeGoal은 기본값이 없어 모든 호출부에서 반드시 제공해야 합니다. 현재는 문제가 되지 않지만,GoalVerifications의 재사용성 측면에서 기본값(= {})을 제공하면 호출부의 유연성이 높아집니다.♻️ 기본값 추가 제안
- onPokeGoal: () -> Unit, + onPokeGoal: () -> Unit = {},🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@feature/main/src/main/java/com/twix/home/component/GoalVerifications.kt` at line 39, GoalVerifications의 onPokeGoal 콜백에 기본값이 없어 호출부마다 항상 전달해야 하는데, onMyClick/onPartnerClick처럼 재사용성을 위해 기본값을 추가하세요; GoalVerifications 선언에서 onPokeGoal: () -> Unit을 onPokeGoal: () -> Unit = {}로 변경하여 기본 빈 람다를 제공하면 호출부에서 생략 가능하게 됩니다.feature/main/src/main/java/com/twix/home/HomeScreen.kt (2)
288-325:onPokeGoal람다가 매 리컴포지션마다 새로 생성될 수 있습니다.Line 320의
onPokeGoal = { onPokeGoal(goal.goalId) }람다는GoalList가 리컴포지션될 때마다 새 객체로 생성됩니다.GoalVerifications가 안정적인 파라미터를 기대한다면 불필요한 리컴포지션이 발생할 수 있습니다.
rememberUpdatedState를 사용하거나, 이미key = { it.goalId }가 적용된items블록 내에서remember로 람다를 안정화하는 방법을 고려해볼 수 있습니다:♻️ 리컴포지션 최적화 제안 예시
items(goals, key = { it.goalId }) { goal -> + val goalId = goal.goalId + val onPokeGoalStable = remember(goalId) { { onPokeGoal(goalId) } } GoalCardFrame( ... content = { if (goal.myVerification != null || goal.partnerVerification != null) { GoalVerifications( ... - onPokeGoal = { onPokeGoal(goal.goalId) }, + onPokeGoal = onPokeGoalStable, ) } }, ) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@feature/main/src/main/java/com/twix/home/HomeScreen.kt` around lines 288 - 325, The inline lambda passed as onPokeGoal to GoalVerifications (onPokeGoal = { onPokeGoal(goal.goalId) }) is recreated on every recomposition of GoalList; stabilize it inside the items(key = { it.goalId }) block by creating a remembered callback bound to the goal id (use remember or rememberUpdatedState) and pass that remembered callback into GoalVerifications so the parameter becomes stable and avoids unnecessary recompositions of GoalVerifications.
114-136: MVI 단방향 데이터 흐름이 깔끔하게 유지되고 있습니다! 👍
onPokeGoal → dispatch(HomeIntent.PokeGoal) → ViewModel → HomeSideEffect.ShowPokeToast → UI흐름이 MVI 패턴에 맞게 올바르게 구성되어 있습니다.다만, 기존
ShowToast와 새로 추가된ShowPokeToast의 토스트 표시 방식이 서로 다른 점이 눈에 띕니다:
ShowToast(line 121):toastManager.tryShow()사용 (non-suspending)ShowPokeToast(line 130):toastManager.show()사용 (suspending)
ObserveAsEvents내부는LaunchedEffect코루틴 컨텍스트이므로show()호출 자체는 문제 없습니다. 그러나 두 케이스에서show/tryShow를 다르게 사용하는 이유가 명확하지 않습니다. 찌르기 토스트는 순차적 표시가 보장되어야 해서 의도적으로show()를 선택하신 건가요? 만약 의도가 동일하다면, 일관성을 위해 같은 메서드를 사용하는 것이 좋을 것 같습니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@feature/main/src/main/java/com/twix/home/HomeScreen.kt` around lines 114 - 136, The side-effect handler in ObserveAsEvents mixes toastManager.tryShow(...) for HomeSideEffect.ShowToast and the suspending toastManager.show(...) for HomeSideEffect.ShowPokeToast, creating an inconsistency; decide whether poke toasts must be shown sequentially (use the suspending show()) or can be best-effort (use tryShow()), then update the handler so both ShowToast and ShowPokeToast call the same API (modify the branches handling HomeSideEffect.ShowToast and is HomeSideEffect.ShowPokeToast in HomeScreen.kt), keeping the ShowPokeToast payload mapping (message/ToastType.SUCCESS) and noting that ObserveAsEvents runs in a LaunchedEffect so calling the suspending show() is safe if you choose sequential behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@core/design-system/src/main/res/drawable/ic_goal_action_poke.xml`:
- Around line 1-139: This drawable (ic_goal_action_poke.xml) hardcodes colors
(`#E8E8E8`, `#F9F9F9`, `#171717`, etc.) which breaks the design-system dark-mode
requirement; create a centralized colors.xml in values defining semantic names
(e.g., icon_stroke, icon_bg_light, icon_fill_wrap) and update
ic_goal_action_poke.xml to use `@color/icon_stroke`, `@color/icon_bg_light`,
`@color/icon_fill_wrap` instead of hex literals; add a values-night/colors.xml
with dark variants for those same semantic names so drawables switch
automatically; apply the same replacement pattern to the other design-system
drawables in a single bulk refactor tracked as a dedicated ticket.
In `@feature/main/src/main/java/com/twix/home/component/GoalVerifications.kt`:
- Around line 93-101: The AppRoundButton's touch target is only 28.dp high
(Modifier.size(width = 64.dp, height = 28.dp)), below the 48dp accessibility
minimum; update the modifier used by AppRoundButton (or the composable wrapping
it) to keep the visual 28.dp size but expand the clickable area to at least 48dp
tall — e.g., replace or augment Modifier.size(...) with a combination that
preserves visual size and increases hit area (use Modifier.size(width = 64.dp,
height = 28.dp).then(Modifier.minimumTouchTargetSize()) if available, or wrap
the AppRoundButton in a Box and add symmetric vertical padding of (48.dp -
28.dp)/2 to the clickable modifier that calls noRippleClickable { onClick() })
so the UI looks unchanged but the touch target meets the 48dp requirement.
In `@feature/main/src/main/java/com/twix/home/HomeSideEffect.kt`:
- Around line 15-17: ShowPokeToast currently exposes raw server strings to the
UI (data class ShowPokeToast(val message: String) : HomeSideEffect), which
breaks i18n and makes UI text maintenance brittle; change the side effect to use
localized string resources like ShowToast (e.g., replace or overload
ShowPokeToast to carry an `@StringRes` resId: Int or a sealed variant that prefers
resId) or, if server text must be shown, make message nullable/optional and add
a fallback to a local string resource when message is blank/empty; update places
that emit ShowPokeToast and the consumer that renders HomeSideEffect to handle
the new variant or fallback logic.
---
Outside diff comments:
In `@feature/main/src/main/java/com/twix/home/HomeScreen.kt`:
- Around line 159-174: Add `@Preview` Composable wrappers for the public
composables HomeScreen and GoalList so design can be validated; create private
preview functions (e.g., HomeScreenPreview and GoalListPreview) that call
HomeScreen and GoalList with a representative HomeUiState and stub lambda
implementations (including the new onPokeGoal: (Long) -> Unit) to satisfy all
parameters and use `@Preview`(showBackground = true) and `@Composable` annotations;
ensure these preview functions are in the same file and construct minimal dummy
data for any models referenced by HomeUiState and GoalList so previews render
without external dependencies.
---
Nitpick comments:
In `@feature/main/src/main/java/com/twix/home/component/GoalVerifications.kt`:
- Line 39: GoalVerifications의 onPokeGoal 콜백에 기본값이 없어 호출부마다 항상 전달해야 하는데,
onMyClick/onPartnerClick처럼 재사용성을 위해 기본값을 추가하세요; GoalVerifications 선언에서
onPokeGoal: () -> Unit을 onPokeGoal: () -> Unit = {}로 변경하여 기본 빈 람다를 제공하면 호출부에서 생략
가능하게 됩니다.
In `@feature/main/src/main/java/com/twix/home/HomeScreen.kt`:
- Around line 288-325: The inline lambda passed as onPokeGoal to
GoalVerifications (onPokeGoal = { onPokeGoal(goal.goalId) }) is recreated on
every recomposition of GoalList; stabilize it inside the items(key = { it.goalId
}) block by creating a remembered callback bound to the goal id (use remember or
rememberUpdatedState) and pass that remembered callback into GoalVerifications
so the parameter becomes stable and avoids unnecessary recompositions of
GoalVerifications.
- Around line 114-136: The side-effect handler in ObserveAsEvents mixes
toastManager.tryShow(...) for HomeSideEffect.ShowToast and the suspending
toastManager.show(...) for HomeSideEffect.ShowPokeToast, creating an
inconsistency; decide whether poke toasts must be shown sequentially (use the
suspending show()) or can be best-effort (use tryShow()), then update the
handler so both ShowToast and ShowPokeToast call the same API (modify the
branches handling HomeSideEffect.ShowToast and is HomeSideEffect.ShowPokeToast
in HomeScreen.kt), keeping the ShowPokeToast payload mapping
(message/ToastType.SUCCESS) and noting that ObserveAsEvents runs in a
LaunchedEffect so calling the suspending show() is safe if you choose sequential
behavior.
In `@feature/main/src/main/java/com/twix/home/HomeViewModel.kt`:
- Around line 127-133: pokeGoal currently forwards the raw server string
(it.message) into HomeSideEffect.ShowPokeToast, which may not match the expected
payload type in HomeSideEffect.kt; inspect the ShowPokeToast constructor/type
and change this call in pokeGoal to supply the correct type (e.g., a `@StringRes`
resource id like R.string.toast_poke_goal_success, or wrap the server text in
the expected ToastMessage/UiText type), provide a safe fallback when the server
message is null/empty, and keep using tryEmitSideEffect in onSuccess while
preserving pokeRepository.pokeGoal as the block.
ℹ️ Review info
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (17)
core/design-system/src/main/java/com/twix/designsystem/components/button/AppRoundButton.ktcore/design-system/src/main/res/drawable/ic_goal_action_poke.xmlcore/design-system/src/main/res/values/strings.xmlcore/network/src/main/java/com/twix/network/di/ApiServiceModule.ktcore/network/src/main/java/com/twix/network/model/response/poke/mapper/PokeMapper.ktcore/network/src/main/java/com/twix/network/model/response/poke/model/PokeResponse.ktcore/network/src/main/java/com/twix/network/service/PokeService.ktdata/src/main/java/com/twix/data/di/RepositoryModule.ktdata/src/main/java/com/twix/data/repository/DefaultPokeRepository.ktdomain/src/main/java/com/twix/domain/model/poke/PokeResult.ktdomain/src/main/java/com/twix/domain/repository/PokeRepository.ktfeature/main/src/main/java/com/twix/home/HomeIntent.ktfeature/main/src/main/java/com/twix/home/HomeScreen.ktfeature/main/src/main/java/com/twix/home/HomeSideEffect.ktfeature/main/src/main/java/com/twix/home/HomeViewModel.ktfeature/main/src/main/java/com/twix/home/component/GoalVerifications.ktfeature/task-certification/src/main/java/com/twix/task_certification/detail/component/TaskCertificationCardContent.kt
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
| android:width="79dp" | ||
| android:height="52dp" | ||
| android:viewportWidth="79" | ||
| android:viewportHeight="52"> | ||
| <path | ||
| android:pathData="M52.537,51.999C40.298,51.999 30.375,50.053 30.375,47.653C30.375,45.252 40.298,43.307 52.537,43.307C64.777,43.307 74.699,45.252 74.699,47.653C74.699,50.053 64.777,51.999 52.537,51.999Z" | ||
| android:fillColor="#C6C6C6"/> | ||
| android:width="86dp" | ||
| android:height="54dp" | ||
| android:viewportWidth="86" | ||
| android:viewportHeight="54"> | ||
| <path | ||
| android:strokeWidth="1" | ||
| android:pathData="M17.411,19.017C17.411,19.017 12.738,14.69 13.846,11.821C14.183,10.962 14.887,10.305 15.715,9.928L17.17,9.272C17.17,9.272 15.975,6.857 18.355,5.785C20.735,4.713 21.815,11.116 21.815,11.116C21.815,11.116 24.156,9.977 24.927,11.677C25.698,13.377 21.949,15.974 21.949,15.974L17.411,19.007V19.017Z" | ||
| android:pathData="M18.463,16.385C18.463,16.385 13.5,11.779 14.676,8.727C15.034,7.813 15.782,7.113 16.662,6.713L18.208,6.014C18.208,6.014 16.938,3.444 19.466,2.301C21.994,1.16 23.141,7.975 23.141,7.975C23.141,7.975 25.628,6.763 26.447,8.571C27.265,10.379 23.285,13.146 23.285,13.146L18.466,16.374V16.385H18.463Z" | ||
| android:strokeLineJoin="round" | ||
| android:fillColor="#ffffff" | ||
| android:strokeColor="#171717" | ||
| android:strokeLineCap="round"/> | ||
| <path | ||
| android:strokeWidth="1" | ||
| android:pathData="M27.11,38.079L17.992,17.801L21.137,16.38L30.255,36.659L27.11,38.079Z" | ||
| android:pathData="M28.414,35.896L19.431,15.873C19.237,15.444 19.431,14.935 19.859,14.741L21.642,13.933C22.073,13.739 22.578,13.933 22.771,14.362L31.755,34.385C31.949,34.815 31.755,35.323 31.327,35.517L29.544,36.326C29.112,36.52 28.608,36.326 28.414,35.896Z" | ||
| android:strokeLineJoin="round" | ||
| android:fillColor="#ffffff" | ||
| android:fillColor="#CDDCE4" | ||
| android:strokeColor="#171717" | ||
| android:strokeLineCap="round"/> | ||
| <path | ||
| android:strokeWidth="1" | ||
| android:pathData="M17.475,20.886L16.41,18.518L22.7,15.676L23.765,18.045L17.475,20.886Z" | ||
| android:pathData="M18.276,17.836L17.631,16.395C17.496,16.098 17.631,15.748 17.927,15.613L23.529,13.076C23.828,12.941 24.177,13.076 24.312,13.373L24.957,14.814C25.092,15.11 24.957,15.46 24.661,15.595L19.059,18.133C18.763,18.268 18.411,18.133 18.276,17.836Z" | ||
| android:strokeLineJoin="round" | ||
| android:fillColor="#ffffff" | ||
| android:fillColor="#CDDCE4" | ||
| android:strokeColor="#171717" | ||
| android:strokeLineCap="round"/> | ||
| <path | ||
| android:strokeWidth="1" | ||
| android:pathData="M13.022,41.772C13.022,41.772 8.088,45.51 5.535,43.955C4.764,43.481 4.263,42.699 4.051,41.84L3.675,40.333C3.675,40.333 1.16,41.067 0.544,38.594C-0.073,36.122 6.238,36.189 6.238,36.189C6.238,36.189 5.544,33.746 7.308,33.302C9.071,32.857 10.911,36.904 10.911,36.904L13.031,41.772H13.022Z" | ||
| android:pathData="M13.796,40.611C13.796,40.611 8.558,44.588 5.845,42.933C5.027,42.427 4.493,41.595 4.27,40.681L3.871,39.076C3.871,39.076 1.199,39.858 0.545,37.224C-0.109,34.593 6.593,34.663 6.593,34.663C6.593,34.663 5.857,32.061 7.728,31.591C9.599,31.118 11.556,35.425 11.556,35.425L13.808,40.608H13.799L13.796,40.611Z" | ||
| android:strokeLineJoin="round" | ||
| android:fillColor="#ffffff" | ||
| android:strokeColor="#171717" | ||
| android:strokeLineCap="round"/> | ||
| <path | ||
| android:pathData="M23.449,38.112L11.964,41.01L11.144,37.745L22.63,34.848" | ||
| android:fillColor="#ffffff"/> | ||
| android:pathData="M24.863,36.711L12.668,39.795L11.797,36.32L23.992,33.236" | ||
| android:fillColor="#CDDCE4"/> | ||
| <path | ||
| android:strokeWidth="1" | ||
| android:pathData="M23.449,38.112L11.964,41.01L11.144,37.745L22.63,34.848" | ||
| android:pathData="M24.863,36.711L12.668,39.795L11.797,36.32L23.992,33.236" | ||
| android:strokeLineJoin="round" | ||
| android:fillColor="#00000000" | ||
| android:strokeColor="#171717" | ||
| android:strokeLineCap="round"/> | ||
| <path | ||
| android:strokeWidth="1" | ||
| android:pathData="M14.81,42.03L12.371,42.645L10.733,36.116L13.172,35.501L14.81,42.03Z" | ||
| android:pathData="M15.124,41.023L13.681,41.388C13.364,41.467 13.041,41.276 12.962,40.959L11.51,35.158C11.431,34.841 11.622,34.521 11.938,34.441L13.381,34.076C13.698,33.997 14.021,34.188 14.1,34.506L15.552,40.306C15.631,40.624 15.44,40.944 15.124,41.023Z" | ||
| android:strokeLineJoin="round" | ||
| android:fillColor="#ffffff" | ||
| android:fillColor="#CDDCE4" | ||
| android:strokeColor="#171717" | ||
| android:strokeLineCap="round"/> | ||
| <path | ||
| android:strokeWidth="1" | ||
| android:pathData="M12.121,20.979C12.121,20.979 8.903,23.21 9.491,29.343" | ||
| android:pathData="M12.837,18.477C12.837,18.477 9.42,20.852 10.045,27.381" | ||
| android:strokeLineJoin="round" | ||
| android:fillColor="#00000000" | ||
| android:strokeColor="#171717" | ||
| android:strokeLineCap="round"/> | ||
| <path | ||
| android:strokeWidth="1" | ||
| android:pathData="M8.832,15.504C8.832,15.504 3.6,20.613 5.864,30.117" | ||
| android:pathData="M9.338,12.646C9.338,12.646 3.782,18.085 6.185,28.201" | ||
| android:strokeLineJoin="round" | ||
| android:fillColor="#00000000" | ||
| android:strokeColor="#171717" | ||
| android:strokeLineCap="round"/> | ||
| <path | ||
| android:pathData="M34.32,33.678C34.32,33.678 27.518,35.117 26.034,30.684C26.034,30.684 23.345,24.87 31.805,22.117" | ||
| android:fillColor="#ffffff"/> | ||
| android:pathData="M76.23,50.878C76.23,49.432 67.9,48.256 57.623,48.256C47.345,48.256 39.016,49.429 39.016,50.878C39.016,52.328 47.345,53.501 57.623,53.501C67.9,53.501 76.23,52.328 76.23,50.878Z" | ||
| android:fillColor="#E8E8E8"/> | ||
| <path | ||
| android:pathData="M38.589,32.252C38.589,32.252 36.055,36.556 31.292,34.907C26.191,32.446 26.159,25.17 34.887,23.244" | ||
| android:fillColor="#F9F9F9"/> | ||
| <path | ||
| android:strokeWidth="1" | ||
| android:pathData="M34.32,33.678C34.32,33.678 27.518,35.117 26.034,30.684C26.034,30.684 23.345,24.87 31.805,22.117" | ||
| android:pathData="M38.933,32.074C38.933,32.074 36.398,36.378 31.635,34.729C26.535,32.268 26.503,24.992 35.231,23.066" | ||
| android:strokeLineJoin="round" | ||
| android:fillColor="#00000000" | ||
| android:strokeColor="#171717" | ||
| android:strokeLineCap="round"/> | ||
| <path | ||
| android:pathData="M56.026,5.18C42.519,6.2 33.911,17.857 34.829,30.228C35.747,42.599 46.766,50.948 59.357,49.987C71.949,49.025 81.349,39.118 80.431,26.747C79.513,14.376 69.957,4.61 56.026,5.18Z" | ||
| android:fillColor="#F9F9F9"/> | ||
| <path | ||
| android:strokeWidth="1" | ||
| android:pathData="M54.824,8.551C54.824,8.551 52.308,2.285 55.805,0.939C56.583,0.638 57.593,0.779 58.21,1.436C58.575,1.82 59.445,3.384 57.882,3.923" | ||
| android:pathData="M55.682,5.356C42.175,6.376 33.567,18.032 34.485,30.403C35.403,42.775 46.422,51.124 59.014,50.162C71.605,49.201 81.005,39.294 80.087,26.923C79.169,14.552 69.614,4.785 55.682,5.356Z" | ||
| android:strokeLineJoin="round" | ||
| android:fillColor="#00000000" | ||
| android:strokeColor="#171717" | ||
| android:strokeLineCap="round"/> | ||
| <path | ||
| android:strokeWidth="1" | ||
| android:pathData="M52.66,47.268C41.03,47.268 31.601,37.945 31.601,26.444C31.601,14.944 41.03,5.621 52.66,5.621C64.29,5.621 73.719,14.944 73.719,26.444C73.719,37.945 64.29,47.268 52.66,47.268Z" | ||
| android:pathData="M57.582,5.108C57.582,5.108 56.406,2.568 58.588,0.986C60.77,-0.595 63.049,2.089 61.149,3.23" | ||
| android:strokeLineJoin="round" | ||
| android:fillColor="#ffffff" | ||
| android:fillColor="#00000000" | ||
| android:strokeColor="#171717" | ||
| android:strokeLineCap="round"/> | ||
| <path | ||
| android:pathData="M51.825,22.06C46.504,22.06 42.19,20.114 42.19,17.713C42.19,15.313 46.504,13.367 51.825,13.367C57.147,13.367 61.461,15.313 61.461,17.713C61.461,20.114 57.147,22.06 51.825,22.06Z" | ||
| android:fillColor="#C6C6C6"/> | ||
| <path | ||
| android:pathData="M42.681,18.787C41.809,18.787 41.101,18.078 41.101,17.203C41.101,16.328 41.809,15.619 42.681,15.619C43.554,15.619 44.262,16.328 44.262,17.203C44.262,18.078 43.554,18.787 42.681,18.787Z" | ||
| android:fillColor="#171717"/> | ||
| android:pathData="M55.743,45.446C65.913,45.446 74.157,37.188 74.157,27C74.157,16.812 65.913,8.553 55.743,8.553C45.573,8.553 37.328,16.812 37.328,27C37.328,37.188 45.573,45.446 55.743,45.446Z" | ||
| android:fillColor="#E2E2E2"/> | ||
| <path | ||
| android:pathData="M61.287,19.113C60.414,19.113 59.707,18.404 59.707,17.529C59.707,16.654 60.414,15.945 61.287,15.945C62.16,15.945 62.867,16.654 62.867,17.529C62.867,18.404 62.16,19.113 61.287,19.113Z" | ||
| android:fillColor="#171717"/> | ||
| android:strokeWidth="1" | ||
| android:pathData="M66.541,19.041C66.306,18.991 66.101,18.909 65.905,18.862C65.67,18.806 65.456,18.762 65.262,18.709C65.03,18.647 64.822,18.579 64.626,18.521C64.403,18.453 64.186,18.43 63.98,18.362C63.775,18.294 63.579,18.206 63.359,18.139C63.162,18.077 62.954,18.024 62.722,17.959C62.529,17.903 62.317,17.856 62.083,17.795C61.886,17.744 61.672,17.692 61.438,17.639" | ||
| android:strokeLineJoin="round" | ||
| android:fillColor="#00000000" | ||
| android:strokeColor="#171717" | ||
| android:strokeLineCap="round"/> | ||
| <path | ||
| android:pathData="M71.277,21.713C71.277,21.713 79.848,24.648 78.322,30.596C77.66,33.171 75.615,35.193 73.168,35.766C71.709,36.106 70.039,35.941 68.762,34.23C66.353,31.004 70.24,27.826 70.24,27.826" | ||
| android:fillColor="#ffffff"/> | ||
| android:strokeWidth="1" | ||
| android:pathData="M48.799,17.734C48.553,17.826 48.318,17.881 48.119,17.961C47.872,18.061 47.661,18.164 47.465,18.252C47.227,18.358 47.013,18.455 46.813,18.552C46.597,18.657 46.409,18.799 46.189,18.904C45.989,18.999 45.769,19.078 45.529,19.181C45.332,19.266 45.118,19.366 44.872,19.463C44.669,19.542 44.45,19.633 44.203,19.722" | ||
| android:strokeLineJoin="round" | ||
| android:fillColor="#00000000" | ||
| android:strokeColor="#171717" | ||
| android:strokeLineCap="round"/> | ||
| <path | ||
| android:strokeWidth="1" | ||
| android:pathData="M71.277,21.713C71.277,21.713 79.848,24.648 78.322,30.596C77.66,33.171 75.615,35.193 73.168,35.766C71.709,36.106 70.039,35.941 68.762,34.23C66.353,31.004 70.24,27.826 70.24,27.826" | ||
| android:pathData="M49.991,24.425C49.991,24.425 49.055,25.23 49.523,26.289C50.289,28.022 52.87,27.523 54.548,26.601C54.548,26.601 58.233,28.581 60.319,26.776C60.319,26.776 61.371,25.7 60.542,24.649" | ||
| android:strokeLineJoin="round" | ||
| android:fillColor="#00000000" | ||
| android:strokeColor="#171717" | ||
| android:strokeLineCap="round"/> | ||
| <path | ||
| android:pathData="M77.875,22.668C77.875,22.668 87.044,24.949 85.269,32.287C84.574,35.077 82.427,37.267 79.858,37.888C78.327,38.255 76.573,38.076 75.233,36.224C72.704,32.731 76.784,29.289 76.784,29.289" | ||
| android:fillColor="#F9F9F9"/> | ||
| <path | ||
| android:strokeWidth="1" | ||
| android:pathData="M60.313,27.392C55.478,23.908 48.799,23.634 43.083,26.551" | ||
| android:pathData="M77.875,22.668C77.875,22.668 87.044,24.949 85.269,32.287C84.574,35.077 82.427,37.267 79.858,37.888C78.327,38.255 76.573,38.076 75.233,36.224C72.704,32.731 76.784,29.289 76.784,29.289" | ||
| android:strokeLineJoin="round" | ||
| android:fillColor="#00000000" | ||
| android:strokeColor="#171717" | ||
| android:strokeLineCap="round"/> | ||
| <path | ||
| android:pathData="M47.829,24.898C47.641,22.653 45.473,21.003 42.986,21.215C40.499,21.426 38.635,23.418 38.824,25.664C39.012,27.91 41.18,29.559 43.667,29.348C46.154,29.136 48.017,27.144 47.829,24.898Z" | ||
| android:strokeAlpha="0.35" | ||
| android:fillColor="#CBA19D" | ||
| android:fillAlpha="0.35"/> | ||
| <path | ||
| android:pathData="M72.735,24.898C72.547,22.653 70.379,21.003 67.892,21.215C65.405,21.426 63.542,23.418 63.73,25.664C63.918,27.91 66.086,29.559 68.573,29.348C71.06,29.136 72.924,27.144 72.735,24.898Z" | ||
| android:strokeAlpha="0.35" | ||
| android:fillColor="#CBA19D" | ||
| android:fillAlpha="0.35"/> | ||
| </vector> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Check for night variant
fd "ic_goal_action_poke" --type f
# Check design-system structure
find core/design-system/src/main/res -type d | head -20
# Check for drawable-night resources
find core/design-system -name "drawable-night*" -type d
# Check other vector drawables for dark mode support patterns
find core/design-system/src/main/res/drawable -name "*.xml" -type f | head -10Repository: YAPP-Github/Twix-Android
Length of output: 857
🏁 Script executed:
# Check values folder for color definitions
ls -la core/design-system/src/main/res/values/
# Check if there are any values-night folders
find core/design-system/src/main/res -type d -name "values*"
# Look for color definitions in values
cat core/design-system/src/main/res/values/colors.xml 2>/dev/null || echo "colors.xml not found"
# Check for attrs.xml for theme attributes
cat core/design-system/src/main/res/values/attrs.xml 2>/dev/null || echo "attrs.xml not found"
# Check all files in values directory
ls -la core/design-system/src/main/res/values/Repository: YAPP-Github/Twix-Android
Length of output: 493
🏁 Script executed:
# Search for color definitions across entire project
find . -name "colors.xml" -type f 2>/dev/null | head -10
# Search for any theme/attr definitions
find . -name "attrs.xml" -type f 2>/dev/null | head -10
find . -name "themes.xml" -type f 2>/dev/null | head -10
# Check if app module has dark mode setup
find . -name "values-night" -type d 2>/dev/null | head -10
# Check main app structure
ls -la app/src/main/res/ 2>/dev/null | grep values
# Look for color usage patterns in other drawables
rg "android:fillColor|android:strokeColor" core/design-system/src/main/res/drawable/ -cRepository: YAPP-Github/Twix-Android
Length of output: 5637
🏁 Script executed:
# Check app's theme implementation
cat app/src/main/res/values/themes.xml | head -50
# Check for values-night in app
find app/src/main/res -type d -name "values-night*"
# Check colors.xml
cat app/src/main/res/values/colors.xml | head -30Repository: YAPP-Github/Twix-Android
Length of output: 593
다크 모드 미지원 — 설계 시스템 전체 색상 체계 검토 필요
이 파일의 하드코딩된 색상들(#E8E8E8, #F9F9F9, #171717 등)은 코딩 가이드라인의 다크 모드 지원 요구사항을 위반하고 있습니다. 더 중요한 점은, 이것이 단순한 한 파일의 문제가 아니라 design-system 내 80개 이상의 모든 드로어블 자산이 동일하게 영향을 받는 체계적인 구조 문제라는 것입니다.
왜 문제인가:
- 코딩 가이드라인에서
core/design-system/**경로는 다크 모드 지원을 명시적으로 요구하고 있습니다 - 현재 설계 시스템은 중앙화된 색상 정의 시스템(
colors.xml)이 없으며, 모든 색상이 각 드로어블에 산재되어 있습니다 - 향후 앱이 다크 모드를 추가할 때 80개 이상의 파일을 개별 수정해야 합니다
개선 방향:
-
즉시 가능한 방안:
core/design-system/src/main/res/values/에colors.xml파일을 생성하고, 모든 색상을 시맨틱 이름으로 정의합니다<color name="icon_stroke">@color/gray_900</color> <color name="icon_background_light">@color/gray_100</color>
-
다크 모드 준비: 추후
values-night/colors.xml을 추가하여 야간 테마 색상을 정의하면, 드로어블 수정 없이 자동 전환됩니다 -
단기 목표: 현재 앱이 다크 모드를 지원하지 않으므로, 이 개선작업을 설계 시스템 구조 개선 티켓으로 별도 추적하는 것을 권장합니다
이 파일 자체는 설계 시스템의 다른 아이콘들과 동일한 패턴을 따르고 있으므로, 설계 시스템 전체의 색상 시스템이 정립된 후 일괄 리팩토링하는 것이 효율적입니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@core/design-system/src/main/res/drawable/ic_goal_action_poke.xml` around
lines 1 - 139, This drawable (ic_goal_action_poke.xml) hardcodes colors
(`#E8E8E8`, `#F9F9F9`, `#171717`, etc.) which breaks the design-system dark-mode
requirement; create a centralized colors.xml in values defining semantic names
(e.g., icon_stroke, icon_bg_light, icon_fill_wrap) and update
ic_goal_action_poke.xml to use `@color/icon_stroke`, `@color/icon_bg_light`,
`@color/icon_fill_wrap` instead of hex literals; add a values-night/colors.xml
with dark variants for those same semantic names so drawables switch
automatically; apply the same replacement pattern to the other design-system
drawables in a single bulk refactor tracked as a dedicated ticket.
| AppRoundButton( | ||
| text = stringResource(R.string.action_sting_emphasized), | ||
| textColor = GrayColor.C500, | ||
| textStyle = AppTextStyle.C2, | ||
| backgroundColor = CommonColor.White, | ||
| modifier = | ||
| Modifier | ||
| .size(width = 64.dp, height = 28.dp) | ||
| .noRippleClickable { onClick() }, |
There was a problem hiding this comment.
AppRoundButton의 터치 영역이 권장 최소 크기보다 작습니다
Modifier.size(width = 64.dp, height = 28.dp)로 설정된 버튼의 높이가 28dp인데, Android 접근성 가이드라인에서 권장하는 최소 터치 타겟 크기는 48dp입니다. 실제 사용자가 터치하기 어려울 수 있으며, 특히 작은 화면에서 UX 문제가 발생할 수 있습니다.
버튼의 시각적 크기는 유지하면서 터치 영역만 확장하는 방법을 고려해 보세요:
💡 터치 영역 확장 방안
AppRoundButton(
text = stringResource(R.string.action_sting_emphasized),
textColor = GrayColor.C500,
textStyle = AppTextStyle.C2,
backgroundColor = CommonColor.White,
modifier =
Modifier
.size(width = 64.dp, height = 28.dp)
- .noRippleClickable { onClick() },
+ .noRippleClickable { onClick() }
+ .sizeIn(minWidth = 48.dp, minHeight = 48.dp),
)또는 버튼을 감싸는 Box에 padding을 추가하여 터치 영역을 넓히는 방법도 있습니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@feature/main/src/main/java/com/twix/home/component/GoalVerifications.kt`
around lines 93 - 101, The AppRoundButton's touch target is only 28.dp high
(Modifier.size(width = 64.dp, height = 28.dp)), below the 48dp accessibility
minimum; update the modifier used by AppRoundButton (or the composable wrapping
it) to keep the visual 28.dp size but expand the clickable area to at least 48dp
tall — e.g., replace or augment Modifier.size(...) with a combination that
preserves visual size and increases hit area (use Modifier.size(width = 64.dp,
height = 28.dp).then(Modifier.minimumTouchTargetSize()) if available, or wrap
the AppRoundButton in a Box and add symmetric vertical padding of (48.dp -
28.dp)/2 to the clickable modifier that calls noRippleClickable { onClick() })
so the UI looks unchanged but the touch target meets the 48dp requirement.
| data class ShowPokeToast( | ||
| val message: String, | ||
| ) : HomeSideEffect |
There was a problem hiding this comment.
서버에서 받은 원시 문자열을 UI에 직접 노출하는 방식 검토 필요
기존 ShowToast는 @StringRes resId: Int를 받아 앱 내 로컬라이즈된 문자열 리소스를 표시하는 반면, 새로 추가된 ShowPokeToast는 서버에서 내려온 String을 그대로 사용합니다.
이 방식은 두 가지 문제를 야기할 수 있습니다:
- i18n/다국어 대응 불가: 서버 메시지가 특정 언어로 고정될 경우, 앱의 로케일과 무관하게 해당 언어로 표시됩니다.
- 유지보수 취약성: 서버 응답 문구가 변경되면 앱 UI 문구가 무단으로 변경됩니다.
성공 메시지도 앱 내 string resource로 관리하는 방식을 검토해 보시는 건 어떨까요?
- data class ShowPokeToast(
- val message: String,
- ) : HomeSideEffect
+ // 기존 ShowToast에 poke 성공 케이스를 추가하거나,
+ // 별도 케이스를 `@StringRes로` 관리:
+ data object ShowPokeSuccessToast : HomeSideEffect만약 서버 메시지를 반드시 표시해야 하는 기획 의도가 있다면, 최소한 message가 비어있을 경우의 fallback 처리를 고려해 주세요.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@feature/main/src/main/java/com/twix/home/HomeSideEffect.kt` around lines 15 -
17, ShowPokeToast currently exposes raw server strings to the UI (data class
ShowPokeToast(val message: String) : HomeSideEffect), which breaks i18n and
makes UI text maintenance brittle; change the side effect to use localized
string resources like ShowToast (e.g., replace or overload ShowPokeToast to
carry an `@StringRes` resId: Int or a sealed variant that prefers resId) or, if
server text must be shown, make message nullable/optional and add a fallback to
a local string resource when message is blank/empty; update places that emit
ShowPokeToast and the consumer that renders HomeSideEffect to handle the new
variant or fallback logic.
이슈 번호
작업내용
결과물
찌르기
2026-02-26.3.47.55.mov
받기
2026-02-26.3.50.33.mov
리뷰어에게 추가로 요구하는 사항 (선택)