-
Notifications
You must be signed in to change notification settings - Fork 0
[Feature/#77] Quiz Content UX를 개선합니다. #78
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Walkthrough새로운 Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Activity
participant TerminateOnDoubleBack
participant BackHandler
participant Toast
participant LaunchedEffect
User->>Activity: 뒤로 버튼 누름 (1)
Activity->>TerminateOnDoubleBack: Back event 전달
TerminateOnDoubleBack->>BackHandler: 첫번째 처리 (상태 설정)
BackHandler->>Toast: 토스트 표시 (메시지)
TerminateOnDoubleBack->>LaunchedEffect: backPressedOnce 타이머 시작
User->>Activity: 뒤로 버튼 누름 (2) (within delay)
Activity->>TerminateOnDoubleBack: Back event 전달
TerminateOnDoubleBack->>BackHandler: 두번째 처리 (finish 호출)
BackHandler->>Activity: finish() 호출
Activity->>User: 앱 종료
Note over LaunchedEffect: delay 경과 시 backPressedOnce 초기화
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~30 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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.
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 (1)
feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/component/QuizDescription.kt (1)
50-56:alignImageEnd = true일 때 패딩 방향 확인 필요
padding(start = 44.dp)가 항상 적용되어,alignImageEnd = true로 이미지가 오른쪽 정렬될 때도 왼쪽에서 44.dp 패딩이 유지됩니다. 의도된 동작인지 확인이 필요합니다.대칭적인 디자인을 원한다면 아래와 같이 조건부 패딩을 고려해 주세요:
💡 조건부 패딩 적용 제안
Image( painter = painterResource(R.drawable.img_quiz_character_top), contentDescription = null, modifier = Modifier - .padding(start = 44.dp) + .padding( + start = if (alignImageEnd) 0.dp else 44.dp, + end = if (alignImageEnd) 44.dp else 0.dp + ) .align(alignment = alignment) )
🤖 Fix all issues with AI agents
In
@feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/daily/DailyQuizScreen.kt:
- Around line 187-202: The "Continue" button is shown when uiState.currentQuiz
is null because the condition uses "uiState.currentQuiz !is MemoryQuiz" which is
true for null; update the check to ensure currentQuiz is non-null and not a
MemoryQuiz before rendering MaButton (e.g., use "uiState.currentQuiz != null &&
uiState.currentQuiz !is MemoryQuiz" or "uiState.currentQuiz?.let { it !is
MemoryQuiz } == true") and keep the existing properties (onContinueClick,
enabled, modifiers, Text) unchanged so the button only appears when a quiz is
loaded and it's not a MemoryQuiz.
In
@feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/linguistic/LinguisticQuizScreen.kt:
- Around line 116-119: Add an isContinueButtonEnabled Boolean to
LinguisticQuizUiState and use it instead of the hardcoded enabled = true in
LinguisticQuizScreen's MaButton (replace enabled = true with enabled =
uiState.isContinueButtonEnabled). Update the state initialization and the
answer-selection logic (e.g., the handler that marks an option selected in your
ViewModel/Composables) to set isContinueButtonEnabled = true when a valid answer
is chosen and false otherwise, following the same pattern used by
AttentionQuizScreen/DailyQuizScreen/SpaceTimeQuizScreen/PersistenceQuizScreen.
In
@feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/memory/component/MemoryQuizTextModeContent.kt:
- Line 36: The code defines a fixed-size val labels = listOf("첫번째", "두번째",
"세번째") but later indexes it using the answers list (labels[index]), risking
IndexOutOfBounds when answers.size > 3; update MemoryQuizTextModeContent so you
either (a) guard when accessing labels by using labels.getOrNull(index) ?:
fallbackLabel (or compute a label from index) wherever labels[index] is used, or
(b) enforce/validate that answers.size <= labels.size before mapping (throw or
truncate) so callers cannot pass more than three answers—locate the labels
declaration and all usages of labels[index] (the mapping logic around answers)
and apply one of these fixes consistently.
In
@feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/spacetime/SpaceTimeQuizScreen.kt:
- Around line 147-155: The preview data uses the wrong quiz category: change the
SpaceTimeQuiz instance's type property from QuizCategory.ATTENTION to
QuizCategory.SPACETIME so the preview reflects the correct category; locate the
SpaceTimeQuiz constructor call (id = 1, questionImageUrl = "", imageOptionsUrl =
persistentListOf("", ""), type = QuizCategory.ATTENTION, ...) and replace the
type value with QuizCategory.SPACETIME.
🧹 Nitpick comments (5)
core/ui/src/main/java/com/moa/app/ui/extension/Modifier.kt (1)
25-25: 매직 넘버428.dp에 대한 문서화 권장
quizMaxWidth()의 최대 너비 값이 하드코딩되어 있습니다. 이 값의 의도(예: 특정 foldable 기기 기준 또는 디자인 가이드라인)를 명확히 하기 위해 상수로 추출하거나 KDoc 주석을 추가하는 것을 권장합니다.♻️ 상수 추출 예시
+private val QuizMaxWidthDp = 428.dp + -fun Modifier.quizMaxWidth(): Modifier = this.then(Modifier.widthIn(max = 428.dp)) +/** + * Quiz 콘텐츠의 최대 너비를 제한합니다. + * Foldable 기기에서 콘텐츠가 과도하게 넓어지는 것을 방지합니다. + */ +fun Modifier.quizMaxWidth(): Modifier = this.then(Modifier.widthIn(max = QuizMaxWidthDp))feature/guardian/src/main/kotlin/com/moa/app/feature/guardian/home/GuardianHomeScreen.kt (1)
105-110:clickableWithoutRipple사용 고려이 파일에서 ripple 없이 클릭 처리를 하는 곳이 여러 군데 있습니다.
MaHomeTopBar에서 사용한 것처럼clickableWithoutRipple확장 함수를 사용하면 코드 일관성이 향상됩니다.♻️ 리팩토링 예시
Image( imageVector = ImageVector.vectorResource(R.drawable.ic_bell), contentDescription = null, modifier = Modifier - .clickable( - onClick = onAlertClick, - role = Role.Button, - interactionSource = null, - indication = null - ) + .clickableWithoutRipple(onClick = onAlertClick, role = Role.Button) .padding(8.dp) )feature/senior/src/main/kotlin/com/moa/app/feature/senior/home/SeniorHomeScreen.kt (1)
219-222: 클릭 처리 방식 불일치Line 173-178의 "퀴즈" 박스는
ripple()을 명시적으로 사용하지만, "리포트" 박스는 기본 indication을 사용합니다. 두 박스의 시각적 피드백이 동일해야 한다면 일관된 방식으로 통일하는 것이 좋습니다.♻️ 일관성 개선 예시
Box( modifier = Modifier .weight(1f) .clip(RoundedCornerShape(24.dp)) .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = ripple(), onClick = onReportClick, role = Role.Button, ) .background(MoaTheme.colors.orange500) .padding(start = 16.dp) )feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/component/quizform/SpaceTimeQuizForm.kt (1)
84-87: Preview 코드에서 non-null assertion(!!) 사용에 주의하세요.
ContextCompat.getDrawable()이null을 반환할 수 있으나, 디자인 시스템의 리소스이므로 Preview 컨텍스트에서는 허용 가능합니다. 다만, 여러 Preview 파일에서 동일한 패턴이 반복되므로 공통 유틸리티로 추출하는 것을 고려해 보세요.♻️ 공통 Preview Handler 유틸리티 제안
core/ui모듈에 공통 Preview Handler를 추가하면 중복을 줄일 수 있습니다:// core/ui/.../preview/DefaultAsyncImagePreviewHandler.kt @OptIn(ExperimentalCoilApi::class) fun createDefaultPreviewHandler(@DrawableRes drawableRes: Int): AsyncImagePreviewHandler { return AsyncImagePreviewHandler { request -> ContextCompat.getDrawable(request.context, drawableRes)?.asImage() ?: error("Drawable resource not found") } }feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/component/QuizDescription.kt (1)
139-147: Preview 함수 네이밍 일관성 개선 제안다른 Preview 함수들(
PreviewTopQuizDescription,PreviewBottomQuizDescription)과 달리 이 함수만Preview()로 명명되어 있습니다. 일관성을 위해PreviewCenterQuizDescription으로 변경을 권장합니다.♻️ 네이밍 변경 제안
@Preview(showBackground = true) @PreviewFontScale @Composable -private fun Preview() { +private fun PreviewCenterQuizDescription() { CenterQuizDescription( quizDescription = "아래의 그림은\n무엇일까요?", onImageClick = {}, ) }
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (30)
app/build.gradle.ktscore/designsystem/build.gradle.ktscore/designsystem/src/main/kotlin/com/moa/app/designsystem/component/product/topbar/MaHomeTopBar.ktcore/ui/.gitignorecore/ui/build.gradle.ktscore/ui/consumer-rules.procore/ui/proguard-rules.procore/ui/src/main/AndroidManifest.xmlcore/ui/src/main/java/com/moa/app/ui/TerminateOnDoubleBack.ktcore/ui/src/main/java/com/moa/app/ui/extension/Modifier.ktcore/ui/src/main/java/com/moa/app/ui/preview/FoldablePreviews.ktfeature/guardian/build.gradle.ktsfeature/guardian/src/main/kotlin/com/moa/app/feature/guardian/home/GuardianHomeScreen.ktfeature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/signup/SignUpProfileScreen.ktfeature/senior/build.gradle.ktsfeature/senior/src/main/kotlin/com/moa/app/feature/senior/home/SeniorHomeScreen.ktfeature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/attention/AttentionQuizScreen.ktfeature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/component/QuizDescription.ktfeature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/component/QuizLoadContent.ktfeature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/component/quizform/AttentionQuizForm.ktfeature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/component/quizform/LinguisticQuizForm.ktfeature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/component/quizform/PersistenceQuizForm.ktfeature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/component/quizform/SpaceTimeQuizForm.ktfeature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/daily/DailyQuizScreen.ktfeature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/linguistic/LinguisticQuizScreen.ktfeature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/memory/MemoryQuizScreen.ktfeature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/memory/component/MemoryQuizTextModeContent.ktfeature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/persistence/PersistenceQuizScreen.ktfeature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/spacetime/SpaceTimeQuizScreen.ktsettings.gradle.kts
💤 Files with no reviewable changes (1)
- feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/component/quizform/AttentionQuizForm.kt
🧰 Additional context used
🧬 Code graph analysis (7)
feature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/signup/SignUpProfileScreen.kt (1)
core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/core/button/MaSelectButton.kt (1)
MaSelectButton(30-64)
feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/spacetime/SpaceTimeQuizScreen.kt (3)
feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/component/QuizSlideAnimation.kt (1)
QuizSlideAnimation(18-43)feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/component/quizform/SpaceTimeQuizForm.kt (1)
SpaceTimeQuizForm(29-78)core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/core/button/MaButton.kt (1)
MaButton(30-68)
feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/attention/AttentionQuizScreen.kt (3)
feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/component/QuizSlideAnimation.kt (1)
QuizSlideAnimation(18-43)feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/component/quizform/AttentionQuizForm.kt (1)
AttentionQuizForm(14-40)core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/core/button/MaButton.kt (1)
MaButton(30-68)
feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/persistence/PersistenceQuizScreen.kt (3)
feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/component/QuizSlideAnimation.kt (1)
QuizSlideAnimation(18-43)feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/component/quizform/PersistenceQuizForm.kt (1)
PersistenceQuizForm(18-54)core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/core/button/MaButton.kt (1)
MaButton(30-68)
feature/senior/src/main/kotlin/com/moa/app/feature/senior/home/SeniorHomeScreen.kt (1)
core/ui/src/main/java/com/moa/app/ui/TerminateOnDoubleBack.kt (1)
TerminateOnDoubleBack(15-40)
feature/guardian/src/main/kotlin/com/moa/app/feature/guardian/home/GuardianHomeScreen.kt (1)
core/ui/src/main/java/com/moa/app/ui/TerminateOnDoubleBack.kt (1)
TerminateOnDoubleBack(15-40)
feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/component/quizform/PersistenceQuizForm.kt (1)
feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/component/QuizDescription.kt (1)
TopQuizDescription(26-58)
⏰ 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 ktlint
- GitHub Check: Run Unit Tests
🔇 Additional comments (36)
feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/memory/component/MemoryQuizTextModeContent.kt (1)
39-39: LGTM!Foldable 기기 지원을 위한 수평 패딩 추가가 적절합니다. AI 요약에 따르면 상위 컨테이너에서
quizMaxWidth()와 중앙 정렬이 적용되므로, 이 패딩이 내부 콘텐츠 여백으로 잘 작동할 것입니다.core/ui/.gitignore (1)
1-1: LGTM!새로운 core:ui 모듈에 대한 표준 .gitignore 항목입니다.
core/ui/src/main/AndroidManifest.xml (1)
1-4: LGTM!Android 라이브러리 모듈에 필요한 최소한의 매니페스트 파일입니다. 네임스페이스는 build.gradle.kts에서 정의되므로 적절합니다.
settings.gradle.kts (1)
39-39: LGTM!core:ui 모듈이 프로젝트 구조에 올바르게 포함되었습니다. 다른 core 모듈들과 일관된 위치에 배치되어 있습니다.
core/ui/build.gradle.kts (1)
1-13: LGTM!core:ui 모듈의 Gradle 설정이 적절합니다. 커스텀 플러그인을 통한 라이브러리 및 Compose 설정, 명확한 네임스페이스 정의, 그리고 최소한의 필수 의존성만 포함하고 있습니다.
core/ui/proguard-rules.pro (1)
1-21: LGTM!표준 ProGuard 템플릿 파일입니다. 현재는 활성화된 규칙이 없으며, 필요 시 추후 커스터마이징할 수 있습니다.
feature/onboarding/src/main/kotlin/com/moa/app/feature/onboarding/signup/SignUpProfileScreen.kt (1)
224-246: LGTM!성별 버튼 순서 변경이 올바르게 구현되었습니다. 각 버튼의
onClick,selected상태, 그리고 표시 텍스트가 일관되게 매핑되어 있습니다. 왼쪽에 남성, 오른쪽에 여성 버튼을 배치하는 것은 한국어 UI에서 일반적인 패턴입니다.core/ui/src/main/java/com/moa/app/ui/preview/FoldablePreviews.kt (1)
5-15: Foldable 디바이스 프리뷰 어노테이션이 정확하게 구현되었습니다.Multi-preview 어노테이션 패턴을 사용하여 Z Fold 6의 펼침/접힘 상태를 한 번에 프리뷰할 수 있어 개발 효율성이 향상됩니다. 디바이스 스펙이 Samsung Galaxy Z Fold 6의 공식 사양과 정확히 일치합니다:
- 펼침 상태 (메인 디스플레이): 795dp × 925dp, 374 dpi
- 접힘 상태 (커버 디스플레이): 378dp × 927dp, 410 dpi
feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/attention/AttentionQuizScreen.kt (2)
97-111:currentQuiz가 null일 때 빈 공간만 남는 점 확인 필요
currentQuiz가 null일 경우QuizSlideAnimation과AttentionQuizForm이 렌더링되지 않아 빈 공간만 남게 됩니다. 이 상태에서MaButton은 여전히 표시되지만, 사용자 입장에서 어떤 퀴즈도 없는 상태로 "계속" 버튼이 보이는 UX가 적절한지 확인해 주세요.
128-150: Foldable 기기 대응 Preview 추가 👍
@FoldablePreviews어노테이션을 활용하여 접이식 기기에서의 레이아웃을 미리 확인할 수 있도록 개선되었습니다.core/ui/src/main/java/com/moa/app/ui/extension/Modifier.kt (1)
10-23:clickableWithoutRipple확장 함수가 잘 구현되었습니다.ripple 효과 없이 클릭 가능한 modifier를 중앙화하여 코드 중복을 줄였습니다.
indication = null과 함께interactionSource를 nullable로 처리하는 방식은 Compose 1.6+ API와 호환됩니다.core/designsystem/src/main/kotlin/com/moa/app/designsystem/component/product/topbar/MaHomeTopBar.kt (1)
53-54:clickableWithoutRipple적용 완료설정 아이콘의 클릭 처리가 새로운 공통 유틸리티 함수로 마이그레이션되었습니다.
Role.Button이 적절히 전달되어 접근성이 유지됩니다.feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/memory/MemoryQuizScreen.kt (1)
97-143:quizMaxWidth()적용으로 Foldable 기기 대응 완료다른 퀴즈 화면(AttentionQuizScreen 등)과 일관된 패턴으로
quizMaxWidth()와CenterHorizontally정렬이 적용되었습니다. 콘텐츠 너비 제한으로 접이식 기기에서의 UX가 개선됩니다.core/ui/src/main/java/com/moa/app/ui/TerminateOnDoubleBack.kt (2)
25-30:LaunchedEffect키 사용 패턴 확인
backPressedOnce가true로 변경될 때마다LaunchedEffect가 재시작됩니다. 사용자가 딜레이 내에 연속으로 백 버튼을 누르면 코루틴이 취소되고 재시작되어 의도한 대로 동작합니다.다만,
backPressedOnce가false로 리셋될 때도LaunchedEffect가 재실행되는데, 조건문으로 인해 실제 동작은 없으므로 문제없습니다.
15-39: 공통 컴포저블로 뒤로가기 종료 로직 중앙화 👍
TerminateOnDoubleBack컴포저블이 여러 화면에서 재사용될 수 있도록 잘 설계되었습니다.enabled,delayMillis,message파라미터를 통해 유연성을 제공합니다.향후 다국어 지원을 고려한다면
message기본값을 string resource로 변경하는 것을 권장합니다.feature/guardian/src/main/kotlin/com/moa/app/feature/guardian/home/GuardianHomeScreen.kt (1)
48-48:TerminateOnDoubleBack적용 완료기존의 커스텀
BackPressHandler로직이 중앙화된TerminateOnDoubleBack컴포저블로 대체되어 코드 중복이 제거되었습니다.feature/senior/src/main/kotlin/com/moa/app/feature/senior/home/SeniorHomeScreen.kt (1)
55-55:TerminateOnDoubleBack적용 완료Senior 홈 화면에서도 중앙화된 뒤로가기 종료 로직이 적용되어 Guardian 홈 화면과 일관된 동작을 보장합니다.
feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/daily/DailyQuizScreen.kt (2)
110-116: Foldable 디바이스 지원을 위한 레이아웃 구조 개선이 잘 되었습니다.
quizMaxWidth()확장 함수와CenterHorizontally정렬을 활용하여 Foldable 기기에서 콘텐츠가 중앙에 적절한 너비로 표시되도록 구조화되었습니다.
118-185: 이 리뷰 의견은 잘못되었습니다. Quiz는 sealed interface이므로 현재 when 분기는 이미 exhaustive하며, else 처리가 필요하지 않습니다.sealed interface에서는 컴파일러가 모든 구현 타입을 추적하기 때문에, 5가지 퀴즈 타입(PersistenceQuiz, LinguisticQuiz, MemoryQuiz, AttentionQuiz, SpaceTimeQuiz)을 모두 처리한 현재 코드가 완전합니다.
Likely an incorrect or invalid review comment.
feature/guardian/build.gradle.kts (1)
14-14: core.ui 모듈 의존성 추가가 적절합니다.Guardian 모듈에서 core.ui 유틸리티(TerminateOnDoubleBack, clickableWithoutRipple 등)를 활용할 수 있도록 의존성이 추가되었습니다.
app/build.gradle.kts (1)
82-82: core.ui 의존성 추가가 적절합니다.앱 모듈에서 core.ui 유틸리티를 직접 사용할 수 있도록 명시적으로 의존성이 추가되었습니다.
core.designsystem을 통해 전이적으로도 의존하지만, 명시적 선언은 의도를 명확히 합니다.feature/senior/build.gradle.kts (1)
14-14: core.ui 의존성이 Senior 모듈 리팩토링에 필요합니다.
quizMaxWidth,clickableWithoutRipple,TerminateOnDoubleBack등 core.ui 유틸리티가 Senior 퀴즈 화면에서 사용되므로 의존성 추가가 적절합니다.core/designsystem/build.gradle.kts (1)
11-11: 순환 의존성 확인 완료 - 문제 없음
core.ui모듈이core.designsystem을 의존하지 않으므로 순환 의존성이 발생하지 않습니다. 의존성 방향이 한 방향(designsystem→ui)으로만 설정되어 있어 빌드 오류의 위험이 없습니다.feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/component/QuizLoadContent.kt (3)
48-55: 레이아웃 수정이 잘 적용되었습니다.
quizMaxWidth()적용 및Alignment.Center정렬을 통해 Foldable 기기에서의 UX가 개선되었습니다. modifier 체인 순서(quizMaxWidth()→fillMaxSize()→align())도 적절합니다.
62-68: clip과 background 순서가 올바릅니다.
clip(RoundedCornerShape(32.dp))이후background(MoaTheme.colors.white)순서로 적용하여 라운드 코너가 정상적으로 적용됩니다.
89-96: Preview 함수에@FoldablePreviews추가 승인.Foldable 기기 프리뷰 지원이 PR 목표에 부합합니다.
feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/component/quizform/SpaceTimeQuizForm.kt (1)
37-37: 레이아웃 수정 승인.외부에서 전달받은
modifier를 직접 사용하도록 변경하여 재사용성이 향상되었습니다.feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/component/quizform/PersistenceQuizForm.kt (1)
26-30:TopQuizDescription컴포넌트로의 리팩토링 승인.기존
Box와Image조합을 재사용 가능한TopQuizDescription컴포넌트로 대체하여 코드 중복이 줄었습니다.alignImageEnd = true설정으로 기존 동작을 유지합니다.feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/component/quizform/LinguisticQuizForm.kt (2)
38-38: 외부 modifier 직접 사용으로 유연성 향상.하드코딩된 horizontal padding을 제거하고 외부에서 전달받은
modifier를 직접 사용하여 호출처에서 레이아웃을 제어할 수 있게 되었습니다.
51-51: 유연한 레이아웃을 위한weight(1f)사용 승인.고정 높이 대신
weight(1f)를 사용하여 다양한 화면 크기에서 적응형 레이아웃이 가능해졌습니다.feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/linguistic/LinguisticQuizScreen.kt (1)
95-127: 레이아웃 구조 개선 승인.
quizMaxWidth()와Alignment.CenterHorizontally를 적용하여 Foldable 기기에서의 UX가 개선되었습니다.LinguisticQuizForm으로의 위임도 적절합니다.feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/spacetime/SpaceTimeQuizScreen.kt (1)
97-129: 레이아웃 및SpaceTimeQuizForm위임 승인.
quizMaxWidth()와 중앙 정렬 적용,SpaceTimeQuizForm으로의 위임이 올바르게 구현되었습니다.feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/persistence/PersistenceQuizScreen.kt (2)
87-119: 레이아웃 구조 및QuizSlideAnimation적용 승인.
quizMaxWidth()와 중앙 정렬,QuizSlideAnimation내부에서PersistenceQuizForm렌더링이 올바르게 구현되었습니다.MaButton의enabled상태도uiState.isContinueButtonEnabled를 사용하여 일관성이 있습니다.
123-143: Preview 개선 승인.
@FoldablePreviews추가 및 간소화된 샘플 데이터로 Preview가 개선되었습니다.feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/component/QuizDescription.kt (2)
66-93: LGTM!변경 사항이 적절합니다:
verticalAlignment = Alignment.CenterVertically로 Row 내 수직 정렬 개선clip()→background()순서로 올바른 모디파이어 체이닝weight(1f)와defaultMinSize로 유연한 레이아웃 구성
96-128: LGTM!
clip()→background()순서 적용 및clickableWithoutRipple사용이 다른 컴포넌트와 일관성 있게 적용되었습니다.
| if (uiState.currentQuiz !is MemoryQuiz) { | ||
| MaButton( | ||
| onClick = onContinueClick, | ||
| enabled = uiState.isContinueButtonEnabled, | ||
| modifier = Modifier | ||
| .fillMaxWidth() | ||
| .padding(horizontal = 20.dp) | ||
| .padding(bottom = 12.dp), | ||
| ) { | ||
| Text( | ||
| text = "계속", | ||
| style = MoaTheme.typography.body1Bold, | ||
| modifier = Modifier.padding(vertical = 16.dp, horizontal = 20.dp), | ||
| ) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
currentQuiz가 null일 때 버튼 표시 여부를 확인하세요.
Line 118에서 currentQuiz?.let으로 null 체크를 하지만, Line 187의 조건문 uiState.currentQuiz !is MemoryQuiz는 currentQuiz가 null일 때도 true를 반환합니다. 따라서 퀴즈가 로드되지 않은 상태에서도 "계속" 버튼이 표시될 수 있습니다.
제안된 수정
- if (uiState.currentQuiz !is MemoryQuiz) {
+ if (uiState.currentQuiz != null && uiState.currentQuiz !is MemoryQuiz) {📝 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.
| if (uiState.currentQuiz !is MemoryQuiz) { | |
| MaButton( | |
| onClick = onContinueClick, | |
| enabled = uiState.isContinueButtonEnabled, | |
| modifier = Modifier | |
| .fillMaxWidth() | |
| .padding(horizontal = 20.dp) | |
| .padding(bottom = 12.dp), | |
| ) { | |
| Text( | |
| text = "계속", | |
| style = MoaTheme.typography.body1Bold, | |
| modifier = Modifier.padding(vertical = 16.dp, horizontal = 20.dp), | |
| ) | |
| } | |
| } | |
| if (uiState.currentQuiz != null && uiState.currentQuiz !is MemoryQuiz) { | |
| MaButton( | |
| onClick = onContinueClick, | |
| enabled = uiState.isContinueButtonEnabled, | |
| modifier = Modifier | |
| .fillMaxWidth() | |
| .padding(horizontal = 20.dp) | |
| .padding(bottom = 12.dp), | |
| ) { | |
| Text( | |
| text = "계속", | |
| style = MoaTheme.typography.body1Bold, | |
| modifier = Modifier.padding(vertical = 16.dp, horizontal = 20.dp), | |
| ) | |
| } | |
| } |
🤖 Prompt for AI Agents
In
@feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/daily/DailyQuizScreen.kt
around lines 187 - 202, The "Continue" button is shown when uiState.currentQuiz
is null because the condition uses "uiState.currentQuiz !is MemoryQuiz" which is
true for null; update the check to ensure currentQuiz is non-null and not a
MemoryQuiz before rendering MaButton (e.g., use "uiState.currentQuiz != null &&
uiState.currentQuiz !is MemoryQuiz" or "uiState.currentQuiz?.let { it !is
MemoryQuiz } == true") and keep the existing properties (onContinueClick,
enabled, modifiers, Text) unchanged so the button only appears when a quiz is
loaded and it's not a MemoryQuiz.
| MaButton( | ||
| onClick = onContinueClick, | ||
| enabled = true, | ||
| modifier = Modifier.fillMaxWidth() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 1. Search for isContinueButtonEnabled in the linguistic module
echo "=== Searching for isContinueButtonEnabled in linguistic module ==="
rg -n "isContinueButtonEnabled" --type=kotlin -g "**/linguistic/**"
# 2. Check the LinguisticQuizUiState file
echo -e "\n=== Finding LinguisticQuizUiState file ==="
fd "LinguisticQuizUiState" --type=kotlin
# 3. Compare with other quiz screens
echo -e "\n=== Checking SpaceTimeQuizScreen for enabled property ==="
fd "SpaceTimeQuizScreen" --type=kotlin
echo -e "\n=== Checking PersistenceQuizScreen for enabled property ==="
fd "PersistenceQuizScreen" --type=kotlinRepository: team-memory-care/MoA-Android
Length of output: 841
🏁 Script executed:
#!/bin/bash
# 1. Search for isContinueButtonEnabled across the codebase
echo "=== Searching for isContinueButtonEnabled ==="
rg "isContinueButtonEnabled" -A 2 -B 2
# 2. Find LinguisticQuizScreen.kt
echo -e "\n=== Finding LinguisticQuizScreen.kt ==="
find . -name "LinguisticQuizScreen.kt" -type f
# 3. Find LinguisticQuizUiState
echo -e "\n=== Finding LinguisticQuizUiState ==="
find . -name "*LinguisticQuiz*" -type f | grep -i uistate
# 4. Find all quiz screens
echo -e "\n=== Finding all quiz screens ==="
find . -name "*QuizScreen.kt" -type f | grep -E "(Linguistic|SpaceTime|Persistence)"
# 5. Check structure of quiz directory
echo -e "\n=== Quiz directory structure ==="
find . -path "*/quiz/*" -name "*.kt" | head -30Repository: team-memory-care/MoA-Android
Length of output: 8560
🏁 Script executed:
#!/bin/bash
# Check if LinguisticQuizUiState has isContinueButtonEnabled
echo "=== LinguisticQuizUiState.kt content ==="
cat -n ./feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/linguistic/model/LinguisticQuizUiState.kt
echo -e "\n=== LinguisticQuizScreen.kt lines 110-130 ==="
sed -n '110,130p' ./feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/linguistic/LinguisticQuizScreen.kt | cat -nRepository: team-memory-care/MoA-Android
Length of output: 2493
LinguisticQuizUiState에 isContinueButtonEnabled 속성을 추가하고 하드코딩된 enabled = true 제거
LinguisticQuizUiState.kt에는 다른 퀴즈 화면들(AttentionQuizScreen, DailyQuizScreen, SpaceTimeQuizScreen, PersistenceQuizScreen)에서 사용하는 isContinueButtonEnabled 속성이 없습니다. LinguisticQuizScreen.kt 116-119줄에서 enabled = true로 하드코딩되어 있으므로, 사용자가 답을 선택하지 않았을 때에도 계속 버튼이 활성화됩니다. 다른 퀴즈들처럼 LinguisticQuizUiState에 isContinueButtonEnabled 속성을 추가하고 이를 통해 선택된 답변 여부에 따라 버튼 활성화 상태를 제어해야 합니다.
🤖 Prompt for AI Agents
In
@feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/linguistic/LinguisticQuizScreen.kt
around lines 116 - 119, Add an isContinueButtonEnabled Boolean to
LinguisticQuizUiState and use it instead of the hardcoded enabled = true in
LinguisticQuizScreen's MaButton (replace enabled = true with enabled =
uiState.isContinueButtonEnabled). Update the state initialization and the
answer-selection logic (e.g., the handler that marks an option selected in your
ViewModel/Composables) to set isContinueButtonEnabled = true when a valid answer
is chosen and false otherwise, following the same pattern used by
AttentionQuizScreen/DailyQuizScreen/SpaceTimeQuizScreen/PersistenceQuizScreen.
| ) { | ||
| val focusManager = LocalFocusManager.current | ||
| val focusRequesters = remember { List(answers.size) { FocusRequester() } } | ||
| val labels = listOf("첫번째", "두번째", "세번째") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
labels 리스트 접근 시 IndexOutOfBoundsException 위험
labels 리스트는 3개의 요소만 가지고 있으나, answers 리스트의 크기에 대한 제약이 없습니다. answers.size > 3인 경우 Line 66에서 labels[index] 접근 시 런타임 크래시가 발생합니다.
🐛 안전한 접근을 위한 수정 제안
- Text(
- text = "${labels[index]} 단어를 작성해주세요",
+ Text(
+ text = "${labels.getOrElse(index) { "${index + 1}번째" }} 단어를 작성해주세요",또는 answers 크기를 3개로 제한하는 것이 비즈니스 로직상 맞다면, 호출부에서 명시적으로 제약을 두는 것을 권장합니다.
Also applies to: 64-70
🤖 Prompt for AI Agents
In
@feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/memory/component/MemoryQuizTextModeContent.kt
at line 36, The code defines a fixed-size val labels = listOf("첫번째", "두번째",
"세번째") but later indexes it using the answers list (labels[index]), risking
IndexOutOfBounds when answers.size > 3; update MemoryQuizTextModeContent so you
either (a) guard when accessing labels by using labels.getOrNull(index) ?:
fallbackLabel (or compute a label from index) wherever labels[index] is used, or
(b) enforce/validate that answers.size <= labels.size before mapping (throw or
truncate) so callers cannot pass more than three answers—locate the labels
declaration and all usages of labels[index] (the mapping logic around answers)
and apply one of these fixes consistently.
feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/spacetime/SpaceTimeQuizScreen.kt
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/spacetime/SpaceTimeQuizScreen.kt (1)
138-141:!!연산자 대신 null-safe 처리를 권장합니다.
getDrawable이 null을 반환할 경우 NPE가 발생할 수 있습니다. Preview 컨텍스트에서는 디자인 타임에 실패하지만, 명시적인 에러 메시지를 위해requireNotNull사용을 고려해 주세요.♻️ 제안된 수정
val previewHandler = AsyncImagePreviewHandler { request -> - val drawable = ContextCompat.getDrawable(request.context, R.drawable.img_default_card)!! + val drawable = requireNotNull( + ContextCompat.getDrawable(request.context, R.drawable.img_default_card) + ) { "Preview drawable not found: R.drawable.img_default_card" } drawable.asImage() }
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/spacetime/SpaceTimeQuizScreen.kt
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Run Unit Tests
🔇 Additional comments (2)
feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/spacetime/SpaceTimeQuizScreen.kt (2)
97-129: Foldable 기기 대응 레이아웃 개선이 잘 적용되었습니다.
quizMaxWidth()modifier와Alignment.CenterHorizontally를 활용하여 Foldable 기기에서 콘텐츠가 중앙 정렬되도록 구현되었습니다.SpaceTimeQuizForm으로 퀴즈 렌더링 로직을 분리한 것도 재사용성 측면에서 좋습니다.
133-163: Coil 이미지 Preview 지원 및 FoldablePreviews 적용이 잘 구현되었습니다.
AsyncImagePreviewHandler와CompositionLocalProvider를 활용하여 Preview에서 Coil 이미지를 올바르게 표시하도록 구현했으며,@FoldablePreviews어노테이션으로 Foldable 기기 프리뷰도 지원됩니다.
ExperimentalCoilApi는 현재 Coil 3에서도 여전히 실험적 단계이며, 실제로 3.2.0-rc01에서 함수형 생성자 동작이 변경된 사례가 있습니다. Coil 버전 업데이트 시 이 API의 변경 사항을 주의 깊게 모니터링해 주세요.
Related issue 🛠
Work Description ✏️
Screenshot 📸
Uncompleted Tasks 😅
Summary by CodeRabbit
새로운 기능
버그 수정
개선
✏️ Tip: You can customize this high-level summary in your review settings.