mirror of
				https://github.com/AdrianKuta/KahootQuiz.git
				synced 2025-10-31 00:43:40 +01:00 
			
		
		
		
	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:
		| @@ -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) | ||||
| } | ||||
|   | ||||
| @@ -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) | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
| ) | ||||
							
								
								
									
										4
									
								
								ui/quiz/src/main/res/values/strings.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								ui/quiz/src/main/res/values/strings.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <string name="quiz">Quiz</string> | ||||
| </resources> | ||||
		Reference in New Issue
	
	Block a user