From d249da5f529e87342306558965173c003b8d88fc Mon Sep 17 00:00:00 2001 From: jonasgaudian <43753916+jonasgaudian@users.noreply.github.com> Date: Tue, 17 Feb 2026 13:22:56 +0100 Subject: [PATCH] add comprehensive logging for exercise setup and state transitions across screens and ViewModels --- .../view/exercises/StartExerciseScreen.kt | 6 ++ .../VocabularyExerciseHostScreen.kt | 88 ++++++++++--------- .../viewmodel/VocabularyExerciseViewModel.kt | 11 +++ .../viewmodel/VocabularyViewModel.kt | 5 ++ 4 files changed, 69 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/eu/gaudian/translator/view/exercises/StartExerciseScreen.kt b/app/src/main/java/eu/gaudian/translator/view/exercises/StartExerciseScreen.kt index c0a0559..46fedb9 100644 --- a/app/src/main/java/eu/gaudian/translator/view/exercises/StartExerciseScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/exercises/StartExerciseScreen.kt @@ -58,6 +58,7 @@ import eu.gaudian.translator.model.Language import eu.gaudian.translator.model.TagCategory import eu.gaudian.translator.model.VocabularyCategory import eu.gaudian.translator.model.VocabularyStage +import eu.gaudian.translator.utils.Log import eu.gaudian.translator.utils.findActivity import eu.gaudian.translator.view.composable.AppButton import eu.gaudian.translator.view.composable.AppIcons @@ -117,6 +118,7 @@ fun StartExerciseScreen( var amount by remember { mutableStateOf(0) } androidx.compose.runtime.LaunchedEffect(totalItemCount) { amount = totalItemCount + Log.d("StartExercise", "Items to show updated: total=$totalItemCount, amount=$amount") } val updateConfig: (eu.gaudian.translator.viewmodel.ExerciseConfig) -> Unit = { config -> @@ -213,12 +215,15 @@ fun StartExerciseScreen( enabled = totalItemCount > 0 && amount > 0, amount = amount, onStart = { + Log.d("StartExercise", "Start pressed. shuffleCards=${exerciseConfig.shuffleCards}, selectedAmount=$amount, items=${itemsToShow.size}, origin=${selectedOriginLanguage?.nameResId}, target=${selectedTargetLanguage?.nameResId}, pairs=${selectedPairsIds.size}, categories=${selectedCategoryIds.size}, stages=${selectedStages.size}") val finalItems = if (exerciseConfig.shuffleCards) { itemsToShow.shuffled().take(amount) } else { itemsToShow.take(amount) } + Log.d("StartExercise", "Final items prepared: count=${finalItems.size}") + exerciseViewModel.startExerciseWithConfig( finalItems, exerciseConfig.copy( @@ -229,6 +234,7 @@ fun StartExerciseScreen( ) ) + Log.d("StartExercise", "Navigating to vocabulary_exercise/false") navController.navigate("vocabulary_exercise/false") } ) 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 fb418c4..3ed283c 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 @@ -27,6 +27,7 @@ import androidx.compose.ui.res.stringResource import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.navigation.NavController import eu.gaudian.translator.R +import eu.gaudian.translator.utils.Log import eu.gaudian.translator.utils.findActivity import eu.gaudian.translator.view.composable.AppAlertDialog import eu.gaudian.translator.viewmodel.ScreenState @@ -67,8 +68,10 @@ fun VocabularyExerciseHostScreen( } LaunchedEffect(categoryIdsAsJson, stageNamesAsJson, languageIdsAsJson, dailyOnly) { + Log.d("ExerciseHost", "LaunchedEffect filters: categories=$categoryIdsAsJson, stages=$stageNamesAsJson, languages=$languageIdsAsJson, dailyOnly=$dailyOnly") // Only reset and prepare if the host is opened via explicit filters. if (!categoryIdsAsJson.isNullOrBlank() || !stageNamesAsJson.isNullOrBlank() || !languageIdsAsJson.isNullOrBlank() || dailyOnly) { + Log.d("ExerciseHost", "Preparing exercise from filters") exerciseViewModel.resetExercise() vocabularyViewModel.prepareExercise( categoryIdsAsJson, @@ -76,10 +79,13 @@ fun VocabularyExerciseHostScreen( languageIdsAsJson, dailyOnly = dailyOnly, ) + } else { + Log.d("ExerciseHost", "No filters provided; skipping prepareExercise") } } LaunchedEffect(cardSet, screenState, pendingConfig) { + Log.d("ExerciseHost", "State update: screenState=$screenState, cardSet=${cardSet?.cards?.size ?: 0}, pendingCount=${pendingConfig.exerciseItemCount}") if (cardSet != null && screenState == ScreenState.START) { val items = cardSet?.cards.orEmpty() if (items.isNotEmpty()) { @@ -91,6 +97,7 @@ fun VocabularyExerciseHostScreen( } else { items.take(selectedCount) } + Log.d("ExerciseHost", "Auto-starting exercise with ${finalItems.size} items") exerciseViewModel.startExerciseWithConfig( finalItems, pendingConfig.copy( @@ -98,52 +105,51 @@ fun VocabularyExerciseHostScreen( originalExerciseItems = finalItems ) ) + } else { + Log.d("ExerciseHost", "CardSet present but empty") } } } - if (cardSet == null && screenState != ScreenState.START) { - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - CircularProgressIndicator() + when (screenState) { + ScreenState.START -> { + Log.d("ExerciseHost", "Rendering START screen") + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + CircularProgressIndicator() + } } - } else { - when (screenState) { - ScreenState.START -> { - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - CircularProgressIndicator() - } - } - ScreenState.EXERCISE -> { - ExerciseScreen( - viewModel = exerciseViewModel, - onClose = onClose, - onFinish = { score, wrong -> - @Suppress("AssignedValueIsNeverRead") - finalScore = score - @Suppress("AssignedValueIsNeverRead") - finalWrongAnswers = wrong - exerciseViewModel.finishExercise(score, wrong) - }, - navController = navController - ) - } - ScreenState.RESULT -> { - val totalItems by exerciseViewModel.totalItems.collectAsState() - val originalItems by exerciseViewModel.originalItems.collectAsState() - ResultScreen( - score = finalScore, - wrongAnswers = finalWrongAnswers, - totalItems = totalItems, - onRestart = { - vocabularyViewModel.clearCardSet() - exerciseViewModel.resetExercise() - }, - onRetryWrong = { _ -> - exerciseViewModel.retryWrongAnswers(originalItems) - }, - onClose = onClose - ) - } + ScreenState.EXERCISE -> { + Log.d("ExerciseHost", "Rendering EXERCISE screen") + ExerciseScreen( + viewModel = exerciseViewModel, + onClose = onClose, + onFinish = { score, wrong -> + @Suppress("AssignedValueIsNeverRead") + finalScore = score + @Suppress("AssignedValueIsNeverRead") + finalWrongAnswers = wrong + exerciseViewModel.finishExercise(score, wrong) + }, + navController = navController + ) + } + ScreenState.RESULT -> { + Log.d("ExerciseHost", "Rendering RESULT screen") + val totalItems by exerciseViewModel.totalItems.collectAsState() + val originalItems by exerciseViewModel.originalItems.collectAsState() + ResultScreen( + score = finalScore, + wrongAnswers = finalWrongAnswers, + totalItems = totalItems, + onRestart = { + vocabularyViewModel.clearCardSet() + exerciseViewModel.resetExercise() + }, + onRetryWrong = { _ -> + exerciseViewModel.retryWrongAnswers(originalItems) + }, + onClose = onClose + ) } } } 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 0b41d00..467dc0c 100644 --- a/app/src/main/java/eu/gaudian/translator/viewmodel/VocabularyExerciseViewModel.kt +++ b/app/src/main/java/eu/gaudian/translator/viewmodel/VocabularyExerciseViewModel.kt @@ -109,6 +109,7 @@ class VocabularyExerciseViewModel @Inject constructor( types: Set, shuffleLanguages: Boolean ) { + Log.d("ExerciseVM", "startExercise called: items=${items.size}, types=$types, shuffleLanguages=$shuffleLanguages") // Reset counters for the new exercise session _correctAnswers.value = 0 _wrongAnswers.value = 0 @@ -161,6 +162,7 @@ class VocabularyExerciseViewModel @Inject constructor( } private fun loadExercise() { + Log.d("ExerciseVM", "loadExercise: index=$currentIndex, total=${currentItems.size}") if (currentIndex < currentItems.size) { // Ensure item categories align with exercise type by attempting a swap instead of replacement val randomType = exerciseTypes.random() @@ -276,8 +278,10 @@ class VocabularyExerciseViewModel @Inject constructor( ) } } + Log.d("ExerciseVM", "exerciseState set: type=$randomType, itemId=${itemToUse.id}") } else { _exerciseState.value = null // End of exercise + Log.d("ExerciseVM", "loadExercise: end of exercise, state cleared") } } @@ -394,6 +398,7 @@ class VocabularyExerciseViewModel @Inject constructor( } fun onTrainingModeChanged(value: Boolean) { + Log.d("ExerciseVM", "onTrainingModeChanged: $value") _trainingMode.value = value } @@ -401,24 +406,29 @@ class VocabularyExerciseViewModel @Inject constructor( items: List, config: ExerciseConfig ) { + Log.d("ExerciseVM", "startExerciseWithConfig called: items=${items.size}, configCount=${config.exerciseItemCount}, shuffleCards=${config.shuffleCards}, shuffleLanguages=${config.shuffleLanguages}, trainingMode=${config.trainingMode}, dueTodayOnly=${config.dueTodayOnly}, types=${config.selectedExerciseTypes}") _exerciseConfig.value = config _pendingExerciseConfig.value = config _totalItems.value = items.size _originalItems.value = items startExercise(items, config.selectedExerciseTypes, config.shuffleLanguages) _screenState.value = ScreenState.EXERCISE + Log.d("ExerciseVM", "screenState set to EXERCISE; totalItems=${_totalItems.value}") } fun updatePendingExerciseConfig(config: ExerciseConfig) { + Log.d("ExerciseVM", "updatePendingExerciseConfig: count=${config.exerciseItemCount}, shuffleCards=${config.shuffleCards}, shuffleLanguages=${config.shuffleLanguages}, trainingMode=${config.trainingMode}, dueTodayOnly=${config.dueTodayOnly}, types=${config.selectedExerciseTypes}") _pendingExerciseConfig.value = config } + fun finishExercise(score: Int, wrongAnswers: Int) { _exerciseResults.value = ExerciseResults(score, wrongAnswers) _screenState.value = ScreenState.RESULT } fun resetExercise() { + Log.d("ExerciseVM", "resetExercise called") _screenState.value = ScreenState.START _exerciseConfig.value = ExerciseConfig() _exerciseResults.value = ExerciseResults() @@ -428,6 +438,7 @@ class VocabularyExerciseViewModel @Inject constructor( _exerciseState.value = null _totalItems.value = 0 _originalItems.value = emptyList() + Log.d("ExerciseVM", "resetExercise completed; screenState=START") } fun retryWrongAnswers(originalItems: List) { diff --git a/app/src/main/java/eu/gaudian/translator/viewmodel/VocabularyViewModel.kt b/app/src/main/java/eu/gaudian/translator/viewmodel/VocabularyViewModel.kt index 238fa61..aa6ce81 100644 --- a/app/src/main/java/eu/gaudian/translator/viewmodel/VocabularyViewModel.kt +++ b/app/src/main/java/eu/gaudian/translator/viewmodel/VocabularyViewModel.kt @@ -714,6 +714,7 @@ class VocabularyViewModel @Inject constructor( languageIdsAsJson: String?, dailyOnly: Boolean = false ) { + Log.d("VocabularyVM", "prepareExercise: categories=$categoryIdsAsJson, stages=$stageNamesAsJson, languages=$languageIdsAsJson, dailyOnly=$dailyOnly") viewModelScope.launch { val categoryList = categoryIdsAsJson?.takeIf { it.isNotBlank() } ?.split(",") @@ -732,6 +733,7 @@ class VocabularyViewModel @Inject constructor( allLangs.filter { it.nameResId in ids } } ?: emptyList() + Log.d("VocabularyVM", "prepareExercise parsed: categories=${categoryList.size}, stages=${stageList.size}, languages=${languageList.size}") loadCardSet(categoryList, stageList, languageList, dailyOnly) } } @@ -743,6 +745,7 @@ class VocabularyViewModel @Inject constructor( languages: List? = null, dailyOnly: Boolean = false ) { + Log.d(TAG, "loadCardSet invoked: categories=${categories?.size ?: 0}, stages=${stages?.size ?: 0}, languages=${languages?.map { it.nameResId }}, dailyOnly=$dailyOnly") Log.d(TAG, "Loading card set with languages: $languages, categories: ${categories?.size}, stages: ${stages?.size}") viewModelScope.launch { statusService.showLoadingMessage("Loading card set") @@ -802,6 +805,8 @@ class VocabularyViewModel @Inject constructor( dueTodayOnly = dailyOnly ).first() + Log.d(TAG, "loadCardSet: filterVocabularyItems returned ${filteredItems.size} items") + Log.d(TAG, "loadCardSet: Filtering completed, found ${filteredItems.size} items") if (filteredItems.isNotEmpty()) {