Skip to content

[Feature/#26] 데모버전 퀴즈 기능을 구현합니다.#27

Merged
wjdrjs00 merged 11 commits intodevelopfrom
feature/26-demo-quiz
Nov 14, 2025
Merged

[Feature/#26] 데모버전 퀴즈 기능을 구현합니다.#27
wjdrjs00 merged 11 commits intodevelopfrom
feature/26-demo-quiz

Conversation

@wjdrjs00
Copy link
Collaborator

@wjdrjs00 wjdrjs00 commented Nov 14, 2025

Related issue 🛠

Work Description ✏️

  • 데모 버전용 퀴즈(지남력)를 구현했습니다.
    • 해당 pr에서는 데모버전이기에 클라이언트사이드로 구현했지만 서버api 요청으로 변경할 예정입니다.
    • 퀴즈 도메인은 지남력 퀴즈 기준으로 돌아가도록 임의로 구현했습니다. (이 또한 변경예정)

Screenshot 📸

Screen_recording_20251114_102622.mp4

Uncompleted Tasks 😅

  • 로딩 뷰 배경 설정 및 분기 처리
  • 퀴즈 도메인(카테고리를 고려한 설계)
  • 결과 다이얼로그 애니메이션 구현
  • 종료 다이얼로그 구현
  • 퀴즈 완료처리
  • tts

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능
    • 시니어 홈 화면 추가 - 일일 퀴즈 및 퀴즈 접근 가능
    • 퀴즈 카테고리 선택 화면 구현
    • 단계별 진행 표시기를 포함한 오리엔테이션 퀴즈 화면 추가
    • 대화형 퀴즈 버튼 - 선택/미선택 상태 시각적 피드백
    • 퀴즈 결과 확인 팝업 (정답/오답 표시)
    • 화면 간 자연스러운 네비게이션

@wjdrjs00 wjdrjs00 self-assigned this Nov 14, 2025
@coderabbitai
Copy link

coderabbitai bot commented Nov 14, 2025

Walkthrough

시니어 사용자를 위한 오리엔테이션 퀴즈 기능을 구현합니다. 새로운 UI 컴포넌트(MaQuizButton, StepIndicator), 도메인 모델(Quiz, Quizzes) 및 유스케이스를 추가하고, 오리엔테이션 퀴즈 화면, ViewModel, 네비게이션 라우트를 추가합니다.

Changes

Cohort / File(s) 변경 요약
디자인 시스템 컴포넌트
core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/core/button/MaQuizButton.kt, core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/core/indicator/StepIndicator.kt, core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/product/topbar/MaStepProgressTopAppBar.kt
퀴즈 상호작용 UI를 위한 새로운 컴포넌트 추가: 상태 기반 버튼(MaQuizButton), 진행 상황 표시(StepIndicator), 진행 상황 표시 상단 앱 바(MaStepProgressTopAppBar)
도메인 모델 및 유스케이스
domain/src/main/kotlin/com/moa/app/domain/quiz/model/Quiz.kt, domain/src/main/kotlin/com/moa/app/domain/quiz/model/Quizzes.kt, domain/src/main/kotlin/com/moa/app/domain/quiz/usecase/CheckAnswerUseCase.kt, domain/src/main/kotlin/com/moa/app/domain/quiz/usecase/FetchOrientationQuizUseCase.kt
퀴즈 데이터 모델(Quiz, Quizzes) 및 비즈니스 로직 유스케이스(CheckAnswerUseCase, FetchOrientationQuizUseCase) 추가
시니어 홈 기능
feature/senior/src/main/kotlin/com/moa/app/feature/senior/home/SeniorHomeScreen.kt, feature/senior/src/main/kotlin/com/moa/app/feature/senior/home/SeniorHomeViewModel.kt
Hilt 기반 ViewModel 통합 및 퀴즈 네비게이션 콜백 구현
퀴즈 카테고리 기능
feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/category/QuizCategory.kt, feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/category/QuizCategoryScreen.kt, feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/category/QuizCategoryViewModel.kt
패키지 구조 정리 및 ViewModel 기반 카테고리 선택 네비게이션 구현
퀴즈 컴포넌트
feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/component/QuizLoadContent.kt, feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/component/QuizResultDialog.kt
퀴즈 로딩 및 결과 표시 UI 컴포넌트 추가
오리엔테이션 퀴즈 기능
feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/orientation/OrientationQuizScreen.kt, feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/orientation/OrientationQuizViewModel.kt
오리엔테이션 퀴즈 완전 구현: UI, 상태 관리, 사용자 상호작용
네비게이션 및 라우팅
core/navigation/src/main/java/com/moa/app/navigation/AppRoute.kt, app/src/main/kotlin/com/moa/app/main/MainActivity.kt
3개의 새로운 라우트(SeniorHome, QuizCategory, OrientationQuiz) 추가 및 네비게이션 그래프 연결
의존성 관리
domain/build.gradle.kts, feature/senior/build.gradle.kts, gradle/libs.versions.toml
불변 컬렉션(kotlinx-collections-immutable) 및 의존성 주입(javax.inject) 라이브러리 추가

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
Loading
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • OrientationQuizViewModel의 상태 관리 로직, 애니메이션 및 네비게이션 흐름 검토 필요
  • OrientationQuizScreen의 복잡한 UI 상태 처리 및 AnimatedContent 전환 로직 검토 필요
  • QuizResultDialog의 상태 정의 및 동작 흐름 검토 필요
  • 각 ViewModel의 의존성 주입 및 네비게이션 콜백 체인 검증 필요
  • 도메인 모델의 불변 컬렉션 사용 패턴 및 유스케이스 설계 검토 필요

Possibly related PRs

Poem

🐰 퀴즈 기능이 쏘옥!

시니어 어르신을 위해
스텝 표시와 버튼들이 춤을 추고
오리엔테이션 퀴즈가 반짝반짝 😨
정답에 웃음, 오답에 배움
모아의 길은 더욱 예뻐지네! ✨

Pre-merge checks and finishing touches

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 데모버전 퀴즈 기능 구현이라는 주요 변경사항을 명확하게 반영하며, 이슈 번호 #26과도 일치합니다.
Description check ✅ Passed PR 설명은 템플릿의 필수 섹션(관련 이슈, 작업 설명, 스크린샷, 미완료 작업)을 모두 포함하고 있습니다.
Linked Issues check ✅ Passed 변경사항이 연결된 이슈 #26의 요구사항(데모버전 퀴즈 및 지남력 퀴즈 구현)을 충족합니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 이슈 #26의 범위 내에 있으며, 퀴즈 기능 구현에 필요한 관련 컴포넌트와 도메인 계층 변경만 포함됩니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/26-demo-quiz

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

@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: 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를 명시적으로 생성하고 있지만, 다른 곳에서 사용되지 않는다면 기본 동작만으로 충분합니다. clickable modifier에서 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는 QuizCategoryScreenContentonQuizCategoryClick 콜백만 의존하도록 분리한 구조가 명확해서 테스트/미리보기에도 유리해 보입니다.

다만 이후 카테고리가 서버나 도메인에서 동적으로 내려오는 구조로 확장될 가능성이 있다면, 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초 delayfetchOrientationQuizUseCase() 결과를 그대로 사용하고 있는데, 실제 서버 연동으로 변경될 경우 네트워크 오류 등으로 예외가 던져지면 Loading 상태에서 멈출 수 있습니다.

  • try/catch로 예외를 감싸서 OrientationQuizUiState.Error로 전환하거나
  • use case 쪽에서 Result/Either 형태로 성공/실패를 표현하도록 한 뒤, 여기서 분기

같은 패턴을 넣어두면 이후 서버 연동 시에도 안정적으로 동작할 것 같습니다. 또한 2초 지연이 데모용 연출이라면 상수로 추출하거나 TODO 코멘트를 남겨둬도 유지보수에 도움이 됩니다.


56-91: 답안 선택/채점 흐름이 명확하지만 null 선택 방어 로직을 고려해 볼 수 있습니다.

selectAnswer에서 isCheckingAnswer를 기준으로 중복 입력을 막고, checkAnswer에서 도메인 Quiz를 가져와 ResultDialogState로 변환하는 흐름은 이해하기 쉽습니다.

다만 checkAnswer 내부에서 selectedAnswerIndexnull인 경우에도 그대로 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

📥 Commits

Reviewing files that changed from the base of the PR and between d4410fc and 3d11ee7.

📒 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 속성이 @Composable getter를 가지고 있습니다. 현재는 상수 값만 반환하지만, 이 패턴은 향후 테마나 컴포지션 로컬에 접근해야 할 경우를 대비한 것으로 보입니다.

이는 유효한 패턴이며 유연성을 제공합니다.

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를 호출하지 않고, CheckAnswerUseCaseinvoke에 위임하는 패턴이라 이후 채점 로직이 복잡해져도 변경 범위가 도메인에 국한될 것 같습니다. 현재는 단순 위임이지만, 확장성을 생각하면 이 레이어를 미리 둔 선택이 좋아 보입니다.

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 + 파생 프로퍼티로 모델링한 구조가 좋습니다.

OrientationQuizUiStateLoading/Error/Success로 나누고, Success 안에서 currentStep, totalSteps, isCheckingAnswer, isContinueButtonEnabled처럼 파생 값을 계산해 두어서 화면 쪽에서는 단순히 상태를 조회만 하면 되는 점이 인상적입니다.

또한 도메인 QuizQuizUiModel로 분리해 둔 덕분에, 이후 도메인 모델이 바뀌더라도 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] 데모버전 퀴즈 기능을 구현합니다.

1 participant