feat: Enhance QuizScreen UI and introduce core design system module

This commit significantly revamps the `QuizScreen` UI to display question details including image and text, and introduces a new `core:designsystem` module to centralize theme, colors, typography, and drawable resources.

Key changes:

- **UI Layer (`ui:quiz` module):**
    - Updated `QuizScreen.kt`:
        - Implemented a background image for the screen.
        - Added a `Toolbar` composable to display question progress and type.
        - Created `QuestionContent` composable to show the question image (using Coil for image loading) and text.
        - Added a placeholder `Choices` composable (currently an empty `LazyVerticalGrid`).
        - Applied `fillMaxSize()` to the main `QuizScreen` modifier.
        - Included a `@Preview` for `QuizScreen` with sample data.
    - Modified `QuizScreenViewModel.kt` to update `QuizUiState` with the first `Question` from the fetched quiz.
    - Added a `quiz` string resource in `strings.xml`.
    - Added dependencies for Coil (compose and okhttp) in `ui/quiz/build.gradle.kts`.
- **Core Design System (`core:designsystem` module):**
    - Created a new Android library module `core:designsystem`.
    - Moved `Color.kt`, `Theme.kt`, and `Type.kt` from `app/src/main/java/dev/adriankuta/kahootquiz/ui/theme` to this new module.
    - Added a new `Grey` color to `Color.kt`.
    - Added `bg_image.webp` and `ic_type.xml` drawable resources.
    - Configured the module with `kahootquiz.android.library.compose` plugin.
- **App Module (`app` module):**
    - Updated `MainActivity.kt` to import `KahootQuizTheme` from the new `core.designsystem` package.
    - Added `implementation(projects.core.designsystem)` dependency in `app/build.gradle.kts`.
- **Domain Layer (`domain` module):**
    - Made several fields in `Question.kt` and `Choice.kt` nullable and provided default null values to accommodate potential missing data from the API.
    - Specifically, `Question.image` is now non-nullable (`String`).
- **Build System:**
    - Added `coilCompose` and `coilNetworkOkhttp` versions to `gradle/libs.versions.toml`.
    - Included `:core:designsystem` in `settings.gradle.kts`.
This commit is contained in:
2025-09-03 21:57:18 +02:00
parent 57313de1d7
commit 45550ecf76
17 changed files with 249 additions and 24 deletions

View File

@@ -9,7 +9,11 @@ android {
}
dependencies {
implementation(projects.core.designsystem)
implementation(projects.domain)
implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.timber)
implementation(libs.coil.compose)
implementation(libs.coil.network.okhttp)
}

View File

@@ -1,12 +1,40 @@
package dev.adriankuta.kahootquiz.ui.quiz
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
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.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil3.compose.AsyncImage
import dev.adriankuta.kahootquiz.core.designsystem.Grey
import dev.adriankuta.kahootquiz.core.designsystem.KahootQuizTheme
import dev.adriankuta.kahootquiz.domain.models.Choice
import dev.adriankuta.kahootquiz.domain.models.Question
import kotlin.time.Duration.Companion.seconds
import dev.adriankuta.kahootquiz.core.designsystem.R as DesignR
@Composable
fun QuizScreen(
@@ -18,7 +46,7 @@ fun QuizScreen(
QuizScreen(
uiState = uiState,
modifier = modifier
modifier = modifier.fillMaxSize()
)
}
@@ -27,7 +55,132 @@ private fun QuizScreen(
uiState: QuizUiState,
modifier: Modifier = Modifier,
) {
Column(modifier) {
Text(uiState.quiz?.id?.value ?: "")
Box(modifier.fillMaxSize()) {
Image(
painter = painterResource(id = DesignR.drawable.bg_image),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize()
)
Column(
modifier = Modifier.fillMaxSize()
) {
Toolbar(
modifier = Modifier
.fillMaxWidth()
.height(72.dp)
.padding(8.dp)
)
QuestionContent(
question = uiState.currentQuestion ?: return,
modifier = Modifier.padding(horizontal = 8.dp)
)
Choices(
choices = uiState.currentQuestion.choices ?: emptyList() // TODO remove empty list
)
}
}
}
@Composable
private fun Toolbar(
modifier: Modifier = Modifier
) {
Box(
modifier = modifier
) {
Text(
text = "2/24",
modifier = Modifier
.align(Alignment.CenterStart)
.background(
color = Grey,
shape = RoundedCornerShape(60.dp)
)
.padding(horizontal = 8.dp, vertical = 4.dp)
)
Row(
modifier = Modifier
.align(Alignment.Center)
.background(
color = Grey,
shape = RoundedCornerShape(60.dp)
)
.padding(horizontal = 8.dp, vertical = 4.dp)
) {
Image(
painter = painterResource(id = DesignR.drawable.ic_type),
contentDescription = "",
modifier = Modifier.size(24.dp)
)
Spacer(Modifier.width(4.dp))
Text(
text = stringResource(R.string.quiz),
)
}
}
}
@Composable
private fun QuestionContent(
question: Question,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier
) {
AsyncImage(
model = question.image,
contentDescription = question.imageMetadata?.altText,
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
)
Spacer(Modifier.height(16.dp))
Text(
text = question.question ?: "",
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
.background(
color = Color.White,
shape = RoundedCornerShape(4.dp)
)
.padding(horizontal = 8.dp, vertical = 16.dp)
)
}
}
@Composable
private fun Choices(
choices: List<Choice>
) {
LazyVerticalGrid() { }
}
@Preview
@Composable
private fun QuizScreenPreview() {
KahootQuizTheme {
val sampleQuestion = Question(
type = "quiz",
image = "", // Add a sample image URL or leave empty
question = "What is the capital of France?",
choices = listOf(
Choice(answer = "Berlin", correct = false),
Choice(answer = "Madrid", correct = false),
Choice(answer = "Paris", correct = true),
Choice(answer = "Rome", correct = false)
),
pointsMultiplier = 1,
time = 30.seconds,
questionFormat = 0,
imageMetadata = null,
)
QuizScreen(
uiState = QuizUiState(currentQuestion = sampleQuestion)
)
}
}

View File

@@ -3,7 +3,7 @@ package dev.adriankuta.kahootquiz.ui.quiz
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import dev.adriankuta.kahootquiz.domain.models.Quiz
import dev.adriankuta.kahootquiz.domain.models.Question
import dev.adriankuta.kahootquiz.domain.usecases.GetQuizUseCase
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -20,12 +20,12 @@ class QuizScreenViewModel @Inject constructor(
init {
viewModelScope.launch {
_uiState.value = QuizUiState(getQuizUseCase())
_uiState.value = QuizUiState(getQuizUseCase().questions.first())
}
}
}
data class QuizUiState(
val quiz: Quiz? = null
val currentQuestion: Question? = null
)

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="quiz">Quiz</string>
</resources>