diff --git a/data/src/main/kotlin/dev/adriankuta/kahootquiz/data/mappers/QuizMapper.kt b/data/src/main/kotlin/dev/adriankuta/kahootquiz/data/mappers/QuizMapper.kt index ba1c66a..d255a10 100644 --- a/data/src/main/kotlin/dev/adriankuta/kahootquiz/data/mappers/QuizMapper.kt +++ b/data/src/main/kotlin/dev/adriankuta/kahootquiz/data/mappers/QuizMapper.kt @@ -153,7 +153,7 @@ private fun QuestionDto.toDomain(): Question = Question( time = time?.milliseconds, points = points, pointsMultiplier = pointsMultiplier, - choices = choices?.map { it.toDomain() }, + choices = choices?.map { it.toDomain() }.orEmpty(), layout = layout, image = image, imageMetadata = imageMetadata?.toDomain(), diff --git a/domain/src/main/kotlin/dev/adriankuta/kahootquiz/domain/models/Question.kt b/domain/src/main/kotlin/dev/adriankuta/kahootquiz/domain/models/Question.kt index 9c09172..a80113b 100644 --- a/domain/src/main/kotlin/dev/adriankuta/kahootquiz/domain/models/Question.kt +++ b/domain/src/main/kotlin/dev/adriankuta/kahootquiz/domain/models/Question.kt @@ -10,7 +10,7 @@ data class Question( val time: Duration?, val points: Boolean? = null, val pointsMultiplier: Int?, - val choices: List?, + val choices: List, val layout: String? = null, val image: String? = null, val imageMetadata: ImageMetadata?, diff --git a/ui/quiz/src/main/kotlin/dev/adriankuta/kahootquiz/ui/quiz/QuizScreen.kt b/ui/quiz/src/main/kotlin/dev/adriankuta/kahootquiz/ui/quiz/QuizScreen.kt index 7c88686..e3a38b6 100644 --- a/ui/quiz/src/main/kotlin/dev/adriankuta/kahootquiz/ui/quiz/QuizScreen.kt +++ b/ui/quiz/src/main/kotlin/dev/adriankuta/kahootquiz/ui/quiz/QuizScreen.kt @@ -1,13 +1,13 @@ package dev.adriankuta.kahootquiz.ui.quiz -import androidx.compose.animation.animateContentSize import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight 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.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.ButtonDefaults @@ -85,28 +85,10 @@ private fun QuizScreenSuccess( onContinue: () -> Unit, modifier: Modifier = Modifier, ) { - LazyColumn( + Column( modifier = modifier - .fillMaxWidth() - .animateContentSize(), + .fillMaxWidth(), ) { - toolbar(uiState) - questionContent(uiState) - choices(uiState, onSelect) - // Timer below choices - if (uiState.selectedChoiceIndex == null && uiState.timerState.totalTimeSeconds > 0) { - timer(uiState) - } else { - continueButton(uiState, onContinue) - } - } -} - -private fun LazyListScope.toolbar( - uiState: ScreenUiState.Success, - modifier: Modifier = Modifier, -) { - item(key = "toolbar") { Box( modifier = modifier .height(72.dp), @@ -124,25 +106,56 @@ private fun LazyListScope.toolbar( ) } } - } -} -private fun LazyListScope.questionContent( - uiState: ScreenUiState.Success, -) { - if (uiState.currentQuestion != null) { - item(key = "question_${uiState.currentQuestionIndex}") { - QuestionContent( - question = uiState.currentQuestion, - modifier = Modifier - .padding(horizontal = 8.dp) - .animateItem(), + QuestionContent( + question = uiState.currentQuestion, + modifier = Modifier + .padding(horizontal = 8.dp) + .fillMaxHeight(0.5f), + ) + Spacer(Modifier.height(8.dp)) + + + Choices( + choices = uiState.currentQuestion.choices, + selectedChoiceIndex = uiState.selectedChoiceIndex, + onSelect = onSelect, + modifier = Modifier + .padding(8.dp) + .weight(1f), + ) + + // Timer below choices + if (uiState.selectedChoiceIndex == null && uiState.timerState.totalTimeSeconds > 0) { + TimerBar( + totalSeconds = uiState.timerState.totalTimeSeconds, + remainingSeconds = uiState.timerState.remainingTimeSeconds, + modifier = Modifier.padding(8.dp), ) - Spacer(Modifier.height(8.dp)) + } else { + Box( + modifier = Modifier.fillMaxWidth(), + ) { + FilledTonalButton( + onClick = onContinue, + colors = ButtonDefaults.filledTonalButtonColors().copy( + containerColor = Grey, + contentColor = Color.Black, + ), + shape = RoundedCornerShape(4.dp), + modifier = Modifier.align(Alignment.Center), + ) { + Text( + text = stringResource(R.string.continue_text), + ) + } + } + } } } + private fun LazyListScope.timer(uiState: ScreenUiState.Success) { item(key = "timer_${uiState.currentQuestionIndex}") { TimerBar( @@ -153,49 +166,6 @@ private fun LazyListScope.timer(uiState: ScreenUiState.Success) { } } -private fun LazyListScope.continueButton( - uiState: ScreenUiState.Success, - onContinue: () -> Unit, -) { - item(key = "continue_${uiState.currentQuestionIndex}") { - Box( - modifier = Modifier.fillMaxWidth(), - ) { - FilledTonalButton( - onClick = onContinue, - colors = ButtonDefaults.filledTonalButtonColors().copy( - containerColor = Grey, - contentColor = Color.Black, - ), - shape = RoundedCornerShape(4.dp), - modifier = Modifier.align(Alignment.Center), - ) { - Text( - text = stringResource(R.string.continue_text), - ) - } - } - } -} - -private fun LazyListScope.choices( - uiState: ScreenUiState.Success, - onSelect: (Int) -> Unit, -) { - uiState.currentQuestion?.choices?.let { choicesList -> - if (choicesList.isNotEmpty()) { - item(key = "choices_${uiState.currentQuestionIndex}") { - Choices( - choices = choicesList, - selectedChoiceIndex = uiState.selectedChoiceIndex, - onSelect = onSelect, - modifier = Modifier.padding(8.dp), - ) - } - } - } -} - @Preview @Composable private fun QuizScreenPreview() { diff --git a/ui/quiz/src/main/kotlin/dev/adriankuta/kahootquiz/ui/quiz/QuizScreenViewModel.kt b/ui/quiz/src/main/kotlin/dev/adriankuta/kahootquiz/ui/quiz/QuizScreenViewModel.kt index 5bd0c39..5d76a71 100644 --- a/ui/quiz/src/main/kotlin/dev/adriankuta/kahootquiz/ui/quiz/QuizScreenViewModel.kt +++ b/ui/quiz/src/main/kotlin/dev/adriankuta/kahootquiz/ui/quiz/QuizScreenViewModel.kt @@ -141,9 +141,9 @@ private fun screenUiState( when (quizState) { QuizUiState.Loading -> ScreenUiState.Loading is QuizUiState.Success -> { - val currentQuestion = quizState.quiz.questions.getOrNull(currentQuestionIndex) + val currentQuestion = quizState.quiz.questions[currentQuestionIndex] val isAnswerCorrect = selectedChoiceIndex?.let { idx -> - currentQuestion?.choices?.getOrNull(idx)?.correct == true + currentQuestion.choices?.getOrNull(idx)?.correct == true } ScreenUiState.Success( @@ -153,7 +153,7 @@ private fun screenUiState( totalQuestions = quizState.quiz.questions.size, timerState = TimerState( remainingTimeSeconds = remainingTimeSeconds, - totalTimeSeconds = currentQuestion?.time?.inWholeSeconds?.toInt() ?: 0, + totalTimeSeconds = currentQuestion.time?.inWholeSeconds?.toInt() ?: 0, ), isAnswerCorrect = isAnswerCorrect, ) @@ -171,7 +171,7 @@ sealed interface QuizUiState { sealed interface ScreenUiState { data object Loading : ScreenUiState data class Success( - val currentQuestion: Question? = null, + val currentQuestion: Question, val selectedChoiceIndex: Int? = null, val currentQuestionIndex: Int = 0, val totalQuestions: Int = 0, diff --git a/ui/quiz/src/main/kotlin/dev/adriankuta/kahootquiz/ui/quiz/components/Choices.kt b/ui/quiz/src/main/kotlin/dev/adriankuta/kahootquiz/ui/quiz/components/Choices.kt index cfd1351..992b3f9 100644 --- a/ui/quiz/src/main/kotlin/dev/adriankuta/kahootquiz/ui/quiz/components/Choices.kt +++ b/ui/quiz/src/main/kotlin/dev/adriankuta/kahootquiz/ui/quiz/components/Choices.kt @@ -1,3 +1,5 @@ +@file:OptIn(ExperimentalLayoutApi::class) + package dev.adriankuta.kahootquiz.ui.quiz.components import androidx.compose.foundation.Image @@ -5,8 +7,8 @@ import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.FlowRow -import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -37,21 +39,22 @@ fun Choices( selectedChoiceIndex: Int?, modifier: Modifier = Modifier, ) { - FlowRow( - maxItemsInEachRow = 2, + EvenGrid( + items = choices, + columns = 2, modifier = modifier, - horizontalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp), - ) { - choices.forEachIndexed { index, choice -> - ChoiceItem( - choice = choice, - index = index, - selectedChoiceIndex = selectedChoiceIndex, - onClick = { onSelect(index) }, - modifier = Modifier.weight(1f), - ) - } + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { choice, index -> + ChoiceItem( + choice = choice, + index = index, + selectedChoiceIndex = selectedChoiceIndex, + onClick = { onSelect(index) }, + modifier = Modifier + .weight(1f) + .fillMaxHeight(), + ) } } @@ -104,7 +107,6 @@ private fun ChoiceItemDefault( Box( modifier = modifier .background(backgroundColor, shape = RoundedCornerShape(4.dp)) - .height(100.dp) .clickable( onClick = onClick, ), @@ -152,8 +154,7 @@ private fun ChoiceItemRevealed( Box( modifier = modifier - .background(backgroundColor, shape = RoundedCornerShape(4.dp)) - .height(100.dp), + .background(backgroundColor, shape = RoundedCornerShape(4.dp)), ) { Image( painter = painterResource(icon), diff --git a/ui/quiz/src/main/kotlin/dev/adriankuta/kahootquiz/ui/quiz/components/EvenGrid.kt b/ui/quiz/src/main/kotlin/dev/adriankuta/kahootquiz/ui/quiz/components/EvenGrid.kt new file mode 100644 index 0000000..44a44a5 --- /dev/null +++ b/ui/quiz/src/main/kotlin/dev/adriankuta/kahootquiz/ui/quiz/components/EvenGrid.kt @@ -0,0 +1,39 @@ +package dev.adriankuta.kahootquiz.ui.quiz.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +fun EvenGrid( + items: List, + columns: Int, + modifier: Modifier = Modifier, + verticalArrangement: Arrangement.Vertical = Arrangement.Top, + horizontalArrangement: Arrangement.Horizontal = Arrangement.Start, + content: @Composable RowScope.(item: T, index: Int) -> Unit, +) { + val rows = (items.size + columns - 1) / columns // total rows needed + + Column( + modifier = modifier, + verticalArrangement = verticalArrangement, + ) { + repeat(rows) { rowIndex -> + Row( + modifier = Modifier.weight(1f), + horizontalArrangement = horizontalArrangement, + ) { + repeat(columns) { columnIndex -> + val itemIndex = rowIndex * columns + columnIndex + if (itemIndex < items.size) { + content(items[itemIndex], itemIndex) + } + } + } + } + } +} diff --git a/ui/quiz/src/main/kotlin/dev/adriankuta/kahootquiz/ui/quiz/components/QuestionContent.kt b/ui/quiz/src/main/kotlin/dev/adriankuta/kahootquiz/ui/quiz/components/QuestionContent.kt index a95128a..a813f1f 100644 --- a/ui/quiz/src/main/kotlin/dev/adriankuta/kahootquiz/ui/quiz/components/QuestionContent.kt +++ b/ui/quiz/src/main/kotlin/dev/adriankuta/kahootquiz/ui/quiz/components/QuestionContent.kt @@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text @@ -34,8 +33,7 @@ fun QuestionContent( contentDescription = question.imageMetadata?.altText, contentScale = ContentScale.FillWidth, modifier = Modifier - .fillMaxWidth() - .heightIn(min = 200.dp) + .weight(1f) .clip(shape = RoundedCornerShape(4.dp)), ) Spacer(Modifier.height(16.dp)) diff --git a/ui/quiz/src/main/kotlin/dev/adriankuta/kahootquiz/ui/quiz/components/TimerBar.kt b/ui/quiz/src/main/kotlin/dev/adriankuta/kahootquiz/ui/quiz/components/TimerBar.kt index d005e76..d1b1455 100644 --- a/ui/quiz/src/main/kotlin/dev/adriankuta/kahootquiz/ui/quiz/components/TimerBar.kt +++ b/ui/quiz/src/main/kotlin/dev/adriankuta/kahootquiz/ui/quiz/components/TimerBar.kt @@ -33,7 +33,7 @@ fun TimerBar( Box( modifier = modifier - .fillMaxWidth(progress.coerceIn(0f, 1f)) + .fillMaxWidth(progress) .background( color = Purple, shape = RoundedCornerShape(percent = 50),