Skip to content

[Refactor/#107] 추천 루틴 화면 리다자인 변경 사항을 반영합니다.#113

Merged
wjdrjs00 merged 11 commits intodevelopfrom
refactor/#107-redesign-recommend-routine
Aug 19, 2025
Merged

[Refactor/#107] 추천 루틴 화면 리다자인 변경 사항을 반영합니다.#113
wjdrjs00 merged 11 commits intodevelopfrom
refactor/#107-redesign-recommend-routine

Conversation

@wjdrjs00
Copy link
Copy Markdown
Member

@wjdrjs00 wjdrjs00 commented Aug 19, 2025

[ PR Content ]

추천 루틴 화면 리디자인 변경사항을 반영했습니다.

Related issue

Screenshot 📸

Screen_recording_20250819_154139.mp4

Work Description

  • 컴포넌트 리디자인 변경사항 반영
  • 선택한 난이도에 맞는 추천 루틴이 없는경우 보여지는 난이도 엠티뷰 추가
  • 추천루틴목록 조회 api 응답값 필드 추가

To Reviewers 📢

미구현 사항(미구현 사항들은 화면이 연결되면서 추가적인 작업을 통해 구현해보겠습니다!)

  • 추천 루틴 등록 시, 해당 추천루틴 "추천타입" 전달해주기
  • 감정 등록 시, 루틴목록 조회 다시 받아오기
  • 추천 루틴 등록 성공 시 "등록완료" 토스트 메시지 제공

추천루틴 화면은 기능적인 부분이 변경된 곳은 없고, 디자인 변경으로 인한 자잘하게 수정된 사항들이 대부분입니다요!
추가적인 궁금증, 틀린부분 등등 리뷰로 남겨주세욤

Summary by CodeRabbit

  • 신기능

    • 추천 루틴에 카테고리 유형 추가(아이콘·색상 표시), UI 모델·매핑 업데이트.
    • 난이도 한글 표기 추가(하/중/상) 및 난이도 선택 시 리스트 자동 스크롤.
    • 빈 상태 화면(해당 난이도 루틴이 없어요) 추가.
  • 스타일

    • 추천 리스트 전반 레이아웃·색상·헤더·타이포 개편.
    • 감정 기반 추천 버튼 리디자인 및 별도 CTA 도입, 위치 변경.
    • 카테고리 칩, 루틴 아이템 카드형 레이아웃·세부 루틴 조건부 표시 업데이트.
  • 기타

    • UI 모델·아이템 시그니처 변경 및 일부 기본값 추가.

@wjdrjs00 wjdrjs00 requested a review from l5x5l August 19, 2025 06:52
@wjdrjs00 wjdrjs00 self-assigned this Aug 19, 2025
@wjdrjs00 wjdrjs00 added ✨ Feature 새로운 기능 구현 🧤 대현 labels Aug 19, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Aug 19, 2025

Walkthrough

추천 루틴의 데이터·도메인·프리젠테이션 계층을 개편했습니다. DTO에 recommendedRoutineType을 추가하고 도메인/매핑을 갱신했으며, UI는 화면 리디자인(아이템 구조, 버튼 위치/노출 조건, 난이도 표기, 빈 상태 뷰, 스크롤 동작 등)을 반영했습니다.

Changes

Cohort / File(s) Summary
Data ↔ Domain 매핑 확장
data/.../RecommendedRoutineDto.kt
recommendedRoutineType: String 필드 추가 및 toDomain()에서 RecommendCategory.fromString(recommendedRoutineType)로 매핑
도메인 모델/열거형 갱신
domain/.../RecommendRoutine.kt, domain/.../RecommendLevel.kt
RecommendRoutinerecommendedRoutineType: RecommendCategory 추가; RecommendLevel 생성자에 koreanLevel: String 파라미터(및 enum 상수에 한글 등급값) 추가
프리젠테이션: 메인 화면 리디자인
presentation/.../RecommendRoutineScreen.kt
리스트 상태 관리 및 카테고리 변경 시 상단 스크롤, 배경색 변경, Emotion 버튼 노출 조건·위치 변경, 섹션 제목/난이도 표기 변경, 빈 상태 처리 추가, 아이템 인자 방식 변경
컴포넌트: 카테고리 칩 스타일 수정
presentation/.../component/atom/RecommendCategoryChip.kt
높이·패딩·배경·텍스트 색·타이포 변경(시그니처 유지)
컴포넌트: 감정 추천 버튼 리디자인
presentation/.../component/block/EmotionRecommendRoutineButton.kt
경계 제거·좌측 이미지+우측 CTA 캡슐 구성으로 변경, 클릭 영역을 CTA로 이동(시그니처 유지)
컴포넌트: 추천 루틴 아이템 재구성
presentation/.../component/block/RecommendRoutineItem.kt
시그니처 변경: (routine: RecommendRoutineUiModel)로 전환, 배지·아이콘·세부루틴 섹션 추가 및 레이아웃 전면 수정
템플릿: 빈 상태 뷰 추가
presentation/.../component/template/EmptyRecommendRoutineView.kt
EmptyRecommendRoutineView 컴포저블 추가
템플릿: 난이도 바텀시트 표기 변경
presentation/.../component/template/RecommendLevelBottomSheet.kt
옵션에 optionLevel 추가, “난이도 {한글}
프리젠테이션 상태 모델 변경
presentation/.../model/RecommendRoutineState.kt
isDefaultCategory 제거, shouldShowEmotionButton 추가(선택 카테고리가 PERSONALIZED이고 emotionMarbleType == null)
프리젠테이션 UI 모델 재설계
presentation/.../model/RecommendRoutineUiModel.kt
스키마 변경(설명·시간 제거), recommendedRoutineType 추가, icon/color 속성 추가 및 도메인→UI 매핑 수정
서브루틴 UI 모델 기본값 추가
presentation/.../model/RecommendSubRoutineUiModel.kt
id, name에 기본값 추가 (no-arg 허용)

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Screen as RecommendRoutineScreen
  participant VM as ViewModel
  participant UI as UI Components

  User->>Screen: 카테고리 선택/변경
  Screen->>Screen: LaunchedEffect -> listState.scrollToItem(0)
  Screen->>VM: 선택 상태 업데이트
  VM-->>Screen: uiState (selectedCategory, selectedRecommendLevel, currentRoutines)

  alt selectedCategory == PERSONALIZED && emotionMarbleType == null
    Screen->>UI: EmotionRecommendRoutineButton 표시
  else
    Screen->>UI: 버튼 숨김
  end

  alt currentRoutines.isEmpty && selectedRecommendLevel != null
    Screen->>UI: EmptyRecommendRoutineView 표시
  else
    Screen->>UI: LazyColumn (RecommendRoutineItem(routine) ...)
  end

  User->>UI: CTA(추천받기) 클릭
  UI->>Screen: onClick 콜백 전달
  Note right of Screen: 네비게이션/등록 로직은 기존 핸들러에 위임
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Assessment against linked issues

Objective Addressed Explanation
리디자인 사항 반영 (#107)
추천루틴 조회 API 버전업 (#107) DTO에 필드가 추가되었으나 API 버전명·엔드포인트·계약(버전) 변경 증거가 없음

Possibly related PRs

Suggested reviewers

  • l5x5l

Poem

"깡충깡충 코드밭을 달려, 당근 스니펫 물고서,
칩은 얇게, 버튼은 둥글게, 리스트는 다시 맨 위로.
빈 화면엔 쿠션을, 서브루틴엔 점심 당근,
오늘도 나는 리팩토링 춤을 춰요—흔들흔들 🥕"

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ 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/#107-redesign-recommend-routine

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown

@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

🧹 Nitpick comments (15)
presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/block/EmotionRecommendRoutineButton.kt (5)

29-41: 행 전체에서 CTA만 클릭 가능하도록 변경된 상호작용 범위 — 의도 확인 및 대안 제안

리디자인 의도라면 OK. 다만 기존 대비 접근성/발견성(탭 영역 축소) 저하 가능성이 있습니다. 리스트 아이템 성격이라면 행 전체를 눌러도 onClick 되도록 유지하는 게 사용자 기대치에 부합합니다. 필요 시 아래처럼 Row에 onClick을 주고, CTA는 비클릭으로 두는 방안 고려해 주세요.

 Row(
     modifier = modifier
         .fillMaxWidth()
         .background(
             color = BitnagilTheme.colors.coolGray10,
             shape = RoundedCornerShape(12.dp),
         )
         .padding(
             vertical = 14.dp,
             horizontal = 16.dp,
-        ),
+        )
+        .clickableWithoutRipple { onClick() },
     verticalAlignment = Alignment.CenterVertically,
 ) {

그리고 CTA Box에서는 클릭 제거:

-                .clickableWithoutRipple { onClick() }

51-54: 문자열 하드코딩 제거: stringResource로 국제화/번역 대응

현재 한글 하드코딩입니다. 리소스로 분리해 번역과 테스트 용이성을 확보하는 게 좋습니다.

-        Text(
-            text = "내 기분에 맞는 루틴 추천받기",
+        Text(
+            text = stringResource(PR.string.emotion_recommend_routine_title),
             color = BitnagilTheme.colors.white,
             style = BitnagilTheme.typography.body2SemiBold,
         )
...
-            Text(
-                text = "추천받기",
+            Text(
+                text = stringResource(PR.string.get_recommendation),
                 color = BitnagilTheme.colors.white,
                 style = BitnagilTheme.typography.caption1SemiBold,
             )

필요 import 및 R alias(디자인시스템 R과 충돌 방지):

import androidx.compose.ui.res.stringResource
import com.threegap.bitnagil.presentation.R as PR

strings.xml 예시(제안):

<resources>
    <string name="emotion_recommend_routine_title">내 기분에 맞는 루틴 추천받기</string>
    <string name="get_recommendation">추천받기</string>
</resources>

Also applies to: 70-73


43-45: 이미지 contentDescription 확인

contentDescription = null은 장식용 이미지일 때 적절합니다. 해당 아이콘이 의미 전달(예: 감정 관련)를 한다면 stringResource 기반 설명을 제공해 주세요. 장식용이 확실하면 현 상태 유지 OK.

예시:

contentDescription = stringResource(PR.string.default_marble_desc)

78-84: Preview에 테마 적용 권장

디자인 토큰(BitnagilTheme)을 사용하는 컴포저블은 Preview에서도 테마로 감싸주는 게 안전합니다.

 @Preview
 @Composable
 private fun EmotionRecommendRoutineButtonPreview() {
-    EmotionRecommendRoutineButton(
-        onClick = {},
-    )
+    BitnagilTheme {
+        EmotionRecommendRoutineButton(
+            onClick = {},
+        )
+    }
 }

17-22: 리소스 import 정리(문자열/아이콘 R 충돌 방지)

stringResource 도입 시 디자인시스템 R과 앱/프레젠테이션 R 충돌이 날 수 있으니 alias 사용을 권장합니다.

 import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
 ...
 import com.threegap.bitnagil.designsystem.R
+import com.threegap.bitnagil.presentation.R as PR
domain/src/main/java/com/threegap/bitnagil/domain/recommendroutine/model/RecommendLevel.kt (1)

12-16: 도메인 계층에서의 한글 문자열 반환은 i18n/레이어링 관점에서 재고 필요

toKoreanLevel()은 UI 표시 목적의 한국어 문자열을 도메인에 고정합니다. 다국어 대응이나 레이어 책임 분리를 고려하면, 프레젠테이션 계층의 리소스/매퍼(예: extension in presentation)로 이동하거나 UI에서 string resource를 통해 매핑하는 편이 깔끔합니다.

원하시면 presentation 모듈에 RecommendLevel.toLocalizedLabel(context) 형태의 확장 함수를 생성하는 패치를 제안드릴게요.

presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/atom/RecommendCategoryChip.kt (1)

29-38: 클릭 영역 축소 이슈: clickable는 padding 이후에 적용하세요

현재 modifier 체인에서 clickable가 padding보다 먼저 적용되어, 내부 패딩 영역이 클릭되지 않을 수 있습니다. UX 저하를 유발할 수 있으니 clickable을 가장 마지막(혹은 최소한 padding 이후)으로 이동을 권장합니다. 또한 터치 타겟 접근성 최소 기준(48dp)을 고려하면 height 36dp는 다소 작습니다.

권장 수정안:

-            .background(
+            .background(
                 color = if (!isSelected) BitnagilTheme.colors.white else BitnagilTheme.colors.coolGray10,
                 shape = RoundedCornerShape(20.dp),
             )
-            .height(36.dp)
-            .clickableWithoutRipple(onClick = onCategorySelected)
-            .padding(
-                vertical = 6.dp,
-                horizontal = 14.dp,
-            ),
+            .height(36.dp)
+            .padding(
+                vertical = 6.dp,
+                horizontal = 14.dp,
+            )
+            .clickableWithoutRipple(onClick = onCategorySelected),

가능하다면 높이를 48.dp로 상향하거나, 외곽에 추가 padding을 두고 clickable을 바깥에 적용해 실제 터치 타겟을 확장하는 것도 고려해주세요.

presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/template/RecommendLevelBottomSheet.kt (1)

54-54: 문자열 합치기 기반의 포맷 의존성 축소 제안

"난이도 ${recommendLevel.toKoreanLevel()} | ${recommendLevel.displayName}"처럼 합성 문자열에 split 로직을 의존하는 대신, LevelOption에 두 개의 인자(예: levelLabel: String, displayLabel: String)를 직접 전달하거나, 도메인 객체(혹은 UI 전용 모델)를 넘겨 내부에서 스타일링하는 구조가 더 견고합니다. 표시 구분자나 레이아웃 변화에 강해집니다.

원하시면 LevelOption(levelLabel: String, displayLabel: String, ...) 형태로의 리팩터링 패치를 제안드릴게요.

presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/model/RecommendSubRoutineUiModel.kt (1)

9-11: 기본값(0, "") 도입에 따른 식별자/키 사용 주의

id 기본값 0은 LazyList key 등에서 충돌을 유발할 수 있습니다. 리스트 키로 사용하는 경우 서버에서 오는 실제 id 또는 stable한 고유 키를 사용하도록 점검 부탁드립니다. name의 빈 문자열 기본값도 UI에서 자리 표시나 접근성(컨텐츠 설명) 측면에서 오해를 줄 수 있으니 필요 시 placeholder 처리 고려 바랍니다.

data/src/main/java/com/threegap/bitnagil/data/recommendroutine/model/response/RecommendedRoutineDto.kt (1)

21-25: 호환성 보완: Serialization 오류 방지 위해 필드 기본값 지정 권장

  • 대상 파일
    data/src/main/java/com/threegap/bitnagil/data/recommendroutine/model/response/RecommendedRoutineDto.kt
    위치: 21–25행
  • 변경 제안:
-    @SerialName("recommendedRoutineType")
-    val recommendedRoutineType: String,
+    @SerialName("recommendedRoutineType")
+    val recommendedRoutineType: String? = null,
  • 매핑 로직에서도 recommendedRoutineType이 nullable 및 기본값(null)을 처리하도록 함께 보완해주세요.

이렇게 하면 서버에서 해당 필드를 누락해서 보내더라도 kotlinx.serialization 역직렬화 실패로 인한 앱 크래시 리스크를 방어할 수 있습니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/RecommendRoutineScreen.kt (1)

192-195: TODO 주석 처리가 필요합니다.

추천 카테고리를 함께 전달해야 한다는 TODO가 있습니다. PR 설명에 따르면 향후 작업으로 명시되어 있으니, 이슈로 등록하여 추적하는 것이 좋겠습니다.

추천 카테고리 전달 기능을 구현하는 이슈를 생성하시겠습니까?

presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/model/RecommendRoutineUiModel.kt (4)

15-20: nullable + 기본값 도입으로 하위 사용처 영향 점검 필요

  • level, recommendedRoutineType가 nullable로 변경되었습니다. UI에서 텍스트 변환/표시 시 NPE 또는 불필요한 Elvis 처리 증가 가능성이 있습니다.
  • id 기본값 0은 리스트 키/식별자로 사용 시 충돌 여지가 있으니(실 ID 0 가능성 포함) 의도된 sentinel인지 확인 바랍니다.
  • recommendSubRoutines의 기본 emptyList는 Compose Preview/초기 렌더링에 유익합니다.

가능하면 도메인/디자인 정책에 맞는 안전한 기본값(예: UNKNOWN 레벨/카테고리)을 사용하거나, UI 전용 파생 프로퍼티(safe getter)를 두어 null 분기를 내부로 캡슐화하는 방식을 권장합니다.


4-9: @DrawableRes 어노테이션으로 리소스 타입 안정성 보강 제안

아이콘 리소스 ID에 @DrawableRes를 부여하면 호출/대입 시 타입 안전성이 올라가고 Lint가 잘 잡아줍니다.

아래처럼 import 및 어노테이션을 추가하는 것을 제안합니다.

+import androidx.annotation.DrawableRes
-    val icon: Int
+    @get:DrawableRes
+    val icon: Int
         get() = recommendedRoutineType?.getIcon() ?: R.drawable.ic_shine
-private fun RecommendCategory.getIcon(): Int =
+@DrawableRes
+private fun RecommendCategory.getIcon(): Int =
     when (this) {
       ...
     }

Also applies to: 21-22, 37-38


49-60: 색상 매핑 중복 분기 축약 및 스펙 확인

  • 동일 색상을 사용하는 분기를 묶어 가독성을 개선할 수 있습니다.
  • OUTING_REPORT/UNKNOWN을 yellow10으로 처리한 것이 디자인 스펙과 일치하는지 확인 바랍니다.

축약 예시:

 @Composable
 private fun RecommendCategory.getColor(): Color =
     when (this) {
         RecommendCategory.OUTING -> BitnagilTheme.colors.skyBlue10
         RecommendCategory.WAKE_UP -> BitnagilTheme.colors.orange25
         RecommendCategory.CONNECT -> BitnagilTheme.colors.purple10
         RecommendCategory.REST -> BitnagilTheme.colors.green10
         RecommendCategory.GROW -> BitnagilTheme.colors.pink10
-        RecommendCategory.PERSONALIZED -> BitnagilTheme.colors.yellow10
-        RecommendCategory.OUTING_REPORT -> BitnagilTheme.colors.yellow10
-        RecommendCategory.UNKNOWN -> BitnagilTheme.colors.yellow10
+        RecommendCategory.PERSONALIZED,
+        RecommendCategory.OUTING_REPORT,
+        RecommendCategory.UNKNOWN -> BitnagilTheme.colors.yellow10
     }

21-26: Fallback 동작에 대한 간단한 유닛 테스트 권장

  • recommendedRoutineType=null일 때 icon/color가 기본값(shine/yellow10)으로 나오는지
  • 각 카테고리별 아이콘/색상 매핑이 의도와 일치하는지

작은 단위 테스트로 회귀를 방지할 수 있습니다. 필요하시면 테스트 스켈레톤을 만들어 드리겠습니다.

Also applies to: 37-60

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a29f624 and 2b354fb.

⛔ Files ignored due to path filters (10)
  • core/designsystem/src/main/res/drawable-hdpi/default_ball.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-hdpi/default_marble.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-mdpi/default_ball.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-mdpi/default_marble.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-xhdpi/default_ball.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-xhdpi/default_marble.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-xxhdpi/default_ball.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-xxhdpi/default_marble.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-xxxhdpi/default_ball.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-xxxhdpi/default_marble.png is excluded by !**/*.png
📒 Files selected for processing (12)
  • data/src/main/java/com/threegap/bitnagil/data/recommendroutine/model/response/RecommendedRoutineDto.kt (3 hunks)
  • domain/src/main/java/com/threegap/bitnagil/domain/recommendroutine/model/RecommendLevel.kt (1 hunks)
  • domain/src/main/java/com/threegap/bitnagil/domain/recommendroutine/model/RecommendRoutine.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/RecommendRoutineScreen.kt (7 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/atom/RecommendCategoryChip.kt (2 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/block/EmotionRecommendRoutineButton.kt (2 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/block/RecommendRoutineItem.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/template/EmptyRecommendRoutineView.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/template/RecommendLevelBottomSheet.kt (3 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/model/RecommendRoutineState.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/model/RecommendRoutineUiModel.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/model/RecommendSubRoutineUiModel.kt (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/block/RecommendRoutineItem.kt (1)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilIcon.kt (1)
  • BitnagilIcon (21-33)
presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/template/RecommendLevelBottomSheet.kt (1)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilIcon.kt (1)
  • BitnagilIcon (21-33)
presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/RecommendRoutineScreen.kt (4)
presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/block/EmotionRecommendRoutineButton.kt (1)
  • EmotionRecommendRoutineButton (24-76)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilIcon.kt (1)
  • BitnagilIcon (21-33)
presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/template/EmptyRecommendRoutineView.kt (1)
  • EmptyRecommendRoutineView (14-45)
presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/block/RecommendRoutineItem.kt (1)
  • RecommendRoutineItem (28-104)
🔇 Additional comments (14)
presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/block/EmotionRecommendRoutineButton.kt (1)

56-57: LGTM: Spacer(weight=1f)로 우측 CTA 정렬

리스트 내 아이템 배치 간결하고 의도에 부합합니다.

data/src/main/java/com/threegap/bitnagil/data/recommendroutine/model/response/RecommendedRoutineDto.kt (1)

34-36: fromString 폴백 로직 이미 구현됨 — 추가 변경 불필요
RecommendCategory.fromString은 알 수 없는 categoryName에 대해 UNKNOWN으로 폴백 처리되며, DTO의 recommendedRoutineType: String 필드는 non-nullable이라 null이 들어올 수 없습니다. 제안하신 ?: "" 코드는 컴파일되지 않을 뿐더러 불필요하니 그대로 두셔도 안전합니다.

  • domain/src/main/java/com/threegap/bitnagil/domain/recommendroutine/model/RecommendCategory.kt: fromString에서 ?: UNKNOWN 폴백 구현
  • data/src/main/java/com/threegap/bitnagil/data/recommendroutine/model/response/RecommendedRoutineDto.kt: recommendedRoutineType은 non-nullable String

Likely an incorrect or invalid review comment.

domain/src/main/java/com/threegap/bitnagil/domain/recommendroutine/model/RecommendRoutine.kt (1)

9-9: LGTM! 추천 루틴 타입 필드 추가가 적절합니다.

리디자인 요구사항에 따라 recommendedRoutineType 필드가 추가되었습니다. 이 변경은 UI에서 루틴별 아이콘과 색상을 표시하는 데 필요한 정보를 제공합니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/template/EmptyRecommendRoutineView.kt (1)

14-45: Empty 상태 뷰 구현이 잘 되었습니다.

난이도별 추천 루틴이 없을 때 표시되는 Empty 뷰가 적절하게 구현되었습니다. 디자인 가이드라인에 맞는 스타일과 메시지를 제공합니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/model/RecommendRoutineState.kt (1)

18-19: 감정 버튼 표시 로직이 명확하게 구현되었습니다.

shouldShowEmotionButton 프로퍼티가 적절한 조건으로 구현되었습니다. PERSONALIZED 카테고리이면서 감정이 선택되지 않은 경우에만 버튼을 표시하는 로직이 명확합니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/block/RecommendRoutineItem.kt (3)

29-33: 데이터 모델 기반 API로 변경이 적절합니다.

개별 문자열 파라미터 대신 RecommendRoutineUiModel 객체를 받도록 변경한 것이 좋습니다. 데이터의 일관성과 확장성이 향상됩니다.


76-102: 세부 루틴 표시 구현이 잘 되었습니다.

세부 루틴이 있을 때만 조건부로 렌더링하는 로직이 적절합니다. 구분선과 함께 세부 루틴 목록을 깔끔하게 표시합니다.


49-58: routine.color의 null 처리 불필요
현재 RecommendRoutineUiModel에서 color는 non-nullable Color 타입이며, 내부 구현이 recommendedRoutineType이 null일 때도
BitnagilTheme.colors.yellow10을 기본값으로 반환하도록 되어 있어 추가 처리가 필요 없습니다:

val color: Color
    @Composable get() = recommendedRoutineType?.getColor() ?: BitnagilTheme.colors.yellow10

해당 코멘트는 무시하셔도 좋습니다.

Likely an incorrect or invalid review comment.

presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/RecommendRoutineScreen.kt (3)

85-91: 스크롤 위치 초기화 로직이 적절합니다.

카테고리 변경 시 리스트를 맨 위로 스크롤하는 기능이 잘 구현되었습니다. 사용자 경험을 개선하는 좋은 추가사항입니다.


121-128: 감정 버튼 조건부 렌더링이 잘 구현되었습니다.

shouldShowEmotionButton 상태에 따라 감정 추천 버튼을 표시하는 로직이 적절합니다.


173-177: Empty 상태 처리가 적절합니다.

난이도가 선택되었지만 해당하는 루틴이 없을 때 EmptyRecommendRoutineView를 표시하는 로직이 잘 구현되었습니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/model/RecommendRoutineUiModel.kt (3)

4-9: Compose/Theme 및 R 의존성 추가 방향 문제 없습니다

카테고리별 색상/아이콘 계산 로직 도입에 따른 import 정리가 일관적입니다.


28-35: toUiModel 매핑 LGTM

도메인 모델에서 recommendedRoutineType을 전달하고, 서브루틴 매핑도 일관됩니다. 제거된 필드(description/executionTime)와 충돌하는 부분도 없습니다.


37-47: 아이콘 매핑 중복 분기 병합 제안 및 드로어블 리소스 재확인

  • RecommendCategory.PERSONALIZED, OUTING_REPORT, UNKNOWN이 같은 R.drawable.ic_shine 을 가리키므로 한 줄로 묶어 가독성과 유지보수성 향상 권장
  • 아래 리소스가 실제로 프로젝트 내에 존재하는지 확인 필요
    • ic_outside
    • ic_wakeup
    • ic_connect
    • ic_rest
    • ic_grow
    • ic_shine
      경로 예시:
    • presentation/src/main/res/drawable/
    • presentation/src/main/res/drawable-/

중복 분기 축약 예시:

 private fun RecommendCategory.getIcon(): Int =
     when (this) {
         RecommendCategory.OUTING       -> R.drawable.ic_outside
         RecommendCategory.WAKE_UP      -> R.drawable.ic_wakeup
         RecommendCategory.CONNECT      -> R.drawable.ic_connect
         RecommendCategory.REST         -> R.drawable.ic_rest
         RecommendCategory.GROW         -> R.drawable.ic_grow
-        RecommendCategory.PERSONALIZED -> R.drawable.ic_shine
-        RecommendCategory.OUTING_REPORT -> R.drawable.ic_shine
-        RecommendCategory.UNKNOWN       -> R.drawable.ic_shine
+        RecommendCategory.PERSONALIZED,
+        RecommendCategory.OUTING_REPORT,
+        RecommendCategory.UNKNOWN       -> R.drawable.ic_shine
     }

Copy link
Copy Markdown

@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

🧹 Nitpick comments (20)
presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/atom/RecommendCategoryChip.kt (2)

30-37: 선택/비선택 배경색 반전은 의도 이해. 다만 터치 타깃(36dp)·경계 가시성·접근성(선택 상태 표시) 보완을 권장

  • 36dp 고정 높이는 Material 가이드(최소 48dp) 미만이라 터치 미스가 늘 수 있습니다.
  • 비선택 배경이 white로 바뀌면서 컨테이너도 white인 경우 칩 경계가 흐려질 수 있습니다(보더 필요 가능).
  • 현재 clickableWithoutRipple을 쓰고 있어 접근성(선택 상태 전달) 관점에서 semantics/selectable 도입이 안전합니다.

권장 수정:

  • 최소 터치 타깃 확보: minimumInteractiveComponentSize() 또는 heightIn(min = 48.dp) 사용.
  • 경계 가시성: 비선택 상태에 한해 1dp 보더(coolGray20 등) 고려.
  • 접근성: selectable(role = Role.Tab, selected = isSelected, indication = null)로 semantics 부여.

예시 diff:

-                shape = RoundedCornerShape(20.dp),
+                shape = RoundedCornerShape(18.dp),
@@
-            .height(36.dp)
+            .minimumInteractiveComponentSize()

minimumInteractiveComponentSize 사용 시 import가 필요합니다:

import androidx.compose.material3.minimumInteractiveComponentSize

보더는 조건부 적용이므로 체이닝 내 if/then으로 처리하거나 확장함수(conditional Modifier) 사용을 권장합니다.

필요 시 selectable로 대체하는 예시(참고용):

modifier = modifier
    .selectable(
        selected = isSelected,
        onClick = onCategorySelected,
        role = Role.Tab,
        indication = null,
        interactionSource = remember { MutableInteractionSource() },
    )

원하시면 conditional Modifier 확장과 함께 보더/semantics 반영한 패치를 만들어드리겠습니다.


42-43: 선택 상태 텍스트 색상 대비 확인 필요(white on coolGray10 가능성) — 대비 부족 시 텍스트 색상 조정 권장

배경이 coolGray10일 때 텍스트를 white로 유지하면 대비가 부족할 수 있습니다. 디자인 의도대로 배경을 밝게 가져가려면 선택 상태 텍스트를 진한 톤(예: navy500 또는 coolGray90)으로 변경하는 편이 안전합니다.

제안 diff:

-            color = if (!isSelected) BitnagilTheme.colors.coolGray60 else BitnagilTheme.colors.white,
+            color = if (!isSelected) BitnagilTheme.colors.coolGray60 else BitnagilTheme.colors.navy500,
  • 타이포 변화(caption1Medium/ caption1SemiBold)는 의도대로 보입니다. 다만 대비 이슈가 남으면 굵기보다 색상을 먼저 조정하는 것이 효과적입니다.

디자인 토큰(coolGray10/white/navy500)의 실제 HEX를 공유해주시면 명도 대비(AA/AAA) 기준 충족 여부를 계산해 드릴 수 있습니다.

domain/src/main/java/com/threegap/bitnagil/domain/recommendroutine/model/RecommendLevel.kt (1)

12-16: 도메인 계층에 하드코딩된 현지화 문자열이 섞여 있습니다 — 프레젠테이션 레이어로 이동 권장

toKoreanLevel()이 도메인 모델에서 직접 "하/중/상" 한글 문자열을 반환하고 있어 i18n/로캘 변경에 취약합니다. 도메인은 UI 언어에 독립적인 값을 유지하고, 표시 문자열은 Presentation에서 stringResource로 매핑하는 편이 유지보수에 유리합니다.

제안 1) Presentation 모듈에 확장함수로 옮기기:

// presentation 모듈 어딘가
fun RecommendLevel.labelRes(): Int = when (this) {
    RecommendLevel.LEVEL1 -> R.string.recommend_level_low   // "하"
    RecommendLevel.LEVEL2 -> R.string.recommend_level_mid   // "중"
    RecommendLevel.LEVEL3 -> R.string.recommend_level_high  // "상"
}

사용 예:

Text(text = stringResource(id = selectedRecommendLevel.labelRes()))

제안 2) 혹은 "LOW/MID/HIGH" 같은 중립 레이블(또는 별도 enum/값 객체)을 도메인이 반환하고, UI에서만 현지화 문자열로 변환하세요.

presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/template/RecommendLevelBottomSheet.kt (3)

54-54: " | " 구분자 기반 문자열 분할은 취약합니다 — LevelOption에 구조화된 파라미터로 변경 제안

optionText를 "난이도 X | 설명" 형태의 단일 문자열로 만들고 split(" | ")로 파싱하는 방식은 i18n, 카피 변경, 구분자 포함 가능성에 취약합니다. left/right 텍스트를 별도 파라미터로 받아 AnnotatedString을 구성하도록 변경하면 견고합니다.

아래 diff 적용을 제안합니다:

@@
-                LevelOption(
-                    optionText = "난이도 ${recommendLevel.toKoreanLevel()} | ${recommendLevel.displayName}",
+                LevelOption(
+                    leftText = "난이도 ${recommendLevel.toKoreanLevel()}",
+                    rightText = recommendLevel.displayName,
                     isSelected = selectedRecommendLevel == recommendLevel,
                     onClick = {
@@
-private fun LevelOption(
-    optionText: String,
+private fun LevelOption(
+    leftText: String,
+    rightText: String,
     isSelected: Boolean,
     onClick: () -> Unit,
     modifier: Modifier = Modifier,
 ) {
@@
-        val parts = optionText.split(" | ")
-        val annotatedString = buildAnnotatedString {
-            if (parts.size >= 2) {
+        val annotatedString = buildAnnotatedString {
             withStyle(
                 style = BitnagilTheme.typography.body1SemiBold.toSpanStyle(),
             ) {
-                append(parts[0])
+                append(leftText)
             }
 
             withStyle(
                 style = SpanStyle(
                     color = BitnagilTheme.colors.coolGray10,
                     baselineShift = BaselineShift(0.1f),
                 ),
             ) {
                 append(" | ")
             }
 
             withStyle(
                 style = BitnagilTheme.typography.body1Regular.toSpanStyle(),
             ) {
-                append(parts[1])
+                append(rightText)
             }
-            }
         }
@@
-        optionText = "난이도 상 | 가볍게 할 수 있어요",
+        leftText = "난이도 상",
+        rightText = "가볍게 할 수 있어요",
         isSelected = true,
         onClick = {},

추가로, "난이도 ${...}" 문자열은 stringResource 포맷으로 전환하면 i18n에 안전합니다. (예: stringResource(R.string.level_prefix, recommendLevel.toKoreanLevel()))

Also applies to: 82-86, 93-117, 119-123, 134-141


54-54: 하드코딩된 한글 문자열을 stringResource로 이전 권장

"난이도 ..." 문자열을 string 리소스로 분리하면 다국어 대응과 A/B 테스트에 유리합니다. 포맷 리소스(예: "난이도 %s")를 만들어 사용해 주세요.


102-109: 분리자 색상 지정은 중복입니다

Text의 color가 이미 coolGray10으로 지정되어 있어, 분리자 " | "에 동일 색상을 SpanStyle로 다시 지정하는 부분은 불필요합니다. 유지해도 무방하지만 제거하면 코드가 간결해집니다.

-                withStyle(
-                    style = SpanStyle(
-                        color = BitnagilTheme.colors.coolGray10,
-                        baselineShift = BaselineShift(0.1f),
-                    ),
-                ) {
+                withStyle(
+                    style = SpanStyle(
+                        baselineShift = BaselineShift(0.1f),
+                    ),
+                ) {
                     append(" | ")
                 }
presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/template/EmptyRecommendRoutineView.kt (3)

22-33: 중앙 정렬 가독성 개선: textAlign/width 지정 권장

Column 정중앙 정렬만으로는 멀티라인 시 시각적 중심이 어긋날 수 있습니다. 각 Text에 textAlign = Center 및 fillMaxWidth를 부여해 중앙 정렬을 보장하는 것을 권장합니다.

         Text(
             text = "해당 난이도 루틴이 없어요",
             color = BitnagilTheme.colors.coolGray30,
             style = BitnagilTheme.typography.subtitle1SemiBold.copy(
                 lineHeightStyle = LineHeightStyle(
                     alignment = LineHeightStyle.Alignment.Center,
                     trim = LineHeightStyle.Trim.None,
                 ),
             ),
-            modifier = Modifier.padding(bottom = 2.dp),
+            modifier = Modifier
+                .fillMaxWidth()
+                .padding(bottom = 2.dp),
+            textAlign = TextAlign.Center,
         )
@@
         Text(
             text = "다른 난이도를 살펴보거나 루틴을 추가해 보세요.",
             color = BitnagilTheme.colors.coolGray70,
             style = BitnagilTheme.typography.body2Regular.copy(
                 lineHeightStyle = LineHeightStyle(
                     alignment = LineHeightStyle.Alignment.Center,
                     trim = LineHeightStyle.Trim.None,
                 ),
             ),
+            modifier = Modifier.fillMaxWidth(),
+            textAlign = TextAlign.Center,
         )

참고: 위 변경에는 import 추가가 필요합니다.

import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.ui.text.style.TextAlign

Also applies to: 34-43


23-23: 하드코딩된 문구를 stringResource로 이전하세요

한글 고정 문자열을 string 리소스로 옮기면 다국어/카피 변경에 유연합니다. 예) stringResource(R.string.empty_recommend_primary), stringResource(R.string.empty_recommend_secondary)

Also applies to: 35-35


19-21: 175.dp 매직 넘버 대신 디자인 토큰/리소스 사용 권장

고정 top padding(175.dp)은 기기/폰트 스케일에 따라 레이아웃 균형이 깨질 수 있습니다. Spacer + weight, verticalArrangement, 또는 dimen 리소스/테마 스페이싱 토큰을 사용하는 방식을 고려해 주세요.

presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/block/EmotionRecommendRoutineButton.kt (3)

58-67: CTA 터치 타겟이 38dp로 권고 기준(>=48dp)에 미달합니다

Material/Android 접근성 가이드에서 최소 48dp 터치 타겟을 권장합니다. 높이를 48dp 이상으로 상향해 주세요.

-                .height(38.dp)
+                .height(48.dp)

대안: 고정 height 대신 .heightIn(min = 48.dp)로 유연하게 보장하는 방법도 있습니다.


50-54: 하드코딩된 문구를 stringResource로 이전하세요

"내 기분에 맞는 루틴 추천받기", "추천받기"는 string 리소스로 옮기면 i18n/카피 변경에 유연합니다.

Also applies to: 70-73


65-65: 리플 피드백 부재 — 클릭 시 시각적 피드백 고려

clickableWithoutRipple은 미려하지만 피드백 부재로 체감 반응성이 떨어질 수 있습니다. 디자인 허용 시 ripple 또는 pressed state 변화를 도입하는 것을 권장합니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/model/RecommendSubRoutineUiModel.kt (1)

9-10: 기본 id 값 충돌 방지 제안: -1 사용
현재 코드에서 RecommendSubRoutineUiModel

  • id = 1 (예시)
  • id = this.id (toUiModel 변환)
    등으로만 호출되고, 기본값 0이 실제로 사용되거나 id == 0 분기 로직도 없습니다.
    그럼에도 placeholder 용도의 기본값은 서버에서 유효할 수 없는 값인 -1로 두는 편이 안전합니다.

적용 예시:

-    val id: Int = 0,
+    val id: Int = -1,
data/src/main/java/com/threegap/bitnagil/data/recommendroutine/model/response/RecommendedRoutineDto.kt (1)

21-25: 신규 필드 recommendedRoutineType 역직렬화 안정성 보강 권장

서버/클라이언트 릴리스 타이밍 차이, 캐시된 응답, A/B 실험 등으로 필드가 누락되거나 알 수 없는 값이 올 수 있습니다. 현재는 필수(String) + 직접 변환으로 실패 시 크래시 가능성이 있습니다. 역직렬화 기본값과 매핑 시 안전한 폴백을 두는 것을 권장합니다.

적용 예시:

 @Serializable
 data class RecommendedRoutineDto(
@@
-    @SerialName("recommendedRoutineType")
-    val recommendedRoutineType: String,
+    @SerialName("recommendedRoutineType")
+    val recommendedRoutineType: String? = null,
@@
 fun RecommendedRoutineDto.toDomain(): RecommendRoutine =
     RecommendRoutine(
@@
-        recommendedRoutineType = RecommendCategory.fromString(recommendedRoutineType),
+        // 서버에서 누락되거나 미지의 값일 경우 기본 카테고리로 폴백
+        recommendedRoutineType = recommendedRoutineType
+            ?.let { runCatching { RecommendCategory.fromString(it) }.getOrNull() }
+            ?: RecommendCategory.PERSONALIZED,
         recommendSubRoutines = recommendedSubRoutineSearchResult.map { it.toDomain() },
     )
  • Domain의 RecommendCategory에 Unknown/기본값이 존재하는지, fromString이 예외를 던지는지 확인 부탁드립니다. Unknown이 있다면 위 폴백을 Unknown으로 교체하는 편이 더 명확합니다.

Also applies to: 34-36

presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/block/RecommendRoutineItem.kt (2)

88-100: 하드코딩된 문자열은 string 리소스로 이동 권장 ("세부 루틴", 불릿 라인)

국제화/번역/일관된 카피 관리를 위해 하드코딩 문구는 string 리소스로 추출하는 것을 권장합니다. 불릿 접두사("• ")도 포맷 문자열로 관리하면 좋습니다.

예:

  • strings.xml
    • key: recommend_detail_title → "세부 루틴"
    • key: recommend_detail_item → "• %1$s"
  • 코드:
    • text = stringResource(R.string.recommend_detail_title)
    • text = stringResource(R.string.recommend_detail_item, subRoutine.name)

69-73: 플로팅 추가 아이콘 접근성 고려

카드 전체가 클릭 가능한 지금 구조라도, 보조기기 사용자에게는 명시적인 역할 노출이 도움됩니다. Plus 아이콘에 contentDescription 또는 Semantics를 부여해 “루틴 추가” 역할을 노출하는 것을 고려해 주세요.

예:

  • Modifier.semantics { contentDescription = "루틴 추가" }
presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/RecommendRoutineScreen.kt (2)

141-144: 헤더 카피 가독성 및 i18n 개선 제안

"추천 루틴리스트"는 띄어쓰기 없이 붙어 가독성이 떨어집니다. "추천 루틴 리스트" 또는 “추천 루틴 목록” 등으로 수정하고 string 리소스로 이동하는 것을 권장합니다.

적용 예시(텍스트만 수정):

-                text = "추천 루틴리스트",
+                text = "추천 루틴 리스트",

추가로 strings.xml로 추출하여 stringResource 사용을 추천합니다.


186-196: TODO: 추천 카테고리 함께 전달 미구현 — 파라미터 확장 제안

등록 화면으로 네비게이션 시 routine.id만 전달 중이며, PR 설명의 미구현 항목(추천타입 전달)이 남아있습니다. onRegisterRoutineClick 시그니처를 확장하거나, NavArgs에 recommendedRoutineType을 포함하는 방향을 제안합니다.

예시(호출부 기준):

- onRegisterRoutineClick(routine.id.toString())
+ onRegisterRoutineClick(
+   routine.id.toString(),
+   routine.recommendedRoutineType  // NavArgs/모델에 맞게 직렬화 형태 결정
+ )

필요하시면 네비게이션 인자/라우트 정의까지 변경안 작성해 드리겠습니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/model/RecommendRoutineUiModel.kt (2)

13-14: Compose 재구성 효율 향상을 위해 @immutable 추가 제안

UI 모델이 불변 데이터로 쓰이는 만큼 @immutable을 부여하면 Compose가 변경 추적을 더 효율적으로 수행할 수 있습니다.

다음 변경을 제안합니다:

-@Parcelize
-data class RecommendRoutineUiModel(
+@Parcelize
+@Immutable
+data class RecommendRoutineUiModel(

추가 import (선택):

import androidx.compose.runtime.Immutable

15-26: 카테고리 기본값을 UNKNOWN으로 고정해 널 분기/폴백 제거 (+ @DrawableRes 명시)

도메인에서 recommendedRoutineType이 비널로 전달된다면 UI 모델도 기본값을 UNKNOWN으로 두고, 아이콘/컬러에서 널 안전 연산자와 폴백을 제거하는 편이 명확합니다. 또한 icon에는 @DrawableRes를 붙여 타입 안정성을 높이는 것을 권장합니다.

-    val recommendedRoutineType: RecommendCategory? = null,
+    val recommendedRoutineType: RecommendCategory = RecommendCategory.UNKNOWN,
@@
-    val icon: Int
-        get() = recommendedRoutineType?.getIcon() ?: R.drawable.ic_shine
+    @DrawableRes
+    val icon: Int
+        get() = recommendedRoutineType.getIcon()
@@
-    val color: Color
-        @Composable get() = recommendedRoutineType?.getColor() ?: BitnagilTheme.colors.yellow10
+    val color: Color
+        @Composable get() = recommendedRoutineType.getColor()

아이콘 어노테이션용 import:

import androidx.annotation.DrawableRes

참고: level도 도메인에서 항상 존재한다면 비널로 유지할 필요가 없을 수 있습니다. 도메인 보장 여부에 따라 비널 → 널 제거도 고려해 주세요.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a29f624 and 2b354fb.

⛔ Files ignored due to path filters (10)
  • core/designsystem/src/main/res/drawable-hdpi/default_ball.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-hdpi/default_marble.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-mdpi/default_ball.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-mdpi/default_marble.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-xhdpi/default_ball.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-xhdpi/default_marble.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-xxhdpi/default_ball.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-xxhdpi/default_marble.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-xxxhdpi/default_ball.png is excluded by !**/*.png
  • core/designsystem/src/main/res/drawable-xxxhdpi/default_marble.png is excluded by !**/*.png
📒 Files selected for processing (12)
  • data/src/main/java/com/threegap/bitnagil/data/recommendroutine/model/response/RecommendedRoutineDto.kt (3 hunks)
  • domain/src/main/java/com/threegap/bitnagil/domain/recommendroutine/model/RecommendLevel.kt (1 hunks)
  • domain/src/main/java/com/threegap/bitnagil/domain/recommendroutine/model/RecommendRoutine.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/RecommendRoutineScreen.kt (7 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/atom/RecommendCategoryChip.kt (2 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/block/EmotionRecommendRoutineButton.kt (2 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/block/RecommendRoutineItem.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/template/EmptyRecommendRoutineView.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/template/RecommendLevelBottomSheet.kt (3 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/model/RecommendRoutineState.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/model/RecommendRoutineUiModel.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/model/RecommendSubRoutineUiModel.kt (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/template/RecommendLevelBottomSheet.kt (1)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilIcon.kt (1)
  • BitnagilIcon (21-33)
presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/block/RecommendRoutineItem.kt (1)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilIcon.kt (1)
  • BitnagilIcon (21-33)
presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/RecommendRoutineScreen.kt (4)
presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/block/EmotionRecommendRoutineButton.kt (1)
  • EmotionRecommendRoutineButton (24-76)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilIcon.kt (1)
  • BitnagilIcon (21-33)
presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/template/EmptyRecommendRoutineView.kt (1)
  • EmptyRecommendRoutineView (14-45)
presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/block/RecommendRoutineItem.kt (1)
  • RecommendRoutineItem (28-104)
🔇 Additional comments (17)
presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/atom/RecommendCategoryChip.kt (1)

7-7: height import 추가 적절

고정 높이 적용을 위한 import 추가는 변경 의도와 일치합니다.

domain/src/main/java/com/threegap/bitnagil/domain/recommendroutine/model/RecommendLevel.kt (1)

12-16: 복사체계/UX 카피 확인 필요: "하/중/상" 표기가 최신 디자인/카피 가이드와 일치하는지 확인해 주세요

레벨 표기 방식(예: "하/중/상" vs "낮음/보통/높음")은 디자인/카피 팀 기준에 따라 변경 가능성이 큽니다. 도메인에 고정 문자열이 있으면 변경 반영이 어려우니, 현행 표기가 맞는지 확인 부탁드립니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/template/RecommendLevelBottomSheet.kt (1)

127-129: 선택 표시 아이콘 교체 LGTM

ic_check_md로의 교체와 테마 색상 사용이 일관되고 명확합니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/block/EmotionRecommendRoutineButton.kt (2)

29-41: Row 전체 클릭 → CTA만 클릭으로 변경된 상호작용 의도 확인 필요

컴포넌트 좌측(이미지/문구) 터치는 반응하지 않고, 우측 Pill만 클릭 가능합니다. 기존 대비 상호작용 범위 축소가 의도된 리디자인인지 확인 부탁드립니다.

Also applies to: 58-67


42-48: 이미지 접근성 확인: contentDescription = null 적절성

순수 장식용이라면 null이 맞습니다. 만약 의미가 있다면 적절한 contentDescription을 제공해야 합니다.

domain/src/main/java/com/threegap/bitnagil/domain/recommendroutine/model/RecommendRoutine.kt (2)

9-11: 필드 추가 자체는 LGTM

recommendedRoutineType 도입으로 카테고리 표현이 명확해졌습니다.


9-11: 생성자 변경 영향 점검 및 매핑 로직 검증 결과

  • RecommendedRoutineDto.toDomain()RecommendRoutinesDto.toDomain()에서
    · 신규 필드(recommendedRoutineType, recommendSubRoutines) 모두 반영 완료
  • RecommendCategory.fromString 구현부(entries.find { … } ?: UNKNOWN)로 UNKNOWN fallback 처리 확인
  • app/src/.../navigation/home/HomeRoute.ktRecommendRoutine(...) 호출은 패키지 임포트(…)로 보아 도메인 모델이 아닌 네비게이션 전용 로컬 클래스일 가능성이 큽니다.

앱 전체에서 도메인 RecommendRoutine 생성자가 호출되는 곳이 DTO 매핑부 외에 없는지, 그리고 HomeRoute 측 호출이 실제 도메인 클래스가 아닌지 최종 확인 부탁드립니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/model/RecommendRoutineState.kt (2)

18-20: Emotion 버튼 노출 조건 로직 적절합니다.

PERSONALIZED 카테고리이면서 감정 선택 전(emotionMarbleType == null)일 때만 노출하는 의도가 코드로 명확하게 반영되었습니다.


9-17: 불필요한 Parcelable 선언 요청 무시
MviState 인터페이스가 이미 Parcelable을 상속하고 있어, RecommendRoutineState는 : MviState 만으로도 Parcelable을 구현합니다. @Parcelize 어노테이션만으로 정상 동작하므로 별도 Parcelable 상속 선언은 필요하지 않습니다.

Likely an incorrect or invalid review comment.

presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/block/RecommendRoutineItem.kt (2)

28-43: 아이템 컴포넌트 API 단일화 및 카드 인터랙션 정리 좋습니다

routine 단일 모델로 Props를 수렴하고 카드 전체 클릭으로 액션을 일원화한 점이 명확하고 유지보수에 유리합니다. 배경/라운딩/패딩도 디자인 가이드를 잘 따르고 있습니다.


49-58: 아이콘 틴트 null 처리 + 배경색 배지 구현 적절

BitnagilIcon의 기본 틴트를 무력화(null)하고 배경색을 별도 레이어로 주어 아이콘 본연의 색상을 유지하는 접근이 맞습니다. 재사용성도 좋습니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/RecommendRoutineScreen.kt (3)

85-91: 카테고리 변경 시 리스트 최상단 스크롤 처리 적절

rememberLazyListState + LaunchedEffect로 포지션 리셋을 부드럽게 처리한 구현이 깔끔합니다. 불필요한 애니메이션 방지를 위한 가드도 좋습니다.


121-128: Emotion 추천 버튼의 스크린 상단 배치 전환 적합

uiState.shouldShowEmotionButton으로 상단에서 조건부 노출하는 변경이 사용자 시선 흐름에 더 자연스럽습니다. spacing도 적절합니다.


173-177: 난이도 선택 후 빈 상태(Empty) 노출 로직 합리적

선택된 난이도가 있고 결과가 비었을 때만 Empty 뷰를 노출하는 조건이 UX에 부합합니다. 빈 상태 컴포넌트도 톤/타이포가 일관적입니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/model/RecommendRoutineUiModel.kt (3)

4-8: Compose/디자인시스템 의존 추가 적절

프레젠테이션(UI) 모델에서 Color/Theme 및 디자인 리소스(R) 의존을 갖는 방향은 이번 리디자인 목적과 일치하며, 책임 범위도 적절해 보입니다.


28-35: toUiModel 매핑 적절

도메인 모델의 recommendedRoutineType과 서브 루틴 매핑이 일관되게 반영되어 있습니다.


37-60: 아이콘 리소스 존재 확인 완료
모든 기대 아이콘(ic_outside, ic_wakeup, ic_connect, ic_rest, ic_grow, ic_shine)이 정상적으로 존재함을 확인했습니다.

컬러 스펙 확인 요청

  • OUTING_REPORT, PERSONALIZED, UNKNOWN이 모두 BitnagilTheme.colors.yellow10으로 매핑되어 있습니다. 디자인 스펙 상 해당 세 카테고리에 동일한 색상을 사용하는 것이 맞는지 검토 부탁드립니다.

Copy link
Copy Markdown

@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

🧹 Nitpick comments (6)
presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/RecommendRoutineScreen.kt (3)

141-144: 문구 니트픽 — ‘추천 루틴리스트’ 띄어쓰기 권장

가독성을 위해 ‘추천 루틴 리스트’ 또는 ‘추천 루틴 목록’으로의 변경을 제안드립니다. 디자인 카피 가이드에 맞춰 선택해 주세요.

-            Text(
-                text = "추천 루틴리스트",
+            Text(
+                text = "추천 루틴 목록",
                 color = BitnagilTheme.colors.coolGray60,
                 style = BitnagilTheme.typography.body2SemiBold,

155-157: 하드코딩 문자열 리소스화(i18n) 권장

"난이도", "선택" 등 문자열은 string 리소스로 분리하면 다국어 대응과 카피 변경에 유리합니다. 아래 영역은 stringResource(...)로의 치환을 고려해 주세요.

Also applies to: 163-163


191-195: TODO: 추천 카테고리 함께 전달 — 이벤트 시그니처 확장 제안

주석대로 등록 시 추천 카테고리(또는 타입)를 함께 넘기면 이후 화면에서의 컨텍스트 유지가 쉬워집니다. 예: (id: String, category: RecommendCategory) 또는 DTO/parcelable 모델로 묶어 전달.

원하시면 이벤트/네비게이션 시그니처 확장에 맞춘 전반 수정안까지 생성해 드릴게요.

presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/template/RecommendLevelBottomSheet.kt (3)

32-33: BottomSheet 부분 확장 상태 방지 옵션 고려

절반(expanded/partiallyExpanded) 상태를 사용하지 않을 계획이라면 부분 확장을 막아 의도치 않은 중간 상태를 방지할 수 있습니다.

-    val sheetState = rememberModalBottomSheetState()
+    val sheetState = rememberModalBottomSheetState(
+        skipPartiallyExpanded = true,
+    )

92-101: 문자열 하드코딩 분리 및 접근성 개선 제안

  • "난이도", 구분자 " | ", optionText는 string 리소스로 분리 권장(i18n/카피 변경 용이).
  • 스크린 리더(TalkBack) 접근성: 시각 구분자 " | " 대신 두 텍스트를 의미 단위로 읽히도록 semantics { }로 문장을 합치거나, 단일 Text에 포맷팅된 문자열을 전달하는 쪽이 더 자연스럽습니다.

필수는 아니나 장기 유지보수/접근성 품질을 위해 고려 부탁드립니다.


104-109: 체크 아이콘 터치 타깃/접근성 니트픽

선택 아이콘이 작다면 .size(24.dp) 정도로 명시해 터치 타깃을 보장하고, 필요 시 semantics { contentDescription = ... }로 보조 기술 사용자에게 상태를 전달하는 것도 고려해 주세요. (장식 아이콘이라면 현 상태도 무방)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2b354fb and cc9a419.

📒 Files selected for processing (3)
  • domain/src/main/java/com/threegap/bitnagil/domain/recommendroutine/model/RecommendLevel.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/RecommendRoutineScreen.kt (7 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/template/RecommendLevelBottomSheet.kt (3 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/template/RecommendLevelBottomSheet.kt (1)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilIcon.kt (1)
  • BitnagilIcon (21-33)
presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/RecommendRoutineScreen.kt (4)
presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/block/EmotionRecommendRoutineButton.kt (1)
  • EmotionRecommendRoutineButton (24-76)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilIcon.kt (1)
  • BitnagilIcon (21-33)
presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/template/EmptyRecommendRoutineView.kt (1)
  • EmptyRecommendRoutineView (14-45)
presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/block/RecommendRoutineItem.kt (1)
  • RecommendRoutineItem (28-104)
⏰ 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 (5)
presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/RecommendRoutineScreen.kt (3)

85-91: 카테고리 변경 시 자동 스크롤 처리: UX 관점에서 적절합니다

선택 카테고리 변경 시 리스트를 최상단으로 스무스하게 이동시키는 처리 깔끔합니다. 불필요한 재컴포지션도 없고, 사용성 측면에서 긍정적이에요.


121-129: Emotion 추천 버튼 위치/노출 제어 리팩터링 좋습니다

상단 게이트(uiState.shouldShowEmotionButton)로 통일된 위치에서 노출을 관리하는 방향이 구조적이며, 이후 리스트 내부 렌더링과의 결합도 줄여 유지보수성이 좋아졌습니다.


189-189: items key 안정성 확보: LGTM

id 기반 key로 안정성 확보되어 스크롤/애니메이션 측면에서 안전합니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/recommendroutine/component/template/RecommendLevelBottomSheet.kt (1)

54-63: 선택 토글 + hide 후 onDismiss 호출 패턴: 적절합니다

같은 항목 재클릭 시 해제 처리 및 hide() 완료 후 onDismiss() 호출 플로우가 안정적입니다. 사용자 인지/상태 일관성 측면에서 👍

domain/src/main/java/com/threegap/bitnagil/domain/recommendroutine/model/RecommendLevel.kt (1)

5-10: koreanLevel 도입으로 UI 표기 일관성 확보: 좋습니다

UI에서 직접 사용 가능한 koreanLevel 추가와 각 상수 값 보강이 명확합니다. 프리젠테이션 레이어와의 결합도가 줄고 매핑도 간결해집니다.

Copy link
Copy Markdown
Contributor

@l5x5l l5x5l left a comment

Choose a reason for hiding this comment

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

고생하셨습니다!

@wjdrjs00 wjdrjs00 merged commit b86184d into develop Aug 19, 2025
2 checks passed
@wjdrjs00 wjdrjs00 deleted the refactor/#107-redesign-recommend-routine branch August 19, 2025 13:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 새로운 기능 구현 🧤 대현

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[REFACTOR] 추천 루틴 화면 리디자인 변경사항을 반영합니다.

2 participants