Commit Graph

14 Commits

Author SHA1 Message Date
Adrian Kuta
7cd3394098 Refactor: Extract QuizScreen composables and improve timer logic
This commit refactors `QuizScreen.kt` by extracting several composable functions for better organization and readability. It also includes improvements to the timer logic and minor UI adjustments.

Key changes:

- **QuizScreen.kt:**
    - Extracted `QuizScreenLoading` and `QuizScreenSuccess` composables to handle different UI states.
    - Further decomposed `QuizScreenSuccess` into `LazyListScope` extension functions for `toolbar`, `questionContent`, `choices`, `timer`, and `continueButton`. This improves the structure of the `LazyColumn` and allows for more granular recomposition.
    - Improved `TimerBar` logic:
        - Ensured `targetValue` for `animateFloatAsState` is properly calculated to avoid division by zero when `totalSeconds` is 0.
        - Coerced the progress `Float` value to be within `0f` and `1f` before applying it to `fillMaxWidth`.
    - Changed `contentDescription` of the type icon in `QuizType` to `null` as it's decorative.
    - Used `androidx.compose.runtime.remember` for `questionText` in `QuestionContent` to avoid unnecessary recomposition of `HtmlCompat.fromHtml`.
2025-09-04 18:29:45 +02:00
Adrian Kuta
7d38facda5 fix: Correct timer behavior and display
This commit addresses issues with the question timer in `QuizScreen`.

Key changes:

- **QuizScreenViewModel:**
    - Initialize `_remainingTimeSeconds` to `0` instead of `-1` to prevent premature timer display.
    - Start timer only if `timerJob` is `null` (not already running) and it's the first question.
    - Set `timerJob` to `null` when it's cancelled (on choice selection, moving to next question, or finishing the quiz).
    - If a question has no time limit (null or <= 0 seconds), set `_remainingTimeSeconds` to `0` and do not start the `timerJob`.
    - When the timer finishes and no choice was selected, set `_selectedChoiceIndex` to `-1` (indicating time ran out) and ensure `timerJob` is nullified.
