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,
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
}
}
}

View File

@@ -77,7 +77,8 @@ fun VocabularyExerciseHostScreen(
}
LaunchedEffect(Unit) {
// Reset exercise state when starting fresh
exerciseViewModel.resetExercise()
vocabularyViewModel.prepareExercise(
categoryIdsAsJson,
@@ -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)
}
}

View File

@@ -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<Pair<Char, Int>>) {
_exerciseState.value = when (val state = _exerciseState.value) {