Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/src/main/kotlin/com/moa/app/main/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import com.moa.app.feature.senior.quiz.attention.AttentionQuizScreen
import com.moa.app.feature.senior.quiz.persistence.PersistenceQuizScreen
import com.moa.app.feature.senior.quiz.category.QuizCategoryScreen
import com.moa.app.feature.senior.quiz.linguistic.LinguisticQuizScreen
import com.moa.app.feature.senior.quiz.spacetime.SpaceTimeQuizScreen
import com.moa.app.feature.senior.setting.SeniorSettingScreen
import com.moa.app.navigation.AppRoute
import com.moa.app.navigation.ObserveNavigationEvents
Expand Down Expand Up @@ -80,6 +81,7 @@ class MainActivity : ComponentActivity() {
composable<AppRoute.PersistenceQuiz> { PersistenceQuizScreen() }
composable<AppRoute.LinguisticQuiz> { LinguisticQuizScreen() }
composable<AppRoute.AttentionQuiz> { AttentionQuizScreen() }
composable<AppRoute.SpaceTimeQuiz> { SpaceTimeQuizScreen() }
composable<AppRoute.UserConnection> { UserConnectionScreen() }
composable<AppRoute.SeniorSetting> { SeniorSettingScreen() }
}
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ sealed interface AppRoute {
@Serializable
data object AttentionQuiz : AppRoute

@Serializable
data object SpaceTimeQuiz : AppRoute

@Serializable
data class UserConnection(val userRole: String) : AppRoute

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ fun QuizResponse.toDomain(): Quiz {
is PersistenceQuizResponse -> this.toDomain()
is LinguisticQuizResponse -> this.toDomain()
is AttentionQuizResponse -> this.toDomain()
is SpaceTimeQuizResponse -> this.toDomain()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.moa.app.data.quiz.model.response

import com.moa.app.domain.quiz.model.QuizCategory
import com.moa.app.domain.quiz.model.SpaceTimeQuiz
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toPersistentList
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
@SerialName("SPACETIME")
data class SpaceTimeQuizResponse(
override val questionId: Long,
override val questionFormat: String,
override val questionContent: String,
override val answer: String,
@SerialName("questionImageUrl") val questionImageUrl: String,
@SerialName("imageOptionsUrl") val imageOptionsUrl: List<String>
) : QuizResponse()

fun SpaceTimeQuizResponse.toDomain(): SpaceTimeQuiz {
return SpaceTimeQuiz(
id = this.questionId,
type = QuizCategory.SPACETIME,
questionFormat = this.questionFormat,
questionContent = this.questionContent,
answer = this.answer,
questionImageUrl = this.questionImageUrl,
imageOptionsUrl = this.imageOptionsUrl.toPersistentList()
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.moa.app.domain.quiz.model

import kotlinx.collections.immutable.ImmutableList

data class SpaceTimeQuiz(
override val id: Long,
override val type: QuizCategory,
override val questionFormat: String,
override val questionContent: String,
override val answer: String,
val questionImageUrl: String,
val imageOptionsUrl: ImmutableList<String>,
) : Quiz {
fun isAnswerCorrect(selectedAnswerIndex: Int): Boolean {
if (selectedAnswerIndex !in imageOptionsUrl.indices) return false
return (selectedAnswerIndex + 1).toString() == answer
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class QuizCategoryViewModel @Inject constructor(
QuizCategory.PERSISTENCE -> navigateToQuiz(AppRoute.PersistenceQuiz)
QuizCategory.LINGUISTIC -> navigateToQuiz(AppRoute.LinguisticQuiz)
QuizCategory.ATTENTION -> navigateToQuiz(AppRoute.AttentionQuiz)
QuizCategory.SPACETIME -> navigateToQuiz(AppRoute.SpaceTimeQuiz)
else -> {
_sideEffect.emit(QuizCategorySideEffect.ShowToast("${quizCategory.name} 퀴즈는 아직 준비중이에요"))
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package com.moa.app.feature.senior.quiz.spacetime

import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil3.compose.AsyncImage
import com.moa.app.designsystem.R
import com.moa.app.designsystem.component.core.button.MaButton
import com.moa.app.designsystem.component.core.button.MaQuizButton
import com.moa.app.designsystem.component.core.button.QuizButtonState
import com.moa.app.designsystem.component.product.dialog.MaAlertDialog
import com.moa.app.designsystem.component.product.topbar.MaStepProgressTopAppBar
import com.moa.app.designsystem.theme.MoaTheme
import com.moa.app.domain.quiz.model.QuizCategory
import com.moa.app.feature.senior.quiz.component.CenterQuizDescription
import com.moa.app.feature.senior.quiz.component.QuizLoadContent
import com.moa.app.feature.senior.quiz.component.QuizResultDialog
import com.moa.app.feature.senior.quiz.component.QuizSlideAnimation
import com.moa.app.feature.senior.quiz.spacetime.model.SpaceTimeQuizUiState

@Composable
fun SpaceTimeQuizScreen(
viewModel: SpaceTimeQuizViewModel = hiltViewModel()
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
BackHandler(enabled = true, onBack = viewModel::onBackClick)

if (uiState.isLoading && uiState.quizzes.isEmpty()) {
QuizLoadContent(QuizCategory.SPACETIME)
} else {
SpaceTimeQuizContent(
uiState = uiState,
onOptionSelected = viewModel::selectAnswer,
onContinueClick = viewModel::checkAnswer,
onBackClick = viewModel::onBackClick,
)
}

if (uiState.showResultDialog) {
uiState.quizResult?.let { result ->
QuizResultDialog(isCorrect = result.isCorrect, correctAnswer = result.correctAnswer)
}
}

if (uiState.showExitDialog) {
MaAlertDialog(
title = "퀴즈를 그만두시나요?",
content = "그만두면 지금까지\n푼 퀴즈는 저장되지 않아요.",
confirmButtonText = "계속 풀기",
dismissButtonText = "그만두기",
onConfirm = viewModel::onHideExitDialog,
onDismiss = viewModel::exitQuiz,
onDialogDismissRequest = viewModel::onHideExitDialog,
)
}
}

@Composable
private fun SpaceTimeQuizContent(
uiState: SpaceTimeQuizUiState,
onOptionSelected: (Int) -> Unit,
onContinueClick: () -> Unit,
onBackClick: () -> Unit
) {
Column(
modifier = Modifier.fillMaxSize()
) {
MaStepProgressTopAppBar(
title = "시공간 구조 퀴즈",
onBackClick = onBackClick,
totalSteps = uiState.totalSteps,
currentStep = uiState.currentStep,
)

Spacer(modifier = Modifier.height(8.dp))

uiState.currentQuiz?.let { targetQuiz ->
QuizSlideAnimation(
targetState = targetQuiz,
modifier = Modifier.weight(1f),
) { question ->
Column(
modifier = Modifier.padding(horizontal = 20.dp),
) {
CenterQuizDescription(
quizDescription = "겹치는 모양을\n찾아주세요!",
onImageClick = {},
modifier = Modifier.height(120.dp),
)

AsyncImage(
model = question.questionImageUrl,
placeholder = painterResource(R.drawable.img_default_card_2),
contentDescription = null,
modifier = Modifier.fillMaxWidth()
)

Spacer(modifier = Modifier.weight(1f))

Row(
modifier = Modifier.padding(bottom = 16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
question.imageOptionsUrl.forEachIndexed { index, option ->
val buttonState = when (uiState.selectedAnswerIndex) {
null -> QuizButtonState.DEFAULT
index -> QuizButtonState.SELECTED
else -> QuizButtonState.UNSELECTED
}

MaQuizButton(
onClick = { onOptionSelected(index) },
state = buttonState,
modifier = Modifier.weight(1f),
) {
AsyncImage(
model = option,
contentDescription = null,
modifier = Modifier.padding(vertical = 16.dp)
)
}
}
}
}
}
}

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),
)
}
}
}

@Preview(showBackground = true)
@Composable
private fun Preview() {
SpaceTimeQuizContent(
uiState = SpaceTimeQuizUiState.INIT,
onOptionSelected = {},
onContinueClick = {},
onBackClick = {}
)
}
Loading