implement demotion logic in VocabularyRepository and refactor VocabularyExerciseViewModel answer checking

This commit is contained in:
jonasgaudian
2026-02-15 12:14:24 +01:00
parent 03e9aeedae
commit 77b86208c3
3 changed files with 42 additions and 46 deletions

View File

@@ -480,16 +480,32 @@ class VocabularyRepository private constructor(context: Context) {
correctCount: Int, incorrectCount: Int, correctCount: Int, incorrectCount: Int,
criteriaCorrect: Int, criteriaWrong: Int criteriaCorrect: Int, criteriaWrong: Int
): VocabularyStage { ): VocabularyStage {
val readyToAdvance = if (isCorrect) correctCount >= criteriaCorrect else incorrectCount >= criteriaWrong if (isCorrect) {
if (!readyToAdvance) return currentStage // Correct answer: advance to next stage if criteria met
return when (currentStage) { val readyToAdvance = correctCount >= criteriaCorrect
VocabularyStage.NEW -> VocabularyStage.STAGE_1 if (!readyToAdvance) return currentStage
VocabularyStage.STAGE_1 -> VocabularyStage.STAGE_2 return when (currentStage) {
VocabularyStage.STAGE_2 -> VocabularyStage.STAGE_3 VocabularyStage.NEW -> VocabularyStage.STAGE_1
VocabularyStage.STAGE_3 -> VocabularyStage.STAGE_4 VocabularyStage.STAGE_1 -> VocabularyStage.STAGE_2
VocabularyStage.STAGE_4 -> VocabularyStage.STAGE_5 VocabularyStage.STAGE_2 -> VocabularyStage.STAGE_3
VocabularyStage.STAGE_5 -> VocabularyStage.LEARNED VocabularyStage.STAGE_3 -> VocabularyStage.STAGE_4
VocabularyStage.LEARNED -> VocabularyStage.LEARNED 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
}
} }
} }

View File

@@ -77,7 +77,8 @@ fun VocabularyExerciseHostScreen(
} }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
// Reset exercise state when starting fresh
exerciseViewModel.resetExercise()
vocabularyViewModel.prepareExercise( vocabularyViewModel.prepareExercise(
categoryIdsAsJson, categoryIdsAsJson,
@@ -249,7 +250,15 @@ private fun ExerciseScreen(
} }
LaunchedEffect(currentExerciseState, score, wrongAnswers) { 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) onFinish(score, wrongAnswers)
} }
} }

View File

@@ -240,38 +240,12 @@ class VocabularyExerciseViewModel @Inject constructor(
else -> if (shuffleLanguages) Random.nextBoolean() else false 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") @Suppress("HardCodedStringLiteral")
Log.d("ExerciseDebug", "Item: ${itemToUse.wordFirst} (${itemToUse.languageFirstId}) / ${itemToUse.wordSecond} (${itemToUse.languageSecondId}), Switched: $isSwitched") Log.d("ExerciseDebug", "Item: ${itemToUse.wordFirst} (${itemToUse.languageFirstId}) / ${itemToUse.wordSecond} (${itemToUse.languageSecondId}), Switched: $isSwitched")
@Suppress("HardCodedStringLiteral") @Suppress("HardCodedStringLiteral")
Log.d("ExerciseDebug", "Origin Lang: ${config.originLanguageId}, Target Lang: ${config.targetLanguageId}") Log.d("ExerciseDebug", "Origin Lang: ${config.originLanguageId}, Target Lang: ${config.targetLanguageId}")
// Set the exercise state based on the random type
_exerciseState.value = when (randomType) { _exerciseState.value = when (randomType) {
VocabularyExerciseType.GUESSING -> VocabularyExerciseState.Guessing( VocabularyExerciseType.GUESSING -> VocabularyExerciseState.Guessing(
item = itemToUse, item = itemToUse,
@@ -344,18 +318,16 @@ class VocabularyExerciseViewModel @Inject constructor(
} }
} }
private fun checkAnswer(answer: Any) { private suspend fun checkAnswer(answer: Any) {
viewModelScope.launch { val state = _exerciseState.value ?: return
val state = _exerciseState.value ?: return@launch
val correctAnswer = if (state.isSwitched) state.item.wordFirst else state.item.wordSecond 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) { val isCorrect = when (state) {
is VocabularyExerciseState.Spelling -> { is VocabularyExerciseState.Spelling -> {
val userAnswer = (answer as String).trim() val userAnswer = (answer as String).trim()
val languageId = if (state.isSwitched) state.item.languageFirstId else state.item.languageSecondId val languageId = if (state.isSwitched) state.item.languageFirstId else state.item.languageSecondId
val language = languageRepository.getLanguageById(languageId ?: 0) val language = languageRepository.getLanguageById(languageId ?: 0)
?: return@launch ?: return
// Get articles for the language // Get articles for the language
val articles = languageConfigRepository.getArticlesForLanguage(language.code) val articles = languageConfigRepository.getArticlesForLanguage(language.code)
@@ -405,7 +377,6 @@ class VocabularyExerciseViewModel @Inject constructor(
is VocabularyExerciseState.WordJumble -> state.copy(isCorrect = isCorrect, isRevealed = true) is VocabularyExerciseState.WordJumble -> state.copy(isCorrect = isCorrect, isRevealed = true)
} }
} }
}
private fun updateJumbledWord(assembledWord: List<Pair<Char, Int>>) { private fun updateJumbledWord(assembledWord: List<Pair<Char, Int>>) {
_exerciseState.value = when (val state = _exerciseState.value) { _exerciseState.value = when (val state = _exerciseState.value) {