Skip to content

Commit 0be5467

Browse files
authored
[Refactor/#62] 지남력 퀴즈 폼 분리 및 uiState를 변경합니다. (#63)
1 parent 81adba5 commit 0be5467

3 files changed

Lines changed: 230 additions & 204 deletions

File tree

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package com.moa.app.feature.senior.quiz.component.quizform
2+
3+
import androidx.compose.foundation.Image
4+
import androidx.compose.foundation.background
5+
import androidx.compose.foundation.layout.Box
6+
import androidx.compose.foundation.layout.Column
7+
import androidx.compose.foundation.layout.fillMaxWidth
8+
import androidx.compose.foundation.layout.padding
9+
import androidx.compose.foundation.shape.RoundedCornerShape
10+
import androidx.compose.material3.Text
11+
import androidx.compose.runtime.Composable
12+
import androidx.compose.ui.Alignment
13+
import androidx.compose.ui.Modifier
14+
import androidx.compose.ui.draw.clip
15+
import androidx.compose.ui.res.painterResource
16+
import androidx.compose.ui.tooling.preview.Preview
17+
import androidx.compose.ui.unit.dp
18+
import com.moa.app.designsystem.component.core.button.MaQuizButton
19+
import com.moa.app.designsystem.component.core.button.QuizButtonState
20+
import com.moa.app.designsystem.theme.MoaTheme
21+
import com.moa.app.feature.senior.R
22+
import kotlinx.collections.immutable.ImmutableList
23+
import kotlinx.collections.immutable.persistentListOf
24+
25+
@Composable
26+
fun PersistenceQuizForm(
27+
modifier: Modifier = Modifier,
28+
questionContent: String,
29+
answerOptions: ImmutableList<String>,
30+
selectedAnswerIndex: Int?,
31+
onOptionSelected: (Int) -> Unit,
32+
) {
33+
Column(
34+
modifier = modifier,
35+
) {
36+
Box(
37+
contentAlignment = Alignment.CenterStart,
38+
modifier = Modifier
39+
.fillMaxWidth()
40+
.clip(RoundedCornerShape(32.dp))
41+
.background(MoaTheme.colors.coolGray98)
42+
.padding(vertical = 24.dp, horizontal = 32.dp),
43+
) {
44+
Text(
45+
text = questionContent,
46+
color = MoaTheme.colors.black,
47+
style = MoaTheme.typography.title2Semibold,
48+
)
49+
}
50+
51+
Image(
52+
painter = painterResource(R.drawable.img_quiz_character_top),
53+
contentDescription = null,
54+
modifier = Modifier.align(alignment = Alignment.End),
55+
)
56+
57+
answerOptions.forEachIndexed { index, option ->
58+
val buttonState = when (selectedAnswerIndex) {
59+
null -> QuizButtonState.DEFAULT
60+
index -> QuizButtonState.SELECTED
61+
else -> QuizButtonState.UNSELECTED
62+
}
63+
64+
MaQuizButton(
65+
onClick = { onOptionSelected(index) },
66+
state = buttonState,
67+
modifier = Modifier
68+
.fillMaxWidth()
69+
.padding(bottom = 8.dp),
70+
) {
71+
Text(
72+
text = option,
73+
style = MoaTheme.typography.body1Semibold,
74+
modifier = Modifier.padding(vertical = 16.dp),
75+
)
76+
}
77+
}
78+
}
79+
}
80+
81+
@Preview(showBackground = true)
82+
@Composable
83+
private fun PreviewPersistenceQuizForm() {
84+
PersistenceQuizForm(
85+
questionContent = "오늘은 몇 년도인가요?",
86+
answerOptions = persistentListOf("2025년", "2022년", "2020년"),
87+
selectedAnswerIndex = null,
88+
onOptionSelected = {},
89+
)
90+
}

feature/senior/src/main/kotlin/com/moa/app/feature/senior/quiz/persistence/PersistenceQuizScreen.kt

Lines changed: 49 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,30 @@
11
package com.moa.app.feature.senior.quiz.persistence
22

33
import androidx.activity.compose.BackHandler
4-
import androidx.compose.animation.AnimatedContent
5-
import androidx.compose.animation.AnimatedContentTransitionScope
6-
import androidx.compose.animation.SizeTransform
7-
import androidx.compose.animation.core.Spring
8-
import androidx.compose.animation.core.spring
9-
import androidx.compose.animation.togetherWith
10-
import androidx.compose.foundation.Image
11-
import androidx.compose.foundation.background
12-
import androidx.compose.foundation.layout.Box
134
import androidx.compose.foundation.layout.Column
145
import androidx.compose.foundation.layout.Spacer
156
import androidx.compose.foundation.layout.fillMaxSize
167
import androidx.compose.foundation.layout.fillMaxWidth
178
import androidx.compose.foundation.layout.height
189
import androidx.compose.foundation.layout.padding
19-
import androidx.compose.foundation.shape.RoundedCornerShape
2010
import androidx.compose.material3.Text
2111
import androidx.compose.runtime.Composable
2212
import androidx.compose.runtime.getValue
23-
import androidx.compose.runtime.key
24-
import androidx.compose.ui.Alignment
2513
import androidx.compose.ui.Modifier
26-
import androidx.compose.ui.res.painterResource
2714
import androidx.compose.ui.tooling.preview.Preview
2815
import androidx.compose.ui.unit.dp
2916
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
3017
import androidx.lifecycle.compose.collectAsStateWithLifecycle
3118
import com.moa.app.designsystem.component.core.button.MaButton
32-
import com.moa.app.designsystem.component.core.button.MaQuizButton
33-
import com.moa.app.designsystem.component.core.button.QuizButtonState
3419
import com.moa.app.designsystem.component.product.dialog.MaAlertDialog
3520
import com.moa.app.designsystem.component.product.topbar.MaStepProgressTopAppBar
3621
import com.moa.app.designsystem.theme.MoaTheme
3722
import com.moa.app.domain.quiz.model.PersistenceQuiz
3823
import com.moa.app.domain.quiz.model.QuizCategory
39-
import com.moa.app.feature.senior.R
4024
import com.moa.app.feature.senior.quiz.component.QuizLoadContent
4125
import com.moa.app.feature.senior.quiz.component.QuizResultDialog
26+
import com.moa.app.feature.senior.quiz.component.QuizSlideAnimation
27+
import com.moa.app.feature.senior.quiz.component.quizform.PersistenceQuizForm
4228
import kotlinx.collections.immutable.persistentListOf
4329

4430
@Composable
@@ -48,42 +34,39 @@ fun PersistenceQuizScreen(
4834
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
4935
BackHandler(enabled = true, onBack = viewModel::onBackClick)
5036

51-
when (val uiState = uiState) {
52-
is PersistenceQuizUiState.Loading -> QuizLoadContent(QuizCategory.PERSISTENCE)
53-
is PersistenceQuizUiState.Error -> {}
54-
is PersistenceQuizUiState.Success -> {
55-
PersistenceQuizContent(
56-
uiState = uiState,
57-
onBackClick = viewModel::onBackClick,
58-
onOptionSelected = viewModel::selectAnswer,
59-
onContinueClick = viewModel::checkAnswer,
60-
)
61-
62-
if (uiState.showResultDialog && uiState.dialogResult != null) {
63-
QuizResultDialog(
64-
isCorrect = uiState.dialogResult.isCorrect,
65-
correctAnswer = uiState.dialogResult.correctAnswer,
66-
)
67-
}
37+
if (uiState.isLoading && uiState.quizzes.isEmpty()) {
38+
QuizLoadContent(QuizCategory.PERSISTENCE)
39+
} else {
40+
PersistenceQuizContent(
41+
uiState = uiState,
42+
onBackClick = viewModel::onBackClick,
43+
onOptionSelected = viewModel::selectAnswer,
44+
onContinueClick = viewModel::checkAnswer,
45+
)
46+
}
6847

69-
if (uiState.exitDialog) {
70-
MaAlertDialog(
71-
title = "퀴즈를 그만두시나요?",
72-
content = "그만두면 지금까지\n푼 퀴즈는 저장되지 않아요.",
73-
confirmButtonText = "계속 풀기",
74-
dismissButtonText = "그만두기",
75-
onConfirm = viewModel::onHideExitDialog,
76-
onDismiss = viewModel::exitQuiz,
77-
onDialogDismissRequest = viewModel::onHideExitDialog,
78-
)
79-
}
48+
if (uiState.showResultDialog) {
49+
uiState.quizResult?.let { result ->
50+
QuizResultDialog(isCorrect = result.isCorrect, correctAnswer = result.correctAnswer)
8051
}
8152
}
53+
54+
if (uiState.showExitDialog) {
55+
MaAlertDialog(
56+
title = "퀴즈를 그만두시나요?",
57+
content = "그만두면 지금까지\n푼 퀴즈는 저장되지 않아요.",
58+
confirmButtonText = "계속 풀기",
59+
dismissButtonText = "그만두기",
60+
onConfirm = viewModel::onHideExitDialog,
61+
onDismiss = viewModel::exitQuiz,
62+
onDialogDismissRequest = viewModel::onHideExitDialog,
63+
)
64+
}
8265
}
8366

8467
@Composable
8568
private fun PersistenceQuizContent(
86-
uiState: PersistenceQuizUiState.Success,
69+
uiState: PersistenceQuizUiState,
8770
onOptionSelected: (Int) -> Unit,
8871
onContinueClick: () -> Unit,
8972
onBackClick: () -> Unit,
@@ -100,72 +83,18 @@ private fun PersistenceQuizContent(
10083

10184
Spacer(modifier = Modifier.height(28.dp))
10285

103-
AnimatedContent(
104-
modifier = Modifier.weight(1f),
105-
targetState = uiState.currentQuestionIndex,
106-
label = "QuizSlideAnimation",
107-
transitionSpec = {
108-
(slideIntoContainer(
109-
animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
110-
towards = AnimatedContentTransitionScope.SlideDirection.Start
111-
) togetherWith slideOutOfContainer(
112-
animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
113-
towards = AnimatedContentTransitionScope.SlideDirection.Start
114-
))
115-
.using(SizeTransform(clip = true))
116-
},
117-
) { targetIndex ->
118-
key(targetIndex) {
119-
val question = uiState.quizzes[targetIndex]
120-
val quizText = question.questionContent
121-
122-
Column(
86+
uiState.currentQuiz?.let { targetQuiz ->
87+
QuizSlideAnimation(
88+
targetState = targetQuiz,
89+
modifier = Modifier.weight(1f),
90+
) { question ->
91+
PersistenceQuizForm(
12392
modifier = Modifier.padding(horizontal = 20.dp),
124-
) {
125-
Box(
126-
contentAlignment = Alignment.CenterStart,
127-
modifier = Modifier
128-
.fillMaxWidth()
129-
.background(
130-
color = MoaTheme.colors.coolGray98,
131-
shape = RoundedCornerShape(32.dp),
132-
)
133-
.padding(vertical = 24.dp, horizontal = 32.dp),
134-
) {
135-
Text(
136-
text = quizText,
137-
color = MoaTheme.colors.black,
138-
style = MoaTheme.typography.title2Semibold,
139-
)
140-
}
141-
142-
Image(
143-
painter = painterResource(R.drawable.img_quiz_character_top),
144-
contentDescription = null,
145-
modifier = Modifier.align(alignment = Alignment.End)
146-
)
147-
148-
question.answerOptions.forEachIndexed { index, option ->
149-
val buttonState = when (uiState.selectedAnswerIndex) {
150-
null -> QuizButtonState.DEFAULT
151-
index -> QuizButtonState.SELECTED
152-
else -> QuizButtonState.UNSELECTED
153-
}
154-
155-
MaQuizButton(
156-
onClick = { onOptionSelected(index) },
157-
state = buttonState,
158-
modifier = Modifier.fillMaxWidth(),
159-
) {
160-
Text(
161-
text = option,
162-
style = MoaTheme.typography.body1Semibold,
163-
modifier = Modifier.padding(vertical = 16.dp),
164-
)
165-
}
166-
Spacer(Modifier.height(8.dp))
167-
}
168-
}
93+
questionContent = question.questionContent,
94+
answerOptions = question.answerOptions,
95+
selectedAnswerIndex = uiState.selectedAnswerIndex,
96+
onOptionSelected = onOptionSelected,
97+
)
16998
}
17099
}
171100

@@ -190,41 +119,41 @@ private fun PersistenceQuizContent(
190119
@Composable
191120
private fun Preview() {
192121
PersistenceQuizContent(
193-
uiState = PersistenceQuizUiState.Success(
122+
uiState = PersistenceQuizUiState.INIT.copy(
194123
quizzes = persistentListOf(
195124
PersistenceQuiz(
196125
id = 1,
197126
type = QuizCategory.PERSISTENCE,
198-
questionFormat = "오늘은 몇 년도인가요?",
199-
questionContent = "",
127+
questionFormat = "",
128+
questionContent = "오늘은 몇 년도인가요?",
200129
answer = "2025년",
201130
answerOptions = persistentListOf("2025년", "2022년", "2020년"),
202131
),
203132
PersistenceQuiz(
204133
id = 1,
205134
type = QuizCategory.PERSISTENCE,
206-
questionFormat = "오늘은 몇 년도인가요?",
207-
questionContent = "",
135+
questionFormat = "",
136+
questionContent = "오늘은 몇 년도인가요?",
208137
answer = "2025년",
209138
answerOptions = persistentListOf("2025년", "2022년", "2020년"),
210139
),
211140
PersistenceQuiz(
212141
id = 1,
213142
type = QuizCategory.PERSISTENCE,
214-
questionFormat = "오늘은 몇 년도인가요?",
215-
questionContent = "",
143+
questionFormat = "",
144+
questionContent = "오늘은 몇 년도인가요?",
216145
answer = "2025년",
217146
answerOptions = persistentListOf("2025년", "2022년", "2020년"),
218147
),
219148
PersistenceQuiz(
220149
id = 1,
221150
type = QuizCategory.PERSISTENCE,
222-
questionFormat = "오늘은 몇 년도인가요?",
223-
questionContent = "",
151+
questionFormat = "",
152+
questionContent = "오늘은 몇 년도인가요?",
224153
answer = "2025년",
225154
answerOptions = persistentListOf("2025년", "2022년", "2020년"),
226155
),
227-
)
156+
),
228157
),
229158
onOptionSelected = {},
230159
onContinueClick = {},

0 commit comments

Comments
 (0)