Skip to content

[Refactor/#98] 루틴 작성/수정 화면 리디자인 적용#112

Merged
l5x5l merged 17 commits intodevelopfrom
refactor/#98-redesign_write_routine
Aug 19, 2025
Merged

[Refactor/#98] 루틴 작성/수정 화면 리디자인 적용#112
l5x5l merged 17 commits intodevelopfrom
refactor/#98-redesign_write_routine

Conversation

@l5x5l
Copy link
Copy Markdown
Contributor

@l5x5l l5x5l commented Aug 18, 2025

[ PR Content ]

루틴 작성/수정 화면에 2차 UI를 적용합니다.

Related issue

Screenshot 📸

KakaoTalk_Video_2025-08-19-00-12-35.mp4
KakaoTalk_Video_2025-08-19-00-12-21.mp4

Work Description

  • 루틴 작성/수정화면 2차 디자인 반영
  • 루틴 작성/수정/단일 조회 API v2 반영
  • 루틴 리스트 화면에서 루틴 작성/수정 화면 호출 로직 구현
  • 루틴 관련 클래스에 시작날자, 종료 날자 인자 추가

To Reviewers 📢

  • 루틴 시작날자, 종료날자는 필수값이라는 전제 하에 작성했습니다
  • 루틴 작성/수정 화면으로 이동하는 로직 관련 RoutineListScreen, ViewModel 일부가 수정되었습니다
  • 궁금한 점이나 의문점 있으시다면 코멘트 부탁드립니다!

Summary by CodeRabbit

  • New Features

    • 루틴 기간(시작/종료일) 설정 및 날짜 선택 바텀시트 추가
    • 루틴 작성 화면에 접이식 섹션(서브루틴, 반복 요일, 기간, 시간) 도입 및 UI 확장/축소 지원
    • 서브루틴 사용 안함 옵션 및 최대 3개 입력 필드 제공
    • 루틴 추가/편집 내비게이션 분리 및 편집 시 날짜 업데이트 플래그(불리언) 도입
    • 시간/날짜 출력 포맷 및 날짜 유틸리티 추가
  • Style

    • 입력 필드, 선택 셀, 체크박스, 버튼 등 시각·레이아웃 개선 및 신규 아이콘 추가
    • 여러 컴포넌트에 디자인용 미리보기(Preview) 추가
  • Chores

    • 루틴 관련 API 엔드포인트를 v2로 업데이트

@l5x5l l5x5l self-assigned this Aug 18, 2025
@l5x5l l5x5l added 🔨 Refactor 기존 기능 개선 세환 labels Aug 18, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Aug 18, 2025

Walkthrough

루틴 작성/수정 화면이 재구성되어 추가/수정 내비게이션이 분리되고 수정 시 적용 기준(Boolean 전달이 도입), 기간(start/end)과 업데이트 타입이 도메인·데이터·유스케이스·레포지토리 전반에 추가되었으며 다수의 UI 컴포넌트(확장 섹션, 날짜 피커 등)가 추가·정리되었습니다.

Changes

Cohort / File(s) Summary
Navigation & Route
app/src/main/.../MainNavHost.kt, app/src/main/java/com/threegap/bitnagil/Route.kt, presentation/src/main/java/.../routinelist/RoutineListScreen.kt, presentation/src/main/java/.../routinelist/RoutineListViewModel.kt, presentation/src/main/java/.../routinelist/model/RoutineListSideEffect.kt, presentation/src/main/java/.../writeroutine/model/navarg/WriteRoutineScreenArg.kt, presentation/src/main/java/.../writeroutine/model/WriteRoutineType.kt
내비게이션 콜백 추가(추가/수정), WriteRoutine route/arg에 updateRoutineFromNowDate/isUpdateRoutineFromNowDate(Boolean) 추가, WriteRoutineType enum → Parcelable sealed class 변환
API 경로 변경
data/src/main/.../routine/service/RoutineService.kt, data/src/main/.../writeroutine/service/WriteRoutineService.kt
REST 엔드포인트 v1 → v2로 변경(@GET/@POST/@patch 경로 업데이트)
Routine DTO / Domain 확대
data/src/main/.../routine/model/response/RoutineDto.kt, domain/src/main/java/.../routine/model/Routine.kt
RoutineDto에 routineDeletedYn/routineStartDate/routineEndDate 추가 및 toDomain 매핑, 도메인 Routine에 startDate/endDate/routineDeletedYn 필드 추가
Writeroutine DTO 삭제 및 요청 변경
data/src/main/java/.../writeroutine/model/dto/*.kt (deleted), data/src/main/java/.../writeroutine/model/request/RegisterRoutineRequest.kt, .../EditRoutineRequest.kt, data/src/main/java/.../writeroutine/repositoryImpl/WriteRoutineRepositoryImpl.kt
SubRoutine DTO 파일 삭제, Register/Edit 요청에 routineStartDate/routineEndDate/updateApplyDate 추가, subRoutineInfos → subRoutineName(List)로 변경, 레포지토리 서명·요청 빌드 수정
도메인 인터페이스·유스케이스 변경
domain/src/main/java/.../writeroutine/repository/WriteRoutineRepository.kt, domain/src/main/java/.../writeroutine/usecase/RegisterRoutineUseCase.kt, .../EditRoutineUseCase.kt, (removed) domain/src/main/java/.../writeroutine/usecase/GetChangedSubRoutinesUseCase.kt
레포지토리·유스케이스 시그니처에 startDate/endDate 및 RoutineUpdateType 추가, SubRoutineDiff 기반 변경 감지 유스케이스 삭제
WriteRoutine UI 대개편
presentation/src/main/java/.../writeroutine/WriteRoutineScreen.kt, .../WriteRoutineViewModel.kt, .../model/mvi/WriteRoutineIntent.kt, .../model/mvi/WriteRoutineState.kt
확장 섹션 도입, 날짜 피커(바텀시트) 추가, 서브루틴 관리 방식 변경(selectNotUseSubRoutines), 인텐트·상태 확장, 등록/수정 흐름에 날짜·업데이트타입 반영
새 UI 컴포넌트 추가
presentation/src/main/java/.../component/block/expandablecontent/ExpandableContent.kt, .../routinedetailrow/RoutineDetailRow.kt, .../subroutinefield/SubRoutineField.kt, .../template/datepickerbottomsheet/DatePickerBottomSheet.kt, .../datepickerbottomsheet/model/CalendarUtils.kt
확장형 컨텐츠, 상세 행, 서브루틴 입력 필드, 날짜 피커 바텀시트 및 캘린더 유틸 추가
UI 컴포넌트 변경/정리
presentation/src/main/java/.../atom/namefield/NameField.kt, .../atom/selectcell/SelectCell.kt, .../atom/writeroutinebutton/WriteRoutineButton.kt, .../block/labeledcheckbox/LabeledCheckBox.kt, .../template/timepickerbottomsheet/TimePickerBottomSheet.kt, presentation/src/main/java/.../component/template/Preview.kt
레이아웃·스타일 변경, StrokeButton → WriteRoutineButton 리네임, TimePicker preview 가시성 변경 및 일부 프리뷰 삭제
툴팁 컴포넌트 제거
presentation/src/main/java/.../tooltipbutton/TooltipButton.kt, .../tooltipbutton/model/TooltipDirection.kt, .../tooltipbutton/model/TooltipPositionProvider.kt
Tooltip 관련 컴포넌트 및 관련 타입/포지셔너 삭제
Presentation 모델 추가/확장
presentation/src/main/java/.../writeroutine/model/Date.kt, .../Time.kt
presentation Date(포맷·범위·도메인 변환) 추가, Time에 AM/PM 포맷 메서드 추가
Design system 리소스
core/designsystem/src/main/res/drawable/ic_close_circle.xml, .../ic_down_arrow_20.xml, .../ic_up_arrow_20.xml
신규 벡터 드로어블 추가

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant RoutineList
  participant NavHost
  participant WriteRoutineScreen
  participant WriteRoutineVM
  participant UseCases
  participant Repo
  participant Service

  User->>RoutineList: 추가/수정 버튼 클릭
  RoutineList->>NavHost: NavigateToAddRoutine / NavigateToEditRoutine(id, updateFromNow)
  NavHost->>WriteRoutineScreen: Route.WriteRoutine(args)
  WriteRoutineScreen->>WriteRoutineVM: 초기 로드(args)
  alt 수정
    WriteRoutineVM->>UseCases: getRoutine(id)
    UseCases->>Repo: getRoutine
    Repo->>Service: GET /api/v2/routines/{id}
    Service-->>Repo: RoutineDto(start/end,...)
    Repo-->>UseCases: Domain Routine
    UseCases-->>WriteRoutineVM: Routine
  end
  User->>WriteRoutineScreen: 날짜/시간/서브루틴 편집
  WriteRoutineScreen->>WriteRoutineVM: 인텐트 디스패치
  alt 등록
    WriteRoutineVM->>UseCases: registerRoutine(..., startDate, endDate, subNames)
    UseCases->>Repo: registerRoutine
    Repo->>Service: POST /api/v2/routines
  else 수정
    WriteRoutineVM->>UseCases: editRoutine(id, updateType, ..., startDate, endDate, subNames)
    UseCases->>Repo: editRoutine
    Repo->>Service: PATCH /api/v2/routines
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Assessment against linked issues

Objective Addressed Explanation
루틴 작성/수정 화면 2차 UI 적용 (#98)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
API 경로 버전 변경: /api/v1/.../api/v2/... (data/.../routine/service/RoutineService.kt, data/.../writeroutine/service/WriteRoutineService.kt) 이슈(#98)는 UI 리디자인이 목표이며 API 엔드포인트 버전 변경은 백엔드 연계/버전업 관련으로 UI 리디자인 요구와 직접 명시적 연관이 없음.
Writeroutine DTO·Diff 타입 삭제: (data/.../writeroutine/model/dto/*.kt, domain/src/main/java/.../writeroutine/model/SubRoutineDiff.kt) 이슈(#98)에서 요구한 UI 리디자인 범위에 포함되지 않은 데이터/도메인 타입 제거로 보이며 별도의 데이터 레이어 리팩토링임.
GetChangedSubRoutinesUseCase 및 관련 테스트 삭제 (domain/.../usecase/GetChangedSubRoutinesUseCase.kt, domain/src/test/.../GetChangedSubRoutinesUseCaseTest.kt) 기존 도메인 로직 및 테스트 제거는 UI 리디자인 목표와 직접적 관련이 명확하지 않아 범위 외 변경으로 판단됨.

Possibly related PRs

Suggested labels

✨ Feature

Suggested reviewers

  • wjdrjs00

"푹신한 토끼가 말하네,
섹션을 펼치고 달력을 들여다보며,
오늘인지 내일인지 깃발 하나로 가른다.
start와 end를 잇고 네비는 분리되어,
작은 변경이 큰 흐름을 만든다. 🐇🎋"

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/#98-redesign_write_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: 7

🔭 Outside diff range comments (5)
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/timepickerbottomsheet/TimePickerBottomSheet.kt (2)

82-91: 24시간 설정이 실제 TimePicker에 적용되지 않음 + 불필요한 인스턴스 생성

inflate한 뷰에서 찾은 timePicker가 아닌, 새로운 TimePicker 인스턴스에 setIs24HourView(true)를 적용하고 곧바로 버립니다. 실제 UI에는 24시간 설정이 적용되지 않을 수 있고, 불필요한 객체 생성입니다.

다음과 같이 실제 timePicker에 설정하고, 불필요한 인스턴스 생성을 제거하세요.

             factory = { context ->
                 val view = View.inflate(context, R.layout.spinner_time_picker, null)
                 val timePicker = view.findViewById<TimePicker>(R.id.time_picker)
-                view.tag = timePicker
-
-                TimePicker(context).apply {
-                    setIs24HourView(true)
-                }
-
-                view
+                timePicker.setIs24HourView(true)
+                view.tag = timePicker
+                view
             },

95-100: 사용자 선택값이 초기값으로 되돌아가는 버그

update 블록에서 timePicker 값을 prop(hour/minute)으로 강제 동기화하고 있어, 사용자가 시간을 바꾸면 곧바로 초기값으로 롤백됩니다. 현재 상태(currentHour/currentMinute)를 기준으로 뷰를 업데이트해야 합니다.

다음과 같이 수정하세요.

-                if (timePicker.hour != hour) {
-                    timePicker.hour = hour
-                }
-                if (timePicker.minute != minute) {
-                    timePicker.minute = minute
-                }
+                if (timePicker.hour != currentHour) {
+                    timePicker.hour = currentHour
+                }
+                if (timePicker.minute != currentMinute) {
+                    timePicker.minute = currentMinute
+                }
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/writeroutinebutton/WriteRoutineButton.kt (1)

70-97: 접근성(a11y) 개선: Button role/상태 제공 및 toggleable로 교체

선택 상태를 표현하는 컴포넌트이므로 semantics에 Role.Button과 상태 설명을 제공하는 것이 좋습니다. 또한 클릭 가능 요소를 clickable 대신 toggleable로 교체하면 보조기기에서 토글 가능한 선택 항목으로 정확히 인식합니다.

아래처럼 수정 제안드립니다:

     Box(
         modifier = modifier
             .clip(RoundedCornerShape(12.dp))
             .background(
                 color = backgroundColor,
                 shape = RoundedCornerShape(12.dp),
             )
             .border(
                 width = 1.dp,
                 color = borderColor,
                 shape = RoundedCornerShape(12.dp),
             )
-            .clickableWithoutRipple(enabled = enabled, onClick = onClick),
+            // 접근성: 버튼 롤과 선택 상태를 명시
+            .semantics {
+                role = Role.Button
+                stateDescription = if (isSelected) "선택됨" else "선택 안 됨"
+            }
+            // 선택 상태 토글
+            .toggleable(
+                value = isSelected,
+                enabled = enabled,
+                role = Role.Button,
+                onValueChange = { onClick() },
+            ),
         contentAlignment = Alignment.Center,
     ) {
         content()
     }

추가 import가 필요합니다(파일 상단):

import androidx.compose.foundation.selection.toggleable
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.stateDescription
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt (2)

139-144: 버그: ‘하루 종일’ 토글 시 startTime이 항상 AllDay로 고정됨

토글 해제 시에도 startTime이 AllDay로 남아 UI/검증 로직과 불일치가 발생합니다. 해제 시 startTime을 null로 되돌려 주세요.

-            WriteRoutineIntent.SelectAllTime -> {
-                return state.copy(
-                    selectAllTime = !state.selectAllTime,
-                    startTime = Time.AllDay,
-                )
-            }
+            WriteRoutineIntent.SelectAllTime -> {
+                val toggled = !state.selectAllTime
+                return state.copy(
+                    selectAllTime = toggled,
+                    startTime = if (toggled) Time.AllDay else null,
+                )
+            }

234-256: 버그: SetRoutine 리듀서에서 startDate/endDate 상태 반영 누락

loadRoutine/loadRecommendRoutine에서 전달한 기간이 State에 반영되지 않아 화면/요청 값이 초기값(now)로 남습니다. startDate/endDate를 함께 복사해 주세요.

                 return state.copy(
                     routineName = intent.name,
                     repeatDays = repeatDays,
                     repeatType = repeatType,
                     startTime = intent.startTime,
                     subRoutineNames = intent.subRoutines,
+                    startDate = intent.startDate,
+                    endDate = intent.endDate,
                     loading = false,
                 )
🧹 Nitpick comments (60)
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/labeledcheckbox/LabeledCheckBox.kt (5)

28-31: clickableWithoutRipple 대신 toggleable(Role.Checkbox) 적용으로 접근성/키보드·TV 지원 강화 제안

현재 Row 전체를 clickable로 처리하면 TalkBack에서 “체크됨/해제됨” 상태가 노출되지 않을 수 있습니다. Modifier.toggleable(..., role = Role.Checkbox)를 사용하면 체크 상태가 접근성 트리에 반영되고 키보드/TV에서도 자연스럽게 동작합니다. 또한 최소 터치 타깃(48dp)도 함께 보장하는 것이 좋습니다. 리플 제거는 indication = null로 유지할 수 있습니다.

다음 변경을 제안합니다:

-    Row(
-        modifier = modifier.clickableWithoutRipple(onClick = onClick),
-        verticalAlignment = Alignment.CenterVertically,
-    ) {
+    Row(
+        modifier = modifier
+            .heightIn(min = 48.dp)
+            .toggleable(
+                value = checked,
+                onValueChange = { onClick() },
+                role = Role.Checkbox,
+                indication = null,
+            ),
+        verticalAlignment = Alignment.CenterVertically,
+    ) {

추가 필요 import:

import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.selection.toggleable
import androidx.compose.ui.semantics.Role

32-33: 텍스트 컬러/순서 변경은 좋습니다. 길이 대비 한 줄 생략 처리 권장

레이블이 길 경우 레이아웃 깨짐을 방지하려면 한 줄로 고정하고 말줄임표를 적용하는 편이 안전합니다.

-        Text(label, style = BitnagilTheme.typography.body2Medium.copy(color = BitnagilTheme.colors.coolGray30))
+        Text(
+            text = label,
+            style = BitnagilTheme.typography.body2Medium.copy(color = BitnagilTheme.colors.coolGray30),
+            maxLines = 1,
+            overflow = TextOverflow.Ellipsis,
+        )

추가 필요 import:

import androidx.compose.ui.text.style.TextOverflow

34-35: 간격 6.dp 스펙 확인 및 토큰화 제안

간격 6.dp가 2차 UI 가이드의 스펙인지 확인 부탁드립니다. 디자인 시스템에 간격 토큰(예: spacing.xs 또는 dimension 리소스)이 있다면 하드코딩 대신 토큰 사용을 권장합니다.


41-47: 체크 상태 색상 변경: 대비/테두리 의도 확인 + 색상 전환 애니메이션 제안

  • checked 시 배경/테두리를 모두 coolGray10으로 맞춰 테두리가 사실상 사라집니다. 의도된 “솔리드 박스” 표현인지 확인 부탁드립니다.
  • 배경이 밝은 회색(coolGray10) 위에 흰색 체크 아이콘의 대비가 충분한지(접근성)도 한번만 확인해 주세요. 의도된 디자인이면 OK입니다.
  • 상태 전환 시 색상 애니메이션(animateColorAsState)을 주면 전환이 더 자연스럽습니다.

애니메이션 추가 예시(변수 정의):

import androidx.compose.animation.animateColorAsState
import androidx.compose.runtime.getValue

val boxBg by animateColorAsState(
    targetValue = if (checked) BitnagilTheme.colors.coolGray10 else BitnagilTheme.colors.coolGray99,
    label = "checkboxBg",
)
val borderColor by animateColorAsState(
    targetValue = if (checked) BitnagilTheme.colors.coolGray10 else BitnagilTheme.colors.coolGray95,
    label = "checkboxBorder",
)

선택 영역 내 적용 변경:

-                    color = if (checked) BitnagilTheme.colors.coolGray10 else BitnagilTheme.colors.coolGray99,
+                    color = boxBg,
-                    color = if (checked) BitnagilTheme.colors.coolGray10 else BitnagilTheme.colors.coolGray95,
+                    color = borderColor,

51-54: 아이콘 접근성 중복 낭독 방지 확인

Row에 체크박스 역할/상태를 부여하면 내부 체크 아이콘은 장식용이어야 합니다. BitnagilIcon이 별도의 contentDescription을 노출하지 않거나 null 처리되는지 확인 부탁드립니다. 중복 낭독을 막기 위함입니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/selectcell/SelectCell.kt (3)

28-29: 접근성: 터치 타겟 38dp는 권장 48dp 미만 — 최소 인터랙티브 크기 보장을 권장합니다

시각적 높이는 38dp로 유지하되, 실제 터치 영역만 48dp 이상으로 만드는 것이 권장됩니다. Material 권장사항(48dp)을 만족하도록 최소 인터랙티브 크기를 보강해 주세요.

적용 제안 (Material3 사용 시):

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

추가 import (파일 상단에):

import androidx.compose.material3.minimumInteractiveComponentSize

대안 (Material2를 쓴다면):

  • Modifier.minimumTouchTargetSize() 사용

두 환경 중 어떤 것을 쓰는지 확인이 필요합니다.


31-31: 중복된 RoundedCornerShape(12.dp) 상수 추출로 가독성 개선

shape를 한 번 계산해 재사용하면 중복을 줄이고 의도를 더 명확히 할 수 있습니다.

-                shape = RoundedCornerShape(12.dp),
+                shape = shape,

그리고 Box 앞에 다음 한 줄을 추가하세요(변경 범위 밖이라 참고 코드로 제안합니다):

val shape = RoundedCornerShape(12.dp)

Also applies to: 35-35


53-55: Preview: Row에 fillMaxWidth 추가로 weight 동작 보장

Preview에서도 weight 레이아웃이 항상 기대대로 동작하도록 Row에 fillMaxWidth를 권장합니다.

-        Row(
-            horizontalArrangement = Arrangement.spacedBy(4.dp),
-        ) {
+        Row(
+            modifier = Modifier.fillMaxWidth(),
+            horizontalArrangement = Arrangement.spacedBy(4.dp),
+        ) {

추가 import:

import androidx.compose.foundation.layout.fillMaxWidth

미세한 차이지만, 컨테이너 제약이 달라지는 환경(실제 화면, 다른 Preview)에서도 일관된 미리보기에 도움이 됩니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/timepickerbottomsheet/TimePickerBottomSheet.kt (5)

47-56: 람다 파라미터 섀도잉: 가독성 저하

외부 파라미터(hour, minute)와 동일한 이름을 내부 람다 파라미터로 사용해 섀도잉이 발생합니다. 혼동 방지를 위해 의미 있는 이름으로 변경을 권장합니다.

다음과 같이 변경 제안:

-            onTimeSelected = { hour, minute ->
-                onTimeSelected(hour, minute)
+            onTimeSelected = { selectedHour, selectedMinute ->
+                onTimeSelected(selectedHour, selectedMinute)
                 coroutineScope
                     .launch { sheetState.hide() }
                     .invokeOnCompletion {
                         if (!sheetState.isVisible) {
                             onDismiss()
                         }
                     }
             },

12-17: (선택) prop 변경 반영을 위한 LaunchedEffect import 추가

초기값 prop(hour/minute)이 컴포저블 생애주기 중 변경될 가능성이 있다면, 내부 상태와 동기화하는 LaunchedEffect를 쓰는 편이 안전합니다. 이를 위해 LaunchedEffect import가 필요합니다.

다음 import 추가:

 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue

70-72: (선택) prop(hour/minute) 변경 시 내부 상태 동기화

외부에서 전달되는 초기값이 동적으로 바뀔 때를 대비해 내부 상태를 동기화해 두면 견고합니다. (일반적 플로우에서 꼭 필요하진 않으나, 안전장치로 유용)

다음 코드 추가:

     var currentHour by remember { mutableIntStateOf(hour) }
     var currentMinute by remember { mutableIntStateOf(minute) }
+
+    LaunchedEffect(hour, minute) {
+        currentHour = hour
+        currentMinute = minute
+    }

117-118: (사소) enabled = true 기본값 중복 지정

BitnagilTextButton의 enabled 기본값이 true라면 명시 생략해도 됩니다.

다음과 같이 정리 제안:

-            enabled = true,

113-114: (선택) 하드코딩 대신 stringResource 사용 및 리소스 정의 추가

i18n·접근성·일관성을 위해 하드코딩된 "저장"을 문자열 리소스로 관리하세요. 현재 R.string.save가 정의되어 있지 않으니, 먼저 presentation/src/main/res/values/strings.xml에 아래 항목을 추가한 뒤 코드를 변경하시길 권장합니다.

– 리소스 정의 추가 (presentation/src/main/res/values/strings.xml):

<resources>
+   <string name="save">저장</string>
</resources>

– 코드 변경 (TimePickerBottomSheet.kt 113–114):

-            text = "저장",
+            text = stringResource(id = R.string.save),

– import 추가:

+ import androidx.compose.ui.res.stringResource
domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/model/Date.kt (1)

14-16: ISO 포맷 유틸은 적절합니다. Locale 고정과 네이밍 미세 개선 제안

현재 구현은 정상 동작합니다. 다만 숫자 포맷의 Locale 영향을 배제하려면 Locale.ROOT로 명시하는 편이 안전합니다. 또한 API 연동 목적이라면 메서드명을 toIsoDateString처럼 의도를 드러내는 네이밍도 고려해볼 만합니다.

아래와 같이 포맷 호출부만 교체를 제안합니다:

-        return "%04d-%02d-%02d".format(year, month, day)
+        return java.lang.String.format(java.util.Locale.ROOT, "%04d-%02d-%02d", year, month, day)

(선택) 메서드명을 more explicit 하게 바꾸는 경우:

  • toFormattedString → toIsoDateString
  • 호출부 영향 범위가 크지 않다면 이번 PR에서 함께 정리하는 것을 권장합니다.
core/designsystem/src/main/res/drawable/ic_up_arrow_20.xml (1)

1-9: 하드코딩된 색상 대신 테마/틴트 연동 고려

아이콘 자체는 문제없습니다. 다만 fillColor를 고정값(#46474C)으로 두면 다크 모드/동적 컬러에서 일관성이 떨어질 수 있습니다. Icon/Image에서 tint로 제어한다면, 여기서는 단색으로 두고 사용처에서 tint를 입히는 패턴을 추천합니다. 또는 테마 속성 참조(?attr/colorOnSurface 등)로 전환을 고려해 주세요.

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

1-15: 2톤 아이콘의 테마 대응 확인 필요

원형 배경(#AEB0B6) + X 글리프(#F7F7F8) 조합이 다크 모드에서도 충분한 대비를 확보하는지 확인이 필요합니다. 이 리소스는 사용처에서 tint로 일괄 변경하기 어렵기 때문에, 라이트/다크 전용 리소스 분리 또는 테마 색상 참조로의 전환을 검토해 주세요.

다음 체크를 권장합니다:

  • NameField 등 실제 사용 화면에서 라이트/다크 대비 확인
  • 필요 시 res-night 전용 벡터 추가 또는 색상 참조로 변환
domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/model/RoutineUpdateType.kt (1)

3-5: Enum 정의 단순화 및 .name 프로퍼티 사용 제안

  • 현황:
    RoutineUpdateType enum이 val value: String 프로퍼티와 함께 정의되어 있고,
    WriteRoutineRepositoryImpl.kt에서 routineUpdateType.value를 호출 중입니다.
  • 제안: enum을 UPPER_SNAKE_CASE 상수만 갖고 value 프로퍼티를 제거한 뒤,
    모든 사용처에서 .value 대신 .name을 사용합니다.
  • 변경 필요 위치:
    • data/src/main/java/com/threegap/bitnagil/data/writeroutine/repositoryImpl/WriteRoutineRepositoryImpl.kt:55
-enum class RoutineUpdateType(val value: String) {
-    Today("TODAY"), Tomorrow("TOMORROW")
-}
+enum class RoutineUpdateType {
+    TODAY, TOMORROW
+}
- updateApplyDate = routineUpdateType.value
+ updateApplyDate = routineUpdateType.name
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/Time.kt (1)

36-54: toAmPmFormattedString 간결화 및 Locale 고정 제안 (+ i18n 고려)

현재 구현도 올바르지만, when으로 단순화하고 Locale을 명시해 주면 가독성과 안정성이 좋아집니다. 또한 "오전"/"오후" 하드코딩은 i18n에 취약하므로, 향후 다국어 계획이 있다면 문자열 리소스로의 이관을 고려해 주세요.

아래와 같이 정리할 수 있습니다:

-    fun toAmPmFormattedString(): String {
-        val amPm: String
-        var displayHour = hour
-
-        if (hour == 0) { // 자정 (오전 12시)
-            amPm = "오전"
-            displayHour = 12
-        } else if (hour == 12) { // 정오 (오후 12시)
-            amPm = "오후"
-            displayHour = 12
-        } else if (hour > 12) {
-            amPm = "오후"
-            displayHour -= 12
-        } else {
-            amPm = "오전"
-        }
-
-        return "%s %d:%02d".format(amPm, displayHour, minute)
-    }
+    fun toAmPmFormattedString(): String {
+        val amPm = if (hour < 12) "오전" else "오후"
+        val displayHour = when (hour) {
+            0 -> 12
+            in 1..11 -> hour
+            12 -> 12
+            else -> hour - 12
+        }
+        return java.lang.String.format(java.util.Locale.KOREA, "%s %d:%02d", amPm, displayHour, minute)
+    }

이 변경을 적용한다면, 아래 import는 불필요해집니다.

-import kotlin.text.format

추가로, "오전"/"오후"는 문자열 리소스로 분리하면 접근성/현지화 대응이 수월해집니다.

Also applies to: 5-5

presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/model/RoutineListSideEffect.kt (1)

8-8: routineId 타입 일관성 확인 필요 (String ↔ Long 혼재 가능성)

AI 요약 및 다른 파일 컨텍스트에 따르면, 일부 호출부는 (routineId: Long, ...) 시그니처를 사용합니다. 본 파일(Line 8)은 String을 사용 중이라 계층 간 타입 불일치가 발생할 수 있습니다. 앱 전반에서 routineId의 단일 원천 타입을 Long으로 가져가면 파싱/형변환 오류 및 성능 부담을 줄일 수 있습니다. 일단 호출부와의 일치 여부를 먼저 확인해 주세요.

변경이 가능하다면 다음과 같이 제안드립니다:

-    data class NavigateToEditRoutine(val routineId: String, val updateRoutineFromNowDate: Boolean) : RoutineListSideEffect
+    data class NavigateToEditRoutine(val routineId: Long, val updateRoutineFromNowDate: Boolean) : RoutineListSideEffect
data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/request/EditRoutineRequest.kt (2)

16-19: 시작/종료일 Nullable → PR 가정(필수)와 불일치 가능성

PR 설명에 따르면 시작날짜/종료날짜는 “필수값으로 가정”되어 있습니다. 본 요청 모델은 String?로 선언되어 있어 상위 레이어 가정과 불일치할 수 있습니다. 두 가지 중 하나로 정합성을 맞추는 것을 권장합니다.

옵션:

  • 네트워크 레이어도 필수로 고정 가능 시:
-    val routineStartDate: String?,
-    val routineEndDate: String?,
+    val routineStartDate: String,
+    val routineEndDate: String,
  • 서버 스펙상 선택일 수밖에 없다면: 상위 도메인/유스케이스에서 null 방지 검증을 추가해 주세요(빌더/팩토리, requireNotNull 등).
    또한, 날짜 포맷(예: yyyy-MM-dd ISO-8601) 계약을 레이어 공통으로 명시/검증하면 추후 회귀를 줄일 수 있습니다.

10-11: updateApplyDate 문자열 직접 사용 → 직렬화 enum으로 타입 안전성 강화 제안

문자열 상수 오타/변경에 취약합니다. 직렬화 enum을 사용하면 안전합니다.

다음과 같이 제안드립니다(요청 모델 변경 + enum 신규 추가).

변경:

-    @SerialName("updateApplyDate")
-    val updateApplyDate: String,
+    @SerialName("updateApplyDate")
+    val updateApplyDate: UpdateApplyDateDto,

신규 enum(예시 값은 서버 스펙에 맞춰 조정 필요):

// data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/request/UpdateApplyDateDto.kt
package com.threegap.bitnagil.data.writeroutine.model.request

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
enum class UpdateApplyDateDto {
    @SerialName("TODAY")
    TODAY,
    @SerialName("TOMORROW")
    TOMORROW,
    @SerialName("FROM_NOW")
    FROM_NOW,
}

도메인 RoutineUpdateType ↔ DTO 매핑은 RepositoryImpl에서 안전하게 변환하도록 도와드릴 수 있습니다.

data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/request/RegisterRoutineRequest.kt (1)

12-15: 등록 요청의 시작/종료일 Nullable → 상위 가정(필수)과 정합성 재확인

routineStartDate/routineEndDateString?로 선언되어 있으나, PR 설명상 필수로 가정되었다고 합니다. 서버 스펙이 선택일이라면 상위 레이어에서 null 방지 로직을 두고, 서버가 필수라면 비널 타입으로 고정하는 편이 안전합니다. 포맷 계약(yyyy-MM-dd 등)도 함께 문서화/검증을 권장합니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/writeroutinebutton/WriteRoutineButton.kt (2)

19-67: companion object 대신 singleton object로 단순화 권장

현재 class + companion object 패턴은 정적 메서드 모양을 위해 사용된 것으로 보입니다. 동일한 API를 더 간결하게 유지하려면 object WriteRoutineButton로 변환해도 됩니다. 불필요한 중첩 레벨을 줄여 가독성이 좋아지고, 의도(싱글턴 유틸 컴포저블)가 명확해집니다.

적용 예시:

-class WriteRoutineButton {
-
-    companion object {
-        @Composable
-        fun Text(
+object WriteRoutineButton {
+    @Composable
+    fun Text(
             text: String,
             isSelected: Boolean,
             onClick: () -> Unit,
             modifier: Modifier = Modifier,
             enabled: Boolean = true,
         ) {
             ...
-        @Composable
-        fun Custom(
+    @Composable
+    fun Custom(
             modifier: Modifier = Modifier,
             isSelected: Boolean,
             onClick: () -> Unit,
             enabled: Boolean = true,
             content: @Composable () -> Unit,
         ) {
             ...
-        }
-    }
-}
+}

83-86: 불필요한 shape 중복 지정 정리 제안

이미 clip(RoundedCornerShape(12.dp))로 모서리를 잘라냈기 때문에, background에도 동일한 shape를 반복 지정하지 않아도 시각적으로 동일하게 동작합니다(현재 리플도 제거되어 있어 클리핑 유지 목적 외에는 중복입니다). shape 상수/변수를 하나로 빼서 재사용하면 유지보수성이 좋아집니다.

정리 예시:

-    .clip(RoundedCornerShape(12.dp))
-    .background(
-        color = backgroundColor,
-        shape = RoundedCornerShape(12.dp),
-    )
+    .clip(RoundedCornerShape(12.dp))
+    .background(color = backgroundColor)
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/datepickerbottomsheet/model/CalendarUtils.kt (1)

17-25: month 인자 범위(1..12) 명시 필요

현재 구현은 month를 1 기반(1~12)으로 가정하고 있습니다. 사용 실수를 방지하려면 KDoc에 범위를 명시하거나, 유효성 체크(예: require(month in 1..12))를 추가해 주는 것이 안전합니다. 또한 API 26+ 대상이라면 java.time.YearMonthlengthOfMonth() 사용이 가독성 면에서 유리합니다.

간단한 가드 추가 예:

 fun getDayAmountOfMonth(year: Int, month: Int): Int {
+    require(month in 1..12) { "month must be in 1..12, but was $month" }
     return when (month) {
         2 -> {
             if (year % 4 != 0 || (year % 100 == 0 && year % 400 != 0)) { 28 } else { 29 }
         }
         in listOf(1, 3, 5, 7, 8, 10, 12) -> { 31 }
         else -> { 30 }
     }
 }
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineIntent.kt (1)

29-36: Show/Hide Intent 수를 boolean 기반 단일 Intent로 축약 고려

Start/End DatePicker, TimePicker의 Show/Hide를 각각 분리하면 Intent 수가 늘어 관리가 번거로워집니다. SetStartDatePickerBottomSheetVisible(visible: Boolean), SetEndDatePickerBottomSheetVisible(visible: Boolean), SetTimePickerBottomSheetVisible(visible: Boolean)처럼 boolean 기반 단일 Intent로 축소하면 처리/테스트가 단순해집니다.

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

6-9: 하드코딩 색상 제거 검토(테마/다크모드 대응)

#46474C를 벡터에 직접 박아두면 테마/다크모드/동적 색상 적용이 어렵습니다.

  • Compose Icon에서 tint로 색을 제어하거나,
  • 색상을 디자인시스템 컬러 리소스로 분리하여 참조하는 방식을 권장합니다.

예시(컬러 리소스 사용):

-      android:fillColor="#46474C"/>
+      android:fillColor="@color/icon_default"/>

그리고 core/designsystem/src/main/res/values/colors.xml(또는 팔레트 파일)에 아래 추가:

<color name="icon_default">#46474C</color>

Compose에서 Icon(painter, contentDescription, tint = BitnagilTheme.colors.iconDefault)로 일관 제어하는 것도 고려해주세요.

data/src/main/java/com/threegap/bitnagil/data/routine/service/RoutineService.kt (1)

16-19: startDate/endDate 쿼리 포맷 확인 필요

서버가 기대하는 날짜 포맷(예: yyyy-MM-dd, ISO-8601)과 타임존 처리(UTC vs 로컬)를 명확히 맞춰야 합니다. 현재 String으로 전달되며 호출부에서 포맷팅이 일관적으로 적용되는지 확인 부탁드립니다.

예상 이슈:

  • 로캘/타임존 차이로 날짜 경계가 어긋나는 문제
  • 서버 검증 실패(포맷 미스매치)

원하시면 호출부 유틸(포맷터) 추출까지 도와드리겠습니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/navarg/WriteRoutineScreenArg.kt (2)

11-11: Boolean 인자에 기본값을 부여해 직렬화 호환성 확보 권장

kotlinx.serialization 사용 시, 신규 비필수 필드는 기본값이 없으면 이전 시점에 저장된 상태 복원/디코딩에서 실패할 수 있습니다. 기본값을 부여해 역호환성을 확보하는 것을 권장합니다(라우팅/딥링크/프로세스 재생성 시 안전).

-    data class Edit(val routineId: String, val updateRoutineFromNowDate: Boolean) : WriteRoutineScreenArg()
+    data class Edit(
+        val routineId: String,
+        val updateRoutineFromNowDate: Boolean = true,
+    ) : WriteRoutineScreenArg()

또한, Route.WriteRoutine에 isUpdateRoutineFromNowDate가 존재한다면 네이밍 및 기본값을 일치시키면 혼동을 줄일 수 있습니다.


11-11: Boolean 네이밍 일관성(is- 접두) 제안

동일 의미의 값이 Route에서는 isUpdateRoutineFromNowDate로, 여기서는 updateRoutineFromNowDate로 쓰입니다. Boolean은 is- 접두를 권장하므로 통일하면 가독성과 검색성이 좋아집니다. 직렬화 키가 바뀔 경우 @SerialName으로 와이어 포맷을 유지할 수 있습니다.

가능한 리팩터:

@Serializable
data class Edit(
    val routineId: String,
    @SerialName("updateRoutineFromNowDate")
    val isUpdateRoutineFromNowDate: Boolean = true,
) : WriteRoutineScreenArg()
domain/src/main/java/com/threegap/bitnagil/domain/routine/model/Routine.kt (1)

8-9: 날짜를 String 대신 타입 안전한 LocalDate로 노출하는 것을 고려

도메인 레이어에서 날짜를 String으로 유지하면 파싱/포맷 책임이 호출자에게 분산되고, 런타임 오류 여지가 커집니다. java.time.LocalDate로 모델링하면 유효성/연산이 안전해집니다. DTO 계층에서만 문자열을 사용하고 도메인에서는 타입을 강화하는 패턴을 권장합니다.

예시:

data class Routine(
    ...,
    val startDate: LocalDate,
    val endDate: LocalDate,
    ...
)

매핑 계층에서만 포맷팅/파싱을 수행하도록 정리하면 좋습니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/RoutineListScreen.kt (1)

112-116: 하드코딩된 문자열은 string 리소스로 이전 권장

"루틴리스트"는 사용자 노출 문자열입니다. 국제화/접근성/일관성을 위해 string 리소스 사용을 권장합니다.

가능한 수정:

-        BitnagilTopBar(
-            title = "루틴리스트",
+        BitnagilTopBar(
+            title = stringResource(R.string.routine_list_title),
             showBackButton = true,
             onBackClick = onBackClick,
         )

리소스 추가: res/values/strings.xml에 routine_list_title 키 추가.

presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/routinedetailrow/RoutineDetailRow.kt (4)

32-46: 플레이스홀더/값 동시 렌더링 제거 및 텍스트 오버플로 처리

현재 value가 비어있을 때도 value Text가 함께 렌더링됩니다(빈 문자열이라도 컴포지션은 발생). 또한 길이가 긴 값이 잘릴 수 있으므로 ellipsis 처리 권장합니다.

아래처럼 else 블록과 ellipsis를 적용하면 명확하고 성능에도 이점이 있습니다:

-            Box(
-                modifier = Modifier.padding(vertical = 10.dp),
-            ) {
-                if (value.isEmpty()) {
-                    Text(placeHolder, style = BitnagilTheme.typography.body2Medium.copy(color = BitnagilTheme.colors.coolGray90))
-                }
-
-                Text(value, style = BitnagilTheme.typography.body2Medium.copy(color = BitnagilTheme.colors.coolGray30))
-            }
+            Box(
+                modifier = Modifier.padding(vertical = 10.dp),
+            ) {
+                if (value.isEmpty()) {
+                    Text(
+                        text = placeHolder,
+                        style = BitnagilTheme.typography.body2Medium.copy(color = BitnagilTheme.colors.coolGray90),
+                        maxLines = 1,
+                        overflow = TextOverflow.Ellipsis,
+                    )
+                } else {
+                    Text(
+                        text = value,
+                        style = BitnagilTheme.typography.body2Medium.copy(color = BitnagilTheme.colors.coolGray30),
+                        maxLines = 1,
+                        overflow = TextOverflow.Ellipsis,
+                    )
+                }
+            }

추가 import(파일 상단에 필요):

import androidx.compose.ui.text.style.TextOverflow

19-24: 버튼 선택/활성 상태를 컴포넌트 외부에서 제어 가능하도록 확장

isSelected가 false로 고정되어 버튼 상태를 외부에서 제어할 수 없습니다. API로 노출해 재사용성을 높이는 것을 권장합니다. 또한 placeholder 네이밍은 일반적으로 camelCase의 placeholder가 더 일관적입니다.

-fun RoutineDetailRow(
-    title: String,
-    placeHolder: String,
-    value: String,
-    onClick: () -> Unit,
-) {
+fun RoutineDetailRow(
+    title: String,
+    placeholder: String,
+    value: String,
+    onClick: () -> Unit,
+    isSelected: Boolean = false,
+    enabled: Boolean = true,
+) {
-        WriteRoutineButton.Custom(
-            modifier = Modifier.width(120.dp),
-            onClick = onClick,
-            isSelected = false,
-        ) {
+        WriteRoutineButton.Custom(
+            modifier = Modifier.width(120.dp),
+            onClick = onClick,
+            isSelected = isSelected,
+            enabled = enabled,
+        ) {
-                if (value.isEmpty()) {
-                    Text(placeHolder, style = BitnagilTheme.typography.body2Medium.copy(color = BitnagilTheme.colors.coolGray90))
-                }
+                if (value.isEmpty()) {
+                    Text(placeholder, style = BitnagilTheme.typography.body2Medium.copy(color = BitnagilTheme.colors.coolGray90))
+                }

프리뷰 호출부의 인자명도 placeholder로 함께 조정 필요.


33-36: 고정 폭(120.dp)에 대한 지역화/접근성 확인

값이 길어지거나(예: 긴 날짜 포맷/다국어) 폰트 크기 확대 시 잘림이 발생할 수 있습니다. 위의 ellipsis 적용으로 1차 보완되나, 필요 시 minWidth+wrapContentWidth 조합 또는 IntrinsicSize 기반으로 대응을 고려해주세요.


50-61: 프리뷰 인자명 및 확장 파라미터 반영

위 제안대로 placeholder/isSelected/enabled를 도입하면 프리뷰도 함께 반영해주세요. 프리뷰에서 다양한 상태(선택됨/비활성/긴 텍스트)를 보여주면 디자인 검증이 쉬워집니다.

예:

RoutineDetailRow(
    title = "시작일",
    placeholder = "눌러서 선택",
    value = "",
    onClick = {},
    isSelected = true,
    enabled = true,
)
app/src/main/java/com/threegap/bitnagil/MainNavHost.kt (1)

212-216: Edit 분기에서 non-null 단정자(!!)

arg.routineId!!는 런타임 NPE를 유발할 수 있습니다. Route 레벨에서 edit 진입 시 routineId가 null이 아님이 보장되긴 해야 하지만, 여기서 방어해두면 디버깅이 쉬워집니다.

아래처럼 requireNotNull로 의미를 명확히 해주세요.

-            val writeScreenNavArg = if (arg.isRegister) {
-                WriteRoutineScreenArg.Add(baseRoutineId = arg.routineId)
-            } else {
-                WriteRoutineScreenArg.Edit(routineId = arg.routineId!!, updateRoutineFromNowDate = arg.isUpdateRoutineFromNowDate)
-            }
+            val writeScreenNavArg = if (arg.isRegister) {
+                WriteRoutineScreenArg.Add(baseRoutineId = arg.routineId)
+            } else {
+                val editRoutineId = requireNotNull(arg.routineId) { "Edit 모드에서는 routineId가 필수입니다." }
+                WriteRoutineScreenArg.Edit(
+                    routineId = editRoutineId,
+                    updateRoutineFromNowDate = arg.isUpdateRoutineFromNowDate,
+                )
+            }
data/src/main/java/com/threegap/bitnagil/data/routine/model/response/RoutineDto.kt (1)

48-49: startDate/endDate 매핑은 적절합니다.

DTO의 기간 필드를 도메인으로 넘기는 방향성은 맞습니다. 한편 routineDeletedYn은 현재 도메인으로 전달되지 않는데, 향후 리스트 노출/필터링에 쓸 계획이 있는지 정도만 정리되면 좋겠습니다.

domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/usecase/RegisterRoutineUseCase.kt (1)

16-18: 기간 값이 “필수” 가정이라면 null 허용 대신 명시적 보장이 낫습니다

UI/PR 설명에서 “시작/종료 날짜는 필수값”으로 가정하셨습니다. 그 가정을 도메인 레이어에서도 일관되게 표현하면 런타임 오류를 예방하고 계약이 명확해집니다.

선택지:

  • A안: 시그니처를 startDate: Date, endDate: Date로 변경(레포지토리/호출부 일괄 반영).
  • B안: 현재 시그니처 유지하되, 내부에서 requireNotNull로 조기 검증.

B안을 적용한다면:

 suspend operator fun invoke(
     name: String,
     repeatDay: List<RepeatDay>,
     startTime: Time,
-    startDate: Date?,
-    endDate: Date?,
+    startDate: Date?,
+    endDate: Date?,
     subRoutines: List<String>,
 ): Result<Unit> {
+    requireNotNull(startDate) { "RegisterRoutine: startDate는 필수입니다." }
+    requireNotNull(endDate) { "RegisterRoutine: endDate는 필수입니다." }
     return writeRoutineRepository.registerRoutine(
         name = name,
         repeatDay = repeatDay,
         startTime = startTime,
         startDate = startDate,
         endDate = endDate,
         subRoutines = subRoutines,
     )
 }

Also applies to: 24-26

presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/datepickerbottomsheet/DatePickerBottomSheet.kt (1)

108-117: 이전/다음 달 화살표 활성화 판정 로직 보정 제안

현재 (availableStartDate.month < currentMonth) 같은 월 단독 비교는 연도 경계에서 오작동 여지가 있습니다. YearMonth 단위 비교로 단순·정확하게 처리하는 편이 안전합니다.

-    val prevMonthButtonEnabled by remember(availableStartDate) {
-        derivedStateOf {
-            (availableStartDate == null) || (availableStartDate.year < currentYear) || (availableStartDate.month < currentMonth)
-        }
-    }
-    val nextMonthButtonEnabled by remember(availableEndDate) {
-        derivedStateOf {
-            (availableEndDate == null) || (availableEndDate.year > currentYear) || (availableEndDate.month > currentMonth)
-        }
-    }
+    val prevMonthButtonEnabled by remember(currentYear, currentMonth, availableStartDate) {
+        derivedStateOf {
+            val currentYm = currentYear * 100 + currentMonth
+            val startYm = availableStartDate?.let { it.year * 100 + it.month }
+            startYm == null || currentYm > startYm
+        }
+    }
+    val nextMonthButtonEnabled by remember(currentYear, currentMonth, availableEndDate) {
+        derivedStateOf {
+            val currentYm = currentYear * 100 + currentMonth
+            val endYm = availableEndDate?.let { it.year * 100 + it.month }
+            endYm == null || currentYm < endYm
+        }
+    }
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/namefield/NameField.kt (2)

45-52: 플레이스홀더/입력 텍스트 스타일 불일치로 인한 레이아웃 점프 가능성

placeholder는 title3SemiBold, 실제 텍스트는 body2SemiBold로 서로 달라 입력 시작 시 높이/줄바꿈이 바뀌어 보일 수 있습니다. 동일 스타일을 쓰거나 최소한 라인헤이트가 맞도록 정렬하는 편이 안전합니다.

아래처럼 placeholder 스타일을 입력 텍스트와 맞추는 것을 제안합니다:

-        textStyle = BitnagilTheme.typography.body2SemiBold,
+        textStyle = BitnagilTheme.typography.body2SemiBold,
@@
-                            Text(
-                                placeholder,
-                                style = BitnagilTheme.typography.title3SemiBold.copy(color = BitnagilTheme.colors.coolGray90),
-                            )
+                            Text(
+                                placeholder,
+                                style = BitnagilTheme.typography.body2SemiBold.copy(color = BitnagilTheme.colors.coolGray90),
+                            )

Also applies to: 38-38


70-74: HorizontalDivider 두께/높이 이중 지정

thickness와 height를 동시에 2.dp로 지정하고 있습니다. 하나만 지정해도 충분합니다. 중복 제거를 제안합니다.

-                HorizontalDivider(
-                    thickness = 2.dp,
-                    modifier = Modifier.height(2.dp),
-                    color = BitnagilTheme.colors.coolGray90,
-                )
+                HorizontalDivider(
+                    thickness = 2.dp,
+                    color = BitnagilTheme.colors.coolGray90,
+                )
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/Date.kt (3)

14-18: now()에서 LocalDate.now() 3회 호출 불필요

매 호출 시 시스템 시간을 3번 조회하게 됩니다. 한 번만 호출해서 지역 변수에 담아 사용하는 편이 안전/효율적입니다.

-        fun now() = Date(
-            year = java.time.LocalDate.now().year,
-            month = java.time.LocalDate.now().monthValue,
-            day = java.time.LocalDate.now().dayOfMonth,
-        )
+        fun now(): Date {
+            val today = java.time.LocalDate.now()
+            return Date(
+                year = today.year,
+                month = today.monthValue,
+                day = today.dayOfMonth,
+            )
+        }

53-62: start/end 역전 입력 시에도 견고하게 동작하도록 보정

UI/서버에서 실수로 startDate > endDate가 들어오면 현재 로직은 항상 false가 됩니다. 내부에서 범위를 자동 보정해주는 편이 안전합니다.

-        val appliedStartDate = startDate ?: Date(year = 2000, month = 1, day = 1)
-        val appliedEndDate = endDate ?: Date(year = 2999, month = 12, day = 31)
-
-        val startValue = appliedStartDate.year * 10000 + appliedStartDate.month * 100 + appliedStartDate.day
-        val endValue = appliedEndDate.year * 10000 + appliedEndDate.month * 100 + appliedEndDate.day
-        val targetValue = year * 10000 + month * 100 + day
-
-        return targetValue in startValue..endValue
+        val rawStart = startDate ?: Date(year = 2000, month = 1, day = 1)
+        val rawEnd = endDate ?: Date(year = 2999, month = 12, day = 31)
+        val appliedStart = Companion.min(rawStart, rawEnd)
+        val appliedEnd = Companion.max(rawStart, rawEnd)
+
+        val startValue = appliedStart.year * 10000 + appliedStart.month * 100 + appliedStart.day
+        val endValue = appliedEnd.year * 10000 + appliedEnd.month * 100 + appliedEnd.day
+        val targetValue = year * 10000 + month * 100 + day
+
+        return targetValue in startValue..endValue

1-71: 추가 제안: Comparable 구현으로 비교/정렬 가독성 향상

Date 비교가 빈번합니다. Comparable를 구현하면 min/max/범위 체크 로직 가독성이 올라갑니다(선택사항).

presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/expandablecontent/ExpandableContent.kt (2)

60-66: 헤더(Row) 클릭 영역에 접근성 세만틱 추가

확장/축소 가능한 헤더입니다. Role.Button 및 상태 설명(stateDescription)을 추가하면 스크린 리더 접근성이 좋아집니다.

         Row(
             modifier = Modifier
                 .fillMaxWidth()
                 .clickableWithoutRipple(onClick = onClick)
-                .padding(start = 18.dp, end = 12.dp, top = 18.dp, bottom = 18.dp)
+                .padding(start = 18.dp, end = 12.dp, top = 18.dp, bottom = 18.dp)
+                .semantics {
+                    role = Role.Button
+                    stateDescription = if (expand) "펼침" else "접힘"
+                }
                 .animateContentSize(),
             verticalAlignment = Alignment.CenterVertically,
         ) {

파일 상단에 다음 import가 필요합니다:

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

93-98: 축소 상태의 보조 텍스트는 한 줄 말줄임 처리 권장

valueText가 길거나 줄바꿈 문자를 포함하면 헤더가 의도치 않게 커질 수 있습니다. 한 줄 + Ellipsis로 고정하세요.

                 if (!expand) {
                     Text(
                         text = if (showValueText) valueText ?: placeHolder else placeHolder,
-                        style = if (showValueText) mainTextStyle else subTextStyle,
+                        style = if (showValueText) mainTextStyle else subTextStyle,
+                        maxLines = 1,
+                        overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,
                     )
                 }
domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/repository/WriteRoutineRepository.kt (2)

20-29: editRoutine 파라미터 의미 명확화 및 포맷 일관성 확인

  • routineUpdateType의 적용 기준(예: 즉시/특정일/이후 전부)을 KDoc로 명확히 하길 권장합니다.
  • startDate/endDate 포맷이 서버 v2(EditRoutineRequest.updateApplyDate/routineStartDate/routineEndDate)와 일치하는지 구현체 매핑을 확인해 주세요.

11-18: registerRoutine 시그니처 반영 완료 및 KDoc 보강 권장

  • 구현체(data, domain)와 호출부(presentation) 모두에 startDate/endDate 파라미터가 정상 반영됨을 확인했습니다.
  • 다만 인터페이스에 null 의미(무제한), 경계 포함 여부, subRoutines 용도 등을 명확히 문서화하기 위해 KDoc 보강을 권장드립니다.

수정 대상:

  • domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/repository/WriteRoutineRepository.kt

예시 KDoc 적용:

/**
 * 루틴을 등록합니다.
 *
 * @param name         루틴 이름
 * @param repeatDay    반복 요일 목록
 * @param startTime    실행 시간
 * @param startDate    null이면 시작일을 과거 무제한으로 간주합니다. 양끝 포함.
 * @param endDate      null이면 종료일을 미래 무제한으로 간주합니다. 양끝 포함.
 * @param subRoutines  서버 v2 규격에 맞는 세부루틴 이름 목록
 */
suspend fun registerRoutine(
    name: String,
    repeatDay: List<RepeatDay>,
    startTime: Time,
    startDate: Date?,
    endDate: Date?,
    subRoutines: List<String>,
): Result<Unit>
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/subroutinefield/SubRoutineField.kt (2)

64-68: HorizontalDivider 두께/높이 이중 지정

thickness와 height 동시 지정은 중복입니다. 하나만 남기세요.

-                    HorizontalDivider(
-                        thickness = 1.dp,
-                        modifier = Modifier.height(1.dp),
-                        color = BitnagilTheme.colors.coolGray90,
-                    )
+                    HorizontalDivider(
+                        thickness = 1.dp,
+                        color = BitnagilTheme.colors.coolGray90,
+                    )

75-87: 프리뷰 함수명 혼동 가능 — 컴포넌트명과 일치 권장

동일 프로젝트 내에 NameFieldPreview가 또 있어(다른 패키지지만) 검색/미리보기 목록에서 혼동될 수 있습니다. SubRoutineFieldPreview로 변경을 제안합니다.

-@Composable
-@Preview(showBackground = true, widthDp = 300, heightDp = 300)
-fun NameFieldPreview() {
+@Composable
+@Preview(showBackground = true, widthDp = 300, heightDp = 300)
+fun SubRoutineFieldPreview() {
domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/usecase/EditRoutineUseCase.kt (1)

19-21: EditRoutineUseCase: 도메인 레이어에서 기간 유효성 방어 검증 추가 요청

  • startDate·endDate가 모두 non-null인 경우, 반드시 startDate ≤ endDate인지 검사하도록 방어 로직을 추가해주세요.
  • 현재 domain/model/Date에는 compareTo·isBefore·isAfter 등의 비교 API가 없으므로,
    LocalDate 변환(LocalDate.of(year, month, day)) 또는 Comparable<Date> 구현을 우선 도입해야 합니다.
  • 유효성 실패 시 Result.failure(IllegalArgumentException("시작일이 종료일보다 이후입니다.")) 등을 반환하도록 처리하면 좋습니다.

대상 파일:
• domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/usecase/EditRoutineUseCase.kt (19–21, 29–31)

data/src/main/java/com/threegap/bitnagil/data/writeroutine/repositoryImpl/WriteRoutineRepositoryImpl.kt (2)

28-35: 요청 DTO 매핑 시 서브루틴 문자열 정제(트림/공백 제거) 제안

서브루틴 입력은 UI에서 비어있는 항목을 필터링하고 있지만, 데이터 계층에서 한 번 더 정제하면 서버로의 노이즈를 줄일 수 있습니다.

아래와 같이 간단히 보강 가능:

         val request = RegisterRoutineRequest(
             routineName = name,
             repeatDay = repeatDay.map { it.fullName },
             executionTime = startTime.toFormattedString(),
             routineStartDate = startDate?.toFormattedString(),
             routineEndDate = endDate?.toFormattedString(),
-            subRoutineName = subRoutines,
+            subRoutineName = subRoutines.map(String::trim).filter { it.isNotEmpty() },
         )

53-62: Edit 요청 매핑도 동일한 정제 적용 권장

updateApplyDate는 enum.value로 올바르게 매핑되어 있습니다. subRoutineName도 위와 동일한 정제를 적용하면 일관성이 좋아집니다.

         val request = EditRoutineRequest(
             routineId = routineId,
             updateApplyDate = routineUpdateType.value,
             routineName = name,
             repeatDay = repeatDay.map { it.fullName },
             executionTime = startTime.toFormattedString(),
             routineStartDate = startDate?.toFormattedString(),
             routineEndDate = endDate?.toFormattedString(),
-            subRoutineName = subRoutines,
+            subRoutineName = subRoutines.map(String::trim).filter { it.isNotEmpty() },
         )
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineState.kt (2)

16-16: 오타: selectNotUseSUbRoutines → selectNotUseSubRoutines 이름 정정 권장

필드명에 대문자 U가 끼어 있어 읽기성/일관성이 떨어집니다. 전역 참조가 많아 후속 PR에서 일괄 변경을 권장합니다.


85-87: 등록 버튼 활성화 조건 소폭 보완 제안

사용자 입력 실수를 줄이기 위해 공백만 입력된 이름을 막는 isNotBlank()가 더 안전합니다.

-        get() = routineName.isNotEmpty() && startTime != null && !loading
+        get() = routineName.isNotBlank() && startTime != null && !loading
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt (3)

74-84: 수정(Edit) 화면에서 과도한 시작일 제한 가능성 — 과거 시작일 유지 허용 필요

Start Date Picker에 availableStartDate = Date.now()로 제한되어 있어, 기존 루틴의 과거 시작일을 표시/수정할 때 제약이 생길 수 있습니다. Edit 모드에서는 제한을 완화(또는 최소값을 기존 시작일 이하로 설정)하는 것을 권장합니다.

-            availableStartDate = Date.now(),
+            // 추가(등록)일 때만 오늘 이후로 제한, 수정일 때는 제한 해제
+            availableStartDate = if (state.writeRoutineType is WriteRoutineType.Add) Date.now() else null,
             availableEndDate = state.endDate,

333-341: 하단 CTA 라벨 동적 적용 권장(등록/수정 구분)

상단 타이틀은 Add/Edit에 따라 변경되지만, CTA는 항상 “등록하기”로 표시됩니다. Edit에서는 “수정하기”가 자연스럽습니다.

-        BitnagilTextButton(
-            text = "등록하기",
+        BitnagilTextButton(
+            text = if (state.writeRoutineType == WriteRoutineType.Add) "등록하기" else "수정하기",
             onClick = onClickRegister,
             enabled = state.registerButtonEnabled,

220-223: 문구 오탈자(구두점) — 가독성 개선

예시 플레이스홀더에 ‘수.목’으로 표기되어 있습니다. 쉼표로 통일하면 자연스럽습니다.

-                    placeHolder = "ex) 매주 월,화,수.목,금",
+                    placeHolder = "ex) 매주 월,화,수,목,금",
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt (1)

472-479: 서브루틴 문자열 정제 일관성 유지 제안(트림/공백 제거)

데이터 레이어에서 보정하더라도, VM 단계에서 먼저 정제하면 불필요한 상태 변동/전송을 줄일 수 있습니다.

-                    val subRoutines = if (currentState.selectNotUseSUbRoutines) emptyList() else currentState.subRoutineNames.filter { it.isNotEmpty() }
+                    val subRoutines = if (currentState.selectNotUseSUbRoutines) {
+                        emptyList()
+                    } else {
+                        currentState.subRoutineNames.map(String::trim).filter { it.isNotEmpty() }
+                    }

그리고 Edit 분기에도 동일 적용:

-                    val subRoutines = if (currentState.selectNotUseSUbRoutines) emptyList() else currentState.subRoutineNames.filter { it.isNotEmpty() }
+                    val subRoutines = if (currentState.selectNotUseSUbRoutines) {
+                        emptyList()
+                    } else {
+                        currentState.subRoutineNames.map(String::trim).filter { it.isNotEmpty() }
+                    }

Also applies to: 489-506

@l5x5l l5x5l requested a review from wjdrjs00 August 18, 2025 16:02
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

♻️ Duplicate comments (2)
domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/repository/WriteRoutineRepository.kt (1)

15-17: registerRoutine의 startDate/endDate non-null 반영 LGTM

도메인 계약이 명확해졌고, 이전 코멘트(Nullable 제거)와도 일치합니다.

data/src/main/java/com/threegap/bitnagil/data/writeroutine/repositoryImpl/WriteRoutineRepositoryImpl.kt (1)

54-62: updateApplyDate 값 재확인 필요 (Edit 요청)

RoutineUpdateType.value를 그대로 updateApplyDate에 매핑하고 있습니다. 필드명과 값의 의미가 어긋나 보이는 지점은 앞서 DTO 코멘트와 동일합니다. 서버 스키마가 이 형태를 요구하는지 한번만 더 확인해 주세요.

🧹 Nitpick comments (7)
data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/request/EditRoutineRequest.kt (1)

16-19: 시작/종료일 포맷(예: yyyy-MM-dd) 합의 사항을 코드에 명시해 주세요

DTO 자체는 String이지만, Repository에서 Date.toFormattedString()으로 내려보냅니다. 서버와 합의된 포맷을 KDoc/주석으로 명시해 두면 추후 유지보수(포맷 변경 등) 시 혼선을 줄일 수 있습니다.

domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/usecase/RegisterRoutineUseCase.kt (1)

16-18: startDate/endDate 도입 맞습니다. 도메인 유효성(시작 ≤ 종료) 선제 검증을 UseCase에 추가 제안

UI에서 이미 보장하더라도, 도메인 계층에서 한 번 더 방어하면 잘못된 입력이 레포지토리까지 내려가는 것을 차단할 수 있습니다. 예: 시작일이 종료일을 초과할 경우 즉시 Result.failure로 단락.

원하시면 제가 Date 유틸(비교 함수) 시그니처를 확인한 후, 구체적인 가드 코드까지 패치 드리겠습니다.

domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/repository/WriteRoutineRepository.kt (1)

31-31: Flow 반환에 suspend 불필요 — API 사용성/호환성 개선 제안

Flow는 콜드 스트림이 아니며 단순 반환에 suspend가 필요 없습니다. 인터페이스/구현 모두에서 suspend를 제거하면 호출부가 간결해집니다.

아래 인터페이스 변경을 제안합니다:

-    suspend fun getWriteRoutineEventFlow(): Flow<WriteRoutineEvent>
+    fun getWriteRoutineEventFlow(): Flow<WriteRoutineEvent>

구현부(WriteRoutineRepositoryImpl)도 동일하게 suspend를 제거해야 합니다. 필요하시면 관련 호출부까지 일괄 패치 드리겠습니다.

data/src/main/java/com/threegap/bitnagil/data/writeroutine/repositoryImpl/WriteRoutineRepositoryImpl.kt (4)

28-35: 요청 페이로드 매핑(등록): 포맷/키 정합성 좋습니다

  • repeatDay.map { it.fullName }, startTime.toFormattedString(), start/endDate.toFormattedString() 모두 DTO와 스키마에 맞게 매핑되어 있습니다.
  • 사소하지만, 동일 매핑이 edit에도 반복되니, 향후 중복 제거를 위해 private 헬퍼로 추출을 고려할 수 있습니다.

36-41: Result 처리 가독성 향상 제안: also → onSuccess

동작은 동일하지만 onSuccess가 의도를 더 잘 드러냅니다.

아래처럼 교체하면 가독성이 올라갑니다:

-        return writeRoutineDataSource.registerRoutine(request).also {
-            if (it.isSuccess) {
-                _writeRoutineEventFlow.emit(WriteRoutineEvent.AddRoutine)
-            }
-        }
+        return writeRoutineDataSource.registerRoutine(request)
+            .onSuccess { _writeRoutineEventFlow.emit(WriteRoutineEvent.AddRoutine) }

64-68: Result 처리 가독성 향상 제안: also → onSuccess (Edit)

등록과 동일한 패턴으로 통일하면 코드 일관성과 가독성이 좋아집니다.

-        return writeRoutineDataSource.editRoutine(request).also {
-            if (it.isSuccess) {
-                _writeRoutineEventFlow.emit(WriteRoutineEvent.EditRoutine(routineId))
-            }
-        }
+        return writeRoutineDataSource.editRoutine(request)
+            .onSuccess { _writeRoutineEventFlow.emit(WriteRoutineEvent.EditRoutine(routineId)) }

71-73: SharedFlow 설정값 개선 및 suspend 제거 제안: 이벤트 손실/서스펜션 리스크 낮추기

  • 기본 생성자(replay=0, extraBufferCapacity=0)에서는 수집자가 없을 때 이벤트가 소실되거나 상황에 따라 emit이 서스펜드될 수 있습니다. 1개 버퍼를 두고 overflow를 DROP으로 두면 one-shot 이벤트 전달 신뢰도가 높아집니다.
  • 또한, Flow 반환에 suspend는 불필요하므로 인터페이스와 함께 제거 권장.

아래 변경을 제안합니다:

-    private val _writeRoutineEventFlow = MutableSharedFlow<WriteRoutineEvent>()
-    override suspend fun getWriteRoutineEventFlow(): Flow<WriteRoutineEvent> = _writeRoutineEventFlow.asSharedFlow()
+    private val _writeRoutineEventFlow = MutableSharedFlow<WriteRoutineEvent>(
+        replay = 0,
+        extraBufferCapacity = 1,
+        onBufferOverflow = BufferOverflow.DROP_OLDEST,
+    )
+    override fun getWriteRoutineEventFlow(): Flow<WriteRoutineEvent> = _writeRoutineEventFlow.asSharedFlow()

아울러 다음 import가 필요합니다(파일 상단):

import kotlinx.coroutines.channels.BufferOverflow

인터페이스(WriteRoutineRepository.getWriteRoutineEventFlow)에서도 suspend 제거가 필요합니다(별도 코멘트 참고).

📜 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 4efe102 and 16cbba8.

📒 Files selected for processing (7)
  • data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/request/EditRoutineRequest.kt (1 hunks)
  • data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/request/RegisterRoutineRequest.kt (1 hunks)
  • data/src/main/java/com/threegap/bitnagil/data/writeroutine/repositoryImpl/WriteRoutineRepositoryImpl.kt (3 hunks)
  • domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/repository/WriteRoutineRepository.kt (2 hunks)
  • domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/usecase/EditRoutineUseCase.kt (2 hunks)
  • domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/usecase/RegisterRoutineUseCase.kt (2 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/datepickerbottomsheet/DatePickerBottomSheet.kt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/usecase/EditRoutineUseCase.kt
  • presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/datepickerbottomsheet/DatePickerBottomSheet.kt
🔇 Additional comments (5)
data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/request/EditRoutineRequest.kt (2)

22-23: 복수 컬렉션인데 키가 단수(subRoutineName) — 서버 스키마와 키 네이밍 재확인 권장

타입은 List<String>인데 직렬화 키가 단수형입니다. 서버가 단수 키에 배열을 받도록 정의되었는지 확인해 주세요. 만약 복수형 키(subRoutineNames)를 요구한다면 @SerialName만 맞추면 되므로 변경 영향은 최소화됩니다.


10-11: updateApplyDate 스키마·명명 일치 여부 재확인 필요
검증 결과 RoutineUpdateType enum(“TODAY”, “TOMORROW” 등)의 value 프로퍼티는 정의되어 있으나, 실제로 EditRoutineRequest.updateApplyDate에 어떤 값이 할당되는지는 코드상 명확히 확인되지 않았습니다.
따라서 해당 필드가 ‘날짜(YYYY-MM-DD)’ 문자열인지, ‘적용 기준 타입(FROM_NOW, FROM_START 등)’ 값인지 서버 v2 스펙 문서 및 운영팀에 재확인해 주세요. 스키마·명명 불일치 시 API 연동 오류 및 혼동 우려가 있으므로, 필요 시 필드명 또는 스펙을 수정해야 합니다.

점검 대상

  • domain/src/.../writeroutine/model/RoutineUpdateType.value 정의
  • data/src/.../writeroutine/model/request/EditRoutineRequest.updateApplyDate 매핑부
  • 서버 v2 API 스펙 문서 (“updateApplyDate” 필드 타입)
domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/usecase/RegisterRoutineUseCase.kt (1)

24-26: RegisterRoutineUseCase 호출부 시그니처 불일치

현재 WriteRoutineViewModel.kt(라인 472) 등에서 여전히

registerRoutineUseCase(
    name = currentState.routineName,
    repeatDay = repeatDay,
    startTime = startTime.toDomainTime(), // ❌ 이전 시그니처
    …
)

처럼 호출하고 있어, 변경된 도메인 시그니처(startDate, endDate, subRoutines)가 반영되지 않았습니다.

• 수정할 위치

  • presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt:472 외

• 예시 수정안

val registerRoutineResult = registerRoutineUseCase(
    name        = currentState.routineName,
    repeatDay   = repeatDay,
    startDate   = startDate.toDomainDate(),   // 변경된 파라미터
    endDate     = endDate.toDomainDate(),     // 변경된 파라미터
    subRoutines = subRoutines                  // 누락된 파라미터
)

위와 같이 모든 호출부가 새 시그니처에 맞춰 업데이트되어야 합니다.

Likely an incorrect or invalid review comment.

data/src/main/java/com/threegap/bitnagil/data/writeroutine/model/request/RegisterRoutineRequest.kt (1)

12-15: 루틴 등록 요청에 시작/종료일 추가 LGTM

도메인/레포 변경과 일관되며, 직렬화 키도 Edit와 동일하게 맞춰진 점이 좋습니다.

domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/repository/WriteRoutineRepository.kt (1)

20-29: editRoutine의 routineUpdateType·기간·subRoutines(List) 변경 일관성 확보 LGTM

데이터/DTO·UseCase·UI 변경과 모두 정합적으로 맞습니다.

Copy link
Copy Markdown
Member

@wjdrjs00 wjdrjs00 left a comment

Choose a reason for hiding this comment

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

이정도면 리펙이 아니라 피쳐 수준이네욤,, 고생하셨습니다!
남긴 리뷰 확인해주세요!

- "세부루틴 설정 안 함" 선택시 기존 입력한 세부루틴이 초기화되도록 수정
- 반복요일, 목표 기간을 필수 항목으로 수정
- 시작일/종료일 선택시 선택 범위 해제
- ExpandableContent의 패키지 경로 누락 추가
- Routine 내 routineDeleteYn 변수 추가
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt (1)

234-256: 버그: SetRoutine 인텐트 처리 시 startDate/endDate 상태 반영 누락

loadRoutine/loadRecommendRoutine에서 SetRoutine 인텐트로 startDate/endDate를 전달하지만, reduceState(SetRoutine)에서 해당 값을 state에 반영하지 않아 UI/등록·수정 로직이 잘못된 날짜(초기값)로 동작할 수 있습니다. 치명적인 기능 오류로 보입니다.

아래 수정으로 startDate/endDate를 함께 세팅하세요.

             is WriteRoutineIntent.SetRoutine -> {
                 val selectedDaySet = intent.repeatDays.toSet()
                 val repeatDays = SelectableDay.defaultList.map {
                     it.copy(
                         selected = it.day in selectedDaySet,
                     )
                 }
                 val repeatType = if (repeatDays.none { it.selected }) {
                     null
                 } else if (repeatDays.all { it.selected }) {
                     RepeatType.DAILY
                 } else {
                     RepeatType.DAY
                 }
                 return state.copy(
                     routineName = intent.name,
                     repeatDays = repeatDays,
                     repeatType = repeatType,
                     startTime = intent.startTime,
                     subRoutineNames = intent.subRoutines,
+                    startDate = intent.startDate,
+                    endDate = intent.endDate,
                     loading = false,
                 )
             }
♻️ Duplicate comments (1)
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt (1)

35-35: 이전 코멘트의 잘못된 import 문제 해결 확인

ExpandableContent의 패키지 import가 올바르게 수정되었습니다. 빌드 이슈 원인이었던 임포트 오류가 해소된 것으로 보입니다.

🧹 Nitpick comments (9)
domain/src/main/java/com/threegap/bitnagil/domain/routine/model/Routine.kt (1)

12-12: 도메인 네이밍 일관성: routineDeletedYn → isDeleted 권장

  • YN 접미사는 DB/DTO에선 관례적이지만 도메인 모델에선 불필요한 구현 세부입니다. Kotlin/도메인 관례에 맞춰 Boolean은 is~ 접두를 권장합니다.
  • DTO 매퍼에서 서버 필드명(routineDeletedYn) ↔ 도메인 필드명(isDeleted) 매핑으로 분리하면 가독성과 유지보수성이 좋아집니다.

제안 수정:

-    val routineDeletedYn: Boolean,
+    val isDeleted: Boolean,

후속 작업

  • RoutineDto.toDomain()/fromDomain 등 매퍼 필드명 동기화
  • 레포/유즈케이스/뷰모델 사용처 검색 후 리네이밍 반영

원하시면 대체 필드명 적용과 전역 리팩터링 스크립트를 제안드릴게요.

presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt (5)

84-94: 종료일 선택 시 시작일 이전 선택 방지: DatePicker 제약 전달 권장

종료일 바텀시트에서 availableStartDate/availableEndDate 모두 null로 전달되어 사용자가 시작일 이전의 날짜를 종료일로 선택할 수 있고, ViewModel에서 이를 보정하기 위해 startDate를 과거로 끌어내리는 동작이 발생합니다. UX 관점에서 종료일 선택 시 시작일 이전 날짜를 비활성화하는 편이 자연스럽습니다.

아래와 같이 종료일 DatePicker에 최소 선택 가능 날짜(availableStartDate)를 전달해 주세요.

     if (state.showEndDatePickerBottomSheet) {
         DatePickerBottomSheet(
             modifier = Modifier.fillMaxWidth(),
             onDateSelected = viewModel::setEndDate,
             date = state.endDate,
             onDismiss = viewModel::hideEndDatePickerBottomSheet,
-            availableStartDate = null,
-            availableEndDate = null,
+            availableStartDate = state.startDate,
+            availableEndDate = null,
         )
     }

215-223: 필수 표시(required) 모드 분기 필요 (등록/수정 화면 간 상이)

리뷰 코멘트 [1]에서 지적된 것처럼 등록/수정 화면의 필수 항목이 서로 다르면, ExpandableContent.required 값을 화면 모드에 맞게 분기해야 합니다. 현재는 반복 요일/목표 기간/시간 모두 required=true로 고정되어 있습니다. 아래처럼 간단히 분기하는 방식을 권장합니다. 실제 비즈니스 규칙에 맞추어 항목별로 조정해 주세요.

-    required = true,
+    required = state.writeRoutineType == WriteRoutineType.Add,

위 변경을 각 ExpandableContent(반복 요일, 목표 기간, 시간)에 동일 적용해 주세요.

Also applies to: 271-279, 300-308


183-211: 오탈자/네이밍 일관성: selectNotUseSUbRoutines → selectNotUseSubRoutines

state/selectNotUseSUbRoutines의 대문자 U가 비정상적으로 섞여 있습니다(SUb). State/Intent/VM/호출부 전반에 걸쳐 일괄 리네이밍을 권장합니다. API 공개 표면이므로 조기에 정리하는 것이 좋습니다.


332-339: 하단 CTA 라벨 모드 분기 (등록/수정)

수정 화면에서도 항상 "등록하기"로 표기됩니다. 화면 상단 타이틀과 동일하게 CTA도 분기하는 편이 자연스럽습니다.

-        BitnagilTextButton(
-            text = "등록하기",
+        BitnagilTextButton(
+            text = if (state.writeRoutineType == WriteRoutineType.Add) "등록하기" else "수정하기",
             onClick = onClickRegister,
             enabled = state.registerButtonEnabled,

143-147: 하드코딩된 문자열을 string 리소스로 이전 권장

"루틴 등록", "세부루틴", "반복 요일" 등 다수의 하드코딩 문자열이 존재합니다. 다국어/접근성/일관성 관점에서 strings.xml로 이전을 권장합니다. 리디자인 반영 시점에 함께 처리하면 추후 변경 비용을 줄일 수 있습니다.

Also applies to: 157-163, 215-223, 271-299, 300-329, 332-339

presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt (3)

303-314: 날짜 보정 로직의 예측 가능성 개선 (상대 날짜를 자동 이동시키지 않기)

SetEndDate에서 startDate를 Date.min으로, SetStartDate에서 endDate를 Date.max로 자동 보정합니다. 사용자가 종료일을 앞당겼을 때 시작일이 과거로 “끌려 내려가는” 것은 UX 상 놀랄 수 있습니다. 종료일 Picker에서 시작일 이전 날짜를 비활성화하고, 상태에서는 선택한 값만 그대로 반영하는 접근을 권장합니다. (화면 측 제안 주석 참고)

필요 시 다음처럼 단순 반영으로 바꾸고, Picker로 제약을 위임해 주세요.

-            is WriteRoutineIntent.SetEndDate -> {
-                return state.copy(
-                    startDate = Date.min(intent.date, state.startDate),
-                    endDate = intent.date,
-                )
-            }
+            is WriteRoutineIntent.SetEndDate -> {
+                return state.copy(endDate = intent.date)
+            }
 
-            is WriteRoutineIntent.SetStartDate -> {
-                return state.copy(
-                    startDate = intent.date,
-                    endDate = Date.max(intent.date, state.endDate),
-                )
-            }
+            is WriteRoutineIntent.SetStartDate -> {
+                return state.copy(startDate = intent.date)
+            }

화면에서는 종료일 DatePicker에 availableStartDate = state.startDate를 전달(제안 반영)하면 일관된 UX를 만들 수 있습니다.


139-144: '하루 종일' 토글 시 시간 보존 여부 확인 필요

SelectAllTime에서 토글할 때마다 startTime을 항상 Time.AllDay로 덮어씁니다. 사용자가 토글을 해제해도 이전에 선택했던 시각 정보가 복원되지 않습니다. 요구사항에 따라:

  • (안1) 토글 해제 시 직전의 사용자 선택 시각으로 복귀
  • (안2) 토글 해제 시 기본값(Time.Init)로 설정

중 하나를 선택해 주세요. 현재 구현은 (안2/미정)으로 보이며, 의도 확인이 필요합니다.

가능한 수정 예시:

-            WriteRoutineIntent.SelectAllTime -> {
-                return state.copy(
-                    selectAllTime = !state.selectAllTime,
-                    startTime = Time.AllDay,
-                )
-            }
+            WriteRoutineIntent.SelectAllTime -> {
+                val turnOnAllDay = !state.selectAllTime
+                return state.copy(
+                    selectAllTime = turnOnAllDay,
+                    startTime = if (turnOnAllDay) Time.AllDay else (state.startTime ?: Time.Init),
+                )
+            }

316-323: 세부 루틴 미사용 토글 시 동작은 명확 — 네이밍만 정리하면 좋습니다

토글 시 subRoutineNames를 빈 문자열로 초기화하고, 미사용일 경우 등록/수정 시 빈 리스트로 전달하는 흐름이 명확합니다. 앞서 언급한 네이밍만 정리되면 가독성/일관성이 좋아집니다.

Also applies to: 339-343

📜 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 16cbba8 and a0af44e.

📒 Files selected for processing (5)
  • data/src/main/java/com/threegap/bitnagil/data/routine/model/response/RoutineDto.kt (2 hunks)
  • domain/src/main/java/com/threegap/bitnagil/domain/routine/model/Routine.kt (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt (6 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt (10 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/expandablecontent/ExpandableContent.kt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/expandablecontent/ExpandableContent.kt
  • data/src/main/java/com/threegap/bitnagil/data/routine/model/response/RoutineDto.kt
🧰 Additional context used
🧬 Code Graph Analysis (1)
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt (1)
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/expandablecontent/ExpandableContent.kt (1)
  • ExpandableContent (40-124)
⏰ 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)
domain/src/main/java/com/threegap/bitnagil/domain/routine/model/Routine.kt (1)

8-9: 도메인 날짜 필드 LocalDate 전환 시 영향 영역 점검 요청

String → LocalDate로 전환할 경우 아래 영역 전반에 걸쳐 타입·매핑·API 시그니처 변경이 필요합니다. 변경 전 최소한 KDoc에 기대 포맷(예: yyyy-MM-dd)·타임존 규약을 명시하고, init 블록 또는 검증 로직을 추가해 주세요.

  • domain/src/main/java/com/threegap/bitnagil/domain/routine/model/Routine.kt
    startDate/endDate 타입 변경 및 init 불변식(require) 검토
  • data/src/main/java/com/threegap/bitnagil/data/routine/model/response/RoutineDto.kt
    toDomain()에서 String 파싱 → LocalDate.parse 로직 추가
  • data/src/main/java/com/threegap/bitnagil/data/routine/datasource/RoutineRemoteDataSource.ktRoutineService.kt
    fetchWeeklyRoutines(startDate: String, endDate: String) API 파라미터 처리
  • data/src/main/java/com/threegap/bitnagil/data/writeroutine/service/WriteRoutineService.kt
    RegisterRoutineRequest/EditRoutineRequest 내 날짜 필드 타입 검토
  • domain/src/main/java/com/threegap/bitnagil/domain/writeroutine/usecase/
    RegisterRoutineUseCase, EditRoutineUseCase 인자 타입 및 호출부 영향
  • presentation/navigation/UI 계층
    Route.WriteRoutine, ViewModel 등 생성자 파라미터로 전달되는 날짜 처리
  • 단위 테스트 및 DTO/mapper
    LocalDate ↔ String 변환 테스트 추가

전체 호출부 · 매퍼 · API 레이어에 미치는 영향을 수동으로 검토해 주시고, 필요 시 리팩토링 계획을 공유 부탁드립니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt (1)

471-484: 반복 없음(noRepeatRoutine)일 때 날짜를 무조건 오늘로 고정 — 의도 확인 필요

등록 플로우에서 repeatDay가 비어있으면 startDate/endDate를 UI 선택값과 무관하게 Date.now()로 강제하고 있습니다. PR 설명에는 시작/종료일을 필수값으로 가정한다고 되어 있어, 사용자가 선택한 기간을 유지하는 것이 자연스러워 보입니다. 도메인 정책상 “반복 없음 = 당일”이 맞는지 확인 부탁드립니다.

UI 선택값을 존중하려면 다음과 같이 수정할 수 있습니다.

-                        startDate = if (noRepeatRoutine) Date.now().toDomainDate() else currentState.startDate.toDomainDate(),
-                        endDate = if (noRepeatRoutine) Date.now().toDomainDate() else currentState.endDate.toDomainDate(),
+                        startDate = currentState.startDate.toDomainDate(),
+                        endDate = currentState.endDate.toDomainDate(),

- NameField TextStyle 수정
- 루틴 수정 화면에서 기존 루틴의 시작일/종료일을 반영하지 못하던 문제 수정
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

📜 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 a0af44e and b09b684.

📒 Files selected for processing (2)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt (11 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/namefield/NameField.kt (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/namefield/NameField.kt
🧰 Additional context used
🧬 Code Graph Analysis (1)
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt (1)
presentation/src/main/java/com/threegap/bitnagil/presentation/common/mviviewmodel/MviViewModel.kt (1)
  • 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 (14)
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt (14)

8-8: 새로운 도메인 타입 임포트가 적절합니다.

RoutineUpdateType 도메인 타입의 임포트가 루틴 수정 시 Today/Tomorrow 구분을 위해 적절히 추가되었습니다.


12-12: 프레젠테이션 레이어의 Date 모델 임포트가 적절합니다.

UI에서 사용할 Date 타입의 임포트가 도메인과 프레젠테이션 레이어 분리를 위해 적절히 추가되었습니다.


89-96: 서브루틴 데이터 구조 변경이 적절합니다.

서브루틴을 3개 슬롯으로 고정하여 UI에서 관리하는 방식으로 변경되었습니다. getOrNull() 사용으로 안전하게 처리되고 있습니다.


94-95: 날짜 필드 추가가 적절합니다.

시작날짜와 종료날짜 필드가 도메인에서 프레젠테이션 Date 객체로 적절히 변환되어 추가되었습니다.


117-124: 추천 루틴 로딩 시 날짜 처리가 적절합니다.

추천 루틴의 경우 현재 날짜로 시작/종료 날짜를 설정하는 것이 합리적입니다.


253-254: 상태에 날짜 필드 추가가 적절합니다.

UI 상태에 startDateendDate 필드가 적절히 추가되었습니다.


265-326: 새로운 UI 인텐트들이 적절히 구현되었습니다.

날짜 피커 표시/숨김, UI 확장 상태 관리, 날짜 설정 등의 새로운 인텐트들이 적절히 구현되었습니다.


307-315: 날짜 설정 로직에서 자동 조정 기능이 유용합니다.

시작날짜 설정 시 종료날짜를 자동으로 조정하고, 종료날짜 설정 시 시작날짜를 자동으로 조정하는 로직이 사용자 경험을 향상시킵니다.


341-344: 함수명과 인텐트명 불일치.

함수명은 selectNotUseSubRoutines이지만 인텐트는 SelectNotUseSubRoutines로 일관성이 있습니다.


384-446: 새로운 공개 함수들이 적절히 구현되었습니다.

날짜 피커 관리와 UI 확장 상태 토글을 위한 공개 함수들이 일관된 패턴으로 잘 구현되었습니다.


470-470: 빈 반복 일정 처리가 적절합니다.

반복 타입이 null일 때 빈 리스트로 처리하는 것이 적절합니다.


473-493: 루틴 등록 로직 개선이 적절합니다.

서브루틴 사용 안함 옵션과 반복 없는 루틴의 날짜 처리 로직이 적절히 구현되었습니다.


494-521: 루틴 수정 로직에서 RoutineUpdateType 사용이 적절합니다.

updateRoutineFromNowDate 플래그에 따라 RoutineUpdateType.Today 또는 RoutineUpdateType.Tomorrow를 설정하는 로직이 명확합니다.


58-58: 일관된 WriteRoutineType enum 사용 확인 완료
Kotlin 코드 전역에서 WriteRoutineType.ADD/EDIT 참조가 없으며, 모두 WriteRoutineType.Add/Edit(updateRoutineFromNowDate=…) 형태로 일관되게 업데이트되어 있습니다. 추가 수정이 필요 없습니다.

- 오타 수정 "selectNotUseSUbRoutines" -> "selectNotUseSubRoutines"
- TimePicker 구분선 색상을 주황색으로 수정
Copy link
Copy Markdown
Member

@wjdrjs00 wjdrjs00 left a comment

Choose a reason for hiding this comment

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

고생하셨습니다! 머지 고고

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/timepickerbottomsheet/TimePickerBottomSheet.kt (1)

70-72: 사용자 선택 시간이 즉시 초기값으로 되돌아가는 버그 수정 필요

update 블록이 외부 파라미터(hour/minute)를 기준으로 매번 TimePicker 값을 재설정하고 있어, 사용자가 시간을 바꾸면 곧바로 초기값으로 덮어씌워집니다. 또한 hour/minute prop 변경 시 내부 상태가 갱신되지 않습니다. 내부 상태를 prop 변화에 동기화하고, TimePicker 업데이트는 상태(currentHour/currentMinute)를 기준으로 하도록 수정이 필요합니다.

아래 두 부분 패치 제안드립니다.

  1. 상태 초기화가 prop 변경에 동기화되도록 remember 키를 지정:
-    var currentHour by remember { mutableIntStateOf(hour) }
-    var currentMinute by remember { mutableIntStateOf(minute) }
+    var currentHour by remember(hour, minute) { mutableIntStateOf(hour) }
+    var currentMinute by remember(hour, minute) { mutableIntStateOf(minute) }
  1. AndroidView.update에서 시간 재설정 기준을 상태로 변경:
-                if (timePicker.hour != hour) {
-                    timePicker.hour = hour
-                }
-                if (timePicker.minute != minute) {
-                    timePicker.minute = minute
-                }
+                if (timePicker.hour != currentHour) {
+                    timePicker.hour = currentHour
+                }
+                if (timePicker.minute != currentMinute) {
+                    timePicker.minute = currentMinute
+                }

Also applies to: 91-96, 98-101

♻️ Duplicate comments (1)
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/timepickerbottomsheet/TimePickerBottomSheet.kt (1)

82-84: 이전 리뷰 Follow-up: 스피너 분할선(라인) 오렌지 컬러 반영 여부 확인

spinner_time_picker 레이아웃에서 NumberPicker/TimePicker의 selection divider 색상을 오렌지로 적용했는지 확인 부탁드립니다. 코드 레벨에서는 확인이 어려워 레이아웃/스타일로 처리되었는지 검증 필요합니다. 미반영 시 스타일 혹은 커스텀 드로어블로 selectionDivider를 설정해야 합니다.

🧹 Nitpick comments (17)
presentation/src/main/res/values/styles.xml (2)

3-3: 위젯 스타일에 전체 Theme 상속 사용은 범위 과도 — ThemeOverlay로 범위 축소 권장

CustomTimePickerStyle가 Theme.AppCompat.Light.NoActionBar를 부모로 사용하고 있습니다. 개별 다이얼로그/위젯 스타일에 전체 Theme를 부모로 두면 불필요한 테마 속성(액션바/윈도우 속성 등)까지 주입되어 부작용이 생길 수 있습니다. ThemeOverlay.AppCompat(또는 프로젝트 표준 오버레이)로 범위를 축소하는 것을 권장합니다.

  • 제안 수정안:
-<style name="CustomTimePickerStyle" parent="Theme.AppCompat.Light.NoActionBar">
+<style name="CustomTimePickerStyle" parent="ThemeOverlay.AppCompat.Light">

5-7: 하드코딩 색상 값 → 디자인 토큰/팔레트 리소스로 치환 권장

#171719, #FE7120 등 직접 색상 값을 지정하고 있습니다. 전역 다크모드/테마 전환, 브랜딩 변경을 고려하면 @color 혹은 @attr 기반 디자인 토큰을 사용하는 편이 유지보수에 유리합니다. 또한 colorControlNormal은 테마 전역 컨트롤 컬러이므로, 이 스타일이 오버레이로 적용될 경우 의도치 않은 컴포넌트까지 색이 확산될 수 있어 범위 관리가 중요합니다.

  • 제안(예시):
-<item name="android:textColorPrimary">#171719</item>
-<item name="android:textColorSecondary">#171719</item>
-<item name="colorControlNormal">#FE7120</item>
+<item name="android:textColorPrimary">@color/text_primary</item>
+<item name="android:textColorSecondary">@color/text_secondary</item>
+<item name="colorControlNormal">@color/brand_accent</item>
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/timepickerbottomsheet/TimePickerBottomSheet.kt (3)

20-23: 하드코딩 문자열을 string 리소스로 이전

"저장"을 하드코딩하지 말고 string 리소스를 사용해주세요. 다국어/접근성 및 일관된 카피 관리 측면에서 이점이 큽니다.

-import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.res.stringResource
-            text = "저장",
+            text = stringResource(R.string.save),

참고: R.string.save(또는 프로젝트 컨벤션에 맞는 키: e.g. R.string.common_save, R.string.writeroutine_save)를 res/values/strings.xml에 추가가 필요합니다.

Also applies to: 109-114


73-76: 중복된 배경색 지정 제거 제안

ModalBottomSheet에 이미 containerColor = BitnagilTheme.colors.white를 지정했습니다. 내부 Column의 background도 동일 색이면 오버드로우만 증가합니다. 제거를 권장합니다.

-    Column(
-        modifier = modifier.background(
-            color = BitnagilTheme.colors.white,
-        ),
+    Column(
+        modifier = modifier,
         horizontalAlignment = Alignment.CenterHorizontally,
     ) {

113-114: enabled = true 기본값은 생략 가능

명시하지 않아도 기본 enabled = true입니다. 불필요한 속성은 제거해 간결하게 유지하세요.

-            },
-            enabled = true,
+            },
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt (5)

89-96: 서브루틴 3슬롯 패딩/절단 로직 중복 — 유틸로 추출 권장

loadRoutine에서 3개 슬롯으로 맞추는 로직이 반복됩니다(아래 추천 루틴에서도 동일). 도메인 제약(최대 3개)이 확정이라면 헬퍼로 캡슐화해 중복을 제거하면 가독성과 회귀 리스크가 줄어듭니다. 또한 3개 초과 시 조용히 절단되므로 도메인 제한이 명확한지 확인 바랍니다.

아래처럼 치환을 제안합니다.

-                            subRoutines = listOf(
-                                routine.subRoutineNames.getOrNull(0) ?: "",
-                                routine.subRoutineNames.getOrNull(1) ?: "",
-                                routine.subRoutineNames.getOrNull(2) ?: "",
-                            ),
+                            subRoutines = routine.subRoutineNames.toThreeSlots(),

도우미 함수(파일 내 임의 위치에 추가):

private fun List<String>.toThreeSlots(): List<String> {
    val trimmed = this.take(3)
    return when (trimmed.size) {
        0 -> listOf("", "", "")
        1 -> listOf(trimmed[0], "", "")
        2 -> listOf(trimmed[0], trimmed[1], "")
        else -> trimmed
    }
}

117-124: 추천 루틴 기본값 설정 합리적이나, 중복 로직 유틸화 권장

  • subRoutines 슬롯 구성은 위와 동일하게 유틸로 대체 권장(toThreeSlots()).
  • startDate/endDate를 Date.now()로 초기화하는 선택은 초기 UX에 적합합니다. 이후 사용자가 변경할 수 있으므로 OK.
-                            subRoutines = listOf(
-                                oldSubRoutines.getOrNull(0)?.name ?: "",
-                                oldSubRoutines.getOrNull(1)?.name ?: "",
-                                oldSubRoutines.getOrNull(2)?.name ?: "",
-                            ),
+                            subRoutines = oldSubRoutines.mapNotNull { it.name }.toThreeSlots(),

341-344: 메서드명은 토글 동작을 드러내면 더 명확합니다

selectNotUseSubRoutines는 내부적으로 토글 동작을 수행합니다. toggleNotUseSubRoutines 등으로 명확화하면 의도 파악이 쉬워집니다.


384-446: UI 토글 메서드 중복 제거 제안

start/end date picker show/hide 및 여러 expanded 토글 메서드 패턴이 동일합니다. 간단한 헬퍼로 중복을 줄일 수 있습니다.

예시:

private inline fun send(intent: WriteRoutineIntent) {
    viewModelScope.launch { sendIntent(intent) }
}

// 사용 예
fun showStartDatePickerBottomSheet() = send(WriteRoutineIntent.ShowStartDatePickerBottomSheet)
fun hideStartDatePickerBottomSheet() = send(WriteRoutineIntent.HideStartDatePickerBottomSheet)

318-325: 서브루틴 미사용 토글 시 기존 입력값 유지 권장

현재 토글 켜기(SelectNotUseSubRoutines) 동작에서
subRoutineNames를 listOf("", "", "")로 초기화하고 있어,
다시 끌 때(state.selectNotUseSubRoutines = false) 사용자 입력이 복원되지 않습니다.
실제 저장 로직은 selectNotUseSubRoutines 플래그만으로 빈 리스트 처리하기 때문에
초기화 코드는 불필요하며 UX 관점에서도 입력값을 그대로 보존하는 편이 안전합니다.

  • 수정 위치

    • presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt
      라인 322
  • 제안된 diff

             WriteRoutineIntent.SelectNotUseSubRoutines -> {
                 val toggledSelectNotUseSubRoutines = !state.selectNotUseSubRoutines

                 return state.copy(
                     selectNotUseSubRoutines = toggledSelectNotUseSubRoutines,
-                    subRoutineNames = if (toggledSelectNotUseSubRoutines) listOf("", "", "") else state.subRoutineNames,
+                    // subRoutineNames 초기화 제거: 토글 플래그만 반영하고 기존 값 유지
                 )
             }

레거시 오타(selectNotUseSUbRoutines)는 코드베이스에 존재하지 않음을 확인했습니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt (7)

73-82: 날짜 피커 가용 범위 제약 전달 누락 — 선택 보정(ViewModel) 외에 UI 단계에서도 제약 제안

두 바텀시트에서 availableStartDate/availableEndDate를 모두 null로 전달하여 사용자가 역순 날짜를 쉽게 선택할 수 있습니다. ViewModel에서 보정하더라도, 피커에서 즉시 제약을 보여주는 편이 UX가 덜 혼란스럽습니다.
옵션: 자유 선택은 유지하되, 최소한 아래와 같이 상호 제약을 전달하는 것을 권장합니다.

적용 제안 (Start Date Sheet: 종료일 기준 상한, End Date Sheet: 시작일 기준 하한):

@@
-            availableStartDate = null,
-            availableEndDate = null,
+            availableStartDate = null,
+            availableEndDate = state.endDate,
@@
-            availableStartDate = null,
-            availableEndDate = null,
+            availableStartDate = state.startDate,
+            availableEndDate = null,

확인 요청:

  • ViewModel에서 시작/종료 역전 시 자동 보정 로직이 이미 들어가 있으므로, 위 제약을 추가해도 기능 요구사항과 충돌하지 않는지 검토 부탁드립니다.

Also applies to: 84-93


95-112: 네이밍 정확도: selectRepeatTime → selectRepeatType 리네임 제안

파라미터·콜백이 RepeatType을 다루므로 time 대신 type이 의미상 더 정확합니다. 가독성 향상을 위해 다음과 같이 리네임을 제안합니다.

@@
-        selectRepeatTime = viewModel::selectRepeatType,
+        selectRepeatType = viewModel::selectRepeatType,
@@
-    selectRepeatTime: (RepeatType) -> Unit,
+    selectRepeatType: (RepeatType) -> Unit,
@@
-                                    selectRepeatTime(RepeatType.DAILY)
+                                    selectRepeatType(RepeatType.DAILY)
@@
-                                    selectRepeatTime(RepeatType.DAY)
+                                    selectRepeatType(RepeatType.DAY)

Also applies to: 116-133, 228-235, 243-246


95-112: 토글 의도 반영: selectNotUseSubRoutines → toggleNotUseSubRoutines 리네임 제안

현재 시그니처가 파라미터 없는 onClick이므로 “select” 보다는 “toggle”이 의도를 더 잘 드러냅니다. (ViewModel 메서드명도 함께 변경 필요)

@@
-        selectNotUseSubRoutines = viewModel::selectNotUseSubRoutines,
+        toggleNotUseSubRoutines = viewModel::toggleNotUseSubRoutines,
@@
-    selectNotUseSubRoutines: () -> Unit,
+    toggleNotUseSubRoutines: () -> Unit,
@@
-                            onClick = selectNotUseSubRoutines,
+                            onClick = toggleNotUseSubRoutines,
@@
-            selectNotUseSubRoutines = {},
+            toggleNotUseSubRoutines = {},

Also applies to: 116-133, 207-211, 370-373


300-307: ‘시간’ 섹션 필수 표기 고정 — ‘하루 종일’ 선택 시 동적 처리 권장

All-day 선택 시 시작 시간은 실질적으로 필수가 아닐 수 있습니다. 섹션의 required 뱃지도 이에 맞춰 동적으로 반영하는 편이 자연스럽습니다.

-                    required = true,
+                    required = !state.selectAllTime,

확인 요청: registerButtonEnabled 계산에서도 동일한 기준(하루 종일이면 시간 미입력 허용)이 반영되어 있는지 점검 부탁드립니다.


332-339: 수정 모드 CTA 텍스트 고정 — 모드별로 가변 처리 권장

수정 모드에서도 버튼 텍스트가 “등록하기”로 고정되어 있어 UX 일관성이 떨어집니다. 아래와 같이 모드에 따라 변경을 권장합니다.

-        BitnagilTextButton(
-            text = "등록하기",
+        BitnagilTextButton(
+            text = if (state.writeRoutineType == WriteRoutineType.Add) "등록하기" else "수정하기",

144-147: 하드코딩된 UI 문자열 — string 리소스 추출 권장

다수의 사용자 노출 텍스트가 하드코딩되어 있습니다(예: 상단 타이틀, 플레이스홀더, 섹션 타이틀, 버튼 라벨 등). 현지화/일관성/테스트 용이성을 위해 string 리소스로 분리해 주세요.
예) text = stringResource(id = R.string.write_routine_title_add)


173-176: 용어 표기 일관성: ‘세부루틴’ vs ‘세부 루틴’

헤더 타이틀(“세부루틴”)과 다른 곳(체크박스 라벨/플레이스홀더)의 “세부 루틴” 표기가 불일치합니다. 하나로 통일해 주세요. 예시:

-                    title = "세부루틴",
+                    title = "세부 루틴",

Also applies to: 207-211

📜 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 b09b684 and 5ec5940.

📒 Files selected for processing (6)
  • app/src/main/res/values/themes.xml (1 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt (6 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt (11 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/timepickerbottomsheet/TimePickerBottomSheet.kt (4 hunks)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineState.kt (3 hunks)
  • presentation/src/main/res/values/styles.xml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/model/mvi/WriteRoutineState.kt
🧰 Additional context used
🧬 Code Graph Analysis (1)
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt (7)
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/datepickerbottomsheet/DatePickerBottomSheet.kt (1)
  • DatePickerBottomSheet (47-84)
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/namefield/NameField.kt (1)
  • NameField (24-78)
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/expandablecontent/ExpandableContent.kt (1)
  • ExpandableContent (40-124)
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/subroutinefield/SubRoutineField.kt (1)
  • SubRoutineField (23-73)
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/labeledcheckbox/LabeledCheckBox.kt (1)
  • LabeledCheckBox (21-58)
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/atom/selectcell/SelectCell.kt (1)
  • SelectCell (19-47)
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/block/routinedetailrow/RoutineDetailRow.kt (1)
  • RoutineDetailRow (18-48)
⏰ 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 (16)
app/src/main/res/values/themes.xml (1)

4-4: [확인 필요] AppCompat 기반 테마 전환 시 Material Components 사용 화면 검증
스크립트로 com.google.android.material.*, Material(Date|Time)Picker, Theme.MaterialComponents 문자열을 검색했으나 결과가 없습니다.
그러나 다음과 같은 경우 검색엔 누락될 수 있으므로 수동으로도 꼭 확인해 주세요:

  • res/layout/**/*.xml 에서 <com.google.android.material.button.MaterialButton> 등 Material 컴포넌트 태그 사용 여부
  • Kotlin/Java 코드에서 MaterialButton, TextInputLayout, MaterialCardView 등 클래스 직접 참조 여부
  • 앱 실행 중 로그캣에 “You need to use a Theme.MaterialComponents theme” 크래시 메시지 발생 여부

또한 android:windowIsTranslucent 속성은 일부 기기·OS 버전에서 제스처 내비게이션 전환 애니메이션과 충돌 이슈가 보고되어, 적용 범위를 최소화하는 것을 권장드립니다.

선택적 대안:

  • 전역 테마를 MaterialComponents 계열(예: Theme.Material3.DayNight.NoActionBar)로 유지하고
  • 비Material 화면에만 ThemeOverlay.MaterialComponents.*를 적용
presentation/src/main/res/values/styles.xml (1)

3-7: CustomTimePickerStyle 적용 확인 완료 및 AppCompat TimePicker 사용됨
spinner_time_picker.xml에서 <TimePicker>android:theme="@style/CustomTimePickerStyle"를 통해 스타일이 정상 적용되고 있으며,
실제 코드(TimePickerBottomSheet)에도 android.widget.TimePicker(뷰 기반)가 사용되고 있습니다.
MaterialTimePicker나 Compose 기반 위젯은 검색 결과 전혀 없으므로 현 스타일 정의대로 문제없이 동작합니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/component/template/timepickerbottomsheet/TimePickerBottomSheet.kt (3)

81-87: 24시간/12시간 모드 설정 의도 확인 필요

이전 구현에서 24시간 모드 강제 설정이 제거되었습니다. 제품 정책이 시스템 설정을 따르는 것인지(권장), 항상 24시간 고정인지 확인 부탁드립니다. 의도대로가 아니라면 아래와 같이 시스템 설정을 따르도록 설정하는 것을 권장합니다.

             factory = { context ->
                 val view = View.inflate(context, R.layout.spinner_time_picker, null)
                 val timePicker = view.findViewById<TimePicker>(R.id.time_picker)
+                timePicker.setIs24HourView(android.text.format.DateFormat.is24HourFormat(context))
                 view.tag = timePicker

추가적으로 파일 상단에 별도 import가 필요 없다면 위처럼 FQCN을 사용하거나, 다음 import를 추가하세요:

import android.text.format.DateFormat

63-69: 내부 전용 컴포저블로의 전환 👍

TimePickerBottomSheetContent를 private으로 한 점 좋습니다. 외부 API 표면을 최소화해 모듈 경계를 명확히 했습니다.


118-127: 미리보기(@Preview) 추가 좋습니다

컴포넌트 자체에 프리뷰를 내장해 빠른 UI 검증이 가능해졌습니다. 유지보수성 향상에 도움됩니다.

presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt (9)

8-12: 도메인/프리젠테이션 타입 추가 반영 적절

RoutineUpdateType 도입 및 UI Date 모델 사용으로 의도한 리디자인 변화와 일치합니다.


58-69: WriteRoutineType 적용 및 내비게이션 파라미터 연동 적절

  • Add/ Edit(updateRoutineFromNowDate) 분기 및 상태 반영이 일관적입니다.

253-255: SetRoutine 시 날짜 상태 주입 OK

초기 로딩에서 startDate/endDate를 상태로 일괄 주입 후 이후 인텐트에서 불변식 유지하는 흐름이 명확합니다.


265-284: 날짜 피커 표시/숨김 인텐트 처리 LGTM

표시/숨김 상태 분리와 단방향 데이터 흐름이 잘 지켜졌습니다.


285-304: 확장(Expandable) UI 토글 인텐트 처리 LGTM

의도 분리 및 상태 반영이 명확합니다.


470-471: 무반복(null) 시 repeatDay를 빈 리스트로 매핑 OK

도메인 측에 빈 목록 의미(무반복)가 명확하다면 적절합니다.


494-501: 루틴 업데이트 기준(Today/Tomorrow) 계산 LGTM

updateRoutineFromNowDate -> RoutineUpdateType 매핑이 명확합니다.


506-513: editRoutineUseCase 파라미터 확장 반영 LGTM

routineUpdateType, repeatDay, start/endDate, subRoutines 전달이 도메인 변경과 일치합니다.


473-486: 무반복 루틴 날짜 전달 일관화 필요
Add 분기에서도 Edit 분기처럼 currentState.startDate/endDate를 그대로 전달하고, noRepeatRoutineDate.now() 조건문을 제거해주세요.

  • 대상 파일:
    • presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt
    • 473–486라인 (WriteRoutineType.Add 분기)
  • 수정 예시:
  • // 무반복일 때 Date.now()로 덮어쓰는 로직 제거
  • val noRepeatRoutine = repeatDay.isEmpty()
    val registerRoutineResult = registerRoutineUseCase(
    name = currentState.routineName,
    repeatDay = repeatDay,
    startTime = startTime.toDomainTime(),
  •   startDate = if (noRepeatRoutine) Date.now().toDomainDate() else currentState.startDate.toDomainDate(),
    
  •   endDate   = if (noRepeatRoutine) Date.now().toDomainDate() else currentState.endDate.toDomainDate(),
    
  •   startDate = currentState.startDate.toDomainDate(),
    
  •   endDate   = currentState.endDate.toDomainDate(),
      subRoutines = subRoutines,
    
    )
  • 추가 검토 필요:
    무반복 루틴 정책이 “오늘만”으로 고정되어야 한다면 도메인 레이어와 UI 기획을 확정하고, 그에 맞춰 코드 반영 여부를 결정해주세요.
presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineScreen.kt (2)

35-35: ExpandableContent import 경로 수정 확인 — 컴파일 이슈 해결됨

이전 리뷰에서 지적된 잘못된 import가 패키지 경로로 올바르게 반영되었습니다. 빌드 안정성 측면에서 👍


271-279: 기간 포맷 일관성 확인 요청

placeholder "ex) 25.08.06 - 25.08.06"과 실제 value(state.startDate/endDate.toFormattedString())의 포맷이 동일한지 확인 부탁드립니다. 포맷이 다르면 사용자가 혼란을 겪을 수 있습니다. 필요 시 toFormattedString과 placeholder 모두 동일 포맷(로캘 고려)로 정렬 권장.

Also applies to: 287-296

@l5x5l l5x5l merged commit 66e9b0c into develop Aug 19, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

세환 🔨 Refactor 기존 기능 개선

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[REFACTOR] 루틴 작성/수정 화면 리디자인 적용

2 participants