Conversation
Walkthrough캘린더 기능 연동을 위해 공용 응답 타입(code)을 문자열로 변경하고, 캘린더용 DTO/모델/매퍼, Retrofit 서비스, 데이터소스·레포지토리, DI 바인딩을 추가·교체하며 ViewModel·UI에 캐시·비동기 로직과 모드 전환 흐름을 도입합니다. Changes
Sequence Diagram(s)sequenceDiagram
participant VM as CalendarViewModel
participant Repo as CalendarRepository
participant DS as CalendarDataSource
participant Service as CalendarService
participant API as Backend API
VM->>Repo: getCalendarMonthly(year, month)
Repo->>DS: getCalendarMonthly(year, month)
DS->>Service: getCalendarMonthly(year, month)
Service->>API: GET /api/calendar/monthly?year=Y&month=M
API-->>Service: BaseResponse<CalendarMonthlyResponseDto>
Service-->>DS: BaseResponse<CalendarMonthlyResponseDto>
DS-->>Repo: BaseResponse<CalendarMonthlyResponseDto>
Repo->>Repo: data!!.toModel()
Repo-->>VM: Result<CalendarMonthlyResponseModel>
VM->>VM: updateUiState (selectedYearMonth, cached counts, procedure list)
sequenceDiagram
participant VM as CalendarViewModel
participant Repo as CalendarRepository
participant DS as CalendarDataSource
participant Service as CalendarService
participant API as Backend API
VM->>Repo: getCalendarDaily(date)
Repo->>DS: getCalendarDaily(date)
DS->>Service: getCalendarDaily(date)
Service->>API: GET /api/calendar/daily?date=YYYY-MM-DD
API-->>Service: BaseResponse<CalendarDailyResponseDto>
Service-->>DS: BaseResponse<CalendarDailyResponseDto>
DS-->>Repo: BaseResponse<CalendarDailyResponseDto>
Repo->>Repo: data!!.toModel()
Repo-->>VM: Result<CalendarDailyResponseModel>
VM->>VM: map events -> ProcedureInfoModel, updateUiState
sequenceDiagram
participant VM as CalendarViewModel
participant Repo as CalendarRepository
participant DS as CalendarDataSource
participant Service as CalendarService
participant API as Backend API
VM->>Repo: getCalendarEventDowntime(id)
Repo->>DS: getCalendarEventDowntime(id)
DS->>Service: getCalendarEventDowntime(id)
Service->>API: GET /api/calendar/events/{id}/downtime
API-->>Service: BaseResponse<CalendarDownTimeResponseDto>
Service-->>DS: BaseResponse<CalendarDownTimeResponseDto>
DS-->>Repo: BaseResponse<CalendarDownTimeResponseDto>
Repo->>Repo: data!!.toModel()
Repo-->>VM: Result<CalendarDownTimeResponseModel>
VM->>VM: switch to Downtime mode, updateUiState (downtime map)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧹 Recent nitpick comments
📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro 📒 Files selected for processing (5)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (1)
🧰 Additional context used📓 Path-based instructions (1)**/*.kt⚙️ CodeRabbit configuration file
Files:
🧠 Learnings (1)📚 Learning: 2026-01-12T19:49:27.085ZApplied to files:
🧬 Code graph analysis (1)app/src/main/java/com/cherrish/android/presentation/calendar/CalendarViewModel.kt (1)
⏰ 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). (2)
🔇 Additional comments (5)
✏️ Tip: You can disable this entire section by setting 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. Comment |
There was a problem hiding this comment.
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)
app/src/main/java/com/cherrish/android/presentation/calendar/component/ProcedureInfoItem.kt (1)
104-108:ProcedureScheduleInfo의downTimeDuration타입 불일치공개 함수
ProcedureInfoItem에서는downTimeDuration: Int로 non-nullable 타입을 사용하지만, private 함수ProcedureScheduleInfo에서는 여전히Int?로 nullable 타입을 사용하고 있습니다. 일관성을 위해 타입을 통일해 주세요.🔧 수정 제안
`@Composable` private fun ProcedureScheduleInfo( colors: ProcedureColors, procedureDay: String, - downTimeDuration: Int? + downTimeDuration: Int ) {
🤖 Fix all issues with AI agents
In `@app/src/main/java/com/cherrish/android/core/network/BaseResponse.kt`:
- Line 9: BaseResponse.code is never used to validate API success, so update the
response handling in CalendarRepositoryImpl and DummyRepositoryImpl to check
BaseResponse.code (using the API's success value, e.g., "0" or "SUCCESS") before
accessing data; replace direct .data!! usage with safe null handling: if code
indicates success return data (or map to domain model), otherwise throw or
return a meaningful error/exception including BaseResponse.code and message;
ensure the checks reference BaseResponse (the data class) and the repository
methods that currently force-unwrap data.
In
`@app/src/main/java/com/cherrish/android/data/repositoryimpl/CalendarRepositoryImpl.kt`:
- Around line 14-30: The code uses the unsafe `!!` on `calendarDataSource`
responses in getCalendarMonthly, getCalendarDaily, and getCalendarEventDowntime
which can produce unclear NPEs; replace the `!!.toModel()` usage with explicit
null handling: retrieve the response, check `response.data` and if null
return/throw a clear error (e.g. throw IllegalStateException or return
Result.failure(Exception("calendarDataSource returned null data for
getCalendarMonthly/date/id: ..."))) otherwise call `toModel()` and return the
success; include the endpoint name and input (year/month or date or id) in the
error message so failures are informative.
In
`@app/src/main/java/com/cherrish/android/presentation/calendar/CalendarViewModel.kt`:
- Around line 36-67: The onDateClick implementation runs viewModelScope.launch
inside _uiState.updateSuccess causing a race; instead read the current state
first (e.g., val current = _uiState.value or call updateSuccess once to obtain
the snapshot), then branch on whether current.calendarDisplayMode is
CalendarDisplayMode.Downtime: if Downtime, start the coroutine outside
updateSuccess and inside that coroutine call
calendarRepository.getCalendarDaily(...) and then call _uiState.updateSuccess to
apply the response (mapping response.events to ProcedureInfoModel) and set
calendarDisplayMode/selectedDate/procedureInfoList; if not Downtime, call
loadDailyCalendar(date) and synchronously update selectedDate via
_uiState.updateSuccess or by copying the snapshot. Ensure all asynchronous
updates to _uiState happen from the coroutine (using _uiState.updateSuccess)
rather than launching a coroutine from within the updateSuccess lambda.
🧹 Nitpick comments (12)
app/src/main/java/com/cherrish/android/data/remote/dto/request/CalendarDailyRequestDto.kt (1)
6-10: LGTM! 표준 DTO 패턴을 잘 따르고 있습니다.
@Serializable어노테이션과 data class 구조가 적절하게 사용되었습니다.선택 사항:
@SerialName("date")는 프로퍼티 이름이 이미date이므로 중복입니다. kotlinx.serialization은 기본적으로 프로퍼티 이름을 JSON 키로 사용합니다. 명시성을 위해 유지하는 것도 괜찮지만, 제거해도 동일하게 동작합니다.♻️ 중복 어노테이션 제거 (선택 사항)
`@Serializable` data class CalendarDailyRequestDto( - `@SerialName`("date") val date: String )app/src/main/java/com/cherrish/android/presentation/calendar/util/CalendarCalculator.kt (1)
8-14:LocalDateTime.parse()에서 파싱 실패 시 예외 처리가 없습니다.
scheduledAt문자열이 ISO-8601 형식이 아니거나 유효하지 않은 경우DateTimeParseException이 발생하여 앱이 크래시될 수 있습니다. 서버 응답이 예상과 다를 경우를 대비한 방어적 처리를 권장합니다.♻️ 예외 처리 추가 제안
-fun formatProcedureDay(scheduledAt: String): String { - val dateTime = LocalDateTime.parse(scheduledAt) +fun formatProcedureDay(scheduledAt: String): String? { + val dateTime = runCatching { LocalDateTime.parse(scheduledAt) }.getOrNull() + ?: return null val month = dateTime.month.value val day = dateTime.dayOfMonth val dayOfWeek = dateTime.dayOfWeek.toKoreanName() return "${month}월 ${day}일 $dayOfWeek" }또는 호출부에서 기본값 처리가 필요한 경우:
fun formatProcedureDay(scheduledAt: String, default: String = ""): String { return runCatching { val dateTime = LocalDateTime.parse(scheduledAt) val month = dateTime.month.value val day = dateTime.dayOfMonth val dayOfWeek = dateTime.dayOfWeek.toKoreanName() "${month}월 ${day}일 $dayOfWeek" }.getOrDefault(default) }app/src/main/java/com/cherrish/android/presentation/calendar/procedure/component/CautionDescription.kt (1)
28-29: 하드코딩된 줄바꿈(\n) 사용에 대한 고려텍스트 내
\n으로 줄바꿈을 강제하면 다양한 화면 크기나 폰트 크기 설정에서 레이아웃이 의도와 다르게 보일 수 있습니다. 디자인 의도라면 유지해도 되지만, 자연스러운 텍스트 래핑을 원한다면 줄바꿈을 제거하는 것을 고려해 보세요.app/src/main/java/com/cherrish/android/presentation/calendar/procedure/component/ProcedureTitleSection.kt (1)
16-43:modifier파라미터 추가를 권장합니다.재사용 가능한 컴포저블은
modifier파라미터를 노출하는 것이 Compose 컨벤션입니다. 현재 구조에서는 호출하는 쪽에서 padding, size 등을 조절할 수 없습니다.또한 여러 sibling 컴포저블(HorizontalDivider, Column, HorizontalDivider)을 직접 배치하고 있어, modifier 적용 시 첫 번째 요소에만 적용되는 문제가 발생할 수 있습니다.
Column으로 감싸는 것을 고려해 보세요.♻️ 제안하는 수정
`@Composable` fun ProcedureTitleSection( - procedureName: String + procedureName: String, + modifier: Modifier = Modifier ) { - HorizontalDivider( - thickness = 1.dp, - color = CherrishTheme.colors.gray500 - ) - Column( - modifier = Modifier - .fillMaxWidth() - .background(color = CherrishTheme.colors.gray100) - .padding(horizontal = 25.dp, vertical = 20.dp), - verticalArrangement = Arrangement.spacedBy(4.dp) - ) { - Text( - text = "$procedureName 관련 시술 리스트", - style = CherrishTheme.typography.title1SB18, - color = CherrishTheme.colors.gray1000 - ) - - CautionDescription() + Column(modifier = modifier) { + HorizontalDivider( + thickness = 1.dp, + color = CherrishTheme.colors.gray500 + ) + Column( + modifier = Modifier + .fillMaxWidth() + .background(color = CherrishTheme.colors.gray100) + .padding(horizontal = 25.dp, vertical = 20.dp), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + Text( + text = "$procedureName 관련 시술 리스트", + style = CherrishTheme.typography.title1SB18, + color = CherrishTheme.colors.gray1000 + ) + CautionDescription() + } + HorizontalDivider( + thickness = 1.dp, + color = CherrishTheme.colors.gray500 + ) } - HorizontalDivider( - thickness = 1.dp, - color = CherrishTheme.colors.gray500 - ) }app/src/main/java/com/cherrish/android/presentation/calendar/component/ProcedureInfoItem.kt (1)
121-123: non-nullableInt에.let사용은 불필요
downTimeDuration이 이제 non-nullableInt이므로.let블록 없이 직접 사용해도 됩니다.♻️ 간소화 제안
Text( - text = downTimeDuration.let { - if (it == 0) "-" else "다운타임 ${it}일" - }, + text = if (downTimeDuration == 0) "-" else "다운타임 ${downTimeDuration}일", style = CherrishTheme.typography.body3R12, color = colors.procedureDateText )app/src/main/java/com/cherrish/android/presentation/calendar/CalendarUiState.kt (1)
17-18:cachedProcedureCountByDate에ImmutableMap사용을 권장합니다.
procedureInfoList는ImmutableList를 사용하는 반면,cachedProcedureCountByDate는 일반Map을 사용하고 있습니다.@Immutable어노테이션이 있지만, Compose 안정성 일관성을 위해kotlinx.collections.immutable.ImmutableMap또는persistentMapOf()를 사용하는 것이 더 적합합니다.♻️ 제안된 수정
-import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.ImmutableMap +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.persistentMapOfval procedureInfoList: ImmutableList<ProcedureInfoModel> = persistentListOf(), - val cachedProcedureCountByDate: Map<LocalDate, Int> = emptyMap() + val cachedProcedureCountByDate: ImmutableMap<LocalDate, Int> = persistentMapOf()app/src/main/java/com/cherrish/android/data/model/CalendarDownTimeResponseModel.kt (1)
5-21: 날짜 타입 파싱 고려
scheduledAt과sensitiveDays/cautionDays/recoveryDays가 String으로 유지되어 있습니다. 현재 구현도 문제없지만, 도메인 모델에서LocalDate나LocalDateTime으로 파싱하면 타입 안전성이 향상되고 날짜 연산이 간편해집니다. 이 변환을 ViewModel이나 UseCase에서 처리하고 있다면 현재 구조도 괜찮습니다.app/src/main/java/com/cherrish/android/data/remote/service/CalendarService.kt (1)
12-26: API 경로 포맷 불일치 확인 필요
getCalendarMonthly와getCalendarDaily는 선행 슬래시 없이"api/calendar/..."형태로 정의되어 있지만,getCalendarEventDowntime은"/api/calendar/events/{id}/downtime"으로 선행 슬래시가 포함되어 있습니다.Retrofit에서 선행 슬래시가 있으면 base URL의 path를 무시하고 절대 경로로 처리되므로, 일관성 있게 수정하는 것이 좋습니다.
♻️ 경로 포맷 통일 제안
`@GET`("api/calendar/monthly") suspend fun getCalendarMonthly( `@Query`("year") year: Int, `@Query`("month") month: Int ): BaseResponse<CalendarMonthlyResponseDto> `@GET`("api/calendar/daily") suspend fun getCalendarDaily( `@Query`("date") date: String ): BaseResponse<CalendarDailyResponseDto> - `@GET`("/api/calendar/events/{id}/downtime") + `@GET`("api/calendar/events/{id}/downtime") suspend fun getCalendarEventDowntime( `@Path`("id") id: Long ): BaseResponse<CalendarDownTimeResponseDto>app/src/main/java/com/cherrish/android/data/repository/CalendarRepository.kt (1)
7-20: LGTM! Repository 인터페이스가 깔끔하게 정의되었습니다.
Result<T>래퍼를 사용한 에러 핸들링 패턴과 suspend 함수 사용이 적절합니다. Google 권장 Android 아키텍처를 잘 준수하고 있습니다.선택적 개선 사항:
getCalendarDaily의date: String파라미터를LocalDate로 변경하면 타입 안전성을 높이고 날짜 형식 오류를 컴파일 타임에 방지할 수 있습니다. 다만 이는 API 계층과의 일관성에 따라 결정하시면 됩니다.app/src/main/java/com/cherrish/android/presentation/calendar/CalendarViewModel.kt (3)
79-96:onDateClick과 동일한 패턴 문제가 있습니다.
updateSuccess내에서loadDowntimeDetail(코루틴 실행)을 호출하고currentState를 반환하는 패턴입니다. 위에서 제안한 것처럼 상태를 먼저 읽고 조건 분기하는 방식으로 리팩토링을 권장합니다.♻️ 제안된 수정
fun onEventClick(procedureId: Long) { - _uiState.updateSuccess { currentState -> - val currentMode = currentState.calendarDisplayMode - - if (currentMode is CalendarDisplayMode.Downtime && - currentMode.selectedProcedureId == procedureId - ) { - currentState.copy( - calendarDisplayMode = CalendarDisplayMode.Normal( - procedureCountByDate = currentState.cachedProcedureCountByDate - ) + val currentState = (_uiState.value as? UiState.Success)?.data ?: return + val currentMode = currentState.calendarDisplayMode + + if (currentMode is CalendarDisplayMode.Downtime && + currentMode.selectedProcedureId == procedureId + ) { + _uiState.updateSuccess { state -> + state.copy( + calendarDisplayMode = CalendarDisplayMode.Normal( + procedureCountByDate = state.cachedProcedureCountByDate + ) ) - } else { - loadDowntimeDetail(procedureId) - currentState } + } else { + loadDowntimeDetail(procedureId) } }
102-140: 에러 발생 시 UI가Loading상태에 머물 수 있습니다.
getCalendarMonthly가 실패하면onLogFailure { }만 호출되고_uiState는 업데이트되지 않아 UI가 영구적으로 로딩 상태에 머물게 됩니다. 또한dailyDeferred는 await되지 않고 방치됩니다.에러 상태를 UI에 반영하거나 최소한 에러 로깅을 추가하는 것을 권장합니다:
♻️ 에러 처리 개선 제안
}.onLogFailure { } + .onFailure { + _uiState.update { UiState.Failure(it.message ?: "Unknown error") } + }또는 최소한
onLogFailure에서 에러를 로깅하도록 수정:- }.onLogFailure { } + }.onLogFailure { throwable -> + // 에러 로깅 또는 상태 업데이트 + }
149-156:ProcedureInfoModel매핑 로직이 여러 곳에서 중복됩니다.동일한 이벤트 →
ProcedureInfoModel변환 로직이 라인 49-55, 125-131, 149-154에서 반복됩니다. 확장 함수나 헬퍼 함수로 추출하면 유지보수성이 향상됩니다.♻️ 헬퍼 함수 추출 제안
// CalendarViewModel.kt 또는 별도 파일에 추가 private fun CalendarDailyModel.Event.toProcedureInfoModel() = ProcedureInfoModel( procedureId = userProcedureId, procedureName = name, procedureDay = formatProcedureDay(scheduledAt), downTimeDuration = downtimeDays ) // 사용 예시 procedureInfoList = response.events.map { it.toProcedureInfoModel() }.toImmutableList()
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (28)
.kotlin/sessions/kotlin-compiler-1697198284998868167.saliveapp/src/main/java/com/cherrish/android/core/network/BaseResponse.ktapp/src/main/java/com/cherrish/android/data/di/DataSourceModule.ktapp/src/main/java/com/cherrish/android/data/di/RepositoryModule.ktapp/src/main/java/com/cherrish/android/data/di/ServiceModule.ktapp/src/main/java/com/cherrish/android/data/model/CalendarDailyResponseModel.ktapp/src/main/java/com/cherrish/android/data/model/CalendarDownTimeResponseModel.ktapp/src/main/java/com/cherrish/android/data/model/CalendarMonthlyRequestModel.ktapp/src/main/java/com/cherrish/android/data/model/CalendarMonthlyResponseModel.ktapp/src/main/java/com/cherrish/android/data/remote/datasource/CalendarDataSource.ktapp/src/main/java/com/cherrish/android/data/remote/datasourceimpl/CalendarDataSourceImpl.ktapp/src/main/java/com/cherrish/android/data/remote/dto/request/.gitkeepapp/src/main/java/com/cherrish/android/data/remote/dto/request/CalendarDailyRequestDto.ktapp/src/main/java/com/cherrish/android/data/remote/dto/request/CalendarMonthlyRequestDto.ktapp/src/main/java/com/cherrish/android/data/remote/dto/response/CalendarDailyResponseDto.ktapp/src/main/java/com/cherrish/android/data/remote/dto/response/CalendarDownTimeResponseDto.ktapp/src/main/java/com/cherrish/android/data/remote/dto/response/CalendarMonthlyResponseDto.ktapp/src/main/java/com/cherrish/android/data/remote/service/CalendarService.ktapp/src/main/java/com/cherrish/android/data/repository/CalendarRepository.ktapp/src/main/java/com/cherrish/android/data/repositoryimpl/CalendarRepositoryImpl.ktapp/src/main/java/com/cherrish/android/presentation/calendar/CalendarUiState.ktapp/src/main/java/com/cherrish/android/presentation/calendar/CalendarViewModel.ktapp/src/main/java/com/cherrish/android/presentation/calendar/component/ProcedureInfoItem.ktapp/src/main/java/com/cherrish/android/presentation/calendar/model/ProcedureInfoModel.ktapp/src/main/java/com/cherrish/android/presentation/calendar/procedure/component/CautionDescription.ktapp/src/main/java/com/cherrish/android/presentation/calendar/procedure/component/ProcedureTitleSection.ktapp/src/main/java/com/cherrish/android/presentation/calendar/util/CalendarCalculator.ktapp/src/main/java/com/cherrish/android/presentation/calendar/util/ProcedureUtil.kt
🧰 Additional context used
📓 Path-based instructions (1)
**/*.kt
⚙️ CodeRabbit configuration file
**/*.kt: - Jetpack Compose 구조, 상태 관리, recomposition 최적화에 집중
- ViewModel, UiState, 단방향 데이터 흐름 패턴 검토
- 불필요한 recomposition 가능성, remember/derivedStateOf 적절한 사용 확인
- 네이밍 컨벤션, 가독성, Google 권장 Android 아키텍처 준수 여부
Files:
app/src/main/java/com/cherrish/android/core/network/BaseResponse.ktapp/src/main/java/com/cherrish/android/data/di/ServiceModule.ktapp/src/main/java/com/cherrish/android/data/repository/CalendarRepository.ktapp/src/main/java/com/cherrish/android/data/model/CalendarMonthlyResponseModel.ktapp/src/main/java/com/cherrish/android/presentation/calendar/model/ProcedureInfoModel.ktapp/src/main/java/com/cherrish/android/data/model/CalendarDownTimeResponseModel.ktapp/src/main/java/com/cherrish/android/presentation/calendar/util/ProcedureUtil.ktapp/src/main/java/com/cherrish/android/data/repositoryimpl/CalendarRepositoryImpl.ktapp/src/main/java/com/cherrish/android/data/di/DataSourceModule.ktapp/src/main/java/com/cherrish/android/data/model/CalendarDailyResponseModel.ktapp/src/main/java/com/cherrish/android/presentation/calendar/procedure/component/ProcedureTitleSection.ktapp/src/main/java/com/cherrish/android/presentation/calendar/util/CalendarCalculator.ktapp/src/main/java/com/cherrish/android/data/di/RepositoryModule.ktapp/src/main/java/com/cherrish/android/data/remote/dto/request/CalendarDailyRequestDto.ktapp/src/main/java/com/cherrish/android/data/remote/dto/request/CalendarMonthlyRequestDto.ktapp/src/main/java/com/cherrish/android/data/remote/dto/response/CalendarDailyResponseDto.ktapp/src/main/java/com/cherrish/android/data/remote/dto/response/CalendarDownTimeResponseDto.ktapp/src/main/java/com/cherrish/android/presentation/calendar/CalendarViewModel.ktapp/src/main/java/com/cherrish/android/data/model/CalendarMonthlyRequestModel.ktapp/src/main/java/com/cherrish/android/presentation/calendar/procedure/component/CautionDescription.ktapp/src/main/java/com/cherrish/android/data/remote/dto/response/CalendarMonthlyResponseDto.ktapp/src/main/java/com/cherrish/android/data/remote/datasourceimpl/CalendarDataSourceImpl.ktapp/src/main/java/com/cherrish/android/data/remote/service/CalendarService.ktapp/src/main/java/com/cherrish/android/data/remote/datasource/CalendarDataSource.ktapp/src/main/java/com/cherrish/android/presentation/calendar/component/ProcedureInfoItem.ktapp/src/main/java/com/cherrish/android/presentation/calendar/CalendarUiState.kt
🧠 Learnings (1)
📚 Learning: 2026-01-12T19:49:27.085Z
Learnt from: nhyeonii
Repo: TEAM-Cherrish/Cherrish-Android PR: 41
File: app/src/main/java/com/cherrish/android/presentation/challenge/ChallengeScreen.kt:30-39
Timestamp: 2026-01-12T19:49:27.085Z
Learning: When a Jetpack Compose screen composable receives a Scaffold paddingValues: PaddingValues parameter (commonly from Scaffold), apply it to the root container's modifier first (e.g., .padding(paddingValues)) before applying any additional padding. This ensures content respects system bars (status/navigation) and avoids layout overlap. This pattern should be followed across presentation screens under app/src/main/java/com/cherrish/android/presentation/ to maintain consistent insets handling.
Applied to files:
app/src/main/java/com/cherrish/android/presentation/calendar/model/ProcedureInfoModel.ktapp/src/main/java/com/cherrish/android/presentation/calendar/util/ProcedureUtil.ktapp/src/main/java/com/cherrish/android/presentation/calendar/procedure/component/ProcedureTitleSection.ktapp/src/main/java/com/cherrish/android/presentation/calendar/util/CalendarCalculator.ktapp/src/main/java/com/cherrish/android/presentation/calendar/CalendarViewModel.ktapp/src/main/java/com/cherrish/android/presentation/calendar/procedure/component/CautionDescription.ktapp/src/main/java/com/cherrish/android/presentation/calendar/component/ProcedureInfoItem.ktapp/src/main/java/com/cherrish/android/presentation/calendar/CalendarUiState.kt
🧬 Code graph analysis (2)
app/src/main/java/com/cherrish/android/presentation/calendar/CalendarViewModel.kt (1)
app/src/main/java/com/cherrish/android/presentation/calendar/util/CalendarCalculator.kt (1)
formatProcedureDay(8-14)
app/src/main/java/com/cherrish/android/presentation/calendar/procedure/component/CautionDescription.kt (1)
app/src/main/java/com/cherrish/android/core/designsystem/theme/Theme.kt (1)
CherrishTheme(38-61)
⏰ 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). (2)
- GitHub Check: PR Build Check
- GitHub Check: PR Lint Check
🔇 Additional comments (21)
app/src/main/java/com/cherrish/android/presentation/calendar/util/CalendarCalculator.kt (1)
16-24: LGTM!
when표현식으로 모든DayOfWeek값을 exhaustive하게 처리하고 있어 안전합니다. 한글 요일명 매핑이 정확합니다.app/src/main/java/com/cherrish/android/presentation/calendar/procedure/component/CautionDescription.kt (1)
13-34: LGTM - 깔끔한 stateless 컴포넌트입니다.
modifier파라미터를 올바르게 노출하고 있고, 불필요한 상태 없이 간결하게 구현되었습니다.app/src/main/java/com/cherrish/android/presentation/calendar/procedure/component/ProcedureTitleSection.kt (1)
45-53: Preview 함수 잘 작성되었습니다.
CherrishTheme으로 감싸고 샘플 데이터를 사용한 Preview가 적절합니다.app/src/main/java/com/cherrish/android/presentation/calendar/component/ProcedureInfoItem.kt (1)
53-59: 클릭 가능 여부 조건 로직 LGTM다운타임이 없을 때(0) 클릭을 비활성화하는 로직이 적절합니다.
Modifier.then()을 사용한 조건부 modifier 적용 방식도 올바릅니다.app/src/main/java/com/cherrish/android/data/remote/dto/request/CalendarMonthlyRequestDto.kt (1)
6-12: LGTM!DTO 구조가 깔끔하고 kotlinx.serialization 어노테이션이 올바르게 적용되었습니다.
app/src/main/java/com/cherrish/android/data/model/CalendarMonthlyRequestModel.kt (1)
5-13: LGTM!Model-DTO 분리와
toDto()확장 함수 패턴이 적절합니다. Clean Architecture 원칙을 잘 따르고 있습니다.app/src/main/java/com/cherrish/android/data/remote/dto/response/CalendarMonthlyResponseDto.kt (1)
6-10: 이 코드는 표준 동작이며 문제가 없습니다kotlinx.serialization은
Map<Int, Long>을 기본적으로 지원하며, JSON의 문자열 키를 자동으로 Int로 변환합니다. 예를 들어 서버가{"1": 100, "15": 200}형식으로 응답하면 자동으로 올바르게 역직렬화됩니다. 커스텀 시리얼라이저나 추가 설정이 필요하지 않으며, 현재 구현은 kotlinx.serialization의 표준 패턴을 따르고 있습니다.app/src/main/java/com/cherrish/android/data/model/CalendarMonthlyResponseModel.kt (1)
5-11: LGTM!DTO에서 모델로의 매핑이 깔끔하고 직관적입니다. 확장 함수를 사용한 변환 패턴이 적절합니다.
app/src/main/java/com/cherrish/android/data/remote/dto/response/CalendarDailyResponseDto.kt (1)
6-28: 필드 null 가능성 확인 필요모든 필드가 non-null로 선언되어 있습니다. 서버에서 특정 필드가 null이나 누락된 값을 반환하는 경우 역직렬화 오류가 발생할 수 있습니다. API 명세서를 확인하여 optional 필드가 있는지 검토해 주세요.
app/src/main/java/com/cherrish/android/data/remote/dto/response/CalendarDownTimeResponseDto.kt (1)
6-20: LGTM!DTO 구조가 명확하고 직렬화 설정이 적절합니다.
app/src/main/java/com/cherrish/android/data/remote/datasource/CalendarDataSource.kt (1)
8-12: LGTM!DataSource 인터페이스가 깔끔하게 정의되어 있으며, CalendarService와 일관된 메서드 시그니처를 제공합니다. 표준적인 데이터 레이어 패턴을 잘 따르고 있습니다.
app/src/main/java/com/cherrish/android/data/di/ServiceModule.kt (1)
14-18: LGTM!CalendarService를 Singleton 스코프로 제공하는 표준적인 Hilt 모듈 설정입니다. Retrofit 서비스 생성 패턴이 올바르게 적용되었습니다.
app/src/main/java/com/cherrish/android/data/remote/datasourceimpl/CalendarDataSourceImpl.kt (1)
11-29: LGTM!CalendarDataSource 인터페이스를 올바르게 구현하고 있으며, CalendarService로의 위임 패턴이 깔끔하게 적용되었습니다.
@Inject생성자를 통한 의존성 주입이 적절하게 설정되어 있습니다.app/src/main/java/com/cherrish/android/data/di/DataSourceModule.kt (1)
14-18: LGTM!
@Binds를 사용한 인터페이스-구현체 바인딩이 올바르게 설정되어 있습니다. Singleton 스코프와 함께 표준적인 Hilt 모듈 패턴을 따르고 있습니다.app/src/main/java/com/cherrish/android/data/model/CalendarDailyResponseModel.kt (1)
1-32: LGTM! DTO에서 모델로의 매핑이 깔끔합니다.확장 함수를 통한 DTO-Model 변환 패턴이 적절하며, 코드가 간결하고 읽기 쉽습니다.
app/src/main/java/com/cherrish/android/presentation/calendar/model/ProcedureInfoModel.kt (1)
5-11: LGTM! Non-nullable 타입으로의 변경이 적절합니다.
downTimeDuration을Int?에서Int로 변경한 것은 0을 "다운타임 없음"으로 표현하는 명확한 의미를 부여합니다.@Immutable어노테이션도 Compose recomposition 최적화에 적합합니다.app/src/main/java/com/cherrish/android/presentation/calendar/util/ProcedureUtil.kt (1)
47-66: LGTM! Null 체크에서 0 체크로의 변경이 일관성 있게 적용되었습니다.
ProcedureInfoModel의downTimeDuration타입 변경과 일관되게 로직이 업데이트되었습니다.다만,
downTimeDuration이 음수일 경우 현재 로직에서는ACTIVE로 처리됩니다. API에서 음수 값이 반환될 가능성이 없는지 확인해 주세요. 필요시 방어적 코드를 추가할 수 있습니다:if (downTimeDuration <= 0) { ProcedureType.ACTIVE_NO_DOWNTIME }app/src/main/java/com/cherrish/android/data/di/RepositoryModule.kt (1)
11-19: LGTM!Hilt DI 바인딩이 표준 패턴을 따르며
@Binds와@Singleton스코프가 적절하게 사용되었습니다. CalendarRepository 바인딩으로의 전환이 깔끔합니다.app/src/main/java/com/cherrish/android/presentation/calendar/CalendarViewModel.kt (3)
25-34: LGTM!ViewModel 초기화 패턴이 적절합니다.
UiState.Loading으로 초기 상태 설정 후init블록에서 데이터 로드를 시작하는 것이 올바른 패턴입니다.
69-77: LGTM!월 변경 로직이 적절합니다. 현재 월이면 오늘 날짜를, 다른 월이면 1일을 선택하는 것은 좋은 UX입니다.
167-177: 날짜가 여러 상태 목록에 중복될 경우 마지막 상태만 적용됩니다.
buildMap에서put을 순차적으로 호출하므로, 동일 날짜가sensitiveDays,cautionDays,recoveryDays에 모두 포함되면recoveryDays의 상태가 최종 적용됩니다. 이것이 의도된 동작인지 확인이 필요합니다.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In
`@app/src/main/java/com/cherrish/android/presentation/calendar/CalendarViewModel.kt`:
- Around line 79-94: The updateSuccess block in onEventClick reads currentState
and calls loadDowntimeDetail(...) inside that block, causing a race because
loadDowntimeDetail launches coroutines and updates state asynchronously; to fix,
read the current mode from _uiState.value (or capture currentState before
mutating), then perform the synchronous state transition with
_uiState.updateSuccess only when switching CalendarDisplayMode.Downtime ->
CalendarDisplayMode.Normal (using CalendarDisplayMode.Downtime and
CalendarDisplayMode.Normal symbols), and call loadDowntimeDetail(procedureId)
outside of the updateSuccess block when you need to load details so the async
load is not invoked from inside updateSuccess and cannot race with the immediate
state return.
♻️ Duplicate comments (1)
app/src/main/java/com/cherrish/android/presentation/calendar/CalendarViewModel.kt (1)
36-67:updateSuccess내부에서 코루틴을 실행하는 패턴에 대한 기존 리뷰 참조이 race condition 이슈는 이전 리뷰에서 이미 지적되었습니다. 기존 제안대로 현재 상태를 먼저 읽고 조건 분기 후 코루틴을 외부에서 실행하는 패턴으로 수정이 필요합니다.
🧹 Nitpick comments (1)
app/src/main/java/com/cherrish/android/presentation/calendar/CalendarViewModel.kt (1)
140-159: 에러 처리 로직이 비어있습니다.API 호출 실패 시 사용자에게 피드백이 없습니다. 최소한 토스트 메시지나 스낵바를 통해 에러를 알리는 것을 고려해주세요.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
app/src/main/java/com/cherrish/android/presentation/calendar/CalendarViewModel.kt
🧰 Additional context used
📓 Path-based instructions (1)
**/*.kt
⚙️ CodeRabbit configuration file
**/*.kt: - Jetpack Compose 구조, 상태 관리, recomposition 최적화에 집중
- ViewModel, UiState, 단방향 데이터 흐름 패턴 검토
- 불필요한 recomposition 가능성, remember/derivedStateOf 적절한 사용 확인
- 네이밍 컨벤션, 가독성, Google 권장 Android 아키텍처 준수 여부
Files:
app/src/main/java/com/cherrish/android/presentation/calendar/CalendarViewModel.kt
🧠 Learnings (1)
📚 Learning: 2026-01-12T19:49:27.085Z
Learnt from: nhyeonii
Repo: TEAM-Cherrish/Cherrish-Android PR: 41
File: app/src/main/java/com/cherrish/android/presentation/challenge/ChallengeScreen.kt:30-39
Timestamp: 2026-01-12T19:49:27.085Z
Learning: When a Jetpack Compose screen composable receives a Scaffold paddingValues: PaddingValues parameter (commonly from Scaffold), apply it to the root container's modifier first (e.g., .padding(paddingValues)) before applying any additional padding. This ensures content respects system bars (status/navigation) and avoids layout overlap. This pattern should be followed across presentation screens under app/src/main/java/com/cherrish/android/presentation/ to maintain consistent insets handling.
Applied to files:
app/src/main/java/com/cherrish/android/presentation/calendar/CalendarViewModel.kt
🧬 Code graph analysis (1)
app/src/main/java/com/cherrish/android/presentation/calendar/CalendarViewModel.kt (1)
app/src/main/java/com/cherrish/android/presentation/calendar/util/CalendarCalculator.kt (1)
formatProcedureDay(8-14)
🔇 Additional comments (3)
app/src/main/java/com/cherrish/android/presentation/calendar/CalendarViewModel.kt (3)
25-34: LGTM!Hilt 의존성 주입과 초기 상태 설정이 적절합니다.
init블록에서 현재 월의 캘린더를 로드하는 패턴이 올바릅니다.
69-77: LGTM!현재 월인 경우 오늘 날짜를, 그 외에는 해당 월의 첫 날을 선택하는 로직이 적절합니다.
161-186: 날짜 중복 시 덮어쓰기 동작 확인이 필요합니다.
downtimeByDate맵 구성 시 동일한 날짜가sensitiveDays,cautionDays,recoveryDays에 중복으로 존재하면 마지막에 처리된 상태(RECOVERY)로 덮어씌워집니다. 이것이 의도된 동작인지 확인이 필요합니다.만약 의도된 우선순위라면 코드에 주석을 추가하여 명시하는 것을 권장합니다.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In
`@app/src/main/java/com/cherrish/android/presentation/calendar/component/ProcedureScheduleCard.kt`:
- Line 27: Remove the unused import painterResource from the top of the file;
locate the ProcedureScheduleCard composable (or its surrounding imports) and
delete the line "import androidx.compose.ui.res.painterResource" so only used
imports remain.
🧹 Nitpick comments (1)
app/src/main/java/com/cherrish/android/presentation/calendar/component/ProcedureScheduleCard.kt (1)
49-51: LGTM -isDowntimeMode도입 적절함
displayMode에서 다운타임 모드 여부를 추출하여 하위 컴포넌트에 전달하는 방식이 적절합니다. 파라미터 기반의 간단한 타입 체크이므로remember가 불필요하며, 현재 구현이 올바릅니다.선택적으로, 선언 전후의 빈 줄을 하나로 줄이면 코드가 더 깔끔해집니다.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
app/src/main/java/com/cherrish/android/presentation/calendar/component/ProcedureScheduleCard.kt
🧰 Additional context used
📓 Path-based instructions (1)
**/*.kt
⚙️ CodeRabbit configuration file
**/*.kt: - Jetpack Compose 구조, 상태 관리, recomposition 최적화에 집중
- ViewModel, UiState, 단방향 데이터 흐름 패턴 검토
- 불필요한 recomposition 가능성, remember/derivedStateOf 적절한 사용 확인
- 네이밍 컨벤션, 가독성, Google 권장 Android 아키텍처 준수 여부
Files:
app/src/main/java/com/cherrish/android/presentation/calendar/component/ProcedureScheduleCard.kt
🧠 Learnings (1)
📚 Learning: 2026-01-12T19:49:27.085Z
Learnt from: nhyeonii
Repo: TEAM-Cherrish/Cherrish-Android PR: 41
File: app/src/main/java/com/cherrish/android/presentation/challenge/ChallengeScreen.kt:30-39
Timestamp: 2026-01-12T19:49:27.085Z
Learning: When a Jetpack Compose screen composable receives a Scaffold paddingValues: PaddingValues parameter (commonly from Scaffold), apply it to the root container's modifier first (e.g., .padding(paddingValues)) before applying any additional padding. This ensures content respects system bars (status/navigation) and avoids layout overlap. This pattern should be followed across presentation screens under app/src/main/java/com/cherrish/android/presentation/ to maintain consistent insets handling.
Applied to files:
app/src/main/java/com/cherrish/android/presentation/calendar/component/ProcedureScheduleCard.kt
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: PR Lint Check
- GitHub Check: PR Build Check
🔇 Additional comments (1)
app/src/main/java/com/cherrish/android/presentation/calendar/component/ProcedureScheduleCard.kt (1)
113-124: LGTM - 하위 컴포넌트로 모드 전달 적절함
isDowntimeMode플래그를ProcedureInfoItem에 전달하여 다운타임 모드에 따른 렌더링 분기를 가능하게 합니다. Boolean은 stable 타입이므로 불필요한 recomposition을 유발하지 않습니다.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In
`@app/src/main/java/com/cherrish/android/presentation/calendar/CalendarViewModel.kt`:
- Around line 108-125: The cached month branch keeps cached.procedureInfoList
even when cached.selectedDate != selectedDate causing the previous day's
procedures to flash; update the _uiState.update call (inside the monthlyCache
check) to set procedureInfoList to an empty list or a loading placeholder when
cached.selectedDate != selectedDate (e.g., in the
UiState.Success(cached.copy(...)) use conditional: if (cached.selectedDate ==
selectedDate) cached.procedureInfoList else emptyList() or a loading state) and
ensure loadDailyCalendar(selectedDate) is still invoked to fetch the real list.
♻️ Duplicate comments (3)
app/src/main/java/com/cherrish/android/presentation/calendar/CalendarViewModel.kt (3)
38-73:updateSuccess내부에서 코루틴 실행은 상태 레이스 위험
updateSuccess는 즉시currentState를 반환하는데, 내부 코루틴이 나중에 상태를 다시 갱신하면서 직전 변경을 덮거나 순서가 꼬일 수 있습니다. 상태 스냅샷을 먼저 읽고 비동기 로딩은 블록 밖에서 실행하도록 분리해 주세요.🛠️ 수정 제안
fun onDateClick(date: LocalDate) { - _uiState.updateSuccess { currentState -> - if (currentState.calendarDisplayMode is CalendarDisplayMode.Downtime) { - viewModelScope.launch { - calendarRepository.getCalendarDaily( - date = date.toString() - ).onSuccess { response -> - val procedureList = response.events.map { event -> - ProcedureInfoModel( - procedureId = event.userProcedureId, - procedureName = event.name, - procedureDay = formatProcedureDay(event.scheduledAt), - downTimeDuration = event.downtimeDays - ) - }.toImmutableList() - - _uiState.updateSuccess { state -> - val newState = state.copy( - calendarDisplayMode = CalendarDisplayMode.Normal( - procedureCountByDate = state.cachedProcedureCountByDate - ), - selectedDate = date, - procedureInfoList = procedureList - ) - - monthlyCache[newState.selectedYearMonth] = newState - newState - } - }.onLogFailure { } - } - currentState - } else { - loadDailyCalendar(date) - currentState.copy(selectedDate = date) - } - } + val currentState = (_uiState.value as? UiState.Success)?.data ?: return + if (currentState.calendarDisplayMode is CalendarDisplayMode.Downtime) { + viewModelScope.launch { + calendarRepository.getCalendarDaily(date = date.toString()) + .onSuccess { response -> + val procedureList = response.events.map { event -> + ProcedureInfoModel( + procedureId = event.userProcedureId, + procedureName = event.name, + procedureDay = formatProcedureDay(event.scheduledAt), + downTimeDuration = event.downtimeDays + ) + }.toImmutableList() + + _uiState.updateSuccess { state -> + val newState = state.copy( + calendarDisplayMode = CalendarDisplayMode.Normal( + procedureCountByDate = state.cachedProcedureCountByDate + ), + selectedDate = date, + procedureInfoList = procedureList + ) + monthlyCache[newState.selectedYearMonth] = newState + newState + } + }.onLogFailure { } + } + } else { + _uiState.updateSuccess { it.copy(selectedDate = date) } + loadDailyCalendar(date) + } }
86-101:onEventClick도 동일한 비동기 레이스 패턴입니다.
loadDowntimeDetail가 코루틴을 시작하는데updateSuccess안에서 호출되어 상태 갱신 순서가 비결정적입니다. 스냅샷을 밖에서 가져와 분기하고, 모드 전환만updateSuccess로 처리하는 편이 안전합니다.🛠️ 수정 제안
fun onEventClick(procedureId: Long) { - _uiState.updateSuccess { currentState -> - val currentMode = currentState.calendarDisplayMode - - if (currentMode is CalendarDisplayMode.Downtime) { - currentState.copy( - calendarDisplayMode = CalendarDisplayMode.Normal( - procedureCountByDate = currentState.cachedProcedureCountByDate - ) - ) - } else { - loadDowntimeDetail(procedureId) - currentState - } - } + val currentState = (_uiState.value as? UiState.Success)?.data ?: return + val currentMode = currentState.calendarDisplayMode + + if (currentMode is CalendarDisplayMode.Downtime) { + _uiState.updateSuccess { + it.copy( + calendarDisplayMode = CalendarDisplayMode.Normal( + procedureCountByDate = it.cachedProcedureCountByDate + ) + ) + } + } else { + loadDowntimeDetail(procedureId) + } }
129-165: 월/일 동시 로드 실패 시 대기 코루틴이 남고 로딩이 해제되지 않습니다.
월별 호출이 실패하면dailyDeferred가 취소되지 않고, 일별 호출 실패 시에도UiState가 갱신되지 않아 로딩이 지속될 수 있습니다. 실패 경로에서 취소 및 에러 상태 업데이트를 추가해 주세요.🛠️ 수정 제안
viewModelScope.launch { val dailyDeferred = async { calendarRepository.getCalendarDaily(date = selectedDate.toString()) } calendarRepository.getCalendarMonthly( year = yearMonth.year, month = yearMonth.monthValue ).onSuccess { response -> val procedureCountByDate = response.dailyProcedureCounts.mapKeys { (day, _) -> yearMonth.atDay(day) }.mapValues { it.value.toInt() } dailyDeferred.await().onSuccess { dailyResponse -> val procedureList = dailyResponse.events.map { event -> ProcedureInfoModel( procedureId = event.userProcedureId, procedureName = event.name, procedureDay = formatProcedureDay(event.scheduledAt), downTimeDuration = event.downtimeDays ) }.toImmutableList() val newState = CalendarUiState( selectedYearMonth = yearMonth, calendarDisplayMode = CalendarDisplayMode.Normal(procedureCountByDate), selectedDate = selectedDate, procedureInfoList = procedureList, cachedProcedureCountByDate = procedureCountByDate ) monthlyCache[yearMonth] = newState _uiState.update { UiState.Success(newState) } - } - }.onLogFailure { } + }.onLogFailure { + _uiState.update { UiState.Error(it) } // 프로젝트의 에러 상태에 맞게 조정 + } + }.onLogFailure { + dailyDeferred.cancel() + _uiState.update { UiState.Error(it) } // 프로젝트의 에러 상태에 맞게 조정 + } }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
app/src/main/java/com/cherrish/android/presentation/calendar/CalendarViewModel.kt
🧰 Additional context used
📓 Path-based instructions (1)
**/*.kt
⚙️ CodeRabbit configuration file
**/*.kt: - Jetpack Compose 구조, 상태 관리, recomposition 최적화에 집중
- ViewModel, UiState, 단방향 데이터 흐름 패턴 검토
- 불필요한 recomposition 가능성, remember/derivedStateOf 적절한 사용 확인
- 네이밍 컨벤션, 가독성, Google 권장 Android 아키텍처 준수 여부
Files:
app/src/main/java/com/cherrish/android/presentation/calendar/CalendarViewModel.kt
🧠 Learnings (1)
📚 Learning: 2026-01-12T19:49:27.085Z
Learnt from: nhyeonii
Repo: TEAM-Cherrish/Cherrish-Android PR: 41
File: app/src/main/java/com/cherrish/android/presentation/challenge/ChallengeScreen.kt:30-39
Timestamp: 2026-01-12T19:49:27.085Z
Learning: When a Jetpack Compose screen composable receives a Scaffold paddingValues: PaddingValues parameter (commonly from Scaffold), apply it to the root container's modifier first (e.g., .padding(paddingValues)) before applying any additional padding. This ensures content respects system bars (status/navigation) and avoids layout overlap. This pattern should be followed across presentation screens under app/src/main/java/com/cherrish/android/presentation/ to maintain consistent insets handling.
Applied to files:
app/src/main/java/com/cherrish/android/presentation/calendar/CalendarViewModel.kt
🧬 Code graph analysis (1)
app/src/main/java/com/cherrish/android/presentation/calendar/CalendarViewModel.kt (1)
app/src/main/java/com/cherrish/android/presentation/calendar/util/CalendarCalculator.kt (1)
formatProcedureDay(8-14)
⏰ 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). (2)
- GitHub Check: PR Lint Check
- GitHub Check: PR Build Check
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In
`@app/src/main/java/com/cherrish/android/presentation/calendar/CalendarViewModel.kt`:
- Line 208: 현재 CalendarViewModel의 API 호출 체인 끝에 있는 .onLogFailure { }가 비어 있어 실패 시
아무런 사용자 피드백을 주지 않습니다; onLogFailure 블록 안에서 CalendarViewModel의 상태를 업데이트하거나 에러 이벤트를
발행하도록 변경하세요 — 예를 들어 calendarUiState 또는 _uiState를 변경해 isError/message 필드를 설정하거나
emit(OneShotEvent.ShowToast(errorMessage)) 식의 단발성 이벤트(또는 LiveData/SharedFlow)를
발행하도록 구현하고, 오류 메시지는 Throwable 또는 응답의 에러 메시지에서 추출해 전달하세요 (참조: CalendarViewModel,
.onLogFailure).
♻️ Duplicate comments (3)
app/src/main/java/com/cherrish/android/presentation/calendar/CalendarViewModel.kt (3)
41-76: 이전 리뷰에서 지적된 race condition 패턴이 여전히 존재합니다.
updateSuccess블록 내부에서viewModelScope.launch를 호출하고currentState를 즉시 반환하는 패턴은 동시성 문제를 유발합니다. 이전 리뷰의 제안대로 현재 상태를 먼저 읽고 조건에 따라 로직을 분기하는 패턴으로 수정이 필요합니다.
88-103: 이전 리뷰에서 지적된 race condition 패턴이 여전히 존재합니다.
loadDowntimeDetail(procedureId)가updateSuccess블록 내부에서 호출되고currentState가 즉시 반환되어 동시성 문제가 발생할 수 있습니다. 이전 리뷰의 제안대로 수정이 필요합니다.
132-173: 에러 처리 개선이 필요합니다 (이전 리뷰 참조).이전 리뷰에서 지적된 대로:
getCalendarMonthly실패 시dailyDeferred가 대기 중인 상태로 남습니다.getCalendarDaily실패 시 UI가 Loading 상태로 유지됩니다..onLogFailure { }블록이 비어있어 사용자에게 에러 피드백이 없습니다.
🧹 Nitpick comments (2)
app/src/main/java/com/cherrish/android/presentation/calendar/CalendarViewModel.kt (2)
34-35: 캐시에 대한 스레드 안전성 문제가 있습니다.
mutableMapOf는 스레드 안전하지 않습니다. 여러 코루틴에서 동시에 읽기/쓰기가 발생할 수 있어ConcurrentModificationException또는 데이터 손상이 발생할 수 있습니다.♻️ ConcurrentHashMap 사용 권장
- private val monthlyCache = mutableMapOf<YearMonth, Map<LocalDate, Int>>() - private val dailyCache = mutableMapOf<LocalDate, ImmutableList<ProcedureInfoModel>>() + private val monthlyCache = java.util.concurrent.ConcurrentHashMap<YearMonth, Map<LocalDate, Int>>() + private val dailyCache = java.util.concurrent.ConcurrentHashMap<LocalDate, ImmutableList<ProcedureInfoModel>>()
48-55:ProcedureInfoModel매핑 로직이 중복됩니다.동일한 매핑 로직이
onDateClick,loadMonthlyCalendarWithDate,loadDailyCalendar에서 반복됩니다. 헬퍼 함수로 추출하면 유지보수성이 향상됩니다.♻️ 매핑 헬퍼 함수 추출 제안
private fun CalendarDailyResponseModel.Event.toProcedureInfoModel() = ProcedureInfoModel( procedureId = userProcedureId, procedureName = name, procedureDay = formatProcedureDay(scheduledAt), downTimeDuration = downtimeDays ) // 사용 예시 response.events.map { it.toProcedureInfoModel() }.toImmutableList()
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
app/src/main/java/com/cherrish/android/presentation/calendar/CalendarViewModel.kt
🧰 Additional context used
📓 Path-based instructions (1)
**/*.kt
⚙️ CodeRabbit configuration file
**/*.kt: - Jetpack Compose 구조, 상태 관리, recomposition 최적화에 집중
- ViewModel, UiState, 단방향 데이터 흐름 패턴 검토
- 불필요한 recomposition 가능성, remember/derivedStateOf 적절한 사용 확인
- 네이밍 컨벤션, 가독성, Google 권장 Android 아키텍처 준수 여부
Files:
app/src/main/java/com/cherrish/android/presentation/calendar/CalendarViewModel.kt
🧠 Learnings (1)
📚 Learning: 2026-01-12T19:49:27.085Z
Learnt from: nhyeonii
Repo: TEAM-Cherrish/Cherrish-Android PR: 41
File: app/src/main/java/com/cherrish/android/presentation/challenge/ChallengeScreen.kt:30-39
Timestamp: 2026-01-12T19:49:27.085Z
Learning: When a Jetpack Compose screen composable receives a Scaffold paddingValues: PaddingValues parameter (commonly from Scaffold), apply it to the root container's modifier first (e.g., .padding(paddingValues)) before applying any additional padding. This ensures content respects system bars (status/navigation) and avoids layout overlap. This pattern should be followed across presentation screens under app/src/main/java/com/cherrish/android/presentation/ to maintain consistent insets handling.
Applied to files:
app/src/main/java/com/cherrish/android/presentation/calendar/CalendarViewModel.kt
🧬 Code graph analysis (1)
app/src/main/java/com/cherrish/android/presentation/calendar/CalendarViewModel.kt (1)
app/src/main/java/com/cherrish/android/presentation/calendar/util/CalendarCalculator.kt (1)
formatProcedureDay(8-14)
⏰ 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). (2)
- GitHub Check: PR Build Check
- GitHub Check: PR Lint Check
🔇 Additional comments (2)
app/src/main/java/com/cherrish/android/presentation/calendar/CalendarViewModel.kt (2)
78-86: LGTM!현재 월이면 오늘 날짜를, 그렇지 않으면 해당 월의 첫날을 선택하는 로직이 명확하고 적절합니다.
216-226: 동일 날짜가 여러 리스트에 존재할 경우 덮어쓰기 동작을 확인해 주세요.
buildMap에서sensitiveDays→cautionDays→recoveryDays순서로put을 호출하므로, 동일 날짜가 여러 리스트에 포함된 경우 마지막으로 처리된 상태(RECOVERY)가 최종 값이 됩니다. 이 동작이 의도된 것인지 확인이 필요합니다.만약 우선순위가 다르게 적용되어야 한다면(예:
SENSITIVE>CAUTION>RECOVERY), 순서를 역순으로 변경하거나putIfAbsent를 사용하세요:♻️ 우선순위 보장을 위한 수정 예시
val downtimeByDate = buildMap<LocalDate, DownTimeStatus> { - response.sensitiveDays.forEach { dateString -> - put(LocalDate.parse(dateString), DownTimeStatus.SENSITIVE) - } - response.cautionDays.forEach { dateString -> - put(LocalDate.parse(dateString), DownTimeStatus.CAUTION) - } response.recoveryDays.forEach { dateString -> put(LocalDate.parse(dateString), DownTimeStatus.RECOVERY) } + response.cautionDays.forEach { dateString -> + put(LocalDate.parse(dateString), DownTimeStatus.CAUTION) + } + response.sensitiveDays.forEach { dateString -> + put(LocalDate.parse(dateString), DownTimeStatus.SENSITIVE) + } }
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| return "${month}월 ${day}일 $dayOfWeek" | ||
| } | ||
|
|
||
| fun DayOfWeek.toKoreanName(): String = when (this) { |
There was a problem hiding this comment.
p3: 이거 공통으로 빼주시면 안되나여?!!
이거 다른 화면에서도 쓰여가지구요!!
| } | ||
|
|
||
| fun onDateClick(date: LocalDate) { | ||
| fun onMonthChanged(yearMonth: YearMonth) { |
There was a problem hiding this comment.
p3: (단순 질문) 그 click은 onxxClick으로 컨벤션 맞춰주자고 하셨는데 다른 경우에는 상관없는 건가요??
| year: Int, | ||
| month: Int | ||
| ): Result<CalendarMonthlyResponseModel> = | ||
| runCatching { |
There was a problem hiding this comment.
p1: 여기 suspendRunCahtching 쓰시려고 따로 만들어두신거 아니신가여??!
| } | ||
|
|
||
| private fun loadMonthlyCalendar(yearMonth: YearMonth) { | ||
| loadMonthlyCalendarWithDate(yearMonth, LocalDate.now()) |
There was a problem hiding this comment.
p3: 여기 loadMonthlyCalendarWithDate 함수 가독성을 위해 묶어주신거가요??
하나로 합쳐줘도 괜찮을 것 같다는 생각도 들긴 합니다..!ㅎㅎㅎ
There was a problem hiding this comment.
우앙 마자용 !! 근데 저도 지금 보니 합치는것두 괜찮을거 같네여 ~~
| procedureInfoList = if (cached.selectedDate == selectedDate) { | ||
| cached.procedureInfoList | ||
| } else { | ||
| cached.procedureInfoList | ||
| } |
There was a problem hiding this comment.
p1: 여기 if문 처리안하나 하나 똑같은 동작인거 아닌가여??
|
|
||
| private fun loadMonthlyCalendarWithDate(yearMonth: YearMonth, selectedDate: LocalDate) { | ||
| val cached = monthlyCache[yearMonth] | ||
| if (cached != null) { |
There was a problem hiding this comment.
p3: 여기 cached != null로만 분기처리해도 괜찮나여??
| getProcedureColors(procedureType, themeColors) | ||
| } | ||
|
|
||
| val isClickable = isDowntimeMode || downTimeDuration != 0 |
There was a problem hiding this comment.
P99: 규일이가 말햇던거처럼 매직 넘버 제거 어떠세욤 ㅎㅎ
private const val NO_DOWNTIME = 0
There was a problem hiding this comment.
지금 우리가 이런거 매직 넘버를 다 설정 안해쥬는 쪽이라 얘만 NO_DOWNTIME 상수를 추가하면 맥락을 한 번 더 따라가야해서 현재는 0을 직접 사용하는 쪽이 더 직관적이라고 생각합니다요잉 ~~ 어떠케 생각하세여
| month: Int | ||
| ): Result<CalendarMonthlyResponseModel> = | ||
| suspendRunCatching { | ||
| calendarDataSource.getCalendarMonthly(year = year, month = month).data!!.toModel() |
There was a problem hiding this comment.
P3: (단순 궁금) data!! 사용은 NullPointerException 위험이 있다는데 괜찮은가용~/!?!?
There was a problem hiding this comment.
data!! 사용 시 NPE 가능성은 잇지용 ~~ 다만 앱 크래시는 안 나고, 에러 흐름은 통제할 수 잇어 이런식으로 사용했엉요 ! 해당 API는 스펙상 성공 응답일 경우 data가 항상 내려오도록 보장되어 있고 null인 경우는 서버 계약 위반 또는 비정상 응답으로 판단하고 있답니닿ㅎㅎㅎ 그래서 suspendRunCatching을 통해 실패로 감싸 상위 레이어에서 공통 에러 처리하도록 의도햇어요 !! 그냥 runCatching이 아니라 suspendRunCatching util 함수를 사용하는 이유랍니도 !

Related issue 🛠
Work Description ✏️
Screenshot 📸
Screen_Recording_20260115_212520_Cherrish.mp4
Uncompleted Tasks 😅
To Reviewers 📢
캘린더 끝이다 우하하
Summary by CodeRabbit
새로운 기능
개선사항
주의
호환성
✏️ Tip: You can customize this high-level summary in your review settings.