Repository navigation
Conversation
Walkthrough원격 API부터 DI, 데이터/도메인 레이어, ViewModel 및 UI까지 캘린더 날짜 조회 기능이 추가되었습니다. 원격 호출로 년/월 단위 날짜 목록을 받아 로컬에서 파싱·캐싱하고 UI에 반영하도록 흐름을 연결합니다. Changes
Sequence DiagramsequenceDiagram
participant User
participant CalendarScreen
participant CalendarViewModel
participant GetCalendarContentDatesUseCase
participant CalendarRepository
participant CalendarApiService
participant Backend
User->>CalendarScreen: 년/월 선택
CalendarScreen->>CalendarViewModel: selectYearMonth(yearMonth)
CalendarViewModel->>CalendarViewModel: ensureMonthContentDatesCached(yearMonth)
CalendarViewModel->>GetCalendarContentDatesUseCase: invoke(year, month)
GetCalendarContentDatesUseCase->>CalendarRepository: getTimeCapsuleOrDailyReportExistDate(year, month)
CalendarRepository->>CalendarApiService: getTimeCapsuleOrDailyReportExistDate(year, month)
CalendarApiService->>Backend: GET /api/v1/calendar/date?year={year}&month={month}
Backend-->>CalendarApiService: CalendarDatesResponse
CalendarApiService-->>CalendarRepository: ResponseDto<CalendarDatesResponse>
CalendarRepository->>CalendarRepository: dates -> LocalDate 파싱
CalendarRepository-->>GetCalendarContentDatesUseCase: Set<LocalDate>
GetCalendarContentDatesUseCase-->>CalendarViewModel: Set<LocalDate>
CalendarViewModel->>CalendarViewModel: 캐시에 저장 및 state 업데이트
CalendarViewModel-->>CalendarScreen: state 반영
CalendarScreen->>User: 캘린더 렌더링 (타임캡슐 + 콘텐츠 날짜)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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: 4
🧹 Nitpick comments (1)
feat/time-capsule/src/main/java/com/emotionstorage/time_capsule/presentation/CalendarViewModel.kt (1)
253-263: 일일리포트 조회가 동일 선택에서 중복 호출될 수 있습니다.
TimeCapsulesLoaded처리에서 Line [254], Line [262]의setDailyReportState(date)가 추가되면서, 같은 날짜 선택 흐름에서 중복 조회가 발생할 수 있습니다. 조회 책임을 한 경로로 정리하면 불필요한 네트워크 호출과 로그 노이즈를 줄일 수 있습니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@feat/time-capsule/src/main/java/com/emotionstorage/time_capsule/presentation/CalendarViewModel.kt` around lines 253 - 263, The TimeCapsulesLoaded handler calls setDailyReportState(date) from two paths causing duplicate fetches; consolidate so setDailyReportState(date) is invoked only once per selection—either remove the second call inside the hasDailyReport branch or add a guard before calling (e.g., check isDailyReportChecked or an isFetching flag) so setDailyReportState(date) is executed only when not already requested; update the TimeCapsulesLoaded flow around the itemCount/hasDailyReport logic and the reduce { state.copy(isDailyReportChecked = true) } usage to ensure a single responsibility for triggering setDailyReportState.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@core/remote/src/main/java/com/emotionstorage/remote/dataSourceImpl/CalendarRemoteDataSourceImpl.kt`:
- Around line 14-15: The method calling
calendarApiService.getTimeCapsuleOrDailyReportExistDate currently returns
emptyList() when response.data is null which hides errors; change this to raise
an error so upstream can handle failures: in the method that calls
calendarApiService.getTimeCapsuleOrDailyReportExistDate (the function in
CalendarRemoteDataSourceImpl that uses response.data), check if response.data is
null and throw a descriptive exception (e.g., IllegalStateException or a domain
RemoteDataException) including context (API name and parameters) instead of
returning emptyList(), so consumers can distinguish "no dates" from
"invalid/failed response".
In
`@data/src/main/java/com/emotionstorage/data/repoImpl/CalendarRepositoryImpl.kt`:
- Line 17: 현재 CalendarRepositoryImpl의 날짜 파싱(날짜 리스트를 처리하는 부분: dates.mapNotNull {
runCatching { LocalDate.parse(it) }.getOrNull() }.toSet())에서 실패한 항목을 무시하고 있어 누락
원인 추적이 어렵습니다; 수정 방법은 해당 블록에서 runCatching의 onFailure를 사용하거나 파싱 결과를 분리하여 실패한 문자열들을
수집한 뒤(예: failedDates 리스트 또는 실패 카운터) CalendarRepositoryImpl의 로거 또는 모니터링 이벤트로 실패
개수와 실패한 원시 값(또는 샘플)을 기록하도록 변경하세요; 이렇게 하면 LocalDate.parse 호출의 실패 사례와 개수를 남겨
운영/디버깅 시 누락 원인 추적이 가능해집니다.
In
`@feat/time-capsule/src/main/java/com/emotionstorage/time_capsule/presentation/CalendarViewModel.kt`:
- Around line 282-291: 현재 getCalendarContentDates 호출 실패 시 runCatching.getOrElse
{ emptySet() }로 대체한 결과를 무조건 monthContentDatesCache에 저장하고 있어 일시적 오류로 영구적으로 빈 데이터가
캐시됩니다; 수정 방법은 실패 시 캐시에 쓰지 않고 성공했을 때만 캐시에 저장하도록 변경하는 것입니다 — 예를 들어 runCatching {
getCalendarContentDates(...) } 결과를 검사해 isSuccess일 때만 cacheMutex.withLock {
monthContentDatesCache[yearMonth] = result }로 갱신하거나 onSuccess 블록을 사용하여 성공 결과만
캐시에 넣도록 변경하세요; 관련 심볼: getCalendarContentDates, monthContentDatesCache,
cacheMutex, yearMonth.
In
`@feat/time-capsule/src/main/java/com/emotionstorage/time_capsule/ui/component/TimeCapsuleBottomSheet.kt`:
- Around line 71-74: Remove itemCount from the LaunchedEffect keys and introduce
a local hasReported flag to ensure the onTimeCapsulesLoaded callback runs only
once after initial load: change LaunchedEffect(date, isNotLoading, itemCount) to
LaunchedEffect(date, isNotLoading) and add a mutable hasReported = remember {
mutableStateOf(false) } (or rememberSaveable as needed); inside the effect, only
call onTimeCapsulesLoaded(date, itemCount) when isNotLoading &&
!hasReported.value, then set hasReported.value = true after invoking it; this
prevents repeated dispatches during paging while preserving the guard in
handleTimeCapsulesLoaded and keeping state.isDailyReportChecked logic intact.
---
Nitpick comments:
In
`@feat/time-capsule/src/main/java/com/emotionstorage/time_capsule/presentation/CalendarViewModel.kt`:
- Around line 253-263: The TimeCapsulesLoaded handler calls
setDailyReportState(date) from two paths causing duplicate fetches; consolidate
so setDailyReportState(date) is invoked only once per selection—either remove
the second call inside the hasDailyReport branch or add a guard before calling
(e.g., check isDailyReportChecked or an isFetching flag) so
setDailyReportState(date) is executed only when not already requested; update
the TimeCapsulesLoaded flow around the itemCount/hasDailyReport logic and the
reduce { state.copy(isDailyReportChecked = true) } usage to ensure a single
responsibility for triggering setDailyReportState.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (13)
core/remote/src/main/java/com/emotionstorage/remote/api/CalendarApiService.ktcore/remote/src/main/java/com/emotionstorage/remote/dataSourceImpl/CalendarRemoteDataSourceImpl.ktcore/remote/src/main/java/com/emotionstorage/remote/di/ApiServiceModule.ktcore/remote/src/main/java/com/emotionstorage/remote/di/RemoteDataSourceModule.ktcore/remote/src/main/java/com/emotionstorage/remote/response/calendar/CalendarDatesResponse.ktdata/src/main/java/com/emotionstorage/data/dataSource/remote/CalendarRemoteDataSource.ktdata/src/main/java/com/emotionstorage/data/di/RepositoryModule.ktdata/src/main/java/com/emotionstorage/data/repoImpl/CalendarRepositoryImpl.ktdomain/src/main/java/com/emotionstorage/domain/repo/CalendarRepository.ktdomain/src/main/java/com/emotionstorage/domain/useCase/calendar/GetCalendarContentDatesUseCase.ktfeat/time-capsule/src/main/java/com/emotionstorage/time_capsule/presentation/CalendarViewModel.ktfeat/time-capsule/src/main/java/com/emotionstorage/time_capsule/ui/CalendarScreen.ktfeat/time-capsule/src/main/java/com/emotionstorage/time_capsule/ui/component/TimeCapsuleBottomSheet.kt
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
feat/time-capsule/src/main/java/com/emotionstorage/time_capsule/presentation/CalendarViewModel.kt (2)
83-84: 월 캐시 무효화 경로가 없어 최신 상태 반영이 지연될 수 있어요Line 83-84의 캐시는 Line 279에서 한 번 적재되면 계속 재사용됩니다. 같은 달에서 타임캡슐 삭제/일일리포트 생성 후에도 이전 데이터가 남을 수 있으니, 최소한 월 재진입 시 강제 갱신 옵션을 두는 편이 안전합니다.
제안 수정안
- val contentDates = ensureMonthContentDatesCached(yearMonth) + val contentDates = ensureMonthContentDatesCached( + yearMonth = yearMonth, + forceRefresh = true, + )- private suspend fun ensureMonthContentDatesCached(yearMonth: YearMonth): Set<LocalDate> { + private suspend fun ensureMonthContentDatesCached( + yearMonth: YearMonth, + forceRefresh: Boolean = false, + ): Set<LocalDate> { Logger.d("ensureMonthContentDatesCached: $yearMonth") cacheMutex.withLock { - monthContentDatesCache[yearMonth]?.let { return it } + if (!forceRefresh) { + monthContentDatesCache[yearMonth]?.let { return it } + } }Also applies to: 164-165, 276-280
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@feat/time-capsule/src/main/java/com/emotionstorage/time_capsule/presentation/CalendarViewModel.kt` around lines 83 - 84, monthContentDatesCache is never invalidated so month data can become stale after creating/deleting items; update the code to invalidate or refresh the cache when state changes. Specifically: add a clear/invalidate method (e.g., invalidateMonthCache or invalidate(YearMonth)) that uses cacheMutex to synchronously remove the affected YearMonth entry from monthContentDatesCache, call it from mutation handlers (e.g., after time-capsule delete, after daily-report create, and on month re-entry/refresh paths), and optionally add a boolean forceRefresh parameter to the existing loader (the function that reads from monthContentDatesCache) to bypass cache when needed. Ensure all places that modify month content call this invalidator so the cache stays current.
195-216: 동일 날짜에서 일일리포트 조회가 중복 호출될 수 있습니다현재 Line 213에서
setDailyReportState(date)를 먼저 호출하고, 이후 Line 254/262에서도 다시 호출됩니다. 네트워크 중복 호출과 상태 깜빡임 가능성이 있어 조회 지점을 하나로 정리하는 게 좋습니다.제안 수정안
private fun handleSelectCalendarDate(date: LocalDate) = baseIntent { val canOpen = (date in state.calendarTimeCapsuleDates) || (date in state.calendarContentDates) if (!canOpen) return@baseIntent reduce { state.copy( calendarSelectedDate = date, timeCapsulesFlow = null, dailyReportId = null, isNewDailyReport = false, isDailyReportChecked = false, ) } setTimeCapsulesFlow(date) - setDailyReportState(date) postSideEffect(CalendarSideEffect.ShowTimeCapsuleBottomSheet) }private fun handleTimeCapsulesLoaded( date: LocalDate, itemCount: Int, ) = baseIntent { if (state.calendarSelectedDate != date) return@baseIntent if (state.isDailyReportChecked) return@baseIntent + reduce { state.copy(isDailyReportChecked = true) } if (itemCount > 0) { setDailyReportState(date) - reduce { state.copy(isDailyReportChecked = true) } } else { val ym = YearMonth.from(date) val monthDates = ensureMonthContentDatesCached(ym) val hasDailyReport = monthDates.contains(date) if (hasDailyReport) { setDailyReportState(date) } else { reduce { state.copy( dailyReportId = null, isNewDailyReport = false, ) } } - - reduce { state.copy(isDailyReportChecked = true) } } }Also applies to: 253-263
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@feat/time-capsule/src/main/java/com/emotionstorage/time_capsule/presentation/CalendarViewModel.kt` around lines 195 - 216, handleSelectCalendarDate currently calls setDailyReportState(date) but the same function is invoked again elsewhere causing duplicate network calls and UI flicker; consolidate the daily report fetch so it's invoked only once by removing the extra setDailyReportState calls from other call sites (or replace them with a no-op guard) and keep the single call inside handleSelectCalendarDate after reduce and setTimeCapsulesFlow; alternatively make setDailyReportState(idempotent by checking if state.calendarSelectedDate == date or isDailyReportChecked/isLoading is true and returning early) so duplicate invocations don't trigger extra network requests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@feat/time-capsule/src/main/java/com/emotionstorage/time_capsule/presentation/CalendarViewModel.kt`:
- Around line 282-290: The code currently converts a failure from
getCalendarContentDates into emptySet() (via fetchedResult.getOrNull() ?: return
emptySet()), which conflates an actual empty-month result with an error and
causes canOpen logic to incorrectly block access; instead detect and propagate
errors separately: change the handling around
runCatching/getCalendarContentDates so that failures are not mapped to
emptySet—e.g., check fetchedResult.isFailure (or use
fetchedResult.getOrThrow()/fold) and return or surface a distinct error/null
result for CalendarViewModel (symbols to update: getCalendarContentDates,
fetchedResult, fetched and the canOpen branch) so the canOpen logic can
differentiate an error from a true empty set and behave correctly.
---
Nitpick comments:
In
`@feat/time-capsule/src/main/java/com/emotionstorage/time_capsule/presentation/CalendarViewModel.kt`:
- Around line 83-84: monthContentDatesCache is never invalidated so month data
can become stale after creating/deleting items; update the code to invalidate or
refresh the cache when state changes. Specifically: add a clear/invalidate
method (e.g., invalidateMonthCache or invalidate(YearMonth)) that uses
cacheMutex to synchronously remove the affected YearMonth entry from
monthContentDatesCache, call it from mutation handlers (e.g., after time-capsule
delete, after daily-report create, and on month re-entry/refresh paths), and
optionally add a boolean forceRefresh parameter to the existing loader (the
function that reads from monthContentDatesCache) to bypass cache when needed.
Ensure all places that modify month content call this invalidator so the cache
stays current.
- Around line 195-216: handleSelectCalendarDate currently calls
setDailyReportState(date) but the same function is invoked again elsewhere
causing duplicate network calls and UI flicker; consolidate the daily report
fetch so it's invoked only once by removing the extra setDailyReportState calls
from other call sites (or replace them with a no-op guard) and keep the single
call inside handleSelectCalendarDate after reduce and setTimeCapsulesFlow;
alternatively make setDailyReportState(idempotent by checking if
state.calendarSelectedDate == date or isDailyReportChecked/isLoading is true and
returning early) so duplicate invocations don't trigger extra network requests.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
feat/time-capsule/src/main/java/com/emotionstorage/time_capsule/presentation/CalendarViewModel.kt
Related Issue
작업 상세 내용
Summary by CodeRabbit
새 기능
개선 사항