feat: Implement QuizScreen and basic navigation structure

This commit introduces the `QuizScreen` composable, its associated `QuizScreenViewModel`, and sets up the basic navigation graph for the application.

Key changes:

- **UI Layer (`ui:quiz` module):**
    - Added `QuizScreen.kt` with a basic composable structure.
    - Implemented `QuizScreenViewModel.kt` which fetches quiz data using `GetQuizUseCase` and exposes it via `QuizUiState`.
    - Created `QuizUiState` data class to hold the quiz data for the UI.
    - Added `navigation/QuizNavigation.kt` to define the `QuizRoute` and `quizScreen` navigation extension.
    - Updated `ui/quiz/build.gradle.kts` to include dependencies for Hilt navigation and Timber.
- **App Module (`app` module):**
    - Created `KahootQuizApp.kt` which sets up the main `Scaffold` and includes the navigation graph.
    - Added `KahootQuizNavGraph.kt` to define the `NavHost` and integrate the `quizScreen`.
    - Modified `MainActivity.kt` to call the new `KahootQuizApp` composable, removing the previous direct use case call and UI.
- **Data Layer (`model:data` module):**
    - Updated `QuizMapper.kt` to convert `time` from `Long?` to `Duration?` (using `milliseconds`) when mapping `QuestionDto` to the domain `Question` model.
This commit is contained in:
GitHub Actions Bot
2025-09-03 13:08:57 +02:00
parent fd0678c1fd
commit 1270ad93d9
8 changed files with 123 additions and 47 deletions

View File

@@ -0,0 +1,20 @@
package dev.adriankuta.kahootquiz
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@Composable
fun KahootQuizApp(
modifier: Modifier = Modifier
) {
Scaffold(
contentWindowInsets = WindowInsets.safeDrawing,
modifier = modifier,
) { paddingValues ->
KahootQuizNavGraph(modifier = modifier.padding(paddingValues))
}
}

View File

@@ -0,0 +1,23 @@
package dev.adriankuta.kahootquiz
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.rememberNavController
import dev.adriankuta.kahootquiz.ui.quiz.navigation.QuizRoute
import dev.adriankuta.kahootquiz.ui.quiz.navigation.quizScreen
@Composable
fun KahootQuizNavGraph(
modifier: Modifier = Modifier,
navController: NavHostController = rememberNavController(),
) {
NavHost(
navController = navController,
startDestination = QuizRoute,
modifier = modifier
) {
quizScreen()
}
}

View File

@@ -4,64 +4,19 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import dagger.hilt.android.AndroidEntryPoint
import dev.adriankuta.kahootquiz.domain.usecases.GetQuizUseCase
import dev.adriankuta.kahootquiz.ui.theme.KahootQuizTheme
import javax.inject.Inject
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
@Inject
lateinit var getQuizUseCase: GetQuizUseCase
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
KahootQuizTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
var quizId by remember { mutableStateOf<String?>(null) }
LaunchedEffect(Unit) {
quizId = getQuizUseCase().id.value
}
Greeting(
name = quizId ?: "Android",
modifier = Modifier.padding(innerPadding)
)
}
KahootQuizApp()
}
}
}
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
KahootQuizTheme {
Greeting("Android")
}
}

View File

@@ -2,6 +2,7 @@ package dev.adriankuta.kahootquiz.model.data.mappers
import dev.adriankuta.kahootquiz.core.network.models.*
import dev.adriankuta.kahootquiz.domain.models.*
import kotlin.time.Duration.Companion.milliseconds
internal fun QuizResponseDto.toDomainModel(): Quiz =
Quiz(
@@ -110,7 +111,7 @@ private fun ChannelDto.toDomain(): Channel = Channel(id = id)
private fun QuestionDto.toDomain(): Question = Question(
type = type,
question = question,
time = time,
time = time?.milliseconds,
points = points,
pointsMultiplier = pointsMultiplier,
choices = choices?.map { it.toDomain() },

View File

@@ -1,6 +1,7 @@
plugins {
alias(libs.plugins.kahootquiz.android.library.compose)
alias(libs.plugins.kahootquiz.android.library.hilt)
alias(libs.plugins.kotlin.serialization)
}
android {
@@ -9,4 +10,6 @@ android {
dependencies {
implementation(projects.domain)
implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.timber)
}

View File

@@ -0,0 +1,29 @@
package dev.adriankuta.kahootquiz.ui.quiz
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@Composable
fun QuizScreen(
modifier: Modifier = Modifier,
viewModel: QuizScreenViewModel = hiltViewModel()
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
QuizScreen(
uiState = uiState,
modifier = modifier
)
}
@Composable
private fun QuizScreen(
uiState: QuizUiState,
modifier: Modifier = Modifier,
) {
}

View File

@@ -0,0 +1,31 @@
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.usecases.GetQuizUseCase
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class QuizScreenViewModel @Inject constructor(
private val getQuizUseCase: GetQuizUseCase
) : ViewModel() {
private val _uiState = MutableStateFlow(QuizUiState())
val uiState: StateFlow<QuizUiState> = _uiState.asStateFlow()
init {
viewModelScope.launch {
_uiState.value = QuizUiState(getQuizUseCase())
}
}
}
data class QuizUiState(
val quiz: Quiz? = null
)

View File

@@ -0,0 +1,14 @@
package dev.adriankuta.kahootquiz.ui.quiz.navigation
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import kotlinx.serialization.Serializable
@Serializable
data object QuizRoute
fun NavGraphBuilder.quizScreen() {
composable<QuizRoute> {
}
}