Conversation
- StepIndicator 컴포넌트 구현
- 지남력 퀴즈 ui 구현 - 지남력 퀴즈 비즈니스 로직 구현
- 라우터 정의
Walkthrough시니어 사용자를 위한 오리엔테이션 퀴즈 기능을 구현합니다. 새로운 UI 컴포넌트(MaQuizButton, StepIndicator), 도메인 모델(Quiz, Quizzes) 및 유스케이스를 추가하고, 오리엔테이션 퀴즈 화면, ViewModel, 네비게이션 라우트를 추가합니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant SeniorHome as SeniorHomeScreen
participant VM as SeniorHomeViewModel
participant Nav as Navigator
User->>SeniorHome: 화면 진입
SeniorHome->>VM: 초기화 (hiltViewModel)
rect rgb(200, 220, 255)
Note over User,VM: 퀴즈 카테고리로 이동
User->>SeniorHome: onQuizClick 실행
SeniorHome->>VM: navigateToQuizCategory()
VM->>Nav: navigate(AppRoute.QuizCategory)
end
rect rgb(220, 240, 220)
Note over User,VM: 오리엔테이션 퀴즈 시작
User->>SeniorHome: onDailyQuizClick 실행
SeniorHome->>VM: navigateToDailyQuiz()
VM->>Nav: navigate(AppRoute.OrientationQuiz)
end
sequenceDiagram
participant User
participant QuizScreen as OrientationQuizScreen
participant QuizVM as OrientationQuizViewModel
participant UseCase as CheckAnswerUseCase
participant Dialog as QuizResultDialog
rect rgb(200, 220, 255)
Note over QuizScreen,QuizVM: 퀴즈 로드 및 초기화
QuizScreen->>QuizVM: 진입 (hiltViewModel)
activate QuizVM
QuizVM->>QuizVM: FetchOrientationQuizUseCase.invoke()
QuizVM->>QuizVM: uiState = Loading → Success
deactivate QuizVM
end
rect rgb(240, 240, 220)
Note over User,QuizVM: 답변 선택 및 확인
User->>QuizScreen: 옵션 클릭 (MaQuizButton)
QuizScreen->>QuizVM: selectAnswer(index)
User->>QuizScreen: 계속 버튼 클릭
QuizScreen->>QuizVM: checkAnswer()
activate QuizVM
QuizVM->>UseCase: invoke(quiz, selectedIndex)
UseCase->>QuizVM: Boolean 반환 (정/오)
QuizVM->>QuizVM: resultDialogState 업데이트
deactivate QuizVM
QuizVM->>Dialog: 결과 표시 (2초)
end
rect rgb(220, 240, 220)
Note over QuizVM,User: 다음 퀴즈 또는 완료
QuizVM->>QuizVM: 마지막 퀴즈 확인
alt 더 있음
QuizVM->>QuizVM: currentQuestionIndex 증가
else 완료
QuizVM->>QuizVM: navigate(SeniorHome)
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ 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: 2
🧹 Nitpick comments (16)
feature/senior/src/main/kotlin/com/moa/app/feature/senior/home/SeniorHomeViewModel.kt (1)
23-25: 빈 구현체에 TODO 주석 또는 NotImplementedError 추가를 권장합니다.현재
navigateToReport()함수가 빈 상태로 구현되어 있어, UI에서 호출 시 아무 동작도 하지 않습니다. 향후 개발자에게 의도를 명확히 전달하기 위해 다음 중 하나를 적용하는 것을 권장합니다:
- TODO 주석 추가
throw NotImplementedError()사용- 구현 전까지 함수 제거
다음 중 하나를 적용할 수 있습니다:
옵션 1: TODO 주석 추가
fun navigateToReport() { - + // TODO: 리포트 화면 네비게이션 구현 필요 }옵션 2: NotImplementedError 사용
fun navigateToReport() { - + throw NotImplementedError("리포트 화면 네비게이션이 아직 구현되지 않았습니다") }feature/senior/src/main/kotlin/com/moa/app/feature/senior/home/SeniorHomeScreen.kt (2)
168-170: interactionSource 생성이 불필요할 수 있습니다.
MutableInteractionSource를 명시적으로 생성하고 있지만, 다른 곳에서 사용되지 않는다면 기본 동작만으로 충분합니다.clickablemodifier에서interactionSource를 제거하면 자동으로 생성되며, 코드가 더 간결해집니다.다음과 같이 단순화할 수 있습니다:
.clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = ripple(), onClick = onQuizClick, role = Role.Button, )
77-77: 설정 및 리포트 버튼의 onClick 핸들러가 아직 구현되지 않았습니다.퀴즈 및 오늘의 퀴즈 버튼은 ViewModel을 통해 적절하게 연결되었으나, 설정 아이콘(line 77)과 리포트 박스(line 214)는 여전히 빈 onClick 핸들러를 사용하고 있습니다. 일관성을 위해 향후 이들도 ViewModel 패턴으로 마이그레이션하는 것을 고려해 주세요.
Also applies to: 214-214
feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/component/QuizLoadContent.kt (3)
51-61: 문자열 리소스 파일로 추출을 권장합니다.현재 한국어 문자열이 하드코딩되어 있어 향후 다국어 지원 시 문제가 될 수 있습니다. 데모 버전이므로 지금 당장 수정이 필요하지는 않지만, 추후 리팩토링 시
strings.xml로 이동하는 것을 고려해주세요.예시:
Text( - text = "지남력은\n시간과 장소를 확인해요", + text = stringResource(R.string.quiz_load_orientation_title), color = MoaTheme.colors.blue700, style = MoaTheme.typography.headLine2Bold ) Text( - text = "퀴즈가 곧 시작돼요...", + text = stringResource(R.string.quiz_load_starting_message), color = MoaTheme.colors.blue200, style = MoaTheme.typography.body2Medium )
36-36: 오프셋 값에 대한 주석 추가를 고려해보세요.
1.dp오프셋이 적용된 이유(디자인 정렬 등)를 간단한 주석으로 남기면 향후 유지보수 시 도움이 될 수 있습니다.
66-70: 프리뷰에 테마 래퍼를 추가하면 더 정확한 미리보기가 가능합니다.현재 프리뷰가 정상적으로 작동하지만,
MoaTheme으로 감싸면 실제 앱과 동일한 색상 및 타이포그래피로 미리볼 수 있습니다.예시:
@Preview @Composable private fun Preview() { - QuizLoadContent() + MoaTheme { + QuizLoadContent() + } }core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/core/button/MaQuizButton.kt (2)
139-143: 네이밍 일관성 개선을 고려해보세요.enum 값은
UNSELECTED를 사용하지만, 관련 색상 속성들은notSelected접두사를 사용합니다(예:notSelectedBackgroundColor). 일관성을 위해 둘 다 동일한 네이밍 컨벤션을 사용하는 것이 좋습니다.예를 들어 enum을
NOT_SELECTED로 변경하거나, 색상 속성을unselectedBackgroundColor로 변경할 수 있습니다.
67-89: 접근성 지원을 강화하는 것을 권장합니다.현재
Role.Button만 설정되어 있습니다. 시니어 사용자를 대상으로 하는 앱이므로, 접근성 지원을 강화하는 것이 좋습니다.예를 들어
contentDescription파라미터를 추가하여 호출자가 스크린 리더를 위한 설명을 제공할 수 있도록 할 수 있습니다:fun MaQuizButton( onClick: () -> Unit, modifier: Modifier = Modifier, state: QuizButtonState = QuizButtonState.DEFAULT, + contentDescription: String? = null, shape: Shape = MaQuizButtonDefaults.shape, colors: MaQuizButtonColors = MaQuizButtonDefaults.maQuizButtonColors(), borderColors: MaQuizButtonBorderColors = MaQuizButtonDefaults.maQuizButtonBorderColors(), content: @Composable BoxScope.() -> Unit, ) { ... Box( modifier = modifier - .semantics { role = Role.Button } + .semantics { + role = Role.Button + contentDescription?.let { this.contentDescription = it } + }feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/category/QuizCategory.kt (1)
1-44: 패키지 분리 방향은 좋고, 텍스트 리터럴은 나중에 리소스로 빼는 것도 고려해 보세요.
quiz.category로 패키지를 세분화한 덕분에 도메인 구조가 더 명확해졌습니다. 다만title·description이 모두 하드코딩 문자열이라, 향후 다국어 지원이나 카피 수정이 잦아질 경우string리소스로 분리해 두면 유지보수가 조금 더 편해질 것 같습니다.feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/category/QuizCategoryViewModel.kt (1)
10-30: 현재 ORIENTATION만 처리하는 로직은 요구사항에 맞지만, 나머지 케이스에 대한 의도를 드러내면 더 좋겠습니다.지금은
QuizCategory.ORIENTATION만 네비게이션하고 나머지는 no-op인데, “데모 버전이라 아직 미구현”이라는 의도가 코드에서는 잘 드러나지 않습니다. 예를 들어:
else블록에// TODO: 다른 카테고리 퀴즈 구현 예정같은 주석을 추가하거나,- UI 쪽에서 아직 준비되지 않은 카테고리를 비활성화하는 방식
정도를 고려해 보시면 이후 기능 확장 시 혼동이 줄어들 것 같습니다.
domain/src/main/kotlin/com/moa/app/domain/quiz/model/Quiz.kt (1)
5-16: 정답 인덱스 범위 체크를 추가하면 안전성이 올라갑니다현재
correctAnswerIndex에 대한 범위 검사가 없어, 잘못된 값이 들어오면getCorrectAnswer()호출 시IndexOutOfBoundsException이 발생할 수 있습니다. 생성 시require(correctAnswerIndex in options.indices)를 추가하거나,getCorrectAnswer()에서options.getOrNull(correctAnswerIndex)를 사용해 방어 코드를 두는 것도 한 번 고려해 보시면 좋겠습니다.isCorrect(null)이false를 반환하는 동작 자체는 직관적이라 그대로 유지해도 무방해 보입니다.domain/src/main/kotlin/com/moa/app/domain/quiz/model/Quizzes.kt (1)
7-24:Quizzes래퍼는 적절하지만 Kotlin 컬렉션 인터페이스를 구현해 두면 재사용성이 좋아집니다현재
size,getQuizAt,map만 노출돼 있어 기본적인 사용에는 충분하지만, 나중에for루프,[]연산자 등 일반 컬렉션 API와 함께 쓰고 싶을 때는 다소 제약이 있을 수 있습니다. 필요해지는 시점에 맞춰Iterable<Quiz>를 구현하거나operator fun get(index: Int): Quiz?등을 추가해 두면 호출 코드가 더 자연스러워질 것 같습니다.feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/category/QuizCategoryScreen.kt (1)
21-26: 뷰모델 주입 + 카테고리 클릭 콜백 분리가 깔끔합니다.
QuizCategoryScreen에서 Hilt 뷰모델을 기본 인자로 받고, 실제 UI는QuizCategoryScreenContent가onQuizCategoryClick콜백만 의존하도록 분리한 구조가 명확해서 테스트/미리보기에도 유리해 보입니다.다만 이후 카테고리가 서버나 도메인에서 동적으로 내려오는 구조로 확장될 가능성이 있다면,
QuizCategory.entries대신 뷰모델 상태(예:uiState.categories)를QuizCategoryScreenContent의 인자로 넘기는 형태로 바꾸기 쉬운지 한 번만 염두에 두시면 좋겠습니다.Also applies to: 29-31, 62-70, 79-81
feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/orientation/OrientationQuizViewModel.kt (2)
37-54: 퀴즈 로딩 시 예외/에러 케이스까지 고려하면 좋겠습니다.
loadQuizzes에서 2초delay후fetchOrientationQuizUseCase()결과를 그대로 사용하고 있는데, 실제 서버 연동으로 변경될 경우 네트워크 오류 등으로 예외가 던져지면Loading상태에서 멈출 수 있습니다.
try/catch로 예외를 감싸서OrientationQuizUiState.Error로 전환하거나- use case 쪽에서
Result/Either형태로 성공/실패를 표현하도록 한 뒤, 여기서 분기같은 패턴을 넣어두면 이후 서버 연동 시에도 안정적으로 동작할 것 같습니다. 또한 2초 지연이 데모용 연출이라면 상수로 추출하거나 TODO 코멘트를 남겨둬도 유지보수에 도움이 됩니다.
56-91: 답안 선택/채점 흐름이 명확하지만 null 선택 방어 로직을 고려해 볼 수 있습니다.
selectAnswer에서isCheckingAnswer를 기준으로 중복 입력을 막고,checkAnswer에서 도메인Quiz를 가져와ResultDialogState로 변환하는 흐름은 이해하기 쉽습니다.다만
checkAnswer내부에서selectedAnswerIndex가null인 경우에도 그대로checkAnswerUseCase에 전달하고 있어, UI에서 항상Success.isContinueButtonEnabled를 이용해 버튼을 비활성화하고 있다는 전제에 의존하고 있습니다.보다 방어적으로 가려면 다음처럼
selectedAnswerIndex == null인 경우를 early return 해 두는 것도 한 방법입니다.fun checkAnswer() { _uiState.update { currentState -> - if (currentState !is OrientationQuizUiState.Success || currentState.isCheckingAnswer) return@update currentState + if (currentState !is OrientationQuizUiState.Success || currentState.isCheckingAnswer) return@update currentState + if (currentState.selectedAnswerIndex == null) return@update currentState val currentDomainQuestion = this.quizzes.getQuizAt(currentState.currentQuestionIndex) ?: return@update currentState val isCorrect = checkAnswerUseCase( quiz = currentDomainQuestion, selectedIndex = currentState.selectedAnswerIndex, ) ...UI에서 이미
isContinueButtonEnabled로 충분히 막고 있다면 필수 변경은 아니지만, 나중에 호출부가 늘어났을 때 실수로 잘못 호출되는 것을 방지해 줄 수 있습니다. 호출부가 이 전제(selectedAnswerIndex != null)를 만족하는지 한 번만 확인해 주세요.feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/component/QuizResultDialog.kt (1)
48-51: 문자열 리소스 분리 및 정답 문구 포맷은 추후 리팩터링 포인트로 보입니다.현재
"정답이에요!","오답이에요...","정답은 ${correctAnswer}이에요"등의 문구가 하드코딩되어 있어,
- 다국어 지원
- 같은 문구를 다른 화면에서 재사용
이 필요해질 경우에는 string 리소스로 분리해 두는 편이 관리에 유리할 것 같습니다. 또한 정답 문구는
${correctAnswer}앞/뒤 조사 때문에 약간 어색해질 여지가 있어, 나중에stringResource(R.string.quiz_incorrect_description, correctAnswer)형태로 포맷 문자열을 쓰면 더 자연스러운 문장 처리도 가능해집니다.데모 단계에서는 그대로 두셔도 무방하지만, 정식 배포 전에 한 번 정리해 두면 좋겠습니다.
Also applies to: 89-99
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (21)
app/src/main/kotlin/com/moa/app/main/MainActivity.kt(2 hunks)core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/core/button/MaQuizButton.kt(1 hunks)core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/core/indicator/StepIndicator.kt(1 hunks)core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/product/topbar/MaStepProgressTopAppBar.kt(1 hunks)core/navigation/src/main/java/com/moa/app/navigation/AppRoute.kt(1 hunks)domain/build.gradle.kts(1 hunks)domain/src/main/kotlin/com/moa/app/domain/quiz/model/Quiz.kt(1 hunks)domain/src/main/kotlin/com/moa/app/domain/quiz/model/Quizzes.kt(1 hunks)domain/src/main/kotlin/com/moa/app/domain/quiz/usecase/CheckAnswerUseCase.kt(1 hunks)domain/src/main/kotlin/com/moa/app/domain/quiz/usecase/FetchOrientationQuizUseCase.kt(1 hunks)feature/senior/build.gradle.kts(1 hunks)feature/senior/src/main/kotlin/com/moa/app/feature/senior/home/SeniorHomeScreen.kt(6 hunks)feature/senior/src/main/kotlin/com/moa/app/feature/senior/home/SeniorHomeViewModel.kt(1 hunks)feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/category/QuizCategory.kt(1 hunks)feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/category/QuizCategoryScreen.kt(4 hunks)feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/category/QuizCategoryViewModel.kt(1 hunks)feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/component/QuizLoadContent.kt(1 hunks)feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/component/QuizResultDialog.kt(1 hunks)feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/orientation/OrientationQuizScreen.kt(1 hunks)feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/orientation/OrientationQuizViewModel.kt(1 hunks)gradle/libs.versions.toml(4 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
app/src/main/kotlin/com/moa/app/main/MainActivity.kt (3)
feature/senior/src/main/kotlin/com/moa/app/feature/senior/home/SeniorHomeScreen.kt (1)
SeniorHomeScreen(37-45)feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/category/QuizCategoryScreen.kt (1)
QuizCategoryScreen(19-26)feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/orientation/OrientationQuizScreen.kt (1)
OrientationQuizScreen(47-67)
core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/product/topbar/MaStepProgressTopAppBar.kt (1)
core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/core/indicator/StepIndicator.kt (1)
StepIndicator(21-98)
feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/orientation/OrientationQuizScreen.kt (5)
feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/component/QuizLoadContent.kt (1)
QuizLoadContent(22-64)feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/component/QuizResultDialog.kt (1)
QuizResultDialog(22-38)core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/product/topbar/MaStepProgressTopAppBar.kt (1)
MaStepProgressTopAppBar(23-72)core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/core/button/MaQuizButton.kt (1)
MaQuizButton(34-90)core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/core/button/MaButton.kt (1)
MaButton(30-68)
⏰ 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: Run Unit Tests
- GitHub Check: Run ktlint
🔇 Additional comments (19)
feature/senior/src/main/kotlin/com/moa/app/feature/senior/home/SeniorHomeViewModel.kt (2)
9-12: LGTM!Hilt 의존성 주입과 ViewModel 구조가 올바르게 구현되었습니다.
15-21: LGTM!퀴즈 카테고리 및 일일 퀴즈로의 네비게이션 로직이 명확하게 구현되었습니다.
feature/senior/src/main/kotlin/com/moa/app/feature/senior/home/SeniorHomeScreen.kt (4)
6-6: 필요한 import들이 올바르게 추가되었습니다.ViewModel 통합과 상호작용 개선에 필요한 import들이 적절하게 추가되었습니다.
Also applies to: 20-20, 22-22, 32-32
39-45: ViewModel 패턴이 올바르게 적용되었습니다.Hilt를 통한 ViewModel 주입과 콜백 전달 패턴이 적절하게 구현되었습니다.
48-51: 컴포저블 시그니처가 적절하게 리팩토링되었습니다.Content 컴포저블을 private으로 설정하고 명확한 콜백 파라미터를 받도록 개선되었습니다.
260-263: Preview가 새로운 시그니처에 맞게 업데이트되었습니다.변경된 컴포저블 시그니처에 맞춰 Preview가 올바르게 수정되었습니다.
core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/core/button/MaQuizButton.kt (3)
44-65: 상태 관리 로직이 잘 구현되었습니다.
isPressed상태를 우선 확인한 후QuizButtonState를 체크하는 로직이 올바르게 구현되어 있습니다. 이를 통해 사용자에게 즉각적인 시각적 피드백을 제공하면서도 선택/미선택 상태를 명확하게 표현할 수 있습니다.
79-83:indication = null설정을 확인해주세요.현재 리플 효과가 비활성화되어 있습니다. 커스텀 색상 변경으로 눌림 상태를 표현하고 있지만, 일반적으로 리플 효과는 터치 피드백을 향상시킵니다. 특히 시니어 사용자의 경우 명확한 시각적 피드백이 중요할 수 있습니다.
이것이 의도된 디자인 결정인지 확인해주세요.
93-94: shape 속성 패턴이 유효하지만 특이합니다.
val로 선언된shape속성이@Composablegetter를 가지고 있습니다. 현재는 상수 값만 반환하지만, 이 패턴은 향후 테마나 컴포지션 로컬에 접근해야 할 경우를 대비한 것으로 보입니다.이는 유효한 패턴이며 유연성을 제공합니다.
core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/core/indicator/StepIndicator.kt (1)
21-98: 지표 계산·애니메이션 로직이 깔끔하게 잘 정리되어 있습니다.
totalSteps <= 0가드, 세그먼트/원 좌표 계산, 단일 스텝 처리까지 엣지 케이스를 잘 커버하고 있고,animateFloatAsState/animateColorAsState로 단계 전환 애니메이션도 자연스럽게 구성돼 보여요. 현재 요구사항(퀴즈 단계 수가 많지 않은 케이스)에서는 성능·가독성 모두 괜찮은 구조 같습니다.Also applies to: 100-124
domain/build.gradle.kts (1)
5-8: 도메인 모듈 의존성 구성이 요구사항과 잘 맞습니다.
javax.inject로 DI,kotlinx.collections.immutable로 불변 컬렉션을 사용하는 현재/향후 도메인 설계와 잘 맞는 선택으로 보입니다. 추후 사용이 줄어들면 불필요한 의존성만 정리해 주시면 될 것 같아요.app/src/main/kotlin/com/moa/app/main/MainActivity.kt (1)
30-32: 새 시니어 플로우 네비게이션 연결이 일관성 있게 잘 추가되었습니다.
AppRoute.SeniorHome / QuizCategory / OrientationQuiz를 별도 인자 없이 연결하고, 각 화면에서 Hilt ViewModel·Navigator를 사용하도록 한 구조가 기존 네비게이션 패턴과 자연스럽게 맞습니다. 추후 시니어 전용 플로우가 더 복잡해지면 별도navigation그래프로 분리하는 정도만 고려하시면 될 것 같습니다.Also applies to: 74-76
feature/senior/build.gradle.kts (1)
11-19: 시니어 기능 모듈의 계층 의존성이 잘 정리되었습니다.
implementation(projects.domain)으로 도메인 유스케이스를 직접 사용할 수 있게 한 점, 그리고 도메인에서 사용하는 동일한kotlinx.collections.immutable을 가져온 점이 구조적으로 자연스럽습니다.domain/src/main/kotlin/com/moa/app/domain/quiz/usecase/CheckAnswerUseCase.kt (1)
1-10: 도메인 유스케이스로 정답 검증을 분리한 구조가 명확합니다.UI에서 직접
Quiz.isCorrect를 호출하지 않고,CheckAnswerUseCase의invoke에 위임하는 패턴이라 이후 채점 로직이 복잡해져도 변경 범위가 도메인에 국한될 것 같습니다. 현재는 단순 위임이지만, 확장성을 생각하면 이 레이어를 미리 둔 선택이 좋아 보입니다.core/navigation/src/main/java/com/moa/app/navigation/AppRoute.kt (1)
31-38: 새 Senior 관련 라우트 추가 방향성 문제 없어 보입니다기존
AppRoute패턴과 동일하게@Serializable data object로 정의돼 있고, 이름도 각 화면 역할과 잘 맞습니다. 다른 모듈에서 이 라우트들을 올바르게 참조하고만 있다면, 이 부분은 그대로 유지하셔도 될 것 같습니다.core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/product/topbar/MaStepProgressTopAppBar.kt (1)
37-49: 리뷰 코멘트가 부정확합니다. 현재 코드는 올바릅니다.Jetpack Compose의
clickable()수정자는interactionSource: MutableInteractionSource? = null로 정의되어 있으며, null 값 전달은 완전히 유효한 패턴입니다. 매개변수가 null을 허용하도록 설계되었으므로:
- 컴파일 오류 없음: 현재 코드는 컴파일되며, 저장소의 다른 두 파일(
MaTopAppBar.kt:39,SeniorHomeScreen.kt:79)도 동일한 패턴을 사용합니다.- NPE 위험 없음: Compose는 null 값을 받으면 필요시 내부
MutableInteractionSource를 지연 생성합니다.- 설계된 동작: null을 전달하면 상호작용 메커니즘을 미리 할당하지 않아 성능 최적화됩니다.
remember { MutableInteractionSource() }를 사용해야 하는 경우는 다음 중 하나일 때만입니다:
- 다중 수정자 간 상호작용 상태 공유
clickable외부에서 상호작용 관찰- 누르기 상태(
isPressedAsState) 수집현재 코드는 이러한 경우에 해당하지 않으므로, 제안된 수정이 필요하지 않습니다.
Likely an incorrect or invalid review comment.
gradle/libs.versions.toml (1)
31-32: kotlinx-collections-immutable / javax.inject 버전 카탈로그 추가 적절해 보입니다.도메인/feature 모듈에서 사용하는 라이브러리를 버전 카탈로그로 올려둔 방향이 일관되고 괜찮습니다. 추후 다른 모듈에서도 같은 alias를 재사용할 수 있어 유지보수에도 도움이 될 것 같습니다.
Gradle sync 및 CI 빌드에서 새 의존성으로 인한 충돌이나 경고가 없는지만 한 번 확인해 주세요.
Also applies to: 53-54, 87-87, 112-113
feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/orientation/OrientationQuizViewModel.kt (1)
132-153: UI 상태를 sealed interface + 파생 프로퍼티로 모델링한 구조가 좋습니다.
OrientationQuizUiState를Loading/Error/Success로 나누고,Success안에서currentStep,totalSteps,isCheckingAnswer,isContinueButtonEnabled처럼 파생 값을 계산해 두어서 화면 쪽에서는 단순히 상태를 조회만 하면 되는 점이 인상적입니다.또한 도메인
Quiz를QuizUiModel로 분리해 둔 덕분에, 이후 도메인 모델이 바뀌더라도 UI에 필요한 필드만 유지하면서 변경 영향을 최소화할 수 있어 보입니다.Also applies to: 156-165
feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/component/QuizResultDialog.kt (1)
22-38: ResultDialogState 기반으로 다이얼로그를 표현한 구조가 명확합니다.
ResultDialogState.Hidden/Correct/Incorrect로 상태를 분리하고,QuizResultDialog에서 Hidden이면 바로 return 하는 패턴 덕분에 화면 쪽에서는 단순히 상태만 넘겨주면 되고, 다이얼로그 표시/비표시 로직이 컴포저블 내부로 깔끔히 캡슐화되어 있습니다.뒤로 가기나 바깥 터치로 닫히지 않게 막아 둔 것도, ViewModel에서 타이머를 돌려 자동으로 넘어가는 UX와 잘 맞는 설정 같습니다.
Also applies to: 82-87
Related issue 🛠
Work Description ✏️
Screenshot 📸
Screen_recording_20251114_102622.mp4
Uncompleted Tasks 😅
Summary by CodeRabbit
릴리스 노트