Skip to content

홈 화면 찌르기 추가#108

Merged
chanho0908 merged 18 commits intodevelopfrom
feat/#107-home-poke
Feb 26, 2026
Merged

홈 화면 찌르기 추가#108
chanho0908 merged 18 commits intodevelopfrom
feat/#107-home-poke

Conversation

@dogmania
Copy link
Member

이슈 번호

작업내용

  • 홈 화면 찌르기 버튼 추가
  • 찌르기 API 연동

결과물

찌르기

2026-02-26.3.47.55.mov

받기

2026-02-26.3.50.33.mov

리뷰어에게 추가로 요구하는 사항 (선택)

@dogmania dogmania requested a review from chanho0908 February 25, 2026 18:55
@dogmania dogmania self-assigned this Feb 25, 2026
@dogmania dogmania added the Feature Extra attention is needed label Feb 25, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 25, 2026

📝 Walkthrough

Walkthrough

이 변경사항은 홈 화면에 새로운 '찌르기' 기능을 구현합니다. 네트워크 계층에서 PokeService, 데이터 계층에서 PokeRepository를 추가하고, 도메인 계층에 PokeResult 모델을 정의했습니다. HomeViewModel에서 찌르기 의도를 처리하며, HomeScreen과 하위 컴포넌트들은 onPokeGoal 콜백을 전파합니다. UI에는 GoalVerifications 컴포넌트에서 파트너 상태에 찌르기 버튼을 추가했고, 아이콘 및 문자열 리소스도 업데이트했습니다.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45분


상세 평가 및 개선 제안

✅ 잘 구조화된 계층 분리

네트워크 → 데이터 → 도메인 → 기능 계층에 걸쳐 일관된 패턴으로 구현되었습니다. 특히 PokeService, DefaultPokeRepository, PokeResult 간의 책임 분리가 명확하며, 의존성 주입도 적절히 구성되었습니다.

🔍 검토 포인트

1. 파트너 문자열 리소스 변경

  • task_certification_detail_partner_sting 제거 → action_sting 사용으로 변경
  • 이 리소스가 다른 곳에서도 사용되고 있는지 확인하셨나요? 검색해보면 안심이 될 것 같습니다.

2. AppRoundButton 테두리 두께 변경

  • 1.6dp → 1dp로 감소했습니다. 이 변경이 다른 버튼 스타일과의 시각적 일관성을 유지하나요?
  • 디자인 시스템 전체에 영향을 줄 수 있는 변경이므로, 의도적이었다면 좋은 결정으로 보입니다.

3. GoalVerifications 컴포넌트의 UI 추가

  • 31줄의 변경 중 이미지 크기 조정(78x52 → 85x53)과 버튼 추가가 포함되어 있습니다.
  • 이 UI가 다양한 화면 크기와 방향에서 제대로 렌더링되는지 확인하셨나요?
  • Preview composable이 추가된 것은 좋은 실천입니다.

4. 에러 처리 일관성

  • HomeViewModel의 pokeGoal() 함수에서 실패 시 toast 에러를 표시합니다.
  • 네트워크 오류와 비즈니스 로직 오류가 동일하게 처리되는데, 세분화가 필요할까요?
  • 예: "찌르기 실패", "네트워크 오류" 등으로 사용자에게 더 유용한 피드백을 줄 수 있을 것 같습니다.

5. 아이콘 리소스 업데이트

  • ic_goal_action_poke.xml이 58줄 추가/37줄 삭제되는 상당한 변경이 있습니다.
  • 벡터 드로어블의 크기 변경(79x52 → 86x54)과 경로 복잡성 증가가 성능에 미칠 영향을 고려해주세요.
  • 새로운 색상 팔레트(#E8E8E8, #F9F9F9, #CDDCE4)가 기존 테마와 조화를 이루나요?

💬 토론이 필요한 부분

  1. Poke 기능의 확장성: 현재는 찌르기만 있는데, 향후 다른 상호작용 기능(예: 응원, 댓글 등)이 추가될 때를 대비한 아키텍처인가요?

  2. 로딩 상태 처리: pokeRepository.pokeGoal() 호출 중 UI가 사용자 입력에 반응하나요? 버튼을 여러 번 탭했을 때의 중복 요청 방지가 필요할 것 같습니다.

  3. 메시지 활용: 응답으로 받는 PokeResponse.message가 실제로 사용되나요? ShowPokeToast로 토스트 메시지가 표시된다면, 기존 토스트 메시지보다 API 응답 메시지를 우선시할 이유가 있을까요?

🎯 개선 제안

// 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)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 변경 사항의 주요 목적을 명확하게 반영하고 있습니다. '홈 화면 찌르기 추가'는 이 PR의 핵심 기능을 간결하게 표현합니다.
Description check ✅ Passed PR 설명이 이슈 번호, 작업 내용, 결과물을 포함하며 변경 사항과 관련된 정보를 제공합니다.
Linked Issues check ✅ Passed 모든 코드 변경사항이 #107의 요구사항을 충족합니다. PokeService와 PokeRepository 구현, 홈 화면 찌르기 기능 추가, API 연동이 완료되었습니다.
Out of Scope Changes check ✅ Passed AppRoundButton 테두리 두께 변경 및 ic_goal_action_poke.xml 리소스 업데이트는 UI/UX 개선과 관련되어 있으며, action_sting 문자열 추가/변경은 네이밍 개선으로 범위 내입니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#107-home-poke

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@Preview Composable이 제공되지 않았습니다.

HomeScreenGoalList는 공개된 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)를 올바르게 사용하고 있습니다. 기존 패턴인 fetchGoalListonErroremitSideEffect를 사용하므로 일관성 있습니다.

