Skip to content

[Feature/#90] 홈 화면 리디자인 변경 사항을 반영합니다. #106

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 26 commits into from
Aug 15, 2025

Conversation

wjdrjs00
Copy link
Member

@wjdrjs00 wjdrjs00 commented Aug 14, 2025

[ PR Content ]

홈 화면 리디자인 변경사항을 반영했습니다.
리디자인에 따라 변경된 api 버전업 작업도 같이 진행했습니다.

Related issue

Screenshot 📸

Screen_recording_20250815_052344.mp4

Work Description

  • 홈 화면 변경된 리디자인 사항 반영
  • 루틴 달성 로직 구현
  • 루틴 조회, 완료, 감정 구슬 조회 api v1 -> v2 버전 업

To Reviewers 📢

  • 홈 화면 디자인 변경사항과 이에 따른 api 버전 변경사항을 모두 반영했습니다.
  • 루틴 조회의 경우 기존 v1 응답값과 v2의 응답값의 변화가 많아 변경되는 파일이 많아졌습니다요..
  • 서브루틴 DTO가 제거됨에 따라, 완료여부 판별시에 기존 서브 루틴 id 로 판별하던 로직을 서브루틴 list의 인덱스 값으로 대체하는 방향으로 진행하여 변경점을 최소화 하였습니다.
  • 이외 궁금하신점 리뷰 해주시면 답변하겠습니다욤

Summary by CodeRabbit

  • 신기능

    • 홈에 오늘의 감정 이미지·메시지 노출 및 "오늘 감정 등록하기" 버튼 추가
    • 주간 날짜 선택기에 날짜별 완료 애니메이션·"더보기" 진입점 추가
    • 플로팅 버튼 활성 상태 테마 및 텍스트 버튼의 텍스트 패딩 설정 추가
  • UI/UX

    • 홈 헤더 문구·애니메이션 개선, 감정 이미지 비동기 로딩 적용
    • 루틴 리스트·아이템 레이아웃·서브루틴 토글이 인덱스 기반으로 변경
    • 시간 표기 24시간 포맷 및 빈 상태 문구·스타일 업데이트
  • 제거

    • 루틴 편집 네비게이션 경로, 정렬/상세 바텀시트 및 삭제 확인 다이얼로그 제거
    • 제보하기 플로팅 메뉴 항목 및 EmotionBall 관련 컴포넌트 삭제

- 소개글 변경
- 앱 아이콘 추가
- 기본 감정구슬 이미지 변경
- 정렬 바텀 시트 컴포넌트 제거
- 정렬 타입 제거
- 정렬 state, intent 제거
- 루틴 상세 바텀시트 컴포넌트 제거
- 루틴 삭제 모달 컴포넌트 제가
- 수정, 삭제 관련 state, intent 제거
- v1 -> v2 버전업
- response 변화에 따른 모델 수정
- v1 -> v2로 마이그레이션
- 루틴 완료 로직 인덱스 값 기반으로 수정
- 루틴 완료 requestDto 수정
@wjdrjs00 wjdrjs00 requested a review from l5x5l August 14, 2025 20:32
@wjdrjs00 wjdrjs00 self-assigned this Aug 14, 2025
@wjdrjs00 wjdrjs00 added ✨ Feature 새로운 기능 구현 📱 UI UI 추가 및 수정 (비지니스 로직을 포함하지 않는 작업) 🧤 대현 labels Aug 14, 2025
Copy link

coderabbitai bot commented Aug 14, 2025

Walkthrough

홈 네비게이션의 루틴 편집 경로 제거, 홈 화면 UI 전면 리디자인 적용, 감정 API를 v2/TodayEmotion으로 전환, 루틴 DTO·도메인·요청·응답 모델을 날짜·완료 중심으로 재구성하고 디자인 시스템 컴포넌트·아이콘·타이포그래피를 조정했습니다.

Changes

Cohort / File(s) Summary
Navigation cleanup
app/src/main/java/com/threegap/bitnagil/MainNavHost.kt, app/src/main/java/com/threegap/bitnagil/navigation/home/HomeNavHost.kt
HomeNavHost 시그니처에서 navigateToEditRoutine 제거 및 호출부 정리.
Design system: FAB & menu
core/designsystem/src/main/java/.../BitnagilFloatingButton.kt, .../BitnagilFloatingActionMenu.kt
FAB에 isActiveBitnagilFloatingButtonColor 추가, 아이콘 렌더링/크기/색상/텍스트 스타일 조정, 기본 아이콘 ic_add로 변경.
Design system: TextButton
core/designsystem/src/main/java/.../BitnagilTextButton.kt
textPadding: PaddingValues 파라미터 추가 및 텍스트에 패딩 적용.
Design system: drawables & typography
core/designsystem/src/main/res/drawable/ic_add.xml, .../ic_arrow_down_up.xml (삭제), .../ic_close.xml, core/designsystem/src/main/java/.../typography/Type.kt
ic_add 추가, ic_arrow_down_up 삭제, ic_close 교체 및 cafe24SsurroundAir 폰트 크기/행간 조정.
Emotion API → v2 / DTO & domain & usecase
data/.../emotion/service/EmotionService.kt, data/.../emotion/datasource/EmotionDataSource.kt, .../datasourceImpl/EmotionDataSourceImpl.kt, .../model/response/TodayEmotionResponseDto.kt, domain/.../emotion/model/TodayEmotion.kt, domain/.../emotion/repository/EmotionRepository.kt, domain/.../emotion/usecase/FetchTodayEmotionUseCase.kt, (삭제) domain/.../emotion/usecase/GetEmotionUseCase.kt
감정 엔드포인트·메서드명 변경(v1→v2, getEmotionMarblefetchTodayEmotion), DTO·도메인 TodayEmotion 도입, UseCase·Repository 시그니처 갱신 및 기존 GetEmotionUseCase 삭제.
Routine API → v2 / response model changes
data/.../routine/service/RoutineService.kt, data/.../routine/model/response/{RoutineDto,DayRoutinesDto,RoutinesResponseDto}.kt, data/.../routine/model/response/SubRoutineDto.kt (삭제)
루틴 API 경로/HTTP 변경(v1→v2, GET/PUT), 응답 구조가 날짜별 DayRoutinesDto 포함 형태로 변경, SubRoutineDto 제거.
Routine request/mapper/repo changes
data/.../routine/model/request/{RoutineCompletionInfoDto.kt,RoutineCompletionRequestDto.kt}, data/.../routine/repositoryImpl/RoutineRepositoryImpl.kt, data/.../routine/mapper/RoutineMapper.kt (삭제)
완료 동기화 DTO 재구성(routineCompleteYn, subRoutineCompleteYn), toDto 유틸 추가, 레거시 매퍼 삭제 및 리포지토리 시그니처/호출 업데이트.
Domain: routines model refactor
domain/.../routine/model/{Routine.kt,Routines.kt,DayRoutines.kt,RecommendedRoutineType.kt,RoutineCompletion*.kt,SubRoutine.kt}
Routine 도메인 구조를 날짜·완료 중심으로 재설계(DayRoutines·RecommendedRoutineType·RoutineCompletionInfos 추가), SubRoutine·RoutineCompletion 등 타입 삭제/대체.
Domain usecases
domain/.../routine/usecase/*, domain/.../emotion/usecase/*
주간 루틴 후처리 제거, 완료 동기화 usecase 시그니처를 RoutineCompletionInfos로 변경, 감정 관련 UseCase 교체/삭제.
Presentation: Home 리디자인 (Screen / VM)
presentation/.../home/HomeScreen.kt, .../home/HomeViewModel.kt
편집/정렬/삭제 BottomSheet 제거, 헤더·빈상태·루틴 목록 레이아웃 재구성, todayEmotion 도입, 서브루틴 토글을 인덱스 기반으로 변경, "더보기" 액션 추가, 인텐트/사이드이펙트 정리.
Presentation components: 추가/삭제/변경
presentation/.../home/component/atom/{EmotionRegisterButton.kt,EmotionBall.kt(삭제),RoundTriangleShape.kt(삭제)}, .../block/{RoutineItem.kt,SubRoutinesItem.kt,SpeechBubbleTooltip.kt(삭제)}, .../template/{CollapsibleHomeHeader.kt,WeeklyDatePicker.kt,EmptyRoutineView.kt, Routine*BottomSheet.kt(삭제)}
EmotionRegisterButton 추가, EmotionBall·툴팁·여러 BottomSheet 삭제, 헤더가 TodayEmotionUiModel 기반으로 변경, WeeklyDatePicker에 routines 인자·완료 애니메이션 추가, RoutineItem/SubRoutinesItem 인터페이스·레이아웃 변경.
Presentation models & state
presentation/.../home/model/{TodayEmotionUiModel.kt,DayRoutinesUiModel.kt,RoutineUiModel.kt,RoutinesUiModel.kt,HomeState.kt,HomeIntent.kt,HomeSideEffect.kt}
UI 모델을 도메인 v2에 맞게 전면 개편(오늘 감정·일별 루틴 포함), 상태에서 정렬/바텀시트/삭제 관련 필드 제거, 인텐트/사이드이펙트 정리 및 서브루틴 인덱스 기반 토글 도입.
Presentation utils & writeroutine
presentation/.../home/util/{CollapsibleHeaderState.kt,LocalDateExtension.kt}, presentation/.../writeroutine/WriteRoutineViewModel.kt, .../writeroutine/model/SubRoutine.kt
헤더 비율 조정, 시간 포맷을 24시간제로 변경(자정 특수처리), WriteRoutine에서 서브루틴 로드 전파 주석 처리 및 도메인 서브루틴 변환 일부 제거.

Sequence Diagram(s)

sequenceDiagram
  actor U as User
  participant HS as HomeScreen
  participant VM as HomeViewModel
  participant UE as FetchTodayEmotionUseCase
  participant ER as EmotionRepository
  participant DS as EmotionDataSource
  participant ES as EmotionService(API v2)

  U->>HS: 앱 진입 또는 헤더 갱신 요청
  HS->>VM: LoadTodayEmotion 요청
  VM->>UE: invoke(currentDate)
  UE->>ER: fetchTodayEmotion(date)
  ER->>DS: fetchTodayEmotion(date)
  DS->>ES: GET /api/v2/emotion-marbles/{date}
  ES-->>DS: BaseResponse<TodayEmotionResponseDto>
  DS-->>ER: Result<TodayEmotionResponseDto>
  ER-->>UE: Result<TodayEmotion?>
  UE-->>VM: Result<TodayEmotion?>
  VM-->>HS: TodayEmotionUiModel 반영
Loading
sequenceDiagram
  actor U as User
  participant HS as HomeScreen
  participant VM as HomeViewModel
  participant RU as RoutineCompletionUseCase
  participant RR as RoutineRepository
  participant RS as RoutineService(API v2)

  U->>HS: 서브루틴 토글(인덱스)
  HS->>VM: OnSubRoutineCompletionToggle(routineId, index, state)
  VM->>VM: 로컬 상태 업데이트 및 변경 항목 수집
  VM->>RU: invoke(RoutineCompletionInfos)
  RU->>RR: syncRoutineCompletion(infos)
  RR->>RS: PUT /api/v2/routines (completion request)
  RS-->>RR: BaseResponse<Unit>
  RR-->>RU: Result<Unit>
  RU-->>VM: Result<Unit>
  VM-->>HS: 완료 상태 확정 또는 롤백
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Assessment against linked issues

Objective Addressed Explanation
홈 화면 리디자인 UI 변경 (#90)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Emotion API v1→v2 및 DTO/도메인 전환 (data/.../emotion/service/EmotionService.kt, data/.../emotion/model/response/TodayEmotionResponseDto.kt, domain/.../emotion/*) UI 리디자인(#90)은 화면 변경을 요구하는 반면, API 경로·DTO·도메인 변경은 백엔드 계약·데이터 계층 변경으로 명시된 UI 범위를 벗어남.
Routine API v1→v2 및 HTTP 메서드 변경 (data/.../routine/service/RoutineService.kt) 화면 리디자인 요구와 직접 관련 없으며 서버 엔드포인트/메서드 계약 변경으로 보임.
도메인 루틴 모델 대대적 재구성 (domain/.../routine/*.kt 삭제·추가) UI 레이아웃 목적과 연관될 수 있으나 공용 도메인 타입 삭제·재설계는 리디자인 이슈의 범위를 초과하는 계약 변경으로 판단됨.

Possibly related PRs

Suggested labels

🔨 Refactor

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 feature/#90-home-redesign

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

@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: 26

🔭 Outside diff range comments (4)
presentation/src/main/java/com/threegap/bitnagil/presentation/home/util/CollapsibleHeaderState.kt (1)

107-139: NestedScrollConnection가 스크롤마다 재생성됨 — 키에서 scrollOffset 제거 필요

Line 107의 remember 키에 scrollOffset이 포함되어 있어, 스크롤할 때마다 NestedScrollConnection 인스턴스가 새로 만들어집니다. 이는 불필요한 객체 생성/할당과 성능 저하, 드물게 이벤트 손실의 잠재적 원인이 됩니다. Compose의 스냅샷 상태는 캡처된 가변 상태를 통해 최신 값을 읽을 수 있으므로 키에서 제외해도 안전합니다.

다음과 같이 수정해 주세요:

-    val nestedScrollConnection = remember(lazyListState, maxScrollOffsetPx, collapseRangePx, scrollOffset) {
+    val nestedScrollConnection = remember(lazyListState, maxScrollOffsetPx, collapseRangePx) {
         object : NestedScrollConnection {
             override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
                 val deltaY = available.y
                 ...
             }
         }
     }
presentation/src/main/java/com/threegap/bitnagil/presentation/home/util/LocalDateExtension.kt (1)

3-8: 예외 범위 축소: DateTimeParseException만 처리

현재 Exception 전체를 포괄적으로 캐치하고 있어 디버깅이 어려워질 수 있습니다. 파싱 실패에만 반응하도록 DateTimeParseException으로 범위를 좁히는 것을 권장합니다.

다음과 같이 수정해 주세요:

 import java.time.LocalTime
 import java.time.format.DateTimeFormatter
+import java.time.format.DateTimeParseException
 import java.time.format.TextStyle
 import java.util.Locale

그리고 아래 catch를 변경:

-    } catch (e: Exception) {
+    } catch (e: DateTimeParseException) {
         "시간 미정"
     }
presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt (2)

51-52: 루틴 동기화 디바운스가 날짜별로 분리되지 않아 다른 날짜 변경이 누락될 수 있습니다

MutableSharedFlow 단일 스트림에 debounce(2s)를 적용하면, 서로 다른 날짜에 대한 빠른 연속 토글이 마지막 날짜 한 건으로 합쳐져 앞선 날짜의 변경이 동기화되지 않는 문제가 발생할 수 있습니다. 날짜별로 독립적인 디바운스/스케줄링이 필요합니다.

아래와 같이 날짜별 Job을 관리하는 방식으로 리팩터링을 제안합니다.

  • Line 51-52: 단일 SharedFlow 대신 날짜별 Job 맵으로 교체
-    private val routineSyncTrigger = MutableSharedFlow<LocalDate>()
+    private val syncJobsByDate = mutableMapOf<LocalDate, Job>()
  • Line 170-179: observeRoutineUpdates 제거(단일 스트림 수집 로직 불필요)
-    @OptIn(FlowPreview::class)
-    private fun observeRoutineUpdates() {
-        viewModelScope.launch {
-            routineSyncTrigger
-                .debounce(2000L)
-                .collect { date ->
-                    syncRoutineChangesForDate(date)
-                }
-        }
-    }
  • Line 263-265: emit 대신 날짜별 스케줄링 호출
-            viewModelScope.launch {
-                routineSyncTrigger.emit(selectedDate)
-            }
+            scheduleRoutineSync(selectedDate)

추가로 필요한 보조 코드(파일 내 임의 위치에 추가):

// imports
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay

// helper
private fun scheduleRoutineSync(date: LocalDate) {
    syncJobsByDate[date]?.cancel()
    syncJobsByDate[date] = viewModelScope.launch {
        delay(2000L)
        syncRoutineChangesForDate(date)
    }
}

init 블록의 observeRoutineUpdates() 호출도 제거해 주세요:

// remove this line in init:
// observeRoutineUpdates()

Also applies to: 170-179, 263-265


389-404: 동기화 실패 시 로딩 상태를 false로 강제하는 코드는 제거하는 편이 안전합니다

토글 동기화는 전역 로딩 플래그를 건드리지 않는데, 실패 경로에서만 UpdateLoading(false)를 호출하면, 다른 비동기 로딩(fetchWeeklyRoutines/fetchUserProfile/fetchTodayEmotion)과 경합하며 로딩 인디케이터가 의도치 않게 꺼질 수 있습니다.

                 pendingChangesByDate.remove(dateKey)
                 backupStatesByDate.remove(dateKey)
-                sendIntent(HomeIntent.UpdateLoading(false))
🧹 Nitpick comments (45)
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/typography/Type.kt (1)

102-102: letterSpacing(-0.5f) 유지 여부 디자인과 재확인 제안

20sp로 다운스케일된 상황에서 -0.5f 트래킹이 다소 조밀해 보일 수 있습니다. 가독성/브랜드 톤앤매너 관점에서 0f 또는 -0.25f 등으로 재보정할지 디자인 확인을 제안드립니다.

원하신다면 다음처럼 조정 가능합니다:

-        letterSpacing = (-0.5f),
+        letterSpacing = 0f,
core/designsystem/src/main/res/drawable/ic_add.xml (1)

6-12: 과도하게 복잡한 pathData — 단순 선분으로 경량화 권장

현재 pathData가 다수의 곡선 커맨드(C)로 구성되어 있어 불필요하게 복잡합니다. + 아이콘은 두 개의 선분만으로 표현 가능하며, 경로 단순화는 벡터 크기/파싱 비용 감소와 렌더링 일관성(특히 스냅/크리스프 라인)에 도움이 됩니다.

다음과 같이 경로를 단순화해 주세요(동일한 stroke 속성 유지):

-    <path
-        android:fillColor="#00000000"
-        android:pathData="M18.551,12.266C18.088,12.266 18.088,11.884 17.613,11.884C17.139,11.884 17.15,11.757 16.676,11.757C16.201,11.757 16.213,12.023 15.75,12.023C15.287,12.023 15.287,11.942 14.812,11.942C14.338,11.942 14.349,12.081 13.875,12.081C13.4,12.081 13.4,12.116 12.937,12.116C12.474,12.116 12.463,12.069 12,12.069M12,12.069C11.537,12.069 11.537,11.884 11.062,11.884C10.588,11.884 10.599,11.757 10.125,11.757C9.65,11.757 9.662,12.023 9.199,12.023C8.736,12.023 8.736,11.942 8.261,11.942C7.787,11.942 7.798,12.081 7.324,12.081C6.849,12.081 6.849,12.116 6.386,12.116C5.923,12.116 5.912,12.069 5.449,12.069M12,12.069C12,11.607 12.115,11.537 12.115,11.063C12.115,10.588 12.243,10.6 12.243,10.125C12.243,9.651 11.976,9.662 11.976,9.199C11.976,8.736 12.058,8.736 12.058,8.262C12.058,7.787 11.919,7.799 11.919,7.324C11.919,6.85 11.884,6.85 11.884,6.387C11.884,5.924 11.93,5.912 11.93,5.449M12,12.069C12,12.533 11.884,12.463 11.884,12.938C11.884,13.412 11.757,13.401 11.757,13.875C11.757,14.35 12.023,14.338 12.023,14.801C12.023,15.264 11.942,15.264 11.942,15.738C11.942,16.213 12.081,16.201 12.081,16.676C12.081,17.151 12.115,17.151 12.115,17.614C12.115,18.076 12.069,18.088 12.069,18.551"
-        android:strokeWidth="2"
-        android:strokeColor="#ffffff"
-        android:strokeLineCap="round"
-        android:strokeLineJoin="round" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M12,5 L12,19 M5,12 L19,12"
+        android:strokeWidth="2"
+        android:strokeColor="#ffffff"
+        android:strokeLineCap="round"
+        android:strokeLineJoin="round" />
presentation/src/main/java/com/threegap/bitnagil/presentation/home/util/CollapsibleHeaderState.kt (2)

141-152: 중복 계산 제거 — collapseProgress를 한 번만 계산하고 재사용

currentHeaderHeight와 collapseProgress 모두 동일한 비율 계산을 수행합니다. collapseProgress를 먼저 계산한 뒤 currentHeaderHeight에서 재사용하면 중복 계산을 피하고 가독성이 좋아집니다.

다음과 같이 순서를 바꾸고 재사용해 주세요:

-    val currentHeaderHeight by remember {
-        derivedStateOf {
-            val progress = (-scrollOffset / collapseRangePx).coerceIn(0f, 1f)
-            expandedHeaderHeight - (collapseRange * progress)
-        }
-    }
-
-    val collapseProgress by remember {
-        derivedStateOf {
-            (-scrollOffset / collapseRangePx).coerceIn(0f, 1f)
-        }
-    }
+    val collapseProgress by remember {
+        derivedStateOf {
+            (-scrollOffset / collapseRangePx).coerceIn(0f, 1f)
+        }
+    }
+
+    val currentHeaderHeight by remember {
+        derivedStateOf {
+            expandedHeaderHeight - (collapseRange * collapseProgress)
+        }
+    }

166-167: 매직 넘버 주석 보강 제안

디자인 스펙(artboard 기준 높이/px→dp 환산)에 근거한 비율임을 주석으로 남기면 추후 유지보수 시 혼동을 줄일 수 있습니다.

예시:

- private const val EXPANDED_HEADER_RATIO = 225f / 722f
- private const val COLLAPSED_HEADER_RATIO = 64f / 722f
+ // Design spec: artboard height 722dp 기준 — expanded 225dp, collapsed 64dp
+ private const val EXPANDED_HEADER_RATIO = 225f / 722f
+ private const val COLLAPSED_HEADER_RATIO = 64f / 722f
domain/src/main/java/com/threegap/bitnagil/domain/emotion/model/TodayEmotion.kt (1)

3-8: 도메인 모델의 타입 안정성/널 처리 재점검

서버 스펙이 닫힌 집합이라면 type을 enum(또는 @JvmInline value class)으로 한정하는 편이 안전합니다. 또한 data 계층에서 null이 전달될 가능성이 있다면 .orEmpty() 등의 처리를 통해 도메인 레이어에서 NPE 가능성을 제거해야 합니다.

  • 데이터 레이어의 toDomain 매퍼에서 각 필드가 null일 때의 처리(기본값 또는 null 방치) 방식을 확인해 주세요. 필요 시 안전한 기본값으로 매핑하는 패치 제안 드릴 수 있습니다.
  • type 문자열의 값 범위가 고정이면 enum 전환을 고려하시겠습니까?
presentation/src/main/java/com/threegap/bitnagil/presentation/home/util/LocalDateExtension.kt (1)

26-33: 자정(하루 종일) 표현 상수화 제안

"하루\n종일" 문자열은 UI 정책상 중요한 레이블입니다. 상수로 올려두면 다국어/문구 변경 시 추적이 쉬워집니다.

예시:

  • 파일 상단에 private const val ALL_DAY_LABEL = "하루\n종일"
  • 사용부에서 ALL_DAY_LABEL 반환
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilTextButton.kt (2)

44-45: API 명명 개선 제안: textPadding → contentPadding

Compose 기본 컴포넌트(Button 등)와의 일관성을 위해 contentPadding 명칭 사용을 권장합니다. 외부 공개 API이므로 조기 정비가 좋습니다.

-    textPadding: PaddingValues = PaddingValues(0.dp),
+    contentPadding: PaddingValues = PaddingValues(0.dp),

함수 내부 사용부도 동일하게 contentPadding으로 변경해 주세요.


61-75: 접근성 강화 제안

role은 지정되어 있으나, 스크린리더 친화성을 위해 onClick 라벨(semantics { onClick(label = "...") }) 제공을 고려해 주세요. disabled 상태에서는 state 설명(예: stateDescription) 추가도 도움이 됩니다.

data/src/main/java/com/threegap/bitnagil/data/emotion/model/response/TodayEmotionResponseDto.kt (1)

19-26: 빈 문자열 방어 로직 추가 고려

서버가 빈 문자열("")을 보낼 경우 현재는 유효값으로 간주됩니다. UX상 이름/메시지가 비어 있으면 의미가 없으므로 isNullOrBlank 기반으로 보완하는 것을 권장합니다.

-    return if (emotionMarbleType != null && emotionMarbleName != null && imageUrl != null && emotionMarbleHomeMessage != null) {
+    val allValid = !emotionMarbleType.isNullOrBlank() &&
+        !emotionMarbleName.isNullOrBlank() &&
+        !imageUrl.isNullOrBlank() &&
+        !emotionMarbleHomeMessage.isNullOrBlank()
+    return if (allValid) {
         TodayEmotion(
             type = emotionMarbleType,
             name = emotionMarbleName,
             imageUrl = imageUrl,
             homeMessage = emotionMarbleHomeMessage,
         )
     } else {
         null
     }
core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/component/atom/BitnagilFloatingButton.kt (2)

165-177: 불필요한 애니메이션 상태 제거 또는 의도 반영 필요

animateFloatAsState의 targetValue가 항상 1f라 실질적으로 아무 애니메이션도 일어나지 않습니다. 삭제하거나, isExpanded 등에 연동해 의미 있는 효과로 조정하세요.

-    val scale by animateFloatAsState(
-        targetValue = 1f,
-        animationSpec = tween(durationMillis = 200),
-        label = "menu_item_scale",
-    )
...
-        modifier = modifier
-            .scale(scale)
-            .clickableWithoutRipple { onClick() },
+        modifier = modifier
+            .clickableWithoutRipple { onClick() },

171-177: 메뉴 아이템의 터치 타겟 최소 높이 확보

현재 아이콘(24dp) + 텍스트만으로는 권장 터치 타겟(최소 48dp)에 미달할 수 있습니다. 접근성 향상을 위해 minHeight를 보강하세요.

-    Row(
+    Row(
         verticalAlignment = Alignment.CenterVertically,
         horizontalArrangement = Arrangement.spacedBy(14.dp),
         modifier = modifier
-            .scale(scale)
-            .clickableWithoutRipple { onClick() },
+            .sizeIn(minHeight = 48.dp)
+            .clickableWithoutRipple { onClick() },
     ) {
domain/src/main/java/com/threegap/bitnagil/domain/routine/model/DayRoutines.kt (1)

4-5: 불리언 프로퍼티 명명 컨벤션 통일 제안 (isAllCompleted)

코틀린/도메인 컨벤션상 불리언은 is- 접두어를 권장합니다. 직렬화 대상이 아닌 도메인 타입이라면 리네이밍을 고려해 주세요.

적용 예시:

 data class DayRoutines(
     val routineList: List<Routine>,
-    val allCompleted: Boolean,
+    val isAllCompleted: Boolean,
 )
domain/src/main/java/com/threegap/bitnagil/domain/routine/model/RoutineCompletionInfos.kt (3)

3-5: 필드명 반복(stutter) 완화 제안

RoutineCompletionInfos.routineCompletionInfos는 중복된 네이밍으로 가독성이 떨어집니다. items 혹은 infos 등으로 간결화하면 사용부 표현력이 좋아집니다.

적용 예시:

 data class RoutineCompletionInfos(
-    val routineCompletionInfos: List<RoutineCompletionInfo>,
+    val items: List<RoutineCompletionInfo>,
 )

도메인/데이터 매핑 전반 영향이 있으므로, 변경 시 일괄 리팩터링을 동반해 주세요.


3-5: 경량 박싱을 위한 value class 고려

단일 프로퍼티 래퍼라면 value class로 전환하여 불필요한 할당을 줄일 수 있습니다. (도메인 레이어 전용일 때 특히 이점이 큽니다)

예시:

-package com.threegap.bitnagil.domain.routine.model
-
-data class RoutineCompletionInfos(
-    val routineCompletionInfos: List<RoutineCompletionInfo>,
-)
+package com.threegap.bitnagil.domain.routine.model
+
+@JvmInline
+value class RoutineCompletionInfos(
+    val items: List<RoutineCompletionInfo>,
+)

주의: 네트워크 직렬화 계층에 직접 사용하지 않는지 확인해 주세요. (직렬화 대상이면 유지/우회 권장)


3-5: 빈 배치에 대한 방어적 설계

빈 리스트 전송은 네트워크 호출 낭비입니다. 유즈케이스/리포지토리 진입 전에 빈 컬렉션 방어 코드를 두는 것을 권장합니다. 필요 시 구현 보조 가능합니다.

domain/src/main/java/com/threegap/bitnagil/domain/emotion/repository/EmotionRepository.kt (1)

12-12: 메서드 네이밍 일관성(get vs fetch) 불일치

동일 리포지토리 내에 getEmotions, registerEmotion, fetchTodayEmotion가 혼재해 있습니다. 일관성을 위해 getTodayEmotion으로의 리네이밍을 제안합니다. (혹은 전반을 fetch로 통일)

예시:

-    suspend fun fetchTodayEmotion(currentDate: String): Result<TodayEmotion?>
+    suspend fun getTodayEmotion(currentDate: String): Result<TodayEmotion?>
domain/src/main/java/com/threegap/bitnagil/domain/routine/model/Routines.kt (1)

4-4: 네이밍 중복 완화 제안

Routines.routines는 약간의 중복감이 있습니다. byDate 같은 이름으로 의미를 강화하는 것도 고려해 주세요. (규모 영향이 커서 즉시 변경은 선택)

data/src/main/java/com/threegap/bitnagil/data/routine/model/response/RoutinesResponseDto.kt (1)

10-10: Map 순서 의존 UI의 경우 정렬 보장 필요

Map<String, DayRoutinesDto>는 순서를 보장하지 않습니다. UI에서 주/일자 순서가 중요하다면, 도메인 변환 시 키(예: yyyy-MM-dd)를 정렬해 LinkedHashMap으로 유지하거나, UI 단에 정렬 책임을 명시하세요.

가능한 변형 예:

  • 도메인 Routines가 주/일자 순서 보장을 요구한다면, toDomain()에서 키를 정렬해 삽입 순서를 고정.
  • 또는 UI 계층의 사용 지점에서 정렬 수행을 명시(주석/문서화).
domain/src/main/java/com/threegap/bitnagil/domain/routine/repository/RoutineRepository.kt (1)

5-5: 네이밍 제안: Infos 대신 Batch/Set 고려

RoutineCompletionInfos는 어색한 복수형입니다. 도메인 용어로 RoutineCompletionBatch 또는 RoutineCompletionSet 등이 더 의도가 분명할 수 있습니다. 전역 변경 범위가 크면 향후 마이그레이션 때 고려해 주세요.

presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/RoutinesUiModel.kt (1)

14-16: mapValues 람다 단순화로 가독성 향상

키를 사용하지 않으므로 엔트리 분해 대신 value만 취하는 형태가 더 간결합니다.

-        routines = this.routines.mapValues { (_, dayRoutines) ->
-            dayRoutines.toUiModel()
-        },
+        routines = this.routines.mapValues { it.value.toUiModel() },
data/src/main/java/com/threegap/bitnagil/data/emotion/repositoryImpl/EmotionRepositoryImpl.kt (1)

40-41: Flow 반환 메서드에 suspend 불필요 — 인터페이스 레벨서 제거 검토

Flow를 돌려주는 메서드는 보통 비동기 스트림 취득 자체가 지연되지 않으므로 suspend가 관용적이지 않습니다. 향후 인터페이스/구현 모두에서 suspend 제거를 고려해 주세요.

presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/TodayEmotionUiModel.kt (1)

3-11: (Compose 사용 시) 안정성 힌트 주입: @immutable 추가 고려

Compose 리컴포지션 최적화를 위해 @immutable 어노테이션을 부여하면 안정성 추론이 명시적입니다. 프레젠테이션 모듈이 Compose를 의존한다면 다음 변경을 권장합니다.

 import android.os.Parcelable
 import com.threegap.bitnagil.domain.emotion.model.TodayEmotion
+import androidx.compose.runtime.Immutable
 import kotlinx.parcelize.Parcelize
 
-@Parcelize
+@Immutable
+@Parcelize
 data class TodayEmotionUiModel(
     val imageUrl: String,
     val homeMessage: String,
 ) : Parcelable
presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/DayRoutinesUiModel.kt (2)

13-17: allCompleted 동기화 전략 점검 (서버 필드 vs. UI 파생값)

서버가 주는 allCompleted를 그대로 사용하고 있는데, UI에서 루틴 완료 토글이 일어나면 일시적으로 리스트와 불일치할 수 있습니다. UI 모델에서 파생값으로 계산하거나, UI 상태에서 재계산하는 전략을 검토해 주세요.

가능한 대안(설계안) 예시:

@Parcelize
data class DayRoutinesUiModel(
    val routineList: List<RoutineUiModel> = emptyList(),
) : Parcelable {
    val allCompleted: Boolean get() = routineList.all { it.completed }
}

현재 매핑을 유지하되, 뷰모델에서 토글 시에는 DayRoutinesUiModel을 재생성하여 일관성을 보장하는지도 확인 부탁드립니다.


8-11: (Compose 사용 시) @immutable 부여로 리컴포지션 최적화

리스트/불리언만 포함된 불변 모델이므로 @immutable을 부여하면 Compose가 더 공격적으로 안정성으로 취급할 수 있습니다.

 import android.os.Parcelable
 import com.threegap.bitnagil.domain.routine.model.DayRoutines
+import androidx.compose.runtime.Immutable
 import kotlinx.parcelize.Parcelize
 
-@Parcelize
+@Immutable
+@Parcelize
 data class DayRoutinesUiModel(
     val routineList: List<RoutineUiModel> = emptyList(),
     val allCompleted: Boolean = false,
 ) : Parcelable
data/src/main/java/com/threegap/bitnagil/data/routine/model/response/DayRoutinesDto.kt (1)

7-13: 역직렬화 안전성 강화: 기본값 추가 제안

서버가 필드를 누락하거나 null을 보낼 가능성에 대비해 기본값을 선언하면 역직렬화 실패를 방지할 수 있습니다. 특히 빈 날짜에 대한 routineList 누락 케이스를 방어할 수 있습니다.

 @Serializable
 data class DayRoutinesDto(
     @SerialName("routineList")
-    val routineList: List<RoutineDto>,
+    val routineList: List<RoutineDto> = emptyList(),
     @SerialName("allCompleted")
-    val allCompleted: Boolean,
+    val allCompleted: Boolean = false,
 )
domain/src/main/java/com/threegap/bitnagil/domain/emotion/usecase/FetchTodayEmotionUseCase.kt (1)

7-12: UseCase 설계 적절. null 의미에 대한 계약 명시 권장

Result<TodayEmotion?> 반환은 “등록 안 됨(null) vs 실패(Result.failure)”를 구분하기 유용합니다. 호출측이 null을 정상 케이스로 처리한다는 계약을 KDoc로 명시해두면 오용을 줄일 수 있습니다.

/**
 * @return Success(null) = 오늘 감정 미등록, Failure = API/매핑 오류
 */
presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/template/EmptyRoutineView.kt (2)

31-38: 문자열 하드코딩 제거: string resource로 이전 권장

국제화/테스트/일관성을 위해 문자열을 리소스로 관리하는 것이 바람직합니다.

예시:

import androidx.compose.ui.res.stringResource

Text(text = stringResource(R.string.home_empty_title), ...)
Text(text = stringResource(R.string.home_empty_description), ...)
Text(text = stringResource(R.string.home_empty_cta), ...)

리소스 키는 프로젝트 컨벤션에 맞게 정의해 주세요.

Also applies to: 57-59


65-71: 프리뷰 함수명 구식 표기 정리

컴포저블 이름 변경에 맞춰 Preview 함수명도 일관되게 정리하면 가독성이 좋아집니다.

-@Preview(showBackground = true)
-@Composable
-private fun RoutineEmptyViewPreview() {
-    EmptyRoutineView(
-        onRegisterRoutineClick = {},
-    )
-}
+@Preview(showBackground = true)
+@Composable
+private fun EmptyRoutineViewPreview() {
+    EmptyRoutineView(
+        onRegisterRoutineClick = {},
+    )
+}
domain/src/main/java/com/threegap/bitnagil/domain/routine/model/RoutineCompletionInfo.kt (1)

5-7: 도메인 계층에서 DTO 스타일 명명(…Yn) 사용은 지양 권장

Boolean에 ‘Yn’ 접미어는 데이터/DTO 흔적입니다. 도메인에선 의미 중심 네이밍이 가독성과 유지보수에 유리합니다.

예시 리팩터:

data class RoutineCompletionInfo(
    val routineId: String,
    val isRoutineCompleted: Boolean,
    val subRoutineCompletionStates: List<Boolean>,
)

동일 PR 내 광범위 변경을 수반하므로 시기 조절은 가능하나, 도메인/데이터 명명 분리를 장기적으로 고려해 주세요.

data/src/main/java/com/threegap/bitnagil/data/emotion/service/EmotionService.kt (1)

22-25: 네이밍 일관성 개선 제안(getEmotions/postEmotions vs fetchTodayEmotion)

Service 인터페이스 내 메서드 네이밍을 get*/post*로 통일하면 검색성과 일관성이 향상됩니다. 또한 파라미터명도 Path와 동일하게 맞추면 가독성이 좋습니다.

-    @GET("/api/v2/emotion-marbles/{searchDate}")
-    suspend fun fetchTodayEmotion(
-        @Path("searchDate") date: String,
-    ): BaseResponse<TodayEmotionResponseDto>
+    @GET("/api/v2/emotion-marbles/{searchDate}")
+    suspend fun getTodayEmotion(
+        @Path("searchDate") searchDate: String,
+    ): BaseResponse<TodayEmotionResponseDto>

호출부 변경이 필요하므로 동일 브랜치 내에서 일괄 치환을 권장합니다.

domain/src/main/java/com/threegap/bitnagil/domain/routine/model/Routine.kt (2)

8-13: 단일 모델에 반복 요일/실행시간과 특정 일자 상태(routineDate/완료여부)를 혼합 보관하는 설계 검토

스케줄(패턴)과 인스턴스(날짜별 상태)를 한 모델에 혼합하면 의미 경계가 흐려지고 변경 영향이 커집니다. 스케줄용 Routine(템플릿)과 날짜 인스턴스용 DayRoutine(또는 RoutineInstance)을 분리하는 것을 권장드립니다.

효과:

  • 변경 파급 최소화(템플릿/인스턴스 분리)
  • 테스트/매핑 단순화
  • v2 응답(일자별)과 스케줄(반복 요일) 도메인 경계를 명확화

9-11: Boolean 필드의 ‘Yn’ 접미사 도메인 누수

도메인 레벨에서는 ‘is…’/’…Completed’ 등 의미 기반 명명을 추천합니다.

예:

val isRoutineCompleted: Boolean,
val subRoutineCompletionStates: List<Boolean>,

데이터 계층(DTO)과의 매핑에서만 ‘Yn’를 유지하고, 도메인에서는 의미형 네이밍을 사용해 주세요.

data/src/main/java/com/threegap/bitnagil/data/routine/model/response/RoutineDto.kt (1)

19-20: routineDate 포맷/타임존 계약 검증 권장

routineDate가 "yyyy-MM-dd"로 고정인지, 서버-클라이언트 간 타임존 변환 여지가 없는지 확인해주세요. 도메인에서 LocalDate 등으로 모델링하는 방안도 고려할 수 있습니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/template/RoutineSection.kt (1)

46-94: 미동작 Preview 정리 또는 예제 데이터로 활성화 권장

현재 Preview 본문이 전부 주석이라 미리보기가 동작하지 않습니다. 유지하지 않을 거면 제거, 유지할 거면 간단한 샘플로 활성화를 권장합니다.

예시(Preview 본문 교체):

 @Preview(showBackground = true)
 @Composable
-private fun RoutineSectionPreview() {
-//    RoutineSection(
-//        ...
-//    )
-}
+private fun RoutineSectionPreview() {
+    val sample = RoutineUiModel(
+        routineId = "uuid1",
+        routineName = "개운하게 일어나기",
+        repeatDay = emptyList(),
+        executionTime = "20:30:00",
+        routineDate = "2025-08-15",
+        routineCompleteYn = false,
+        subRoutineNames = listOf("물 마시기", "스트레칭하기", "심호흡하기"),
+        subRoutineCompleteYn = listOf(true, false, true),
+        recommendedRoutineType = null,
+    )
+    RoutineSection(
+        routine = sample,
+        onRoutineToggle = {},
+        onSubRoutineToggle = { _, _ -> },
+    )
+}
presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/RoutineUiModel.kt (1)

9-20: @parcelize + List 조합 호환성 점검

List<Boolean>의 parcelization은 프레임워크 버전에 따라 호환성 이슈가 있을 수 있습니다(특히 프로세스 재생성/프로세스 간 전달 시). 문제가 관측되면 BooleanArray로 교체하거나 @TypeParceler로 커스텀 parceler를 적용하는 방안을 고려해주세요.

presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/template/CollapsibleHomeHeader.kt (2)

47-51: 알파 값 안전화(coerce) 제안

overscroll/바운스 상황에서 collapseProgress가 [0,1] 범위를 벗어나면 음수/1 초과 알파가 타겟이 될 수 있습니다. UI 글리치 방지를 위해 타겟 값을 강제로 [0f, 1f] 범위로 제한하는 것을 권장합니다.

적용 diff:

-    val alpha by animateFloatAsState(
-        targetValue = 1f - collapsibleHeaderState.collapseProgress,
+    val alpha by animateFloatAsState(
+        targetValue = (1f - collapsibleHeaderState.collapseProgress).coerceIn(0f, 1f),
         animationSpec = tween(durationMillis = 300),
         label = "header_alpha",
     )

53-57: 문자열 구성 방식 정리 및 국제화 대응

hasEmotion 체크 후에도 안전 호출(?.)을 사용하는 것은 불필요합니다. 또한 하드코딩된 문자열은 string 리소스로 이동해 국제화/접근성에 대비하는 것을 권장합니다.

가벼운 정리(diff):

-    val hasEmotion = todayEmotion != null
-    val welcomeMessage = if (hasEmotion) {
-        "${userName}님,\n${todayEmotion?.homeMessage}"
-    } else {
-        "${userName}님, 오셨군요!\n오늘 기분은 어떤가요?"
-    }
+    val hasEmotion = todayEmotion != null
+    val welcomeMessage = todayEmotion?.let {
+        "${userName}님,\n${it.homeMessage}"
+    } ?: "${userName}님, 오셨군요!\n오늘 기분은 어떤가요?"

추가로, string 리소스 사용을 권장합니다:

// 예시
val welcomeMessage = todayEmotion?.let {
    stringResource(R.string.home_welcome_with_emotion, userName, it.homeMessage)
} ?: stringResource(R.string.home_welcome_default, userName)

리소스 추가가 필요하면 알려주세요. 키/형식까지 포함해 드리겠습니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeIntent.kt (1)

13-13: 서브루틴 인덱스 기반 토글: 오프바이원 위험 명시 필요

subRoutineIndex: Int로 전환되면서 0/1 기반 혼동 가능성이 큽니다. 파라미터가 0-based임을 KDoc에 명시하고, 호출부/도메인 변환 시에도 동일 기준을 유지했는지 확인 부탁드립니다.

원한다면 typealias SubRoutineIndex = Int를 두어 의미를 강화하는 것도 방법입니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/template/WeeklyDatePicker.kt (2)

58-58: 오늘 날짜 계산은 remember 불필요

LocalDate.now()는 매우 저비용이며, 자정 경과 후 갱신성 측면에서도 remember 없이 사용하는 편이 낫습니다.

적용 diff:

-    val today = remember { LocalDate.now() }
+    val today = LocalDate.now()

139-145: 접근성(semantics) 보강 제안

clickableWithoutRipple만으로는 접근성 트리에 버튼 역할이 노출되지 않습니다. semantics { role = Role.Button }를 추가해 스크린리더 친화도를 높여주세요.

예시:

import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.Role

modifier = Modifier
    .semantics { role = Role.Button }
    .clickableWithoutRipple { onDateClick() }
presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeScreen.kt (2)

146-168: "더보기" 텍스트 클릭의 접근성 및 가시성 개선

텍스트에 직접 clickableWithoutRipple을 적용하면 포커스/역할 노출이 부족합니다. 디자인 시스템의 버튼/텍스트 버튼 컴포넌트를 활용하거나, 최소한 semantics 역할 지정/터치 타겟(48dp 이상) 보장을 권장합니다.

예시:

  • BitnagilTextButton으로 교체
  • 또는 semantics { role = Role.Button }minimumTouchTargetSize() 적용

90-93: 남겨진 TODO 처리 제안

"루틴 리스트" 화면 이동 TODO가 남아있습니다. 내비게이션 경로/딥링크가 정해져 있으면 연결해 드릴 수 있습니다.

원하시면 라우트 정의부터 HomeSideEffect 추가, 컨테이너 연결까지 PR에 포함할 수 있게 패치 제안 드리겠습니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt (3)

313-318: 메인 루틴 완료 여부 계산을 단순화할 수 있습니다

분기 없이 전체가 true인지로만 판별하면 동일한 결과를 얻습니다.

-            val routineCompleted = if (isCompleted) {
-                updatedSubRoutineCompleteYn.all { it }
-            } else {
-                false
-            }
+            val routineCompleted = updatedSubRoutineCompleteYn.all { it }

215-229: 로딩 상태 관리가 경쟁 상태를 만들 수 있습니다

fetchUserProfile/fetchWeeklyRoutines/fetchTodayEmotion 모두에서 UpdateLoading(true/false)를 독립적으로 토글합니다. 비동기 요청이 동시에 수행될 경우, 가장 늦게 완료된 요청이 false를 세팅하며 로딩 인디케이터가 조기 종료되거나 깜빡임이 발생할 수 있습니다. 로딩 카운터(참조 카운팅)나 로딩 타입별 분리(예: ContentLoading vs. InlineLoading)를 고려해 주세요.

원하시면 전역 로딩 카운터 적용 패턴으로 리팩터링 드리겠습니다.


215-229: 로그 메시지 표현 개선 제안

"나의 감정 실패" 대신 "오늘의 감정 조회 실패"가 의도를 더 명확히 전달합니다.

-                    Log.e("HomeViewModel", "나의 감정 실패: ${error.message}")
+                    Log.e("HomeViewModel", "오늘의 감정 조회 실패: ${error.message}")

- 네이밍 변경
- 감정 조회 호출 시 LocalDate.now()를 통하도록 변경
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 (2)
presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt (2)

61-61: 굿: 초기 감정 조회 기준을 '오늘(LocalDate.now())'로 통일 — 이전 리뷰 반영 확인

init 단계에서 LocalDate.now()를 사용해 초기 감정 조회를 트리거하도록 수정된 점 좋습니다. observeEmotionChangeEvent와의 일관성이 확보되었습니다.


303-311: 서브루틴 인덱스 범위 체크 기준 오류 및 중복 체크 제거 필요

현재 subRoutineNames.size로 검사 후, 내부에서 다시 subRoutineCompleteYn.size를 사실상 재검사하고 있어 불일치/침묵 실패 가능성이 있습니다. 실제 갱신 대상 리스트인 subRoutineCompleteYn 기준으로 단일 검사를 수행하고, 내부 중복 조건은 제거하세요.

아래 패치를 제안합니다.

-            if (subRoutineIndex < 0 || subRoutineIndex >= routine.subRoutineNames.size) {
-                return@updateRoutinesForDate false
-            }
-
-            val updatedSubRoutineCompleteYn = routine.subRoutineCompleteYn.toMutableList().apply {
-                if (subRoutineIndex < size) {
-                    this[subRoutineIndex] = isCompleted
-                }
-            }
+            if (subRoutineIndex !in routine.subRoutineCompleteYn.indices) {
+                return@updateRoutinesForDate false
+            }
+
+            val updatedSubRoutineCompleteYn = routine.subRoutineCompleteYn
+                .toMutableList()
+                .apply { this[subRoutineIndex] = isCompleted }
🧹 Nitpick comments (3)
presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt (3)

215-229: isLoading 플래그가 동시 네트워크 호출과 충돌할 수 있습니다

현재 fetchUserProfile, fetchWeeklyRoutines, fetchTodayEmotion가 병렬로 실행되며 각각 isLoading을 true/false로 토글합니다. 이로 인해 한 작업이 완료되면 다른 작업이 진행 중임에도 로딩이 false로 내려가 UI 깜빡임/오표시가 발생할 수 있습니다.

두 가지 개선안 중 하나를 고려해 주세요:

  • 옵션 A(권장): ViewModel에 로딩 카운터를 두어 in-flight 요청 수를 기반으로 isLoading을 관리
  • 옵션 B: HomeState에 isUserProfileLoading, isWeeklyRoutinesLoading, isEmotionLoading 등 세분화된 플래그를 두고 UI에서 합성

아래는 옵션 A의 예시입니다.

-        sendIntent(HomeIntent.UpdateLoading(true))
+        setLoading(true)
         viewModelScope.launch {
             fetchTodayEmotionUseCase(currentDate.toString()).fold(
                 onSuccess = { todayEmotion ->
-                    sendIntent(HomeIntent.LoadTodayEmotion(todayEmotion?.toUiModel()))
-                    sendIntent(HomeIntent.UpdateLoading(false))
+                    sendIntent(HomeIntent.LoadTodayEmotion(todayEmotion?.toUiModel()))
+                    setLoading(false)
                 },
                 onFailure = { error ->
                     Log.e("HomeViewModel", "나의 감정 실패: ${error.message}")
-                    sendIntent(HomeIntent.UpdateLoading(false))
+                    setLoading(false)
                 },
             )
         }

추가로 ViewModel 내부에 헬퍼를 정의하세요:

// ViewModel 내부
private var loadingCount = 0
private fun setLoading(inFlight: Boolean) {
    loadingCount += if (inFlight) 1 else -1
    if (loadingCount < 0) loadingCount = 0
    sendIntent(HomeIntent.UpdateLoading(loadingCount > 0))
}

원하시면 전체 호출부에 일괄 적용하는 패치를 도와드릴게요.


313-317: routineCompleteYn 계산 단순화 제안

분기 없이 all { it }만으로 동일 의미를 표현할 수 있습니다. 가독성이 향상됩니다.

-            val routineCompleted = if (isCompleted) {
-                updatedSubRoutineCompleteYn.all { it }
-            } else {
-                false
-            }
+            val routineCompleted = updatedSubRoutineCompleteYn.all { it }

356-372: 변경 계산에서 선형 탐색(N^2) → 해시 맵(N)으로 최적화 가능

originalRoutineList.find를 루프마다 호출해 O(n^2)입니다. associateBy로 사전(Map)을 만들어 O(n)으로 줄일 수 있습니다. 루틴 수가 많아지면 체감됩니다.

-        return buildList {
-            updatedRoutineList.forEach { updatedRoutine ->
-                val originalRoutine = originalRoutineList.find { it.routineId == updatedRoutine.routineId }
+        return buildList {
+            val originalById = originalRoutineList.associateBy { it.routineId }
+            updatedRoutineList.forEach { updatedRoutine ->
+                val originalRoutine = originalById[updatedRoutine.routineId]
                 val hasMainRoutineChanged = originalRoutine?.routineCompleteYn != updatedRoutine.routineCompleteYn
                 val hasSubRoutinesChanged = originalRoutine?.subRoutineCompleteYn != updatedRoutine.subRoutineCompleteYn

                 if (hasMainRoutineChanged || hasSubRoutinesChanged) {
                     add(
                         RoutineCompletionInfo(
                             routineId = updatedRoutine.routineId,
                             routineCompleteYn = updatedRoutine.routineCompleteYn,
                             subRoutineCompleteYn = updatedRoutine.subRoutineCompleteYn,
                         ),
                     )
                 }
             }
         }
📜 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 e19aca6 and 97e319d.

📒 Files selected for processing (2)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt (13 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/template/EmptyRoutineView.kt (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/template/EmptyRoutineView.kt
🧰 Additional context used
🧬 Code Graph Analysis (1)
presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt (6)
data/src/main/java/com/threegap/bitnagil/data/emotion/service/EmotionService.kt (1)
  • fetchTodayEmotion (22-25)
data/src/main/java/com/threegap/bitnagil/data/emotion/datasourceImpl/EmotionDataSourceImpl.kt (1)
  • fetchTodayEmotion (28-31)
data/src/main/java/com/threegap/bitnagil/data/emotion/datasource/EmotionDataSource.kt (1)
  • fetchTodayEmotion (10-10)
domain/src/main/java/com/threegap/bitnagil/domain/emotion/repository/EmotionRepository.kt (1)
  • fetchTodayEmotion (12-12)
data/src/main/java/com/threegap/bitnagil/data/emotion/repositoryImpl/EmotionRepositoryImpl.kt (1)
  • fetchTodayEmotion (37-38)
presentation/src/main/java/com/threegap/bitnagil/presentation/common/mviviewmodel/MviViewModel.kt (2)
  • sendSideEffect (23-23)
  • sendIntent (30-37)
⏰ 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 (7)
presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt (7)

109-116: LoadTodayEmotion 상태 업데이트와 감정 등록 네비게이션 분리 적절

감정 로딩은 상태 인텐트로, 화면 전환은 사이드 이펙트로 분리한 설계가 명확하고 유지보수에 유리합니다.


143-144: 감정 변경 이벤트 시 ‘오늘’ 기준 재조회 일관성 확보

이벤트 발생 시 LocalDate.now()를 사용해 fetchTodayEmotion을 호출하는 흐름이 init과 동일 기준으로 유지됩니다.


204-204: v2 응답 → UI 모델 변환 시점을 ViewModel로 집약한 점 좋습니다

toUiModel()를 ViewModel 레이어에서 적용해 프레젠테이션 상태가 일관되게 구성됩니다.


239-245: 인덱스 기반 토글 전환 — UI/도메인 순서 불일치 가능성 검증 필요

서브루틴 id → 인덱스 기반으로 바뀌면서, UI에서 표시되는 서브루틴의 정렬/필터가 도메인 모델의 subRoutineCompleteYn 순서와 항상 일치해야 합니다. 정렬이 다르면 다른 항목이 토글되는 문제가 발생합니다. UI에서 인덱스를 생성할 때 데이터 소스의 원본 순서를 그대로 사용하고 있는지 확인 부탁드립니다.


280-284: 메인 루틴 토글 시 서브루틴 상태 일괄 동기화 처리 적절

routineCompleteYn를 설정하면서 subRoutineCompleteYn도 일관되게 전체 갱신하는 로직이 도메인 제약에 부합합니다.


334-348: 일자별 루틴 업데이트 시 불변성 유지와 allCompleted 재계산 처리 깔끔합니다

dayRoutines.copy와 map 복사로 상태 불변성을 지키며, allCompleted를 루틴 리스트 기반으로 재계산하는 흐름이 명확합니다.


385-387: Batch sync 요청 페이로드 구성 적절

변경된 항목만 RoutineCompletionInfo로 누적해 RoutineCompletionInfos로 싱크하는 설계가 네트워크 효율성과 서버 일관성에 유리합니다.

- items() API를 사용해 키/콘텐츠 타입을 명시
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/home/HomeScreen.kt (1)

170-193: LazyColumn.items 전환 및 안정 키 적용 잘 하셨습니다

forEach { item { ... } }에서 items(...)로 전환하며 안정 키(routineId + selectedDate)를 부여한 점이 목록 성능과 안정성에 유리합니다. 이전 리뷰 피드백이 잘 반영됐습니다.

🧹 Nitpick comments (4)
presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeScreen.kt (4)

91-93: TODO: '더보기' 네비게이션 연결 필요

현재 TODO로 남아 있어 UX 데드엔드가 발생합니다. 컨테이너에서 전달받은 onShowMoreRoutinesClick을 상위 NavGraph로 전달하여 목록 화면으로 이동을 연결해 주세요. 필요하다면 사이드이펙트로 래핑해 일관된 내비게이션 패턴을 유지하는 것도 좋습니다.

원하시면 NavGraph 내 라우팅/인자 정의와 연결 코드까지 만들어 드릴게요.


137-145: 빈 상태에서 헤더 콜랩스가 작동하지 않음 — 스크롤 가능 영역과 nestedScroll 연결 제안

Empty 상태 분기에서는 스크롤 컨테이너가 없어 CollapsibleHomeHeader의 nestedScroll이 구동되지 않습니다. 빈 상태에서도 헤더가 접히도록, EmptyRoutineView의 modifier에 verticalScroll + nestedScroll을 연결하는 것을 권장합니다.

아래 diff 적용 제안:

-                EmptyRoutineView(
-                    onRegisterRoutineClick = onRegisterRoutineClick,
-                    modifier = Modifier
-                        .fillMaxSize()
-                        .background(BitnagilTheme.colors.coolGray99)
-                        .padding(top = 62.dp),
-                )
+                EmptyRoutineView(
+                    onRegisterRoutineClick = onRegisterRoutineClick,
+                    modifier = Modifier
+                        .fillMaxSize()
+                        .nestedScroll(collapsibleHeaderState.nestedScrollConnection)
+                        .verticalScroll(rememberScrollState())
+                        .background(BitnagilTheme.colors.coolGray99)
+                        .padding(top = 62.dp),
+                )

추가로 필요한 import:

import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll

154-167: 문자열 하드코딩 제거 및 터치 타깃 개선 제안

  • "루틴 리스트", "더보기" 하드코딩은 현지화/접근성 측면에서 string 리소스로 분리하는 것이 좋습니다.
  • "더보기"의 터치 타깃은 권장 최소 48dp에 약간 못 미칠 수 있습니다. 세로 padding을 조금 늘려주세요.

변경 diff:

-                    Text(
-                        text = "루틴 리스트",
+                    Text(
+                        text = stringResource(R.string.home_routine_list_title),
                         color = BitnagilTheme.colors.coolGray60,
                         style = BitnagilTheme.typography.body2SemiBold,
                         modifier = Modifier.padding(top = 6.dp),
                     )
-                    Text(
-                        text = "더보기",
+                    Text(
+                        text = stringResource(R.string.common_more),
                         color = BitnagilTheme.colors.coolGray10,
                         style = BitnagilTheme.typography.body2SemiBold,
                         modifier = Modifier
                             .clickableWithoutRipple { onShowMoreRoutinesClick() }
-                            .padding(vertical = 10.dp, horizontal = 12.dp),
+                            .padding(vertical = 14.dp, horizontal = 12.dp),
                     )

추가로 필요한 import:

import androidx.compose.ui.res.stringResource

리소스 예시:

<!-- res/values/strings.xml -->
<string name="home_routine_list_title">루틴 리스트</string>
<string name="common_more">더보기</string>

179-183: items에 contentType 제공으로 재활용 힌트 추가 제안

items(...)contentType을 제공하면 Compose가 동일 타입 아이템 간 재활용/측정 최적화를 더 잘 수행합니다.

-                    items(
-                        items = uiState.selectedDateRoutines,
-                        key = { routine -> "${routine.routineId}_${uiState.selectedDate}" },
-                    ) { routine ->
+                    items(
+                        items = uiState.selectedDateRoutines,
+                        key = { routine -> "${routine.routineId}_${uiState.selectedDate}" },
+                        contentType = { "routine_item" },
+                    ) { routine ->
📜 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 97e319d and 86f33b9.

📒 Files selected for processing (1)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeScreen.kt (4 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeScreen.kt (5)
presentation/src/main/java/com/threegap/bitnagil/presentation/home/util/CollapsibleHeaderState.kt (1)
  • rememberCollapsibleHeaderState (34-53)
presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/template/WeeklyDatePicker.kt (1)
  • WeeklyDatePicker (48-125)
presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/template/EmptyRoutineView.kt (1)
  • EmptyRoutineView (20-63)
presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/template/RoutineSection.kt (1)
  • RoutineSection (17-42)
presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/template/CollapsibleHomeHeader.kt (1)
  • CollapsibleHomeHeader (39-123)
⏰ 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/home/HomeScreen.kt (2)

82-84: v2 서브루틴 토글 시그니처 반영 OK

서브루틴 토글 콜백을 (String, Int, Boolean)으로 변경하고, ViewModel의 toggleSubRoutineCompletion에 동일한 순서로 위임한 점 확인했습니다.


199-202: CollapsibleHomeHeader todayEmotion/등록 콜백 매핑 확인

todayEmotiononRegisterEmotion 매핑이 새 모델/시그니처에 맞게 정리되었습니다.

-  AsyncImage의 model 생성 시 remember를 사용하여 불필요한 재생성 방지
- 중복 검증 처리 제거
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 (2)
presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt (2)

61-62: 초기 감정 조회 Today(LocalDate.now()) 기준 통일 — 이전 코멘트 해결됨

초기 진입 시에도 오늘 날짜 기준으로 감정을 조회하도록 수정되어 일관성이 확보되었습니다.


303-316: 서브루틴 인덱스 범위 체크와 중복 검증 제거 — 이전 코멘트 해결됨

indices 사용과 내부 중복 체크 제거가 반영되었습니다. 불필요한 조건 제거로 코드가 간결해졌습니다.

🧹 Nitpick comments (4)
presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt (4)

215-229: 로딩 상태 관리 충돌 가능성: 동시에 여러 API 호출 시 깜빡임/조기 종료 위험

init에서 fetchUserProfile, fetchWeeklyRoutines, fetchTodayEmotion가 각각 isLoading을 독립적으로 on/off 합니다. 이 경우 먼저 끝난 호출이 isLoading=false로 내려버려 남은 호출 진행 중에도 로딩이 해제될 수 있습니다.

다음 중 하나로 정리하는 것을 권장합니다.

  • 로딩 카운터 방식: 시작 시 +1, 종료 시 -1, 카운터>0이면 isLoading=true.
  • 액션별 로딩 분리: routinesLoading, emotionLoading 등 독립 상태로 관리 후 UI에서 합성.

원하시면 공용 withLoading 헬퍼(카운터 기반)와 적용 패치까지 제공하겠습니다.


223-225: 로그 메시지 표현 개선(의도 명확화)

"나의 감정 실패"보다 "오늘 감정 가져오기 실패"가 현재 API 의도를 더 정확히 설명합니다.

다음 변경을 제안합니다:

-                    Log.e("HomeViewModel", "나의 감정 실패: ${error.message}")
+                    Log.e("HomeViewModel", "오늘 감정 가져오기 실패: ${error.message}")

350-366: O(n^2) 탐색을 O(n)으로 개선하여 성능/가독성 향상 가능

originalRoutineList에서 매번 find를 수행해 변경이 많은 경우 O(n^2)입니다. id→routine 맵을 만들어 선형으로 줄일 수 있습니다.

다음과 같이 리팩터를 제안합니다:

-        return buildList {
-            updatedRoutineList.forEach { updatedRoutine ->
-                val originalRoutine = originalRoutineList.find { it.routineId == updatedRoutine.routineId }
+        val originalById = originalRoutineList.associateBy { it.routineId }
+        return buildList {
+            updatedRoutineList.forEach { updatedRoutine ->
+                val originalRoutine = originalById[updatedRoutine.routineId]
                 val hasMainRoutineChanged = originalRoutine?.routineCompleteYn != updatedRoutine.routineCompleteYn
                 val hasSubRoutinesChanged = originalRoutine?.subRoutineCompleteYn != updatedRoutine.subRoutineCompleteYn
                 if (hasMainRoutineChanged || hasSubRoutinesChanged) {
                     add(
                         RoutineCompletionInfo(
                             routineId = updatedRoutine.routineId,
                             routineCompleteYn = updatedRoutine.routineCompleteYn,
                             subRoutineCompleteYn = updatedRoutine.subRoutineCompleteYn,
                         ),
                     )
                 }
             }
         }

379-397: 실패 분기에서만 isLoading(false) 호출 — 일관성 측면에서 제거 권장

syncRoutineChangesForDate는 시작 시 isLoading(true)을 설정하지 않기 때문에 실패 시에만 false를 보내는 건 상태 비일관성을 만들 수 있습니다.

아래와 같이 불필요한 호출 제거를 제안합니다:

-                sendIntent(HomeIntent.UpdateLoading(false))

또는 위의 로딩 카운터/분리 전략을 도입해 일관성 있게 관리하는 것이 더 좋습니다.

📜 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 5c17495 and 2a655e9.

📒 Files selected for processing (2)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt (13 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/template/CollapsibleHomeHeader.kt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/template/CollapsibleHomeHeader.kt
🧰 Additional context used
🧬 Code Graph Analysis (1)
presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt (6)
data/src/main/java/com/threegap/bitnagil/data/emotion/service/EmotionService.kt (1)
  • fetchTodayEmotion (22-25)
data/src/main/java/com/threegap/bitnagil/data/emotion/datasource/EmotionDataSource.kt (1)
  • fetchTodayEmotion (10-10)
data/src/main/java/com/threegap/bitnagil/data/emotion/datasourceImpl/EmotionDataSourceImpl.kt (1)
  • fetchTodayEmotion (28-31)
domain/src/main/java/com/threegap/bitnagil/domain/emotion/repository/EmotionRepository.kt (1)
  • fetchTodayEmotion (12-12)
data/src/main/java/com/threegap/bitnagil/data/emotion/repositoryImpl/EmotionRepositoryImpl.kt (1)
  • fetchTodayEmotion (37-38)
presentation/src/main/java/com/threegap/bitnagil/presentation/common/mviviewmodel/MviViewModel.kt (2)
  • sendSideEffect (23-23)
  • sendIntent (30-37)
⏰ 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 (10)
presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt (10)

6-11: v2 전환에 맞춘 의존성 교체가 깔끔합니다

FetchTodayEmotionUseCase, RoutineCompletionInfos 도입으로 v2 API와 모델 정합성이 좋아졌습니다. 불필요한 의존성 제거(이 파일에서는 삭제 라인이 없지만)도 함께 점검해 주세요.


40-45: 감정 조회 유스케이스 주입 위치 적절

DI 구성과 사용 지점(fetchTodayEmotion) 연결이 명확합니다.


105-107: 서브루틴 토글 파라미터 인덱스 기반으로 변경 OK

v2 구조(서브루틴 DTO 제거)에 맞게 인덱스 기반으로 일원화되어 가독성과 유지보수성이 좋아졌습니다.


109-111: HomeIntent.LoadTodayEmotion 분기 추가 적절

UI 상태(todayEmotion) 반영 경로가 명확합니다.


114-116: 감정 등록 네비게이션 단순화 적절

편집 경로 제거 후 플로우가 직관적입니다.


139-146: 감정 변경 이벤트 시점에도 Today 기준 조회 — 일관성 확보

observeEmotionChangeEvent에서도 LocalDate.now()를 사용해 의도에 부합합니다.


204-205: 주간 루틴 UI 매핑 시점 적절

네트워크 성공 콜백에서 toUiModel() 적용으로 상태 전이가 명확합니다. v2 응답 구조에 맞춘 매퍼의 필드 정합성만 한 번 더 점검 부탁드립니다.


239-245: 서브루틴 토글 처리 흐름 일관성 좋습니다

optimistic update → 변경 계산 → 배치 동기화 플로우가 메인/서브 모두 동일하게 구성되어 일관성이 좋습니다.


280-286: 메인 루틴 토글 시 서브루틴 전부 동기화 처리 합리적

메인 완료 여부가 서브루틴 전체에 전파되도록 복사/매핑 처리한 점이 v2 모델과 잘 맞습니다.


328-342: 일자별 allCompleted 재계산과 부분 갱신 방식 적절

선택된 날짜만 최소 변경으로 갱신하는 전략이 효율적입니다.

Copy link
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.

작업량 많으셨을텐데 정말 고생 많으셨습니다! 🚀

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/home/component/template/WeeklyDatePicker.kt (1)

58-62: derivedState 키 이슈 해결됨 — weeklyDates, routines 포함해 재계산 보장

이전 리뷰에서 지적된 캐시 키 누락이 잘 반영되었습니다. 주차 변경·루틴 변경 시에만 Map을 재구성하고, selectedDate 변경만으로는 재계산되지 않아 효율적입니다.

🧹 Nitpick comments (3)
presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/template/WeeklyDatePicker.kt (3)

133-152: DateItem의 modifier 적용 위치 수정 제안 (현재 Box에만 적용됨)

현재 modifier 파라미터가 루트 Column이 아니라 내부 Box에 적용되어, 호출자가 외부에서 패딩/테스트 태그/레이아웃 제어를 하기가 어렵습니다. 관례상 루트 컨테이너에 modifier를 적용하고, 내부에는 새 Modifier를 생성하는 편이 좋습니다.

적용 diff:

@@
     Column(
         verticalArrangement = Arrangement.spacedBy(7.dp),
         horizontalAlignment = Alignment.CenterHorizontally,
-        modifier = Modifier.clickableWithoutRipple { onDateClick() },
+        modifier = modifier.clickableWithoutRipple { onDateClick() },
     ) {
@@
         Box(
             contentAlignment = Alignment.Center,
-            modifier = modifier
+            modifier = Modifier
                 .size(30.dp)
                 .background(
                     color = if (!isSelected) Color.Transparent else BitnagilTheme.colors.coolGray10,
                     shape = RoundedCornerShape(8.dp),
                 ),
         ) {

86-98: 내비게이션 아이콘 버튼에 접근성 라벨 추가 권장

스크린리더 사용자를 위해 이전/다음 주 이동 버튼에 contentDescription을 제공하는 것을 권장합니다. 디자인 시스템 컴포넌트가 라벨을 지원한다면 아래처럼 추가해 주세요. (만약 지원하지 않는다면 컴포넌트 레벨에서 파라미터 추가를 고려해 주세요.)

예시 diff:

                 BitnagilIconButton(
                     id = R.drawable.ic_chevron_left_md,
                     onClick = onPreviousWeekClick,
                     paddingValues = PaddingValues(12.dp),
                     tint = BitnagilTheme.colors.coolGray10,
+                    contentDescription = "이전 주로 이동",
                 )
@@
                 BitnagilIconButton(
                     id = R.drawable.ic_chevron_right_md,
                     onClick = onNextWeekClick,
                     paddingValues = PaddingValues(12.dp),
                     tint = BitnagilTheme.colors.coolGray10,
+                    contentDescription = "다음 주로 이동",
                 )

57-57: 오늘(today) 계산 — 자정 경계 업데이트 고려

remember { LocalDate.now() }는 최초 컴포지션 시점의 날짜를 고정합니다. 자정 이후에도 동일 컴포지션이 유지되면 "오늘" 라벨이 갱신되지 않을 수 있습니다. 간단히는 remember를 제거하거나, 더 나아가 자정에 갱신하도록 타이머/Effect를 붙이는 방식을 고려해 보세요.

간단 수정:

-    val today = remember { LocalDate.now() }
+    val today = LocalDate.now()
📜 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 2a655e9 and 17600db.

📒 Files selected for processing (1)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/template/WeeklyDatePicker.kt (8 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/home/component/template/WeeklyDatePicker.kt (2)

163-188: 완료 뱃지 애니메이션 구성 적절

AnimatedVisibilityscaleIn/fadeIn(키프레임)과 scaleOut/fadeOut을 조합한 점이 좋습니다. 7개 고정 아이템이라 성능 부담도 크지 않습니다. tint = null로 아이콘 원색을 살린 것도 의도와 맞아 보입니다.


49-56: WeeklyDatePicker API 변경(파라미터 routines 추가) — 호출부 정합성만 점검해 주세요

프리뷰는 이미 업데이트되어 문제없습니다. 실제 호출부(프로덕션 코드)에서도 모두 routines를 전달하는지 한 번만 재확인 부탁드립니다.

@wjdrjs00 wjdrjs00 merged commit ca58fc2 into develop Aug 15, 2025
2 checks passed
@wjdrjs00 wjdrjs00 deleted the feature/#90-home-redesign branch August 15, 2025 15:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
✨ Feature 새로운 기능 구현 📱 UI UI 추가 및 수정 (비지니스 로직을 포함하지 않는 작업) 🧤 대현
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[FEATURE] 홈 화면 리디자인 변경사항 적용
2 participants