Skip to content

[FEAT#109] 챌린지 미션 진행 api 연결#116

Merged
sohee6989 merged 31 commits intodevelopfrom
feat/#109-todo-challenge-api
Jan 20, 2026
Merged

[FEAT#109] 챌린지 미션 진행 api 연결#116
sohee6989 merged 31 commits intodevelopfrom
feat/#109-todo-challenge-api

Conversation

@sohee6989
Copy link
Copy Markdown
Contributor

@sohee6989 sohee6989 commented Jan 20, 2026

Related issue 🛠

Work Description ✏️

챌린지 미션 진행 api 연결

  • 챌린지 조회
  • 다음 날로 넘어가기
  • 루틴 완료 토글

Screenshot 📸

  • 챌린지 조회
스크린샷 2026-01-20 오후 11 12 04
  • 다음 날로 넘어가기
스크린샷 2026-01-21 오전 1 05 28
  • 루틴 완료 토글
스크린샷 2026-01-21 오전 12 40 29

Uncompleted Tasks 😅

  • N/A

To Reviewers 📢

Summary by CodeRabbit

  • 새로운 기능

    • 챌린지 미션 진행 상황 조회 및 화면 표시 추가
    • 개별 루틴 완료 토글 기능과 하루 전진(완료 집계) 기능 추가
    • 전역 로딩 화면 컴포넌트 도입으로 일관된 로딩 UX 제공
  • 리팩토링

    • 미션 관련 UI 모델 및 화면 데이터 구조 정리/명확화
  • 버그 수정

    • 없음

✏️ Tip: You can customize this high-level summary in your review settings.

@sohee6989 sohee6989 self-assigned this Jan 20, 2026
@sohee6989 sohee6989 requested a review from a team as a code owner January 20, 2026 15:41
@sohee6989 sohee6989 linked an issue Jan 20, 2026 that may be closed by this pull request
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 20, 2026

Walkthrough

챌린지 미션 진행 API 연동을 위해 DTO/모델/서비스/데이터소스/리포지토리와 DI 바인딩을 추가하고, 뷰모델 및 관련 UI를 실제 데이터 흐름에 맞춰 리팩터링했습니다. 또한 공유 LoadingScreen 컴포넌트와 CherrishGaugeType 패키지 이동이 포함됩니다.

Changes

Cohort / File(s) 변경 요약
디자인 시스템 / 공통 컴포넌트
app/src/main/.../core/designsystem/component/LoadingScreen.kt
전체 화면 로딩 컴포넌트(중앙 CircularProgressIndicator) 추가
게이지 타입 패키지 이동
app/src/main/.../presentation/home/type/CherrishGaugeType.kt, app/src/main/.../core/designsystem/component/gaugebar/CherrishGauge*.kt
CherrishGaugeTypecore.designsystempresentation.home 패키지로 이동하고 관련 임포트 업데이트
원격 DTOs
app/src/main/.../data/remote/dto/response/ChallengeMissionProgressResponseDto.kt, app/src/main/.../data/remote/dto/response/ChallengeRoutineCompleteResponseDto.kt
API 응답용 @Serializable DTO 클래스 추가 (챌린지/루틴 응답 및 완료 응답)
원격 서비스 (Retrofit)
app/src/main/.../data/remote/service/ChallengeMissionProgressService.kt
GET / PATCH / POST 엔드포인트를 갖는 Retrofit 인터페이스 추가
데이터 모델 및 변환
app/src/main/.../data/model/ChallengeMissionProgressResponseModel.kt, app/src/main/.../data/model/ChallengeRoutineCompleteResponseModel.kt
DTO → UI/도메인 모델 변환용 데이터 클래스 및 extension 함수 추가
데이터소스 계층
app/src/main/.../data/remote/datasource/ChallengeMissionProgressDataSource.kt, app/src/main/.../data/remote/datasourceimpl/ChallengeMissionProgressDataSourceImpl.kt
원격 데이터소스 인터페이스 추가 및 서비스 호출을 위임하는 구현체 추가
리포지토리 계층
app/src/main/.../data/repository/ChallengeMissionProgressRepository.kt, app/src/main/.../data/repositoryimpl/ChallengeMissionProgressRepositoryImpl.kt
Result 래핑 반환의 리포지토리 인터페이스 및 구현 추가 (toModel 변환 포함)
DI 바인딩
app/src/main/.../data/di/{ServiceModule.kt,DataSourceModule.kt,RepositoryModule.kt}
ChallengeMissionProgress 관련 @Provides / @Binds 추가
프레젠테이션 모델 변경
app/src/main/.../presentation/challenge/missionprogress/{ChallengeMissionProgressUiState.kt,ChallengeMissionProgressViewModel.kt,CherryType.kt}
UiState 구조 변경(challenge → challengeId/challengeName), ViewModel에 리포지토리 주입 및 네트워크 기반 로딩/동기화 로직 추가, CherryType의 level 필드 제거
UI 컴포넌트 업데이트
app/src/main/.../presentation/challenge/missionprogress/ChallengeMissionProgressScreen.kt, .../component/ChallengeMissionTodoSection.kt, .../component/ChallengeMissionProgressCherrygrowth.kt
로딩 상태 처리에 공유 LoadingScreen 사용 추가, 루틴 모델 타입 변경(DailyTodoRoutineModel → ChallengeRoutineUiModel) 및 필드명(id→routineId, routine→routineName) 매핑 업데이트
모델 교체/삭제/추가
app/src/main/.../presentation/challenge/missionprogress/model/*
ChallengeRoutineUiModel 추가, 기존 ChallengeInfoModel, DailyTodoRoutineModel 삭제
Home 통합 변경
app/src/main/.../presentation/home/{HomeScreen.kt,HomeUiState.kt,HomeViewModel.kt,component/ChallengeSection.kt}
인라인 로딩 컴포넌트 제거 및 공유 LoadingScreen 사용, CherrishGaugeType 임포트 패키지 변경

Sequence Diagram(s)

sequenceDiagram
    participant VM as ViewModel
    participant Repo as Repository
    participant DS as DataSource
    participant Svc as RetrofitService
    participant API as Backend

    VM->>VM: loadMissions()
    VM->>Repo: getChallengeMissions()
    Repo->>DS: getChallengeMissions()
    DS->>Svc: getChallengeMissions()
    Svc->>API: GET /api/demo/challenges
    API-->>Svc: DTO
    Svc-->>DS: BaseResponse<DTO>
    DS-->>Repo: BaseResponse<DTO>
    Repo->>Repo: dto.toModel()
    Repo-->>VM: Result<Model>
    VM->>VM: UiState.Success 업데이트

    Note right of VM: onTodoClick flow
    VM->>Repo: patchChallengeRoutinesComplete(routineId)
    Repo->>DS: patchChallengeRoutinesComplete(routineId)
    DS->>Svc: PATCH /api/demo/challenges/routines/{routineId}/toggle
    API-->>Svc: RoutineCompleteDTO
    Repo->>VM: Result<RoutineCompleteModel>
    VM->>VM: UiState 업데이트
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

FEAT✨, 소희🍒

Suggested reviewers

  • hyeminililo
  • nhyeonii
  • usuuhyn
🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 주요 변경사항과 완벽하게 관련되어 있으며, '챌린지 미션 진행 api 연결'로 변경사항의 핵심을 명확히 요약하고 있습니다.
Description check ✅ Passed PR 설명이 대부분의 필수 섹션(Related issue, Work Description, Screenshot)을 포함하고 있으며, 스크린샷과 작업 내용이 체계적으로 구성되어 있습니다.
Linked Issues check ✅ Passed PR이 Issue #109의 요구사항을 충족하고 있으며, 챌린지 조회, 다음 날로 넘어가기, 루틴 완료 토글 기능이 모두 구현되어 있습니다.
Out of Scope Changes check ✅ Passed CherrishGaugeType의 패키지 이동과 LoadingScreen 컴포넌트 추가가 포함되어 있으나, 이들은 모두 챌린지 미션 화면 구현과 직결된 필수 변경사항입니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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

Comment @coderabbitai help to get the list of available commands and usage tips.

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: 4

Caution

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

⚠️ Outside diff range comments (2)
app/src/main/java/com/cherrish/android/presentation/challenge/missionprogress/ChallengeMissionProgressViewModel.kt (2)

78-83: onCompletedTodayClick 함수 미완성

PR 목표에 "다음 날로 넘어가기" API 연결이 포함되어 있으나, 이 함수는 상태 확인 후 아무 작업도 수행하지 않습니다. 현재 구현은 실질적으로 no-op입니다.

"다음 날로 넘어가기" API 호출 구현을 도와드릴까요? Repository에 해당 메서드가 있다면 연결하는 코드를 생성해 드릴 수 있습니다.


64-76: 루틴 완료 토글 API 호출 누락

onTodoClick 함수가 로컬 UI 상태만 업데이트하고 서버에 변경사항을 전송하지 않습니다. 저장소 인터페이스에 patchChallengeRoutinesComplete 메서드가 준비되어 있으므로 이를 호출하여 데이터 영속성을 보장해야 합니다.

🐛 API 호출 추가 제안
 fun onTodoClick(id: Long) {
+    viewModelScope.launch {
+        challengeMissionProgressRepository.patchChallengeRoutinesComplete(id)
+            .onSuccess {
+                _uiState.updateSuccess { state ->
+                    state.copy(
+                        routines = state.routines.map {
+                            if (it.routineId == id) {
+                                it.copy(isCompleted = !it.isCompleted)
+                            } else {
+                                it
+                            }
+                        }.toPersistentList()
+                    )
+                }
+            }
+            .onLogFailure { }
+    }
-    _uiState.updateSuccess { state ->
-        state.copy(
-            routines = state.routines.map {
-                if (it.routineId == id) {
-                    it.copy(isCompleted = !it.isCompleted)
-                } else {
-                    it
-                }
-            }.toPersistentList()
-        )
-    }
 }
🤖 Fix all issues with AI agents
In
`@app/src/main/java/com/cherrish/android/data/repositoryimpl/ChallengeMissionProgressRepositoryImpl.kt`:
- Around line 19-25: patchChallengeRoutinesComplete에서
challengeMissionDataSource.patchChallengeRoutinesComplete(...).data!!.toModel()처럼
강제 null 단언(!!)을 사용하고 있어 NPE 위험이 있으니 안전하게 처리하세요: suspendRunCatching 블록 안에서 응답의
data가 null인지 검사하고 null이면 의미있는 예외(예: IllegalStateException 또는 custom exception)를
발생시키거나 Result.failure로 반환하도록 변경하고, data가 존재할 때만 toModel()을 호출하도록 수정하세요; 관련 심볼:
patchChallengeRoutinesComplete,
challengeMissionDataSource.patchChallengeRoutinesComplete,
ChallengeRoutineCompleteResponseModel, suspendRunCatching, toModel.
- Around line 14-17: Replace the unsafe non-null assertion on
challengeMissionDataSource.getChallengeMissions().data in
ChallengeMissionProgressRepositoryImpl.getChallengeMissions() with an explicit
null check: call challengeMissionDataSource.getChallengeMissions(), inspect its
.data, and if null return/throw a descriptive error (e.g., IllegalStateException
or a domain-specific exception with a message like "Challenge missions response
contained null data") or map to a failed Result; otherwise call .toModel() on
the non-null value. This preserves suspendRunCatching behavior while providing a
clear, debuggable error when data is null.

In
`@app/src/main/java/com/cherrish/android/presentation/challenge/missionprogress/ChallengeMissionProgressViewModel.kt`:
- Around line 45-47: The current assignment of cherryType using
CherryType.entries.first { it.step == response.cherryLevel } can throw
NoSuchElementException if no match exists; change this to use firstOrNull and
handle the null by providing a safe default or explicit error handling. Locate
the cherryType assignment in ChallengeMissionProgressViewModel where
response.cherryLevel is matched against CherryType.entries and replace first {
... } with firstOrNull { ... } ?: <choose a sensible default CherryType or
handle with a graceful fallback/logging/exception>, ensuring the code no longer
crashes when no matching step is found.
- Line 60: The empty onLogFailure {} callback leaves the ViewModel stuck in
UiState.Loading on API errors; update the failure handler inside
ChallengeMissionProgressViewModel (the block where onLogFailure is used) to set
the UI state to an error state (e.g., UiState.Error or a domain-specific error
variant) and surface the error message (or map the exception) so the UI can stop
showing loading and display feedback; ensure you also clear or reset any loading
flags and emit the updated state from the same coroutine/flow that currently
emits UiState.Loading.
♻️ Duplicate comments (2)
app/src/main/java/com/cherrish/android/presentation/home/type/CherrishGaugeType.kt (1)

1-1: (중복) 타입 위치 이동으로 레이어 의존성 역전 가능성

디자인시스템에서 이 타입을 참조하는 구조라면, 타입 위치 이동이 의존성 역전을 유발합니다. 상위 댓글의 방향(타입을 core 쪽으로 배치) 확인 부탁드립니다.

app/src/main/java/com/cherrish/android/core/designsystem/component/gaugebar/CherrishGaugeBar.kt (1)

24-24: (중복) 디자인시스템의 presentation 의존성 확인 필요

상위 댓글과 동일 이슈입니다. 디자인시스템이 presentation 타입에 의존하지 않도록 레이어 방향을 재확인해 주세요.

🧹 Nitpick comments (4)
app/src/main/java/com/cherrish/android/core/designsystem/component/gaugebar/CherrishGaugeColors.kt (1)

5-5: 디자인시스템 → presentation 의존성 역전 우려

core 디자인시스템에서 presentation 타입을 직접 참조하면 레이어 방향이 뒤집혀 재사용성과 모듈 분리가 약해집니다. CherrishGaugeType을 core/designsystem(또는 core/common)으로 두고 presentation에서 참조하는 구조로 되돌리는 편을 권장합니다.

app/src/main/java/com/cherrish/android/data/remote/dto/response/ChallengeMissionProgressResponseDto.kt (1)

6-40: LGTM! kotlinx.serialization을 사용한 DTO 정의가 적절합니다.

@Serializable@SerialName 어노테이션이 올바르게 적용되어 있습니다.

API 응답의 안정성을 높이려면 일부 필드에 기본값을 추가하는 것을 고려해볼 수 있습니다. 예를 들어 todayRoutines가 null로 오는 경우를 대비할 수 있습니다:

♻️ 선택적 개선안
 `@SerialName`("todayRoutines")
-val todayRoutines: List<ChallengeRoutineResponseDto>,
+val todayRoutines: List<ChallengeRoutineResponseDto> = emptyList(),
app/src/main/java/com/cherrish/android/presentation/challenge/component/ChallengeMissionTodoSection.kt (1)

74-90: 리스트 아이템에 key 사용 고려

현재 Column + forEach로 구현되어 있어 동작에는 문제가 없습니다. 다만 루틴 목록이 자주 변경되거나 항목 수가 많아질 경우, LazyColumn으로 전환하고 key를 지정하면 recomposition 최적화에 도움이 될 수 있습니다.

app/src/main/java/com/cherrish/android/presentation/challenge/missionprogress/ChallengeMissionProgressScreen.kt (1)

59-66: 함수명 대소문자 불일치

Route 컴포저블은 ChallengeMissionProgressRoute로 올바르게 명명되었으나, private screen 컴포저블은 여전히 ChallengeMissionprogressScreen (소문자 'p')입니다. 일관성을 위해 ChallengeMissionProgressScreen으로 수정하는 것이 좋습니다.

♻️ 제안된 수정
 `@Composable`
-private fun ChallengeMissionprogressScreen(
+private fun ChallengeMissionProgressScreen(
     paddingValues: PaddingValues,
     uiState: ChallengeMissionProgressUiState,

Line 47과 192에서의 호출부도 함께 수정:

-            ChallengeMissionprogressScreen(
+            ChallengeMissionProgressScreen(

Comment on lines +14 to +17
override suspend fun getChallengeMissions(): Result<ChallengeMissionProgressResponseModel> =
suspendRunCatching {
challengeMissionDataSource.getChallengeMissions().data!!.toModel()
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

data!! 사용 시 null 처리 개선 필요

data!!를 사용하면 서버가 null 데이터를 반환할 경우 NullPointerException이 발생합니다. suspendRunCatching이 예외를 잡아주지만, 디버깅 시 원인 파악이 어려운 generic NPE가 됩니다.

명시적인 null 체크와 의미 있는 에러 메시지를 사용하는 것이 좋습니다.

🔧 제안된 수정
     override suspend fun getChallengeMissions(): Result<ChallengeMissionProgressResponseModel> =
         suspendRunCatching {
-            challengeMissionDataSource.getChallengeMissions().data!!.toModel()
+            challengeMissionDataSource.getChallengeMissions().data
+                ?.toModel()
+                ?: throw IllegalStateException("Challenge missions data is null")
         }
🤖 Prompt for AI Agents
In
`@app/src/main/java/com/cherrish/android/data/repositoryimpl/ChallengeMissionProgressRepositoryImpl.kt`
around lines 14 - 17, Replace the unsafe non-null assertion on
challengeMissionDataSource.getChallengeMissions().data in
ChallengeMissionProgressRepositoryImpl.getChallengeMissions() with an explicit
null check: call challengeMissionDataSource.getChallengeMissions(), inspect its
.data, and if null return/throw a descriptive error (e.g., IllegalStateException
or a domain-specific exception with a message like "Challenge missions response
contained null data") or map to a failed Result; otherwise call .toModel() on
the non-null value. This preserves suspendRunCatching behavior while providing a
clear, debuggable error when data is null.

Comment on lines +19 to +25
override suspend fun patchChallengeRoutinesComplete(routineId: Long):
Result<ChallengeRoutineCompleteResponseModel> =
suspendRunCatching {
challengeMissionDataSource.patchChallengeRoutinesComplete(
routineId = routineId
).data!!.toModel()
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

동일한 data!! 패턴 개선 필요

patchChallengeRoutinesComplete에서도 동일한 문제가 있습니다.

🔧 제안된 수정
     override suspend fun patchChallengeRoutinesComplete(routineId: Long):
         Result<ChallengeRoutineCompleteResponseModel> =
         suspendRunCatching {
-            challengeMissionDataSource.patchChallengeRoutinesComplete(
-                routineId = routineId
-            ).data!!.toModel()
+            challengeMissionDataSource.patchChallengeRoutinesComplete(
+                routineId = routineId
+            ).data
+                ?.toModel()
+                ?: throw IllegalStateException("Routine complete response data is null")
         }
🤖 Prompt for AI Agents
In
`@app/src/main/java/com/cherrish/android/data/repositoryimpl/ChallengeMissionProgressRepositoryImpl.kt`
around lines 19 - 25, patchChallengeRoutinesComplete에서
challengeMissionDataSource.patchChallengeRoutinesComplete(...).data!!.toModel()처럼
강제 null 단언(!!)을 사용하고 있어 NPE 위험이 있으니 안전하게 처리하세요: suspendRunCatching 블록 안에서 응답의
data가 null인지 검사하고 null이면 의미있는 예외(예: IllegalStateException 또는 custom exception)를
발생시키거나 Result.failure로 반환하도록 변경하고, data가 존재할 때만 toModel()을 호출하도록 수정하세요; 관련 심볼:
patchChallengeRoutinesComplete,
challengeMissionDataSource.patchChallengeRoutinesComplete,
ChallengeRoutineCompleteResponseModel, suspendRunCatching, toModel.

Comment on lines +45 to +47
cherryType = CherryType.entries.first {
it.step == response.cherryLevel
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

first { } 사용 시 NoSuchElementException 발생 가능

response.cherryLevelCherryType.entries의 어떤 step과도 일치하지 않으면 NoSuchElementException이 발생하여 앱이 크래시됩니다. firstOrNull을 사용하고 기본값을 제공하거나 예외 처리를 추가해야 합니다.

🐛 제안된 수정
-                            cherryType = CherryType.entries.first {
-                                it.step == response.cherryLevel
-                            },
+                            cherryType = CherryType.entries.firstOrNull {
+                                it.step == response.cherryLevel
+                            } ?: CherryType.entries.first(),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
cherryType = CherryType.entries.first {
it.step == response.cherryLevel
},
cherryType = CherryType.entries.firstOrNull {
it.step == response.cherryLevel
} ?: CherryType.entries.first(),
🤖 Prompt for AI Agents
In
`@app/src/main/java/com/cherrish/android/presentation/challenge/missionprogress/ChallengeMissionProgressViewModel.kt`
around lines 45 - 47, The current assignment of cherryType using
CherryType.entries.first { it.step == response.cherryLevel } can throw
NoSuchElementException if no match exists; change this to use firstOrNull and
handle the null by providing a safe default or explicit error handling. Locate
the cherryType assignment in ChallengeMissionProgressViewModel where
response.cherryLevel is matched against CherryType.entries and replace first {
... } with firstOrNull { ... } ?: <choose a sensible default CherryType or
handle with a graceful fallback/logging/exception>, ensuring the code no longer
crashes when no matching step is found.

)
)
}
}.onLogFailure {}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

API 실패 시 UI 상태가 Loading에서 멈춤

onLogFailure {}가 빈 람다로 되어 있어서 API 호출 실패 시 UI 상태가 UiState.Loading 상태로 계속 유지됩니다. 사용자에게 에러 피드백이 전달되지 않으며, 로딩 화면이 무한히 표시될 수 있습니다.

🐛 에러 상태 처리 제안
-            }.onLogFailure {}
+            }.onLogFailure { throwable ->
+                _uiState.update { UiState.Error(throwable) }
+            }

또는 프로젝트에서 사용하는 에러 처리 패턴에 맞게 UiState.Error 또는 별도의 에러 상태로 업데이트하세요.

🤖 Prompt for AI Agents
In
`@app/src/main/java/com/cherrish/android/presentation/challenge/missionprogress/ChallengeMissionProgressViewModel.kt`
at line 60, The empty onLogFailure {} callback leaves the ViewModel stuck in
UiState.Loading on API errors; update the failure handler inside
ChallengeMissionProgressViewModel (the block where onLogFailure is used) to set
the UI state to an error state (e.g., UiState.Error or a domain-specific error
variant) and surface the error message (or map the exception) so the UI can stop
showing loading and display feedback; ensure you also clear or reset any loading
flags and emit the updated state from the same coroutine/flow that currently
emits UiState.Loading.

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

🤖 Fix all issues with AI agents
In
`@app/src/main/java/com/cherrish/android/core/designsystem/component/gaugebar/CherrishGaugeStyle.kt`:
- Line 5: CherrishGaugeType currently lives in presentation/home/type causing
presentation layer dependency from core/designsystem; move the CherrishGaugeType
definition into core/designsystem/type (matching where CherrishButtonStyle and
CherrishSelectionChipStyle live) and update imports in core/designsystem
components (CherrishGaugeStyle, CherrishGaugeColors, CherrishGaugeBar) to
reference the new package; ensure package declaration in the moved file matches
core.designsystem.type and run a quick compile to update any other references.

In
`@app/src/main/java/com/cherrish/android/data/remote/service/ChallengeMissionProgressService.kt`:
- Around line 11-22: The service is using demo paths; update
ChallengeMissionProgressService to use the production path pattern by replacing
all "api/demo/..." routes with "api/challenges/..." (i.e., change
`@GET`("api/demo/challenges") to `@GET`("api/challenges"),
`@PATCH`("api/demo/challenges/routines/{routineId}/toggle") to
`@PATCH`("api/challenges/routines/{routineId}/toggle"), and
`@POST`("api/demo/challenges/advance-day") to
`@POST`("api/challenges/advance-day")); alternatively, if this interface is only
for testing, move it out of the production module into a demo/test module to
keep prod services consistent.

In
`@app/src/main/java/com/cherrish/android/presentation/challenge/missionprogress/ChallengeMissionProgressViewModel.kt`:
- Around line 64-79: The onTodoClick currently applies a local toggle
immediately and then calls
challengeMissionProgressRepository.patchChallengeRoutinesComplete without
reconciling the server result; change onTodoClick to perform an optimistic
update but capture the previous routines state before calling
viewModelScope.launch, then call patchChallengeRoutinesComplete; on success
update the _uiState using the server-provided routine data (or re-fetch
routines) to ensure consistency, and on failure restore the captured previous
state (rollback) and log the error (replace the empty onLogFailure block). Use
the existing symbols: onTodoClick, _uiState.updateSuccess,
viewModelScope.launch,
challengeMissionProgressRepository.patchChallengeRoutinesComplete and
onLogFailure to locate and implement the changes.
♻️ Duplicate comments (3)
app/src/main/java/com/cherrish/android/presentation/challenge/missionprogress/ChallengeMissionProgressViewModel.kt (3)

45-47: CherryType.entries.first는 매칭 실패 시 크래시 위험이 있습니다.
Line 45-47과 Line 94-96에서 매칭이 없으면 NoSuchElementException이 발생합니다. firstOrNull + 기본값/에러 처리로 안전하게 처리하세요.

🐛 안전 처리 예시 (두 군데 모두 적용)
-                            cherryType = CherryType.entries.first {
-                                it.step == response.cherryLevel
-                            },
+                            cherryType = CherryType.entries.firstOrNull {
+                                it.step == response.cherryLevel
+                            } ?: CherryType.entries.first(),

Also applies to: 94-96


60-60: API 실패 시 Loading에 고정될 수 있습니다.
Line 60의 빈 onLogFailure로 실패 상태가 전달되지 않습니다.

🐛 실패 처리 예시
-            }.onLogFailure {}
+            }.onLogFailure { throwable ->
+                _uiState.update { UiState.Error(throwable) }
+            }

84-109: 하루 넘기기 실패 시 사용자 피드백이 없습니다.
Line 84-109에서 성공만 처리되어 실패가 묵살됩니다. 실패 시 상태 갱신/피드백을 추가하세요.

🐛 실패 처리 예시
         viewModelScope.launch {
-            challengeMissionProgressRepository.postChallengeAdvanceDay()
+            _uiState.update { UiState.Loading }
+            challengeMissionProgressRepository.postChallengeAdvanceDay()
                 .onSuccess { response ->
                     _uiState.update {
                         UiState.Success(
                             ChallengeMissionProgressUiState(
@@
                         )
                     }
                 }
+                .onLogFailure { throwable ->
+                    _uiState.update { UiState.Error(throwable) }
+                }
         }
🧹 Nitpick comments (1)
app/src/main/java/com/cherrish/android/data/remote/datasourceimpl/ChallengeMissionProgressDataSourceImpl.kt (1)

12-19: 코드 스타일 일관성 개선을 권장합니다.

세 메서드의 구현 스타일이 일관되지 않습니다:

  • getChallengeMissions(): expression body 사용
  • patchChallengeRoutinesComplete(): block body + 명시적 반환 타입
  • postChallengeAdvanceDay(): expression body 사용

가독성과 유지보수성을 위해 일관된 스타일로 통일하는 것을 권장합니다.

♻️ 일관된 expression body 스타일로 통일
 class ChallengeMissionProgressDataSourceImpl `@Inject` constructor(
     private val challengeMissionService: ChallengeMissionProgressService
 ) : ChallengeMissionProgressDataSource {
     override suspend fun getChallengeMissions() = challengeMissionService.getChallengeMissions()
-    override suspend fun patchChallengeRoutinesComplete(routineId: Long):
-        BaseResponse<ChallengeRoutineCompleteResponseDto> {
-        return challengeMissionService.patchChallengeRoutinesComplete(routineId = routineId)
-    }
+    override suspend fun patchChallengeRoutinesComplete(routineId: Long) =
+        challengeMissionService.patchChallengeRoutinesComplete(routineId = routineId)

     override suspend fun postChallengeAdvanceDay() =
         challengeMissionService.postChallengeAdvanceDay()
 }

import androidx.compose.ui.graphics.Color
import com.cherrish.android.core.designsystem.component.type.CherrishGaugeType
import com.cherrish.android.core.designsystem.theme.CherrishColors
import com.cherrish.android.presentation.home.type.CherrishGaugeType
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, explore the repository structure
find . -type f -name "settings.gradle.kts" -o -name "settings.gradle" | head -5

Repository: TEAM-Cherrish/Cherrish-Android

Length of output: 95


🏁 Script executed:

# List the main modules in the project
git ls-files | grep -E "^[^/]+/build\.gradle" | head -20

Repository: TEAM-Cherrish/Cherrish-Android

Length of output: 94


🏁 Script executed:

# Find the designsystem module structure
fd -t d "designsystem" | head -10

Repository: TEAM-Cherrish/Cherrish-Android

Length of output: 131


🏁 Script executed:

# Look at the file in question
cat -n app/src/main/java/com/cherrish/android/core/designsystem/component/gaugebar/CherrishGaugeStyle.kt

Repository: TEAM-Cherrish/Cherrish-Android

Length of output: 1503


🏁 Script executed:

# Find where CherrishGaugeType is defined
rg -n "class CherrishGaugeType|object CherrishGaugeType|enum class CherrishGaugeType" --type kt

Repository: TEAM-Cherrish/Cherrish-Android

Length of output: 100


🏁 Script executed:

# Check the build.gradle for core/designsystem module
find . -path "*/core/designsystem*" -name "build.gradle*" -exec cat {} \;

Repository: TEAM-Cherrish/Cherrish-Android

Length of output: 56


🏁 Script executed:

# Check the build.gradle for presentation module
find . -path "*/presentation*" -name "build.gradle*" | grep -v ".gradle" | head -5 | xargs -I {} sh -c "echo '=== {} ===' && cat {}"

Repository: TEAM-Cherrish/Cherrish-Android

Length of output: 56


🏁 Script executed:

# Search for CherrishGaugeType definition without type flag
rg -n "class CherrishGaugeType|object CherrishGaugeType|enum class CherrishGaugeType|enum CherrishGaugeType" --max-count 10

Repository: TEAM-Cherrish/Cherrish-Android

Length of output: 188


🏁 Script executed:

# Find all files importing or defining CherrishGaugeType
rg -l "CherrishGaugeType" | head -20

Repository: TEAM-Cherrish/Cherrish-Android

Length of output: 794


🏁 Script executed:

# Check the package structure under com.cherrish.android
find app/src/main/java/com/cherrish/android -type d -maxdepth 3 | sort

Repository: TEAM-Cherrish/Cherrish-Android

Length of output: 3743


🏁 Script executed:

# Look at the presentation home type file directly
find app/src/main -path "*presentation/home/type*" -type f | xargs cat -n

Repository: TEAM-Cherrish/Cherrish-Android

Length of output: 2942


🏁 Script executed:

# Check if there's a core.model or similar shared type location
find app/src/main/java/com/cherrish/android/core -type d | sort

Repository: TEAM-Cherrish/Cherrish-Android

Length of output: 1352


🏁 Script executed:

# Check what CherrishGaugeType.kt imports to verify if it can be moved to core/designsystem
cat -n app/src/main/java/com/cherrish/android/presentation/home/type/CherrishGaugeType.kt

Repository: TEAM-Cherrish/Cherrish-Android

Length of output: 600


🏁 Script executed:

# Check what other types in core/designsystem/type contain for pattern consistency
find app/src/main/java/com/cherrish/android/core/designsystem/type -type f -name "*.kt" | xargs cat -n

Repository: TEAM-Cherrish/Cherrish-Android

Length of output: 402


🏁 Script executed:

# Verify the full context of how CherrishGaugeType is used in CherrishGaugeColors.kt
cat -n app/src/main/java/com/cherrish/android/core/designsystem/component/gaugebar/CherrishGaugeColors.kt

Repository: TEAM-Cherrish/Cherrish-Android

Length of output: 1591


CherrishGaugeType을 core/designsystem/type으로 이동하여 계층 의존성 역전 제거

CherrishGaugeType은 순수한 UI 스타일링 정보(단계 번호, 드로어블 리소스)만 포함하는 디자인시스템 타입입니다. 현재 presentation/home/type에 위치하면서 core/designsystem의 CherrishGaugeStyle, CherrishGaugeColors, CherrishGaugeBar가 presentation 레이어를 의존하게 되어 계층 구조를 위반합니다.

core/designsystem/type 디렉토리에 이미 유사한 패턴(CherrishButtonStyle, CherrishSelectionChipStyle)이 존재하므로, CherrishGaugeType을 동일한 위치로 옮기고 해당 import를 업데이트하세요.

🤖 Prompt for AI Agents
In
`@app/src/main/java/com/cherrish/android/core/designsystem/component/gaugebar/CherrishGaugeStyle.kt`
at line 5, CherrishGaugeType currently lives in presentation/home/type causing
presentation layer dependency from core/designsystem; move the CherrishGaugeType
definition into core/designsystem/type (matching where CherrishButtonStyle and
CherrishSelectionChipStyle live) and update imports in core/designsystem
components (CherrishGaugeStyle, CherrishGaugeColors, CherrishGaugeBar) to
reference the new package; ensure package declaration in the moved file matches
core.designsystem.type and run a quick compile to update any other references.

Comment on lines +11 to +22
interface ChallengeMissionProgressService {
@GET("api/demo/challenges")
suspend fun getChallengeMissions(): BaseResponse<ChallengeMissionProgressResponseDto>

@PATCH("api/demo/challenges/routines/{routineId}/toggle")
suspend fun patchChallengeRoutinesComplete(
@Path("routineId") routineId: Long
): BaseResponse<ChallengeRoutineCompleteResponseDto>

@POST("api/demo/challenges/advance-day")
suspend fun postChallengeAdvanceDay(): BaseResponse<ChallengeMissionProgressResponseDto>
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

rg -n "api/demo" --type kotlin -A 1 -B 1

Repository: TEAM-Cherrish/Cherrish-Android

Length of output: 1365


🏁 Script executed:

fd -e kt "Service.kt$" app/src/main/java/com/cherrish/android/data/remote/service/ | head -5

Repository: TEAM-Cherrish/Cherrish-Android

Length of output: 481


🏁 Script executed:

cat -n app/src/main/java/com/cherrish/android/data/remote/service/CalendarService.kt | head -20

Repository: TEAM-Cherrish/Cherrish-Android

Length of output: 954


🏁 Script executed:

cat -n app/src/main/java/com/cherrish/android/data/remote/service/HomeService.kt | head -20

Repository: TEAM-Cherrish/Cherrish-Android

Length of output: 466


🏁 Script executed:

cat -n app/src/main/java/com/cherrish/android/data/remote/service/DummyService.kt | head -20

Repository: TEAM-Cherrish/Cherrish-Android

Length of output: 390


API 엔드포인트 경로를 프로덕션 패턴에 맞게 수정해야 합니다.

Retrofit 서비스 인터페이스의 구조와 어노테이션은 올바르게 구현되었으나, 다른 모든 서비스 파일들과 달리 이 서비스만 api/demo/ 경로를 사용하고 있습니다.

다른 프로덕션 서비스들은 다음과 같은 패턴을 따릅니다:

  • CalendarService: api/calendar/...
  • HomeService: api/main-dashboard

ChallengeMissionProgressService의 모든 엔드포인트도 일관성 있게 api/challenges/... 패턴으로 수정하거나, 이것이 데모/테스트 코드라면 프로덕션 코드베이스에서 분리해야 합니다.

🤖 Prompt for AI Agents
In
`@app/src/main/java/com/cherrish/android/data/remote/service/ChallengeMissionProgressService.kt`
around lines 11 - 22, The service is using demo paths; update
ChallengeMissionProgressService to use the production path pattern by replacing
all "api/demo/..." routes with "api/challenges/..." (i.e., change
`@GET`("api/demo/challenges") to `@GET`("api/challenges"),
`@PATCH`("api/demo/challenges/routines/{routineId}/toggle") to
`@PATCH`("api/challenges/routines/{routineId}/toggle"), and
`@POST`("api/demo/challenges/advance-day") to
`@POST`("api/challenges/advance-day")); alternatively, if this interface is only
for testing, move it out of the production module into a demo/test module to
keep prod services consistent.

Comment on lines 64 to +79
fun onTodoClick(id: Long) {
_uiState.updateSuccess { state ->
state.copy(
routines = state.routines.map {
if (it.id == id) {
it.copy(isCompleted = !it.isCompleted)
routines = state.routines.map { routine ->
if (routine.routineId == id) {
routine.copy(isCompleted = !routine.isCompleted)
} else {
it
routine
}
}.toPersistentList()
)
}
viewModelScope.launch {
challengeMissionProgressRepository
.patchChallengeRoutinesComplete(routineId = id)
.onLogFailure { }
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

루틴 토글이 서버 상태와 불일치할 수 있습니다.
Line 64-79에서 로컬 토글을 먼저 적용하고 서버 응답을 반영하지 않아 불일치 위험이 있습니다. 성공 응답 기반 갱신(또는 재조회)으로 동기화하세요. 낙관적 업데이트를 유지하려면 실패 시 롤백도 필요합니다.

🔧 동기화 중심 예시 (성공 응답 기반 갱신)
-        _uiState.updateSuccess { state ->
-            state.copy(
-                routines = state.routines.map { routine ->
-                    if (routine.routineId == id) {
-                        routine.copy(isCompleted = !routine.isCompleted)
-                    } else {
-                        routine
-                    }
-                }.toPersistentList()
-            )
-        }
-        viewModelScope.launch {
-            challengeMissionProgressRepository
-                .patchChallengeRoutinesComplete(routineId = id)
-                .onLogFailure { }
-        }
+        viewModelScope.launch {
+            challengeMissionProgressRepository
+                .patchChallengeRoutinesComplete(routineId = id)
+                .onSuccess {
+                    _uiState.updateSuccess { state ->
+                        state.copy(
+                            routines = state.routines.map { routine ->
+                                if (routine.routineId == id) {
+                                    routine.copy(isCompleted = !routine.isCompleted)
+                                } else {
+                                    routine
+                                }
+                            }.toPersistentList()
+                        )
+                    }
+                }
+        }
🤖 Prompt for AI Agents
In
`@app/src/main/java/com/cherrish/android/presentation/challenge/missionprogress/ChallengeMissionProgressViewModel.kt`
around lines 64 - 79, The onTodoClick currently applies a local toggle
immediately and then calls
challengeMissionProgressRepository.patchChallengeRoutinesComplete without
reconciling the server result; change onTodoClick to perform an optimistic
update but capture the previous routines state before calling
viewModelScope.launch, then call patchChallengeRoutinesComplete; on success
update the _uiState using the server-provided routine data (or re-fetch
routines) to ensure consistency, and on failure restore the captured previous
state (rollback) and log the error (replace the empty onLogFailure block). Use
the existing symbols: onTodoClick, _uiState.updateSuccess,
viewModelScope.launch,
challengeMissionProgressRepository.patchChallengeRoutinesComplete and
onLogFailure to locate and implement the changes.

@nhyeonii nhyeonii added FEAT✨ 새로운 기능 구현 소희🍒 소희 담당 labels Jan 20, 2026
Copy link
Copy Markdown
Contributor

@nhyeonii nhyeonii left a comment

Choose a reason for hiding this comment

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

완벽띠 소히띠

@sohee6989 sohee6989 merged commit 39b367b into develop Jan 20, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

FEAT✨ 새로운 기능 구현 소희🍒 소희 담당

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] 챌린지 투두화면 api 연동

2 participants