다만 onSuccess에서 it.message (서버에서 내려온 원시 문자열)를 ShowPokeToast에 그대로 전달하는 부분은 HomeSideEffect.ktShowPokeToast 설계와 함께 검토가 필요합니다 (해당 파일 코멘트 참조).

🤖 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

📥 Commits

Reviewing files that changed from the base of the PR and between 3af2e62 and 5936999.

📒 Files selected for processing (17)
  • core/design-system/src/main/java/com/twix/designsystem/components/button/AppRoundButton.kt
  • core/design-system/src/main/res/drawable/ic_goal_action_poke.xml
  • core/design-system/src/main/res/values/strings.xml
  • core/network/src/main/java/com/twix/network/di/ApiServiceModule.kt
  • core/network/src/main/java/com/twix/network/model/response/poke/mapper/PokeMapper.kt
  • core/network/src/main/java/com/twix/network/model/response/poke/model/PokeResponse.kt
  • core/network/src/main/java/com/twix/network/service/PokeService.kt
  • data/src/main/java/com/twix/data/di/RepositoryModule.kt
  • data/src/main/java/com/twix/data/repository/DefaultPokeRepository.kt
  • domain/src/main/java/com/twix/domain/model/poke/PokeResult.kt
  • domain/src/main/java/com/twix/domain/repository/PokeRepository.kt
  • feature/main/src/main/java/com/twix/home/HomeIntent.kt
  • feature/main/src/main/java/com/twix/home/HomeScreen.kt
  • feature/main/src/main/java/com/twix/home/HomeSideEffect.kt
  • feature/main/src/main/java/com/twix/home/HomeViewModel.kt
  • feature/main/src/main/java/com/twix/home/component/GoalVerifications.kt
  • feature/task-certification/src/main/java/com/twix/task_certification/detail/component/TaskCertificationCardContent.kt

Comment on lines 1 to 139
<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>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 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 -10

Repository: 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/ -c

Repository: 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 -30

Repository: YAPP-Github/Twix-Android

Length of output: 593


다크 모드 미지원 — 설계 시스템 전체 색상 체계 검토 필요

이 파일의 하드코딩된 색상들(#E8E8E8, #F9F9F9, #171717 등)은 코딩 가이드라인의 다크 모드 지원 요구사항을 위반하고 있습니다. 더 중요한 점은, 이것이 단순한 한 파일의 문제가 아니라 design-system 내 80개 이상의 모든 드로어블 자산이 동일하게 영향을 받는 체계적인 구조 문제라는 것입니다.

왜 문제인가:

  • 코딩 가이드라인에서 core/design-system/** 경로는 다크 모드 지원을 명시적으로 요구하고 있습니다
  • 현재 설계 시스템은 중앙화된 색상 정의 시스템(colors.xml)이 없으며, 모든 색상이 각 드로어블에 산재되어 있습니다
  • 향후 앱이 다크 모드를 추가할 때 80개 이상의 파일을 개별 수정해야 합니다

개선 방향:

  1. 즉시 가능한 방안: 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>
  2. 다크 모드 준비: 추후 values-night/colors.xml을 추가하여 야간 테마 색상을 정의하면, 드로어블 수정 없이 자동 전환됩니다

  3. 단기 목표: 현재 앱이 다크 모드를 지원하지 않으므로, 이 개선작업을 설계 시스템 구조 개선 티켓으로 별도 추적하는 것을 권장합니다

이 파일 자체는 설계 시스템의 다른 아이콘들과 동일한 패턴을 따르고 있으므로, 설계 시스템 전체의 색상 시스템이 정립된 후 일괄 리팩토링하는 것이 효율적입니다.

🤖 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.

Comment on lines +93 to +101
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() },
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Comment on lines +15 to +17
data class ShowPokeToast(
val message: String,
) : HomeSideEffect
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

서버에서 받은 원시 문자열을 UI에 직접 노출하는 방식 검토 필요

기존 ShowToast@StringRes resId: Int를 받아 앱 내 로컬라이즈된 문자열 리소스를 표시하는 반면, 새로 추가된 ShowPokeToast는 서버에서 내려온 String을 그대로 사용합니다.

이 방식은 두 가지 문제를 야기할 수 있습니다:

  1. i18n/다국어 대응 불가: 서버 메시지가 특정 언어로 고정될 경우, 앱의 로케일과 무관하게 해당 언어로 표시됩니다.
  2. 유지보수 취약성: 서버 응답 문구가 변경되면 앱 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.

Copy link
Member

@chanho0908 chanho0908 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생했어 현수야 ! 따로 의견 남길부분이 없기도 하고 이부분을 바로 사용해야해서 머지할게 !

@chanho0908 chanho0908 merged commit dde8b28 into develop Feb 26, 2026
11 checks passed
@chanho0908 chanho0908 deleted the feat/#107-home-poke branch February 26, 2026 06:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Feature Extra attention is needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

홈 화면 찌르기 추가

2 participants