Skip to content

Conversation

l5x5l
Copy link
Contributor

@l5x5l l5x5l commented Sep 10, 2025

[ PR Content ]

감정구슬 화면에 2차 UI를 적용합니다.

Related issue

Screenshot 📸

select_emotion.mp4
under_600_dp.mp4

Work Description

  • 감정구슬 2차 디자인 반영
  • 앱이 표시할 수 있는 영역이 600dp 이하인 경우 1차 UI기반 감정구슬 선택 화면이 표시되도록 구현

To Reviewers 📢

  • 감정구슬 드래그의 경우, horizontalPager 내부의 item에 draggable을 사용하는 방식으로 구현했습니다!
  • 화면에 사용되는 이미지 및 텍스트가 화면 비율보다는 고정된 값을 기반으로 하다 보니, 특정 높이 미만에서는 UI가 깨져 보이는 현상이 발견되어 600dp 이하인 경우에는 기존 감정구슬 선택하는 방식과 유사한 화면을 표시하도록 했습니다
  • horizontalPager 관련 상태값(현재 표시중인 페이지 인덱스 및 감정구슬)을 viewModel이 아닌 SwipeEmotionSelectionScreen 내부에서만 관리하는 방향으로 구현했습니다,
    • viewModel에서 한번에 관리하는 방안도 시도해보았으나, viewModel과 pagerState간 데이터 동기화 문제 및 데이터가 중복되어 저장된다는 생각에 viewModel과는 별도로 관리하는 방향으로 구현했습니다!
  • 개선점이나 궁금한 점 있으시면 코멘트 부탁드립니다!

Summary by CodeRabbit

  • 신기능

    • 스와이프/드래그 감정 캐러셀과 소형 화면용 간단 그리드 추가.
    • 감정 기반 로딩 화면(말풍선 추천 문구, 마블 일러스트, 장식 배경) 추가.
    • 추천 루틴 화면(루틴 선택·등록·건너뛰기) 및 감정 이미지/말풍선 UI 컴포넌트 추가.
    • 감정 이미지 모델과 밀도 변환( dp↔px ) 헬퍼 추가.
  • UI/UX

    • 새로운 타이포그래피 스타일 추가로 가독성 향상.
    • 더블 화살표 아이콘(아래/왼쪽/오른쪽) 추가.
    • 등록 흐름에 로딩 표시 및 경고 토스트로 오류 메시지 표시 추가.

@l5x5l l5x5l self-assigned this Sep 10, 2025
@l5x5l l5x5l added 🔨 Refactor 기존 기능 개선 세환 labels Sep 10, 2025
Copy link

coderabbitai bot commented Sep 10, 2025

Walkthrough

감정 구슬 화면 전면 리디자인: 스와이프/단순 선택/추천 루틴/로딩 템플릿과 아톰 컴포넌트 추가, Emotion 모델 재구성(이미지 타입 분리), MVI 확장(로딩·실패·토스트), ViewModel에 지연·로딩·오류 처리, DP↔PX 유틸 및 아이콘·타이포그래피 추가.

Changes

