From 77b86208c3dc72c25337783d0bea62e5bf57a9ce Mon Sep 17 00:00:00 2001 From: jonasgaudian <43753916+jonasgaudian@users.noreply.github.com> Date: Sun, 15 Feb 2026 12:14:24 +0100 Subject: [PATCH] implement demotion logic in `VocabularyRepository` and refactor `VocabularyExerciseViewModel` answer checking --- .../model/repository/VocabularyRepository.kt | 36 +++++++++++++----- .../VocabularyExerciseHostScreen.kt | 15 ++++++-- .../viewmodel/VocabularyExerciseViewModel.kt | 37 ++----------------- 3 files changed, 42 insertions(+), 46 deletions(-) diff --git a/app/src/main/java/eu/gaudian/translator/model/repository/VocabularyRepository.kt b/app/src/main/java/eu/gaudian/translator/model/repository/VocabularyRepository.kt index 661b3b6..8280bf1 100644 --- a/app/src/main/java/eu/gaudian/translator/model/repository/VocabularyRepository.kt +++ b/app/src/main/java/eu/gaudian/translator/model/repository/VocabularyRepository.kt @@ -480,16 +480,32 @@ class VocabularyRepository private constructor(context: Context) { correctCount: Int, incorrectCount: Int, criteriaCorrect: Int, criteriaWrong: Int ): VocabularyStage { - val readyToAdvance = if (isCorrect) correctCount >= criteriaCorrect else incorrectCount >= criteriaWrong - if (!readyToAdvance) return currentStage - return when (currentStage) { - VocabularyStage.NEW -> VocabularyStage.STAGE_1 - VocabularyStage.STAGE_1 -> VocabularyStage.STAGE_2 - VocabularyStage.STAGE_2 -> VocabularyStage.STAGE_3 - VocabularyStage.STAGE_3 -> VocabularyStage.STAGE_4 - VocabularyStage.STAGE_4 -> VocabularyStage.STAGE_5 - VocabularyStage.STAGE_5 -> VocabularyStage.LEARNED - VocabularyStage.LEARNED -> VocabularyStage.LEARNED + if (isCorrect) { + // Correct answer: advance to next stage if criteria met + val readyToAdvance = correctCount >= criteriaCorrect + if (!readyToAdvance) return currentStage + return when (currentStage) { + VocabularyStage.NEW -> VocabularyStage.STAGE_1 + VocabularyStage.STAGE_1 -> VocabularyStage.STAGE_2 + VocabularyStage.STAGE_2 -> VocabularyStage.STAGE_3 + VocabularyStage.STAGE_3 -> VocabularyStage.STAGE_4 + VocabularyStage.STAGE_4 -> VocabularyStage.STAGE_5 + VocabularyStage.STAGE_5 -> VocabularyStage.LEARNED + VocabularyStage.LEARNED -> VocabularyStage.LEARNED + } + } else { + // Incorrect answer: demote to previous stage if criteria met + val readyToDemote = incorrectCount >= criteriaWrong + if (!readyToDemote) return currentStage + return when (currentStage) { + VocabularyStage.LEARNED -> VocabularyStage.STAGE_5 + VocabularyStage.STAGE_5 -> VocabularyStage.STAGE_4 + VocabularyStage.STAGE_4 -> VocabularyStage.STAGE_3 + VocabularyStage.STAGE_3 -> VocabularyStage.STAGE_2 + VocabularyStage.STAGE_2 -> VocabularyStage.STAGE_1 + VocabularyStage.STAGE_1 -> VocabularyStage.STAGE_1 + VocabularyStage.NEW -> VocabularyStage.NEW + } } } diff --git a/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularyExerciseHostScreen.kt b/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularyExerciseHostScreen.kt index 7b1dc13..610f9ab 100644 --- a/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularyExerciseHostScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularyExerciseHostScreen.kt @@ -77,8 +77,9 @@ fun VocabularyExerciseHostScreen( } LaunchedEffect(Unit) { - - + // Reset exercise state when starting fresh + exerciseViewModel.resetExercise() + vocabularyViewModel.prepareExercise( categoryIdsAsJson, stageNamesAsJson, @@ -249,7 +250,15 @@ private fun ExerciseScreen( } LaunchedEffect(currentExerciseState, score, wrongAnswers) { - if (currentExerciseState == null && (score + wrongAnswers) >= totalItems && totalItems > 0) { + // Only trigger completion when: + // 1. Current exercise state is null (no more items to show) + // 2. We have answered all items (score + wrong = total) + // 3. We have at least one item to process + // 4. We're not already in a completed state (prevent duplicate triggers) + if (currentExerciseState == null && + (score + wrongAnswers) >= totalItems && + totalItems > 0 && + (score + wrongAnswers) > 0) { onFinish(score, wrongAnswers) } } diff --git a/app/src/main/java/eu/gaudian/translator/viewmodel/VocabularyExerciseViewModel.kt b/app/src/main/java/eu/gaudian/translator/viewmodel/VocabularyExerciseViewModel.kt index 7e093bd..3647cca 100644 --- a/app/src/main/java/eu/gaudian/translator/viewmodel/VocabularyExerciseViewModel.kt +++ b/app/src/main/java/eu/gaudian/translator/viewmodel/VocabularyExerciseViewModel.kt @@ -240,38 +240,12 @@ class VocabularyExerciseViewModel @Inject constructor( else -> if (shuffleLanguages) Random.nextBoolean() else false } - _exerciseState.value = when (randomType) { - VocabularyExerciseType.GUESSING -> VocabularyExerciseState.Guessing( - item = itemToUse, - isSwitched = isSwitched - ) - VocabularyExerciseType.SPELLING -> VocabularyExerciseState.Spelling( - item = itemToUse, - isSwitched = isSwitched - ) - VocabularyExerciseType.MULTIPLE_CHOICE -> { - val correctAnswer = if (isSwitched) itemToUse.wordFirst else itemToUse.wordSecond - val options = generateMultipleChoiceOptions(correctAnswer, isSwitched) - VocabularyExerciseState.MultipleChoice( - item = itemToUse, - isSwitched = isSwitched, - options = options - ) - } - VocabularyExerciseType.WORD_JUMBLE -> { - val wordToJumble = if (isSwitched) itemToUse.wordFirst else itemToUse.wordSecond - VocabularyExerciseState.WordJumble( - item = itemToUse, - isSwitched = isSwitched, - jumbledLetters = wordToJumble.toList().mapIndexed { index, char -> Pair(char, index) }.shuffled() - ) - } - } @Suppress("HardCodedStringLiteral") Log.d("ExerciseDebug", "Item: ${itemToUse.wordFirst} (${itemToUse.languageFirstId}) / ${itemToUse.wordSecond} (${itemToUse.languageSecondId}), Switched: $isSwitched") @Suppress("HardCodedStringLiteral") Log.d("ExerciseDebug", "Origin Lang: ${config.originLanguageId}, Target Lang: ${config.targetLanguageId}") + // Set the exercise state based on the random type _exerciseState.value = when (randomType) { VocabularyExerciseType.GUESSING -> VocabularyExerciseState.Guessing( item = itemToUse, @@ -344,18 +318,16 @@ class VocabularyExerciseViewModel @Inject constructor( } } - private fun checkAnswer(answer: Any) { - viewModelScope.launch { - val state = _exerciseState.value ?: return@launch + private suspend fun checkAnswer(answer: Any) { + val state = _exerciseState.value ?: return val correctAnswer = if (state.isSwitched) state.item.wordFirst else state.item.wordSecond - // Check if the state is a Spelling type before proceeding with specific logic val isCorrect = when (state) { is VocabularyExerciseState.Spelling -> { val userAnswer = (answer as String).trim() val languageId = if (state.isSwitched) state.item.languageFirstId else state.item.languageSecondId val language = languageRepository.getLanguageById(languageId ?: 0) - ?: return@launch + ?: return // Get articles for the language val articles = languageConfigRepository.getArticlesForLanguage(language.code) @@ -405,7 +377,6 @@ class VocabularyExerciseViewModel @Inject constructor( is VocabularyExerciseState.WordJumble -> state.copy(isCorrect = isCorrect, isRevealed = true) } } - } private fun updateJumbledWord(assembledWord: List>) { _exerciseState.value = when (val state = _exerciseState.value) {