- **QuizScreen:**
    - Conditionally display the `TimerBar` only if `selectedChoiceIndex` is `null` (no choice made yet) AND `timerState.totalTimeSeconds` is greater than `0` (meaning there's a valid timer for the current question). This prevents showing a timer when a question has no time limit.
2025-09-04 17:59:36 +02:00
Adrian Kuta
d2fce7e7b9 feat: Implement question timer and navigation between questions
This commit introduces a timer for each question in the `QuizScreen` and enables navigation to the next question upon answering or when the timer expires.

Key changes:

- **UI Layer (`ui:quiz` module):**
    - **QuizScreen.kt:**
        - Refactored `QuizScreen` to use `LazyColumn` for better performance and to support animated item changes.
        - Implemented a "Continue" button that appears after a choice is selected, allowing the user to proceed to the next question.
        - The `Choices` layout was changed from `LazyVerticalGrid` to `FlowRow` for more flexible item arrangement.
        - Added a loading state (`CircularProgressIndicator`) while quiz data is being fetched.
        - Question images are now clipped with rounded corners.
        - `ScreenUiState` (formerly `QuizUiState`) now holds `selectedChoiceIndex` directly instead of a separate `AnswerUiState`.
        - The `onContinue` callback is passed to the `QuizScreen` to handle advancing to the next question.
        - Added `animateContentSize` to the main `LazyColumn` for smoother transitions.
    - **QuizScreenViewModel.kt:**
        - Introduced `QuizUiState` (sealed interface with `Loading` and `Success` states) to represent the state of quiz data fetching.
        - Introduced `ScreenUiState` (sealed interface with `Loading` and `Success` states) to represent the overall screen state, including the current question, selected answer, and timer.
        - Implemented timer logic:
            - A countdown timer starts for each question.
            - The timer is cancelled when an answer is selected.
            - `_remainingTimeSeconds` now defaults to -1 to indicate the timer hasn't started for the current question yet.
        - Implemented `onContinue()` function to:
            - Advance to the `_currentQuestionIndex`.
            - Reset `_selectedChoiceIndex`.
            - Start the timer for the new question.
        - The initial quiz fetch now populates the `quiz` StateFlow.
        - The timer starts automatically when the first question of a successfully loaded quiz is ready.
        - `TimerState` data class was created to encapsulate timer-related information.
2025-09-04 17:51:18 +02:00
Adrian Kuta
41fd729271 feat: Display "Continue" button after answer selection and hide timer
This commit modifies the `QuizScreen` to show a "Continue" button once an answer is selected for the current question. The timer bar is hidden when an answer is chosen.

Key changes:

- **UI Layer (`ui:quiz` module):**
    - In `QuizScreen.kt`:
        - Conditionally display either the `TimerBar` or a `FilledTonalButton` with the text "Continue" based on whether `uiState.answer` is null.
        - The "Continue" button is styled with a grey background and black text.
    - Added a new string resource `continue_text` in `ui/quiz/src/main/res/values/strings.xml`.
2025-09-04 14:09:59 +02:00
Adrian Kuta
f0bd963d2d feat: Implement question timer and update toolbar UI
This commit introduces a timer for questions in the `QuizScreen` and updates the toolbar to display the current question number out of the total.

Key changes:

- **UI Layer (`ui:quiz` module):**
    - In `QuizScreen.kt`:
        - Added a `TimerBar` composable to visually represent the remaining time for a question. This bar animates its width and displays the remaining seconds.
        - Updated the `Toolbar` composable to display the current question index and total number of questions (e.g., "1/10").
        - Passed `currentQuestionIndex`, `totalQuestions`, `totalTimeSeconds`, and `remainingTimeSeconds` from `QuizUiState` to the respective composables.
        - Updated previews to reflect new `QuizUiState` properties.
        - Used `RoundedCornerShape(percent = 50)` for more consistent rounded corners in the `Toolbar`.
    - In `QuizScreenViewModel.kt`:
        - Added `_remainingTimeSeconds` MutableStateFlow to track the countdown.
        - Modified `QuizUiState` to include `currentQuestionIndex`, `totalQuestions`, `totalTimeSeconds`, and `remainingTimeSeconds`.
        - Implemented `startCountdown()` logic to decrease `_remainingTimeSeconds` every second.
        - The timer is started when the ViewModel is initialized and for each new question.
        - When a choice is selected, the timer is cancelled.
        - If the timer runs out before a choice is selected, `_selectedChoiceIndex` is set to -1 to indicate a timeout.
        - The `uiState` flow now combines `getQuizUseCase()`, `_selectedChoiceIndex`, and `_remainingTimeSeconds` to derive the `QuizUiState`.
- **Design System (`core:designsystem` module):**
    - Added `Purple` color definition in `Color.kt` for use in the `TimerBar`.
    - Reordered color definitions alphabetically.
2025-09-04 13:56:33 +02:00
Adrian Kuta
710dedb0cc refactor: Format code with spotless
This commit applies automated code formatting using Spotless across multiple modules. The changes primarily involve adjustments to trailing commas, spacing, and import statements to ensure consistency with the project's coding style.

Key changes include:

- **Kotlin Files:**
    - Added trailing commas to multi-line parameter lists, argument lists, and collection literals in various Kotlin files across `app`, `core:designsystem`, `core:network`, `domain`, and `model:data` modules.
    - Standardized spacing around operators and keywords.
    - Optimized import statements in test files (`ExampleUnitTest.kt`, `ExampleInstrumentedTest.kt`).
- **XML Files:**
    - Reformatted XML attributes in drawable vector files (`ic_*.xml`) within the `core:designsystem` module for better readability.
- **JSON Files:**
    - Reformatted `sample_quiz.json` in `core:network` test resources for consistent structure.
- **Detekt Configuration:**
    - Updated `detekt.yml` files in `app` and `core:designsystem` to adjust ignored annotations lists, ensuring proper spacing (e.g., `['Composable']` to `[ 'Composable' ]`).
2025-09-03 23:46:09 +02:00
Adrian Kuta
7568abb775 feat: Implement interactive quiz screen with answer revealed state
This commit enhances the `QuizScreen` to be interactive, allowing users to select choices and view revealed answers. It also introduces HTML parsing for question text and adds new design elements like icons and colors.

Key changes:

- **UI Layer (`ui:quiz` module):**
    - `QuizScreen`:
        - Now takes an `onSelect` callback to handle choice selection.
        - `Choices` composable updated to display choices in a `LazyVerticalGrid` and handles click events.
        - Introduced `ChoiceItem` which branches into `ChoiceItemDefault` (for selectable choices) and `ChoiceItemRevealed` (for displaying correct/incorrect answers).
        - `ChoiceItemDefault` displays choices with background colors and icons based on their index.
        - `ChoiceItemRevealed` displays choices with background colors indicating correctness (green for correct, red for incorrect selected, pink for incorrect unselected) and appropriate icons (tick for correct, cross for wrong).
        - `QuestionContent` now parses HTML in the question text using `HtmlCompat` and a new `toAnnotatedString` extension.
        - Image loading in `QuestionContent` uses `ContentScale.FillWidth` and `heightIn(min = 200.dp)`.
        - Added a new preview `QuizScreenRevealedAnswerPreview` to showcase the revealed answer state.
    - `QuizScreenViewModel`:
        - Now manages `_selectedChoiceIndex` to track the user's answer.
        - `uiState` is now a combination of the fetched quiz and the `_selectedChoiceIndex`, producing `QuizUiState` which includes an `AnswerUiState`.
        - `AnswerUiState` holds the `selectedChoiceIndex`.
        - Implemented `onChoiceSelected(index: Int)` to update the selected choice.
- **Design System (`core:designsystem` module):**
    - Added `TextUtils.kt` with a `Spanned.toAnnotatedString()` extension function to convert HTML formatted text (from `HtmlCompat`) into Jetpack Compose `AnnotatedString`.
    - Added new color definitions: `Pink`, `Red`, `Red2`, `Blue2`, `Yellow3`, `Green`, `Green2`.
    - Added a `contrastiveTo(color: Color)` utility function to determine a contrasting text color (black or white) for a given background color.
    - Added new vector drawables for choice shapes and correctness indicators:
        - `ic_circle.xml`
        - `ic_correct.xml`
        - `ic_diamond.xml`
        - `ic_square.xml`
        - `ic_triangle.xml`
        - `ic_wrong.xml`
    - Added Detekt configuration file (`config/detekt/detekt.yml`) for the design system module.
- **Domain Layer (`domain` module):**
    - `Question.image` is now nullable (`String?`).
    - `Choice.correct` is now non-nullable (`Boolean`).
- **Network Layer (`core:network` module):**
    - `ChoiceDto.correct` is now non-nullable (`Boolean`) to align with the domain model.
- **Project Configuration:**
    - Added `.editorconfig` with Kotlin specific trailing comma settings.
    - Minor reordering of dependencies in `gradle/libs.versions.toml`.
2025-09-03 23:45:29 +02:00
Adrian Kuta
45550ecf76 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`.
2025-09-03 21:57:18 +02:00
GitHub Actions Bot
57313de1d7 feat: Display Quiz ID in QuizScreen and integrate with navigation
This commit updates the `QuizScreen` to display the ID of the fetched quiz and connects the `QuizScreen` composable to the navigation graph.

Key changes:

- **UI Layer (`ui:quiz` module):**
    - In `QuizScreen.kt`:
        - Modified the `QuizScreen` composable to display the `quiz.id.value` from the `QuizUiState` within a `Column` and `Text` element.
    - In `navigation/QuizNavigation.kt`:
        - Updated the `quizScreen` navigation extension to call the `QuizScreen()` composable.
2025-09-03 13:15:45 +02:00
GitHub Actions Bot
1270ad93d9 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.
2025-09-03 13:08:57 +02:00
GitHub Actions Bot
fd0678c1fd feat: Expand domain models and implement full DTO to domain mapping
This commit significantly expands the domain models to fully represent the quiz structure and implements the complete mapping logic in `QuizMapper` to convert `QuizResponseDto` and its nested DTOs to their corresponding domain models.

Key changes:

- **Domain Layer (`domain` module):**
    - Introduced a `QuizId` value class for type safety.
    - Added comprehensive domain models for all aspects of a quiz, including:
        - `Quiz`: Updated to include all fields from the DTO.
        - `Question`, `Choice`, `Video`, `ImageMetadata`, `MediaItem`, `ChoiceRange`: For question details.
        - `CoverMetadata`, `ExtractedColor`, `Crop`, `Point`: For cover image information.
        - `ContentTags`: For curriculum and generated codes.
        - `Metadata`, `Access`, `FeaturedListMembership`, `LastEdit`, `VersionMetadata`: For quiz metadata.
        - `LanguageInfo`, `Channel`: Common reusable models.
    - Organized domain models into separate files for better maintainability (e.g., `Question.kt`, `CoverMetadata.kt`).
    - Added a placeholder `QuestionModels.kt` to indicate that models were moved.
- **Data Layer (`model:data` module):**
    - Implemented a complete `toDomainModel()` extension function in `QuizMapper.kt` to map all fields from `QuizResponseDto` and its nested DTOs (like `CoverMetadataDto`, `QuestionDto`, etc.) to the new domain models.
    - This includes mapping for lists and nullable fields.
- **App Module (`app` module):**
    - Updated `MainActivity` to access `quizId.value` due to `QuizId` being a value class.
- **Network Layer (`core:network` module):**
    - Changed `QuizResponseDto.uuid` to be non-nullable (`String`) as it's essential for mapping to `QuizId`.
    - Removed `QuizResponse.kt` as DTOs were previously split into individual files.
2025-09-03 12:45:53 +02:00
GitHub Actions Bot
293b7f75b9 feat: Implement data layer and basic quiz fetching logic
This commit introduces the data layer, including the `QuizRepository` and its implementation, and integrates it with the domain layer's `GetQuizUseCase`. It also sets up Hilt for dependency injection in the app module and makes preliminary UI changes to display fetched quiz data.

Key changes include:

- **Data Layer (`model:data` module):**
    - Added `QuizRepositoryImpl` which implements `QuizRepository` from the domain layer.
    - Implemented `getQuiz()` in `QuizRepositoryImpl` to fetch quiz data from `QuizApi` and map it to the domain `Quiz` model using `QuizMapper`.
    - Created `QuizMapper` to convert `QuizResponseDto` to the domain `Quiz` model.
    - Added `RepositoryModule` for Hilt to provide `QuizRepository` bindings.
- **Domain Layer (`domain` module):**
    - Created `Quiz` domain model.
    - Defined `QuizRepository` interface.
    - Implemented `GetQuizUseCase` to interact with `QuizRepository`.
- **App Module (`app` module):**
    - Added `MyApplication` class annotated with `@HiltAndroidApp`.
    - Updated `AndroidManifest.xml` to use `MyApplication` and add internet permission.
    - In `MainActivity`:
        - Injected `GetQuizUseCase`.
        - Used `LaunchedEffect` to call the use case and update a mutable state `quizId`.
        - Modified the `Greeting` composable to display the fetched `quizId`.
    - Added dependency on the `domain` module in `app/build.gradle.kts`.
- **Network Layer (`core:network` module):**
    - Moved DTOs from `core.network.model` package to `core.network.models`.
    - Made `NetworkModule` internal.
    - Removed unused `QuizService` interface and `QuizServiceImpl` class.
- **Testing (`core:network` test):**
    - Updated import path for `QuizResponseDto` in `QuizResponseDtoParsingTest`.
2025-09-02 22:29:15 +02:00
GitHub Actions Bot
f12ac0826e feat: Implement initial project structure and network layer
This commit introduces the foundational structure of the Kahoot Quiz application and implements the core network layer.

Key changes include:
- Added new Gradle modules: `core:network`, `domain`, `model:data`, and `ui:quiz`.
- Configured Detekt for static code analysis in the new modules.
- Implemented Retrofit and Gson for network communication and JSON parsing.
- Defined DTOs for the Kahoot quiz API response, splitting them into logical files (QuizResponseDto, CommonDtos, CoverDtos, QuestionDtos, MetadataDtos, ContentTagsDto) for better organization.
- Created `QuizApi` interface with a GET request for fetching quiz data.
- Added `QuizService` interface and its initial implementation `QuizServiceImpl`.
- Set up Hilt for dependency injection in the network module, providing Retrofit and QuizApi instances.
- Included a `sample_quiz.json` file for testing and development.
- Added unit tests (`QuizResponseDtoParsingTest`) to verify the correct parsing of the sample JSON into DTOs.
- Updated `.gitignore` to exclude additional generated files and IDE specific folders.
- Modified `settings.gradle.kts` to include the new modules.
- Updated `app/build.gradle.kts` to include dependencies on the new `ui:quiz` and `model:data` modules and removed unused dependencies.
2025-09-02 22:14:50 +02:00
GitHub Actions Bot
d4ec154b8c Initial commit 2025-09-02 15:43:25 +02:00