add comprehensive logging for exercise setup and state transitions across screens and ViewModels

This commit is contained in:
jonasgaudian
2026-02-17 13:22:56 +01:00
parent c061e41cc6
commit d249da5f52
4 changed files with 69 additions and 41 deletions

View File

@@ -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")
}
)

View File

@@ -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,22 +105,21 @@ 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()
}
} else {
when (screenState) {
ScreenState.START -> {
Log.d("ExerciseHost", "Rendering START screen")
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
CircularProgressIndicator()
}
}
ScreenState.EXERCISE -> {
Log.d("ExerciseHost", "Rendering EXERCISE screen")
ExerciseScreen(
viewModel = exerciseViewModel,
onClose = onClose,
@@ -128,6 +134,7 @@ fun VocabularyExerciseHostScreen(
)
}
ScreenState.RESULT -> {
Log.d("ExerciseHost", "Rendering RESULT screen")
val totalItems by exerciseViewModel.totalItems.collectAsState()
val originalItems by exerciseViewModel.originalItems.collectAsState()
ResultScreen(
@@ -146,7 +153,6 @@ fun VocabularyExerciseHostScreen(
}
}
}
}

View File

@@ -109,6 +109,7 @@ class VocabularyExerciseViewModel @Inject constructor(
types: Set<VocabularyExerciseType>,
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<VocabularyItem>,
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<VocabularyItem>) {

View File

@@ -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<Language>? = 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()) {