Cohort / File(s) Summary
Typography 확장
core/designsystem/src/main/java/.../typography/Type.kt
cafe24SsurroundAir2 텍스트 스타일(private _cafe24SsurroundAir2, public cafe24SsurroundAir2) 추가(16sp, lineHeight 24, letterSpacing -0.5).
아이콘 리소스 추가
core/designsystem/src/main/res/drawable/ic_double_down_arrow_24.xml, .../ic_double_left_arrow_24.xml, .../ic_double_right_arrow_24.xml
24dp 벡터 더블 화살표 아이콘 3종 추가(스트로크 기반, strokeColor #878A93, strokeWidth=2, rounded caps/joins).
단위 변환 유틸
presentation/src/main/java/.../common/dimension/DpToPx.kt, .../PxToDp.kt
Compose용 확장함수 추가: @Composable fun Dp.dpToPx(): Float, @Composable fun Float.pxToDp(): Dp, @Composable fun Int.pxToDp(): Dp.
Emotion 이미지 모델 도입
presentation/src/main/java/.../emotion/model/EmotionImageUiModel.kt
EmotionImageUiModel sealed class 추가: Url(url:String, offlineBackupImageResourceId:Int?)Resource(resourceId:Int) (@parcelize).
Emotion 모델 개편
presentation/src/main/java/.../emotion/model/EmotionUiModel.kt
imageUrlimage: EmotionImageUiModel로 변경, selectable, message, symbolBackgroundColor, symbolColor 필드 추가 및 Default 상수 도입; fromDomain 변경 반영.
MVI 확장
presentation/src/main/java/.../emotion/model/mvi/EmotionState.kt, .../EmotionIntent.kt, .../EmotionSideEffect.kt
EmotionStateshowLoadingView:Boolean 추가; EmotionIntentRegisterEmotionFailure(message:String) 추가; EmotionSideEffectShowToast(message:String) 추가.
ViewModel 갱신
presentation/src/main/java/.../emotion/EmotionViewModel.kt
selectEmotion(emotionType:String, minimumDelay:Long = 0) 시그니처 변경, delay(minimumDelay) 처리, 로딩 플래그 토글 및 실패 시 RegisterEmotionFailure 발행.
화면 컨테이너 수정
presentation/src/main/java/.../emotion/EmotionScreen.kt
높이에 따라 SwipeEmotionSelectionScreen 또는 SimpleEmotionSelectionScreen 분기, ShowToast 사이드이펙트로 토스트 표시, 기존 인라인 컴포저블 제거 및 템플릿 위임.
아톰 컴포넌트 추가
presentation/src/main/java/.../emotion/component/atom/EmotionMarbleImage.kt, .../SpeechBubbleText.kt
Emotion 이미지 렌더러(Url/Resource 분기, Coil 사용) 및 Canvas로 꼬리 그리는 말풍선 텍스트 컴포넌트 추가.
템플릿 화면 추가
presentation/src/main/java/.../emotion/component/template/SimpleEmotionSelectionScreen.kt, .../SwipeEmotionSelectionScreen.kt, .../EmotionRecommendRoutineScreen.kt, .../EmotionLoadingView.kt
단순/스와이프 선택 UI(무한 페이저·드래그 등록 포함), 추천 루틴 화면, 감정 기반 로딩 뷰 등 신규 컴포저블 추가(다수 공개 함수 추가).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as 사용자
  participant C as EmotionScreenContainer
  participant UI_S as SwipeEmotionSelectionScreen
  participant UI_L as SimpleEmotionSelectionScreen
  participant VM as EmotionViewModel
  participant MVI as State/Intent/SideEffect
  participant T as Toast

  rect rgba(224,236,255,0.35)
    C->>C: BoxWithConstraints로 높이 판정 (px→dp 변환)
    alt 높이 > 600.dp
      C->>UI_S: Swipe 템플릿 표시
    else
      C->>UI_L: Simple 템플릿 표시
    end
  end

  rect rgba(235,255,235,0.25)
    U->>UI_S: 감정 드래그/선택
    UI_S->>VM: selectEmotion(type, minimumDelay=1000)
    VM->>MVI: Intent.RegisterEmotionLoading
    MVI-->>UI_S: state.showLoadingView = true
    VM->>VM: delay(minimumDelay)
    alt 성공
      VM-->>MVI: 성공 이벤트 → SideEffect.NavigateToBack
    else 실패
      VM-->>MVI: Intent.RegisterEmotionFailure(message)
      MVI-->>T: SideEffect.ShowToast(message)
      T-->>U: 경고 토스트 표시
      MVI-->>C: SideEffect.NavigateToBack
    end
    MVI-->>UI_S: state.showLoadingView = false
  end
Loading
sequenceDiagram
  autonumber
  participant EL as EmotionLoadingView
  participant BG as BackgroundBox
  participant MB as EmotionMarbleImage

  EL->>BG: 데코 아이콘 배치 계산 및 렌더
  EL->>MB: 감정 이미지 렌더(Url/Resource 분기)
  EL->>EL: SpeechBubbleText 표시 (getRecommendRoutineText(emotion))
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • wjdrjs00

Pre-merge checks (4 passed, 1 warning)

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title Check ✅ Passed 제목 “[Refactor/#125] 감정구슬 리디자인 적용”은 PR의 핵심 변경사항인 감정구슬 UI 리디자인을 명확하고 간결하게 요약하며 이슈 참조도 포함되어 있어 변경 이력 추적에 용이합니다. 한 문장으로 핵심을 전달하고 불필요한 파일 목록이나 이모지가 없어 명세에 부합합니다. 따라서 제목 기준을 충족합니다.
Linked Issues Check ✅ Passed 제공된 변경사항은 linked issue #125(감정구슬 화면 2차 UI 적용)의 주목표를 구현하고 있으며 SwipeEmotionSelectionScreen, SimpleEmotionSelectionScreen, EmotionRecommendRoutineScreen 등 새 UI 템플릿과 600dp 이하 대체 로직이 포함되어 요구사항을 충족합니다. 또한 EmotionViewModel·EmotionState·EmotionIntent·EmotionSideEffect 확장과 이미지 모델 재구성 등 UI 동작에 필요한 코드 변경이 반영되어 있습니다. 다만 EmotionUiModel과 상태/인텐트의 공개 시그니처 변경으로 다른 모듈에 영향이 있을 수 있으므로 병합 전 소비자 호환성 검증이 필요합니다.
Out of Scope Changes Check ✅ Passed 검토 결과 UI 리디자인과 직접 관련된 컴포넌트 추가(템플릿·아톰·프리뷰), 리소스(벡터 드로어블), 타이포그래피 스타일, dp/px 유틸 함수 등은 모두 리디자인 구현을 위해 필요한 보조 변경으로 보이며 범위 밖의 독립적 변경은 탐지되지 않았습니다. 전체 변경이 리디자인 목표와 연관되어 있으나 공용 유틸·스타일 추가는 배치된 모듈 위치와 다른 소비자 영향 여부를 확인할 필요가 있습니다.
Description Check ✅ Passed 현재 PR 설명은 저장소 템플릿 구조를 따르고 있으며 Related issue, Screenshot, Work Description, To Reviewers 섹션이 모두 채워져 있어 변경 목적과 구현 제약(예: 600dp 이하 처리, pager 상태 분리 등)을 명확히 전달합니다. 스크린샷 링크와 리뷰 시점에 확인받고 싶은 항목도 포함되어 있어 리뷰에 필요한 정보가 충분합니다. 전체적으로 템플릿 요구사항을 충족합니다.

Poem

깡총깡총 새 UI로 달려왔네, 페이저 빙글빙글
말풍선 톡톡, 구슬은 반짝—드래그하면 툭!
토스트가 살짝 알려주고, 로딩은 살포시 기다려요
좌우 더블 화살표가 길을 비춰주네 🐇✨

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6aa82ac and d69bd15.

📒 Files selected for processing (1)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/template/EmotionRecommendRoutineScreen.kt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/template/EmotionRecommendRoutineScreen.kt
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/#125-redesign_emotion

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.

@l5x5l l5x5l requested a review from wjdrjs00 September 10, 2025 10:20
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: 12

🧹 Nitpick comments (41)
core/designsystem/src/main/res/drawable/ic_double_down_arrow_24.xml (2)

6-14: 패스 복잡도 감소를 고려(렌더/메모리 비용 절감).

pathData가 곡선 세그먼트로 장문 구성되어 있습니다. 단순한 chevron(ˇ) 2개 형태라면 직선 기반 경로로 단순화해 벡터 파싱/렌더 비용을 낮출 수 있습니다. 디자인 변경 범위 내에서 가능한지 검토 바랍니다.


20-20: EOF 개행 추가 권장.

일부 툴링에서 마지막 개행이 없으면 경고가 발생합니다.

core/designsystem/src/main/res/drawable/ic_double_right_arrow_24.xml (3)

1-5: RTL 대응: autoMirrored로 좌/우 리소스 통합 검토.

우측 화살표는 android:autoMirrored="true"로 RTL 환경에서 자동 반전이 가능합니다. 좌/우를 하나의 리소스로 통합하면 중복을 줄일 수 있습니다(디자인이 완전 대칭일 경우에만).

가능한 변경 예시:

 <vector
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:width="24dp"
     android:height="24dp"
     android:viewportWidth="24"
-    android:viewportHeight="24">
+    android:viewportHeight="24"
+    android:autoMirrored="true">

7-14: pathData 단순화 여지.

세그먼트가 많아 렌더 비용이 증가할 수 있습니다. 직선 기반 chevron 두 개로 단순화 가능하면 리소스 경량화에 도움이 됩니다.


20-20: EOF 개행 추가 권장.

core/designsystem/src/main/res/drawable/ic_double_left_arrow_24.xml (3)

1-5: 리소스 네이밍 컨벤션 확인.

프로젝트 내 다른 아이콘과의 일관성을 위해 chevron/arrow 네이밍 규칙(예: ic_chevron_double_left_24 vs ic_double_left_arrow_24)을 통일하세요.


6-14: 경로 복잡도 최적화 검토.

좌/우 아이콘 모두 단순 직선 chevron으로 표현 가능하면 pathData를 축약해 성능과 유지보수성을 개선할 수 있습니다.


20-20: EOF 개행 추가 권장.

presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/mvi/EmotionSideEffect.kt (1)

7-7: 토스트 메시지는 String 대신 리소스 기반으로 전달하세요.

i18n/테스트 용이성과 민감 정보 노출 방지를 위해 @stringres 또는 UiText 같은 래퍼를 권장합니다.

-    data class ShowToast(val message: String) : EmotionSideEffect()
+    data class ShowToast(@androidx.annotation.StringRes val messageResId: Int, val formatArgs: List<Any> = emptyList()) : EmotionSideEffect()

추가로, 소비부(EmotionScreen 등)에서 messageResIdformatArgs를 사용하도록 함께 변경해주세요.

presentation/src/main/java/com/threegap/bitnagil/presentation/common/dimension/DpToPx.kt (1)

7-8: 반환 타입과 KDoc을 명시해 가독성을 높이세요.

의도 전달을 위해 반환 타입(Float)과 간단한 설명을 추가하는 것을 권장합니다.

-@Composable
-fun Dp.dpToPx() = with(LocalDensity.current) { [email protected]() }
+/**
+ * Density에 따라 Dp를 px(Float)로 변환합니다.
+ */
+@Composable
+fun Dp.dpToPx(): Float = with(LocalDensity.current) { [email protected]() }
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/typography/Type.kt (1)

104-110: 타이포그래피 명명 규칙 정리 제안(cafe24SsurroundAir2 → 보다 의도적인 이름).

...2는 의미 전달이 약합니다. 예: cafe24SsurroundAirSmall 또는 cafe24SsurroundAirBody처럼 크기/용도를 반영하면 유지보수성이 좋아집니다. (현재 참조점이 많다면 리네이밍은 추후 일괄 반영도 OK)

Also applies to: 173-173

presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/mvi/EmotionState.kt (1)

17-18: isLoading vs showLoadingView 역할·토글 일관성 확보
isLoading은 백그라운드 작업 중 입력 제어, showLoadingView는 전체 화면 오버레이 표시용으로 분리되어 있습니다. 하지만 EmotionIntent.RegisterRecommendRoutinesLoading/Success 시 showLoadingView가 토글되지 않아 UI 일관성이 깨질 수 있습니다. 의도된 분리라면 EmotionState와 Reducer에 KDoc/주석으로 역할 구분을 명확히 문서화하거나, RegisterRecommendRoutines 로직에서도 showLoadingView를 일관되게 토글하도록 수정하세요.

presentation/src/main/java/com/threegap/bitnagil/presentation/common/dimension/PxToDp.kt (1)

7-10: 반환 타입(Dp) 명시 및 간단 KDoc 추가 권장.

가독성과 IDE 문서화 품질을 위해 타입/KDoc을 명시해 주세요.

-@Composable
-fun Float.pxToDp() = with(LocalDensity.current) { [email protected]() }
+/** Density에 따라 px(Float)을 Dp로 변환합니다. */
+@Composable
+fun Float.pxToDp(): androidx.compose.ui.unit.Dp = with(LocalDensity.current) { [email protected]() }

-@Composable
-fun Int.pxToDp() = with(LocalDensity.current) { [email protected]() }
+/** Density에 따라 px(Int)을 Dp로 변환합니다. */
+@Composable
+fun Int.pxToDp(): androidx.compose.ui.unit.Dp = with(LocalDensity.current) { [email protected]() }
presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/atom/SpeechBubbleText.kt (3)

3-19: 말풍선 꼬리-본체 경계선(1px 틈) 가능성 — 살짝 겹치게 그려주세요

Canvas가 별도 레이아웃이라 배경 Box와 경계가 미세하게 뜰 수 있습니다. 꼬리를 -1.dp 위로 올려 살짝 겹치면 시각적 이음새가 사라집니다.

적용 diff:

@@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.width
@@
         Canvas(
             modifier = Modifier
-                .height(10.dp)
+                .height(10.dp)
                 .width(24.dp),
+                .offset(y = (-1).dp),
         ) {

Also applies to: 51-63


17-19: 긴 텍스트 말줄임 처리 누락

maxLines/minLines를 쓰는 경우 잘림 대신 말줄임표가 자연스럽습니다.

적용 diff:

@@
 import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
@@
             Text(
                 text = text,
                 style = BitnagilTheme.typography.cafe24SsurroundAir2.copy(color = Color(textColor)),
                 textAlign = TextAlign.Center,
                 maxLines = maxLines,
                 minLines = minLines,
+                overflow = TextOverflow.Ellipsis,
             )

Also applies to: 41-47


23-30: Color를 직접 받는 오버로드 제공 권장

현재 Long을 Color로 매번 감싸야 해 실수 여지가 있습니다. Color 파라미터 오버로드를 추가해 타입 안정성을 높여주세요.

예시:

@Composable
fun SpeechBubbleText(
    text: String,
    backgroundColor: Color,
    textColor: Color,
    modifier: Modifier = Modifier,
    maxLines: Int = 2,
    minLines: Int = 2,
) { /* 내부는 Color 그대로 사용 */ }

// 기존 Long API는 유지하되 Color 오버로드로 위 함수를 위임
@Composable
fun SpeechBubbleText(
    text: String,
    backgroundColor: Long,
    textColor: Long,
    modifier: Modifier = Modifier,
    maxLines: Int = 2,
    minLines: Int = 2,
) = SpeechBubbleText(
    text = text,
    backgroundColor = Color(backgroundColor),
    textColor = Color(textColor),
    modifier = modifier,
    maxLines = maxLines,
    minLines = minLines,
)
presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/template/SimpleEmotionSelectionScreen.kt (4)

49-52: Column 내부에서 fillMaxHeight 대신 weight 사용 권장

Column 자식에 fillMaxHeight()를 쓰면 측정 제약과 충돌해 레이아웃 이슈가 날 수 있습니다. 나머지 영역만 차지하도록 weight(1f)로 교체하세요.

적용 diff:

-        Box(
-            modifier = Modifier.fillMaxHeight(),
+        Box(
+            modifier = Modifier.weight(1f),
             contentAlignment = Alignment.Center,
         ) {

59-76: LazyVerticalGrid 항목에 key 추가로 스크롤/애니메이션 안정성 확보

안정 키가 없으면 재조합 시 포커스/상태 튀는 현상이 생길 수 있습니다.

적용 diff:

-            ) {
-                items(state.emotionTypeUiModels) { emotion ->
+            ) {
+                items(
+                    items = state.emotionTypeUiModels,
+                    key = { it.emotionType },
+                ) { emotion ->

65-68: 이미지 접근성: contentDescription 전달

보조기기 사용자를 위해 감정명으로 설명을 넘겨주세요.

적용 diff:

-                        EmotionMarbleImage(
-                            modifier = Modifier.aspectRatio(1f),
-                            image = emotion.image,
-                        )
+                        EmotionMarbleImage(
+                            modifier = Modifier.aspectRatio(1f),
+                            image = emotion.image,
+                            contentDescription = emotion.emotionMarbleName,
+                        )

45-47: 하드코딩 문자열 리소스화

국제화/카피 수정을 위해 stringResource로 분리하세요.

예시 diff(리소스 키는 제안입니다):

-            title = "오늘 감정 등록하기",
+            title = stringResource(id = R.string.emotion_select_title),

필요 import:

import androidx.compose.ui.res.stringResource

원하시면 strings.xml 스텁도 함께 드리겠습니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/template/EmotionRecommendRoutineScreen.kt (2)

62-77: 스크롤 목록은 LazyColumn 고려

추천 루틴 수가 늘 경우 현재 Column+verticalScroll은 성능/메모리 비효율입니다. LazyColumn으로 전환을 권장합니다.

예시:

LazyColumn(
    modifier = Modifier.weight(1f),
    verticalArrangement = Arrangement.spacedBy(12.dp),
) {
    items(state.recommendRoutines, key = { it.id }) { recommendRoutine ->
        BitnagilSelectButton(
            title = recommendRoutine.name,
            description = recommendRoutine.description,
            onClick = { onClickRoutine(recommendRoutine.id) },
            selected = recommendRoutine.selected,
            modifier = Modifier.fillMaxWidth(),
        )
    }
}

47-56: 모든 사용자 노출 문자열 리소스화 권장

헤더/본문/버튼 텍스트를 strings.xml로 이동하면 추후 카피 수정·다국어 대응이 쉬워집니다.

원하시면 리소스 키 제안과 일괄 패치 드리겠습니다.

Also applies to: 81-86, 90-101

presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/EmotionUiModel.kt (2)

55-66: 함수명 오타: “String” 접미사는 의미와 불일치

색상 Long을 반환하므로 함수명을 간결하게 바꿔주세요. 호출부도 함께 교체 필요.

적용 diff:

-        private fun getSymbolBackgroundColorString(emotionType: String): Long {
+        private fun getSymbolBackgroundColor(emotionType: String): Long {
@@
-        private fun getSymbolColorString(emotionType: String): Long {
+        private fun getSymbolColor(emotionType: String): Long {

호출부:

-            symbolBackgroundColor = getSymbolBackgroundColorString(emotion.emotionType),
-            symbolColor = getSymbolColorString(emotion.emotionType),
+            symbolBackgroundColor = getSymbolBackgroundColor(emotion.emotionType),
+            symbolColor = getSymbolColor(emotion.emotionType),

Also applies to: 67-77, 26-29


19-29: String 기반 타입 비교 최소화(선택사항)

"CALM"/"VITALITY" 등 하드코딩 문자열은 오타/변경에 취약합니다. 도메인 Emotion이 enum/sealed 지원하면 그것을 직접 사용하거나, 최소한 상수(Companion const val)로 치환을 권장합니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/EmotionScreen.kt (3)

41-43: BoxWithConstraints 높이 계산 간소화 및 제약 확실화

  • height 계산 시 constraints.maxHeight.pxToDp() 대신 maxHeight를 바로 쓰면 더 간결하고 안전합니다.
  • 화면 기준 분기라면 가로·세로 모두 제약을 받아야 합니다. fillMaxWidth() 대신 fillMaxSize() 권장.

적용 예시:

-        EmotionScreenStep.Emotion -> BoxWithConstraints(modifier = Modifier.fillMaxWidth()) {
-            val height = constraints.maxHeight.pxToDp()
+        EmotionScreenStep.Emotion -> BoxWithConstraints(modifier = Modifier.fillMaxSize()) {
+            val height = maxHeight

48-53: 불필요한 remember 제거 및 매직 넘버 상수화

  • remember { { ... } }로 람다를 캐시할 필요가 없습니다. 바로 람다 전달해도 재구성이 안전합니다.
  • minimumDelay = 1000은 상수로 분리하면 의도가 명확해집니다.

적용 예시:

-                    onSelectEmotion = remember {
-                        { emotionType ->
-                            viewModel.selectEmotion(emotionType = emotionType, minimumDelay = 1000)
-                        }
-                    },
+                    onSelectEmotion = { emotionType ->
+                        viewModel.selectEmotion(emotionType = emotionType, minimumDelay = MIN_LOADING_MS)
+                    },

파일 상단(또는 companion object)에:

private const val MIN_LOADING_MS = 1_000L

55-59: 대·소 화면 간 로딩 UX 일관성 확인 필요

큰 화면 스와이프 템플릿은 최소 1초 딜레이를 적용해 로딩뷰가 보이지만, 작은 화면 단순 선택 템플릿은 즉시 처리합니다. 의도된 차별이라면 OK이고, 아니라면 동일한 최소 표시 시간을 적용하는 편이 UX 일관성에 좋습니다.

선택적 수정 예시:

-                    onClickEmotion = viewModel::selectEmotion,
+                    onClickEmotion = { emotionType ->
+                        viewModel.selectEmotion(emotionType, minimumDelay = MIN_LOADING_MS)
+                    },
presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/EmotionViewModel.kt (2)

82-85: 성공 시에도 isLoading 해제 필요

RegisterRecommendRoutinesSuccess에서 바로 네비게이션만 보내고 상태를 갱신하지 않습니다. 화면이 남아있는 경우 로딩 상태가 유지될 수 있습니다.

적용 예시:

-            EmotionIntent.RegisterRecommendRoutinesSuccess -> {
-                sendSideEffect(EmotionSideEffect.NavigateToBack)
-                return null
-            }
+            EmotionIntent.RegisterRecommendRoutinesSuccess -> {
+                val newState = state.copy(isLoading = false)
+                sendSideEffect(EmotionSideEffect.NavigateToBack)
+                return newState
+            }

44-45: 에러 처리 TODO 남음

감정 목록 로드 실패 케이스가 미구현입니다. 사용자 피드백(토스트/리트라이)과 로딩 해제를 포함한 최소 처리가 필요합니다.

원하시면 실패시 토스트 + 재시도 인텐트/버튼 처리까지 포함한 패치를 제안드릴게요.

presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/template/EmotionLoadingView.kt (3)

47-51: 하드코딩 색상 → 테마 색상 사용 권장

말풍선 배경/텍스트 색을 Hex로 고정하지 말고 디자인 시스템 색상으로 치환하면 다크모드/테마 대응이 쉬워집니다.

예시:

-            SpeechBubbleText(
-                text = getRecommendRoutineText(emotion),
-                backgroundColor = 0xFF000000,
-                textColor = 0xFFFFFFFF,
-            )
+            SpeechBubbleText(
+                text = getRecommendRoutineText(emotion),
+                backgroundColor = BitnagilTheme.colors.black.value,
+                textColor = BitnagilTheme.colors.white.value,
+            )

105-157: 백그라운드 아이콘 오프셋 계산 단순화

constraints(px)→pxToDp() 변환을 매번 호출하기보다 maxWidth/maxHeight(Dp) 기반 비율 연산으로 가독성과 오버헤드를 줄일 수 있습니다.

예시:

-    BoxWithConstraints(modifier = modifier) {
-        val height = constraints.maxHeight
-        val width = constraints.maxWidth
+    BoxWithConstraints(modifier = modifier) {
+        val height = maxHeight
+        val width = maxWidth
@@
-            offsetX = (width * 0.03f).pxToDp(),
-            offsetY = (height * 0.03f).pxToDp(),
+            offsetX = (width.value * 0.03f).dp,
+            offsetY = (height.value * 0.03f).dp,

(이하 동일 패턴 적용)


84-92: 접근성: 이미지 contentDescription 검토

EmotionMarbleImagecontentDescription = null은 장식 목적이면 OK입니다. 만약 현재 감정을 의미적으로 전달해야 한다면 텍스트 제공을 고려해 주세요.

presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/template/SwipeEmotionSelectionScreen.kt (9)

389-399: pageOffset 비교/사용 안정화 — 클램핑 및 임계치 비교로 플리커/오작동 방지

pageOffset가 1을 초과하면 lerp가 역방향으로 과보정될 수 있고, 0f와의 완전 일치 비교는 부동소수 오차로 인해 드물게 드래그가 비활성화되지 않을 수 있습니다.

-    val pageOffset = (
-        (pagerState.currentPage - page) + pagerState.currentPageOffsetFraction
-        ).absoluteValue
+    val pageOffset = (
+        (pagerState.currentPage - page) + pagerState.currentPageOffsetFraction
+    ).absoluteValue
+    val normalizedPageOffset = pageOffset.coerceIn(0f, 1f)
@@
-            .graphicsLayer {
-                translationY = lerp(start = centerItemYOffset * 1f, stop = 0f, pageOffset)
-            }
+            .graphicsLayer {
+                translationY = lerp(start = centerItemYOffset, stop = 0f, normalizedPageOffset)
+            }
@@
-                enabled = (pageOffset == 0f && enabled),
+                enabled = (pageOffset < 0.001f && enabled),

320-341: 소형 화면 안전장치: 음수 pageSpacing/드래그 한계값 방지

극단적 화면 폭/높이에서 음수 pageSpacing, 음수 maximumDraggableYOffset 가능성이 있습니다. 안전하게 클램프하세요.

-        val pageSpacing = ((screenWidth - itemSize * 2) / 2)
+        val rawPageSpacing = (screenWidth - itemSize * 2) / 2
+        val pageSpacing = if (rawPageSpacing < 0.dp) 0.dp else rawPageSpacing
@@
-                maximumDraggableYOffset = constraints.maxHeight - (itemSize + centerItemYOffset).dpToPx(),
+                maximumDraggableYOffset = (constraints.maxHeight - (itemSize + centerItemYOffset).dpToPx()).coerceAtLeast(0f),

111-115: 하드코딩 문자열을 리소스로 이동

문자열 리소스 사용으로 i18n/접근성/일관성 확보가 필요합니다.

-            BitnagilTopBar(
+            BitnagilTopBar(
                 showBackButton = true,
-                title = "오늘 감정 등록하기",
+                title = stringResource(R.string.emotion_register_today_title),
                 onBackClick = onClickPreviousButton,
             )

필요 import 및 string 리소스 추가:

+import androidx.compose.ui.res.stringResource

res/values/strings.xml:

  • emotion_register_today_title: "오늘 감정 등록하기"

260-273: 지시 문구 하드코딩 해제 및 다국어 개행 처리

지시 문구도 string 리소스로 분리하고 개행은 \n 대신 리소스에서 관리하세요.

-            Text("선택한 감정 구슬을 아래로 놓아주세요", style = BitnagilTheme.typography.body2Medium.copy(color = BitnagilTheme.colors.coolGray50))
+            Text(
+                stringResource(R.string.emotion_drag_down_instruction),
+                style = BitnagilTheme.typography.body2Medium.copy(color = BitnagilTheme.colors.coolGray50),
+            )

strings.xml:

  • emotion_drag_down_instruction: "선택한 감정 구슬을 아래로 놓아주세요"

285-289: 지시 문구 하드코딩 해제 및 정렬 일관성

다국어에서 줄바꿈/길이 차이를 고려해 리소스화하고, CenterHorizontally 유지.

-            Text(
-                "좌우로 스와이프해\n감정 구슬을 골라주세요",
+            Text(
+                stringResource(R.string.emotion_swipe_select_instruction),
                 style = BitnagilTheme.typography.body2Medium.copy(color = BitnagilTheme.colors.coolGray50),
                 textAlign = TextAlign.Center,
             )

strings.xml:

  • emotion_swipe_select_instruction: "좌우로 스와이프해 감정 구슬을 골라주세요"

126-133: 접근성: 썸네일 이미지에 contentDescription 전달

상단 구슬 리스트 이미지에 이름을 전달해 스크린리더 접근성을 개선하세요.

-                state.emotionTypeUiModels.forEach { emotion ->
+                state.emotionTypeUiModels.forEach { emotion ->
                     EmotionMarbleImage(
                         modifier = Modifier.size(40.dp),
                         image = emotion.image,
+                        contentDescription = emotion.emotionMarbleName,
                         alpha = if (emotion.emotionType == currentItem.emotionType) 1f else 0.3f,
                     )
                 }

추가로 EmotionMarbleImage.kt의 Url 분기에서 contentDescription 파라미터가 무시되고 있습니다(AsyncImage에 null 전달). 해당 파일에서도 contentDescription을 전달하도록 수정이 필요합니다.


384-387: 접근성: 중심 구슬에도 contentDescription 제공

선택 대상 구슬 이미지에도 이름을 전달하세요.

     EmotionMarbleImage(
         image = emotion.image,
         modifier = Modifier
             .size(size)
+        ,
+        contentDescription = emotion.emotionMarbleName

92-99: 세로 드래그 중 텍스트 표시 타이밍

현재 showText는 가로 스크롤 진행 여부만으로 토글됩니다. 세로 드래그(선택 제스처) 중에도 텍스트를 숨기면 시각적 간섭이 줄어듭니다. offsetY>0일 때 숨기는 로직을 병행 검토해주세요.


425-462: 프리뷰 네트워크 의존도 낮추기

@Preview에서 외부 URL을 사용하면 오프라인/CI에서 로드 실패가 발생할 수 있습니다. 샘플은 리소스 기반 이미지로 대체 권장.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cec77b0 and 1b08cab.

⛔ Files ignored due to path filters (30)
  • core/designsystem/src/main/res/drawable-hdpi/img_ground.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-hdpi/img_marble_pomo.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-hdpi/img_marble_pomo_left_hand.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-hdpi/img_marble_pomo_right_hand.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-hdpi/img_pomo_hand.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-hdpi/img_pomo_thumb.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-mdpi/img_ground.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-mdpi/img_marble_pomo.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-mdpi/img_marble_pomo_left_hand.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-mdpi/img_marble_pomo_right_hand.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-mdpi/img_pomo_hand.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-mdpi/img_pomo_thumb.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-xhdpi/img_ground.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-xhdpi/img_marble_pomo.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-xhdpi/img_marble_pomo_left_hand.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-xhdpi/img_marble_pomo_right_hand.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-xhdpi/img_pomo_hand.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-xhdpi/img_pomo_thumb.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-xxhdpi/img_ground.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-xxhdpi/img_marble_pomo.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-xxhdpi/img_marble_pomo_left_hand.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-xxhdpi/img_marble_pomo_right_hand.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-xxhdpi/img_pomo_hand.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-xxhdpi/img_pomo_thumb.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-xxxhdpi/img_ground.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-xxxhdpi/img_marble_pomo.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-xxxhdpi/img_marble_pomo_left_hand.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-xxxhdpi/img_marble_pomo_right_hand.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-xxxhdpi/img_pomo_hand.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-xxxhdpi/img_pomo_thumb.png is excluded by !**/*.png
📒 Files selected for processing (19)
  • core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/typography/Type.kt (2 hunks)
  • core/designsystem/src/main/res/drawable/ic_double_down_arrow_24.xml (1 hunks)
  • core/designsystem/src/main/res/drawable/ic_double_left_arrow_24.xml (1 hunks)
  • core/designsystem/src/main/res/drawable/ic_double_right_arrow_24.xml (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/common/dimension/DpToPx.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/common/dimension/PxToDp.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/EmotionScreen.kt (2 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/EmotionViewModel.kt (3 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/atom/EmotionMarbleImage.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/atom/SpeechBubbleText.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/template/EmotionLoadingView.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/template/EmotionRecommendRoutineScreen.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/template/SimpleEmotionSelectionScreen.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/template/SwipeEmotionSelectionScreen.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/EmotionImageUiModel.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/EmotionUiModel.kt (2 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/mvi/EmotionIntent.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/mvi/EmotionSideEffect.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/mvi/EmotionState.kt (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (7)
presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/template/EmotionRecommendRoutineScreen.kt (3)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilSelectButton.kt (1)
  • BitnagilSelectButton (38-123)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilTextButton.kt (1)
  • BitnagilTextButton (34-85)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/Theme.kt (1)
  • BitnagilTheme (26-41)
presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/atom/SpeechBubbleText.kt (1)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/Theme.kt (1)
  • BitnagilTheme (26-41)
presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/template/SimpleEmotionSelectionScreen.kt (3)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/block/BitnagilTopBar.kt (1)
  • BitnagilTopBar (22-63)
presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/atom/EmotionMarbleImage.kt (1)
  • EmotionMarbleImage (13-41)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/Theme.kt (1)
  • BitnagilTheme (26-41)
presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/template/EmotionLoadingView.kt (3)
presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/atom/SpeechBubbleText.kt (1)
  • SpeechBubbleText (22-65)
presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/atom/EmotionMarbleImage.kt (1)
  • EmotionMarbleImage (13-41)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/Theme.kt (1)
  • BitnagilTheme (26-41)
presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/template/SwipeEmotionSelectionScreen.kt (3)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/block/BitnagilTopBar.kt (1)
  • BitnagilTopBar (22-63)
presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/atom/EmotionMarbleImage.kt (1)
  • EmotionMarbleImage (13-41)
presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/template/EmotionLoadingView.kt (1)
  • EmotionLoadingView (32-102)
presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/EmotionViewModel.kt (1)
presentation/src/main/java/com/threegap/bitnagil/presentation/common/mviviewmodel/MviViewModel.kt (2)
  • sendSideEffect (23-23)
  • sendIntent (30-37)
presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/EmotionScreen.kt (2)
presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/template/SwipeEmotionSelectionScreen.kt (1)
  • SwipeEmotionSelectionScreen (73-202)
presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/template/SimpleEmotionSelectionScreen.kt (1)
  • SimpleEmotionSelectionScreen (30-79)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (4)
core/designsystem/src/main/res/drawable/ic_double_down_arrow_24.xml (1)

2-5: 사이즈/뷰포트 설정은 적절합니다.

24dp 크기와 24x24 viewport 매핑, strokeWidth=2 설정이 스펙에 부합합니다.

core/designsystem/src/main/res/drawable/ic_double_right_arrow_24.xml (1)

2-5: 기본 스펙은 적절합니다.

24dp/viewport 24 구성과 stroke 설정 일관성 좋습니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/template/EmotionRecommendRoutineScreen.kt (1)

44-45: 상단 여백 54.dp 재확인 요청

이미 statusBarsPadding + top=32.dp가 있어 Spacer(54.dp)가 과도할 수 있습니다. 디자인 기준 대비 중복 여부를 확인해주세요.

presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/EmotionScreen.kt (1)

34-37: 토스트 사이드 이펙트 처리 LGTM

글로벌 토스트 경고 레벨 매핑이 명확하고, 백 네비게이션과 충돌도 없습니다.

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: 0

♻️ Duplicate comments (1)
presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/EmotionUiModel.kt (1)

43-53: CALM 문구 경어 통일(“상태에요” → “상태예요”)

이전 코멘트와 동일 이슈입니다. 다른 항목과 동일하게 경어·마침표를 맞춰주세요.

-                "CALM" -> "평온함은 마음이 고요하고 편안해\n균형을 이루는 상태에요."
+                "CALM" -> "평온함은 마음이 고요하고 편안해\n균형을 이루는 상태예요."
🧹 Nitpick comments (4)
presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/EmotionUiModel.kt (4)

12-16: 색상 리터럴에 L 접미사 추가로 타입 명확화

컨텍스트 기반 추론 대신 Long 리터럴임을 명시하면 오해·회귀를 줄일 수 있습니다.

적용 diff(이 범위):

-    val symbolBackgroundColor: Long = 0xFFEAEBEC,
-    val symbolColor: Long = 0xFF878A93,
+    val symbolBackgroundColor: Long = 0xFFEAEBECL,
+    val symbolColor: Long = 0xFF878A93L,

추가로 아래 범위에도 동일 적용 권장:

  • 55-65, 67-77, 85-86 라인의 색상 리터럴들에 L 접미사 추가.
-                "CALM" -> 0xFFEFECFF
+                "CALM" -> 0xFFEFECFFL
-                "VITALITY" -> 0xFFE9FAD0
+                "VITALITY" -> 0xFFE9FAD0L
-                "LETHARGY" -> 0xFFEAEBEC
+                "LETHARGY" -> 0xFFEAEBECL
-                "ANXIETY" -> 0xFFFFEEE4
+                "ANXIETY" -> 0xFFFFEEE4L
-                "SATISFACTION" -> 0xFFE2F3F6
+                "SATISFACTION" -> 0xFFE2F3F6L
-                "FATIGUE" -> 0xFFFFE1E1
+                "FATIGUE" -> 0xFFFFE1E1L
-                else -> 0xFFEAEBEC
+                else -> 0xFFEAEBECL
-                "CALM" -> 0xFF692BD0
+                "CALM" -> 0xFF692BD0L
-                "VITALITY" -> 0xFF609F00
+                "VITALITY" -> 0xFF609F00L
-                "LETHARGY" -> 0xFF5A5C63
+                "LETHARGY" -> 0xFF5A5C63L
-                "ANXIETY" -> 0xFFFE7120
+                "ANXIETY" -> 0xFFFE7120L
-                "SATISFACTION" -> 0xFF26A792
+                "SATISFACTION" -> 0xFF26A792L
-                "FATIGUE" -> 0xFFFF5151
+                "FATIGUE" -> 0xFFFF5151L
-                else -> 0xFF878A93
+                else -> 0xFF878A93L
-            symbolBackgroundColor = 0xFFEAEBEC,
-            symbolColor = 0xFF171719,
+            symbolBackgroundColor = 0xFFEAEBECL,
+            symbolColor = 0xFF171719L,

22-29: imageUrl 공백/누락 시 즉시 로컬 리소스로 폴백

빈 문자열일 경우 네트워크 로더가 불필요한 시도를 할 수 있습니다. URL이 비어있으면 Resource를 바로 쓰도록 폴백하면 UX 안정적입니다.

-            image = EmotionImageUiModel.Url(
-                url = emotion.imageUrl,
-                offlineBackupImageResourceId = getOfflineBackupImageResourceId(emotion.emotionType),
-            ),
+            image = if (emotion.imageUrl.isNullOrBlank()) {
+                EmotionImageUiModel.Resource(
+                    getOfflineBackupImageResourceId(emotion.emotionType) ?: R.drawable.default_marble
+                )
+            } else {
+                EmotionImageUiModel.Url(
+                    url = emotion.imageUrl,
+                    offlineBackupImageResourceId = getOfflineBackupImageResourceId(emotion.emotionType),
+                )
+            },

55-65: 함수명 오해 소지: “…ColorString” → “…Color”로 변경

반환 타입이 Long인데 이름에 String이 포함되어 혼동을 줍니다. 함수명 및 호출부를 정리하세요.

-private fun getSymbolBackgroundColorString(emotionType: String): Long {
+private fun getSymbolBackgroundColor(emotionType: String): Long {

호출부(라인 27)도 함께 수정:

-            symbolBackgroundColor = getSymbolBackgroundColorString(emotion.emotionType),
+            symbolBackgroundColor = getSymbolBackgroundColor(emotion.emotionType),

67-77: 함수명 오해 소지(2): “…ColorString” → “…Color”

위와 동일 맥락입니다.

-private fun getSymbolColorString(emotionType: String): Long {
+private fun getSymbolColor(emotionType: String): Long {

호출부(라인 28)도 함께 수정:

-            symbolColor = getSymbolColorString(emotion.emotionType),
+            symbolColor = getSymbolColor(emotion.emotionType),
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1b08cab and 15c5ba3.

📒 Files selected for processing (4)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/atom/EmotionMarbleImage.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/template/EmotionRecommendRoutineScreen.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/EmotionImageUiModel.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/EmotionUiModel.kt (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/atom/EmotionMarbleImage.kt
  • presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/EmotionImageUiModel.kt
  • presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/template/EmotionRecommendRoutineScreen.kt
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-10T10:50:55.009Z
Learnt from: l5x5l
PR: YAPP-Github/Bitnagil-Android#126
File: presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/EmotionViewModel.kt:118-139
Timestamp: 2025-09-10T10:50:55.009Z
Learning: In EmotionViewModel.kt, the minimumDelay parameter in selectEmotion() is intentionally designed as a fixed pre-delay for animation effects, not as a minimum total duration guarantee. This is a deliberate UX decision to ensure proper animation timing.

Applied to files:

  • presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/EmotionUiModel.kt
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (1)
presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/EmotionUiModel.kt (1)

79-87: Default 모델 구성 적절

선택 불가 상태, 안내 문구, 기본 이미지/색상 세팅 모두 합리적입니다. 프리뷰/초기 상태에 그대로 사용 가능해 보입니다.

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: 1

♻️ Duplicate comments (1)
presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/EmotionUiModel.kt (1)

43-53: CALM 문구 맞춤법: “상태에요.” → “상태예요.” (이전 코멘트 재반영)

경어 통일을 위해 “예요”로 교정 부탁드립니다.

-                "CALM" -> "평온함은 마음이 고요하고 편안해\n균형을 이루는 상태에요."
+                "CALM" -> "평온함은 마음이 고요하고 편안해\n균형을 이루는 상태예요."
🧹 Nitpick comments (4)
presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/EmotionUiModel.kt (4)

12-16: 색상 타입을 ULong/Color로 정렬해 타입 캐스팅·부호 이슈 예방

Compose Color가 ULong 기반이라 Long 유지 시 toULong()/toLong() 캐스팅이 퍼질 수 있고, 0xFFxxxxxx가 Int로 축소·음수 취급되는 혼란도 유발합니다. ULong으로 두면 안전합니다.

적용 diff:

-    val symbolBackgroundColor: Long = 0xFFEAEBEC,
-    val symbolColor: Long = 0xFF878A93,
+    val symbolBackgroundColor: ULong = 0xFFEAEBECu,
+    val symbolColor: ULong = 0xFF878A93u,

아래 팔레트 함수 반환 타입도 함께 ULong으로 바꿔주세요(라인 55-77 제안 diff 참고).


43-53: 하드코딩 문자열 → string 리소스로 이전 권장

복수 언어/카피 개정 대응을 위해 메시지는 @stringres로 들고 UI 레이어에서 resolve하는 편이 안전합니다.

원하시면 message: Int?(@stringres)로 변경하는 마이그레이션 패치와 strings.xml 초안 만들어드릴게요.


55-65: 배경 색상 팔레트도 ULong으로 통일

상단 타입 변경과 일관성 있게 반환 타입/리터럴 접미사(u) 정리 바랍니다.

-        private fun getSymbolBackgroundColor(emotionType: String): Long {
+        private fun getSymbolBackgroundColor(emotionType: String): ULong {
             return when (emotionType) {
-                "CALM" -> 0xFFEFECFF
-                "VITALITY" -> 0xFFE9FAD0
-                "LETHARGY" -> 0xFFEAEBEC
-                "ANXIETY" -> 0xFFFFEEE4
-                "SATISFACTION" -> 0xFFE2F3F6
-                "FATIGUE" -> 0xFFFFE1E1
-                else -> 0xFFEAEBEC
+                "CALM" -> 0xFFEFECFFu
+                "VITALITY" -> 0xFFE9FAD0u
+                "LETHARGY" -> 0xFFEAEBECu
+                "ANXIETY" -> 0xFFFFEEE4u
+                "SATISFACTION" -> 0xFFE2F3F6u
+                "FATIGUE" -> 0xFFFFE1E1u
+                else -> 0xFFEAEBECu
             }
         }

67-77: 심볼 색상 팔레트 ULong 통일 + 맵 기반 축약 제안

ULong 통일은 위와 동일. 또한 동일 키에 대해 메시지/배경색/심볼색/드로어블 매핑이 중복됩니다. 단일 spec 맵(or enum)으로 관리하면 추가/수정 시 누락 위험을 줄일 수 있습니다.

타입/리터럴 변경 diff:

-        private fun getSymbolColor(emotionType: String): Long {
+        private fun getSymbolColor(emotionType: String): ULong {
             return when (emotionType) {
-                "CALM" -> 0xFF692BD0
-                "VITALITY" -> 0xFF609F00
-                "LETHARGY" -> 0xFF5A5C63
-                "ANXIETY" -> 0xFFFE7120
-                "SATISFACTION" -> 0xFF26A792
-                "FATIGUE" -> 0xFFFF5151
-                else -> 0xFF878A93
+                "CALM" -> 0xFF692BD0u
+                "VITALITY" -> 0xFF609F00u
+                "LETHARGY" -> 0xFF5A5C63u
+                "ANXIETY" -> 0xFFFE7120u
+                "SATISFACTION" -> 0xFF26A792u
+                "FATIGUE" -> 0xFFFF5151u
+                else -> 0xFF878A93u
             }
         }

중복 제거는 별 PR로 분리 권장합니다. 필요 시 enum 기반 스펙 초안 드릴게요.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 15c5ba3 and 6aa82ac.

📒 Files selected for processing (1)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/EmotionUiModel.kt (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (2)
presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/EmotionUiModel.kt (2)

79-87: Default 프리셋 구성 적절합니다

초기 상태에서 비선택(selectable=false), 안내 문구, 기본 마블 리소스까지 일관되게 설정된 점 좋습니다.


12-16: 불필요한 Parcelize 확인: EmotionImageUiModel은 이미 @parcelize로 Parcelable을 구현하고 있습니다.

Likely an incorrect or invalid review comment.

Copy link
Member

@wjdrjs00 wjdrjs00 left a comment

Choose a reason for hiding this comment

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

복잡한 뷰,,, 고생많으셨습니담!

@l5x5l l5x5l requested a review from wjdrjs00 September 12, 2025 10:45
@l5x5l l5x5l merged commit e4d499e into develop Sep 12, 2025
2 checks passed
@l5x5l l5x5l deleted the refactor/#125-redesign_emotion branch September 12, 2025 11:53
@easyhooon
Copy link

어렵겠다..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

세환 🔨 Refactor 기존 기능 개선

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[REFACTOR] 감정 구슬 화면 리디자인 적용

3 participants