From 4b572f87739cfba3ccaff569023ddf4bde4ef200 Mon Sep 17 00:00:00 2001 From: jonasgaudian <43753916+jonasgaudian@users.noreply.github.com> Date: Tue, 17 Feb 2026 23:53:37 +0100 Subject: [PATCH] Layout issues in the Start Exercise Screen --- .../view/exercises/StartExerciseScreen.kt | 274 ++++++++++-------- app/src/main/res/values-de-rDE/strings.xml | 4 +- app/src/main/res/values-pt-rBR/strings.xml | 2 +- app/src/main/res/values/strings.xml | 1 + 4 files changed, 164 insertions(+), 117 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 3bb9a76..2da92f3 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 @@ -57,7 +57,6 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.navigation.NavHostController import eu.gaudian.translator.R 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.findActivity @@ -157,6 +156,13 @@ fun StartExerciseScreen( .widthIn(max = 700.dp) // Keeps it from over-stretching on tablets .fillMaxSize() ) { + val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity) + val allLanguages by languageViewModel.allLanguages.collectAsState(initial = emptyList()) + + val availableLanguages = remember(availableLanguagesFromItems, allLanguages) { + allLanguages.filter { it.nameResId in availableLanguagesFromItems } + } + TopBarSection( onBackClick = { navController.popBackStack() }, shuffleCards = exerciseConfig.shuffleCards, @@ -166,6 +172,36 @@ fun StartExerciseScreen( shuffleLanguagesEnabled = !isDirectionPreferenceSet, trainingMode = exerciseConfig.trainingMode, onTrainingModeChanged = { updateConfig(exerciseConfig.copy(trainingMode = it)) }, + selectedOriginLanguage = selectedOriginLanguage, + selectedTargetLanguage = selectedTargetLanguage, + languageSelectionEnabled = true, + availableLanguages = availableLanguages, + onOriginLanguageSelected = { language -> + if (language?.nameResId == selectedOriginLanguage?.nameResId) { + selectedOriginLanguage = null + updateConfig(exerciseConfig.copy(originLanguageId = null)) + } else { + selectedOriginLanguage = language + if (selectedTargetLanguage?.nameResId == language?.nameResId) { + selectedTargetLanguage = null + updateConfig(exerciseConfig.copy(targetLanguageId = null)) + } + updateConfig(exerciseConfig.copy(originLanguageId = language?.nameResId)) + } + }, + onTargetLanguageSelected = { language -> + if (language?.nameResId == selectedTargetLanguage?.nameResId) { + selectedTargetLanguage = null + updateConfig(exerciseConfig.copy(targetLanguageId = null)) + } else { + selectedTargetLanguage = language + if (selectedOriginLanguage?.nameResId == language?.nameResId) { + selectedOriginLanguage = null + updateConfig(exerciseConfig.copy(originLanguageId = null)) + } + updateConfig(exerciseConfig.copy(targetLanguageId = language?.nameResId)) + } + } ) LazyColumn( @@ -190,36 +226,7 @@ fun StartExerciseScreen( updateConfig(exerciseConfig.copy(originLanguageId = null, targetLanguageId = null)) } }, - onOriginLanguageSelected = { language -> - if (language?.nameResId == selectedOriginLanguage?.nameResId) { - selectedOriginLanguage = null - updateConfig(exerciseConfig.copy(originLanguageId = null)) - } else { - selectedOriginLanguage = language - if (selectedTargetLanguage?.nameResId == language?.nameResId) { - selectedTargetLanguage = null - updateConfig(exerciseConfig.copy(targetLanguageId = null)) - } - updateConfig(exerciseConfig.copy(originLanguageId = language?.nameResId)) - } - }, - onTargetLanguageSelected = { language -> - if (language?.nameResId == selectedTargetLanguage?.nameResId) { - selectedTargetLanguage = null - updateConfig(exerciseConfig.copy(targetLanguageId = null)) - } else { - selectedTargetLanguage = language - if (selectedOriginLanguage?.nameResId == language?.nameResId) { - selectedOriginLanguage = null - updateConfig(exerciseConfig.copy(originLanguageId = null)) - } - updateConfig(exerciseConfig.copy(targetLanguageId = language?.nameResId)) - } - }, - languageSelectionEnabled = true, - selectedPairsCount = selectedLanguagePairs.size, - selectedOriginLanguage = selectedOriginLanguage, - selectedTargetLanguage = selectedTargetLanguage + selectedPairsCount = selectedLanguagePairs.size ) } item { @@ -298,7 +305,13 @@ fun TopBarSection( onShuffleLanguagesChanged: (Boolean) -> Unit, shuffleLanguagesEnabled: Boolean, trainingMode: Boolean, - onTrainingModeChanged: (Boolean) -> Unit + onTrainingModeChanged: (Boolean) -> Unit, + selectedOriginLanguage: Language?, + selectedTargetLanguage: Language?, + languageSelectionEnabled: Boolean, + availableLanguages: List, + onOriginLanguageSelected: (Language?) -> Unit, + onTargetLanguageSelected: (Language?) -> Unit ) { var showSettings by remember { mutableStateOf(false) } val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) @@ -357,6 +370,12 @@ fun TopBarSection( shuffleLanguagesEnabled = shuffleLanguagesEnabled, trainingMode = trainingMode, onTrainingModeChanged = onTrainingModeChanged, + selectedOriginLanguage = selectedOriginLanguage, + selectedTargetLanguage = selectedTargetLanguage, + languageSelectionEnabled = languageSelectionEnabled, + availableLanguages = availableLanguages, + onOriginLanguageSelected = onOriginLanguageSelected, + onTargetLanguageSelected = onTargetLanguageSelected, onDismiss = { scope.launch { sheetState.hide() }.invokeOnCompletion { if (!sheetState.isVisible) { @@ -402,12 +421,7 @@ fun LanguagePairSection( selectedPairs: List>, availableLanguageIds: Set, onPairsChanged: (List>) -> Unit, - onOriginLanguageSelected: (Language?) -> Unit, - onTargetLanguageSelected: (Language?) -> Unit, - languageSelectionEnabled: Boolean, - selectedPairsCount: Int, - selectedOriginLanguage: Language?, - selectedTargetLanguage: Language? + selectedPairsCount: Int ) { val activity = androidx.compose.ui.platform.LocalContext.current.findActivity() val vocabularyViewModel: VocabularyViewModel = hiltViewModel(viewModelStoreOwner = activity) @@ -442,8 +456,17 @@ fun LanguagePairSection( } } + var isExpanded by remember { mutableStateOf(false) } + val displayedPairs = if (isExpanded) availablePairs else availablePairs.take(3) + Column { - SectionHeader(title = stringResource(R.string.language_pair)) + SectionHeader( + title = stringResource(R.string.language_pair), + actionText = if (availablePairs.size > 3) { + if (isExpanded) stringResource(R.string.label_show_less) else stringResource(R.string.label_show_more) + } else null, + onActionClick = { isExpanded = !isExpanded } + ) if (availablePairs.isEmpty()) { Text( @@ -456,7 +479,7 @@ fun LanguagePairSection( horizontalArrangement = Arrangement.spacedBy(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp) ) { - availablePairs.forEach { pair -> + displayedPairs.forEach { pair -> val isSelected = selectedPairs.contains(pair) LanguageChip( text = "${pair.first.name} ⇄ ${pair.second.name}", @@ -474,74 +497,6 @@ fun LanguagePairSection( } } } - - Spacer(modifier = Modifier.height(16.dp)) - Text( - text = stringResource(R.string.label_language_direction), - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.Bold - ) - Text( - text = stringResource(R.string.text_language_direction_explanation), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - if (!languageSelectionEnabled && selectedPairsCount > 0) { - Spacer(modifier = Modifier.height(8.dp)) - Text( - text = stringResource(R.string.text_language_direction_disabled_with_pairs), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - } - Spacer(modifier = Modifier.height(12.dp)) - Row( - horizontalArrangement = Arrangement.spacedBy(16.dp), - modifier = Modifier.fillMaxWidth() - ) { - Column(modifier = Modifier.weight(1f)) { - Text( - text = stringResource(R.string.label_origin_language), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(bottom = 8.dp) - ) - eu.gaudian.translator.view.composable.SingleLanguageDropDown( - modifier = Modifier.fillMaxWidth(), - languageViewModel = languageViewModel, - selectedLanguage = selectedOriginLanguage, - onLanguageSelected = { language -> - if (selectedTargetLanguage?.nameResId == language.nameResId) return@SingleLanguageDropDown - onOriginLanguageSelected(language) - }, - showNoneOption = true, - onNoneSelected = { onOriginLanguageSelected(null) }, - alternateLanguages = availableLanguages, - restrictToAlternateLanguages = true, - enabled = languageSelectionEnabled - ) - } - Column(modifier = Modifier.weight(1f)) { - Text( - text = stringResource(R.string.label_target_language), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(bottom = 8.dp) - ) - eu.gaudian.translator.view.composable.SingleLanguageDropDown( - modifier = Modifier.fillMaxWidth(), - languageViewModel = languageViewModel, - selectedLanguage = selectedTargetLanguage, - onLanguageSelected = { language -> - if (selectedOriginLanguage?.nameResId == language.nameResId) return@SingleLanguageDropDown - onTargetLanguageSelected(language) - }, - showNoneOption = true, - onNoneSelected = { onTargetLanguageSelected(null) }, - alternateLanguages = availableLanguages, - restrictToAlternateLanguages = true, - enabled = languageSelectionEnabled - ) - } - } } } @@ -590,10 +545,25 @@ fun CategoriesSection( val categories by categoryViewModel.categories.collectAsState(initial = emptyList()) Column { - SectionHeader(title = stringResource(R.string.label_categories)) + val tagCategories = categories + var isExpanded by remember { mutableStateOf(false) } + val displayedCategories = if (isExpanded) tagCategories else tagCategories.take(3) - val tagCategories = categories.filterIsInstance() - if (tagCategories.size > 15) { + SectionHeader( + title = stringResource(R.string.label_categories), + actionText = if (tagCategories.size > 3) { + if (isExpanded) stringResource(R.string.label_show_less) else stringResource(R.string.label_show_more) + } else null, + onActionClick = { isExpanded = !isExpanded } + ) + + if (tagCategories.isEmpty()) { + Text( + text = stringResource(R.string.no_vocabulary_items_found_perhaps_try_changing_the_filters), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } else if (tagCategories.size > 15) { CategoryDropdown( onCategorySelected = { selections -> onCategoriesChanged(selections.filterNotNull()) @@ -610,7 +580,7 @@ fun CategoriesSection( verticalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxWidth() ) { - tagCategories.forEach { category -> + displayedCategories.forEach { category -> val isSelected = selectedCategories.contains(category) Surface( shape = RoundedCornerShape(20.dp), @@ -893,8 +863,17 @@ private fun StartExerciseSettingsBottomSheet( shuffleLanguagesEnabled: Boolean, trainingMode: Boolean, onTrainingModeChanged: (Boolean) -> Unit, + selectedOriginLanguage: Language?, + selectedTargetLanguage: Language?, + languageSelectionEnabled: Boolean, + availableLanguages: List, + onOriginLanguageSelected: (Language?) -> Unit, + onTargetLanguageSelected: (Language?) -> Unit, onDismiss: () -> Unit ) { + val activity = androidx.compose.ui.platform.LocalContext.current.findActivity() + val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity) + ModalBottomSheet( onDismissRequest = onDismiss, sheetState = sheetState @@ -903,7 +882,7 @@ private fun StartExerciseSettingsBottomSheet( modifier = Modifier .fillMaxWidth() .padding(horizontal = 20.dp, vertical = 16.dp), - verticalArrangement = Arrangement.spacedBy(12.dp) + verticalArrangement = Arrangement.spacedBy(16.dp) ) { Text( text = stringResource(R.string.options), @@ -941,6 +920,73 @@ private fun StartExerciseSettingsBottomSheet( checked = trainingMode, onCheckedChange = onTrainingModeChanged ) + + // Language Direction Section + Text( + text = stringResource(R.string.label_language_direction), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold + ) + Text( + text = stringResource(R.string.text_language_direction_explanation), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + if (!languageSelectionEnabled) { + Text( + text = stringResource(R.string.text_language_direction_disabled_with_pairs), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + Spacer(modifier = Modifier.height(12.dp)) + Row( + horizontalArrangement = Arrangement.spacedBy(16.dp), + modifier = Modifier.fillMaxWidth() + ) { + Column(modifier = Modifier.weight(1f)) { + Text( + text = stringResource(R.string.label_origin_language), + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(bottom = 8.dp) + ) + eu.gaudian.translator.view.composable.SingleLanguageDropDown( + modifier = Modifier.fillMaxWidth(), + languageViewModel = languageViewModel, + selectedLanguage = selectedOriginLanguage, + onLanguageSelected = { language -> + if (selectedTargetLanguage?.nameResId == language.nameResId) return@SingleLanguageDropDown + onOriginLanguageSelected(language) + }, + showNoneOption = true, + onNoneSelected = { onOriginLanguageSelected(null) }, + alternateLanguages = availableLanguages, + restrictToAlternateLanguages = true, + enabled = languageSelectionEnabled + ) + } + Column(modifier = Modifier.weight(1f)) { + Text( + text = stringResource(R.string.label_target_language), + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(bottom = 8.dp) + ) + eu.gaudian.translator.view.composable.SingleLanguageDropDown( + modifier = Modifier.fillMaxWidth(), + languageViewModel = languageViewModel, + selectedLanguage = selectedTargetLanguage, + onLanguageSelected = { language -> + if (selectedOriginLanguage?.nameResId == language.nameResId) return@SingleLanguageDropDown + onTargetLanguageSelected(language) + }, + showNoneOption = true, + onNoneSelected = { onTargetLanguageSelected(null) }, + alternateLanguages = availableLanguages, + restrictToAlternateLanguages = true, + enabled = languageSelectionEnabled + ) + } + } } } -} \ No newline at end of file +} diff --git a/app/src/main/res/values-de-rDE/strings.xml b/app/src/main/res/values-de-rDE/strings.xml index a702710..3ff9e4a 100644 --- a/app/src/main/res/values-de-rDE/strings.xml +++ b/app/src/main/res/values-de-rDE/strings.xml @@ -834,7 +834,7 @@ App beenden Ziel für richtige Antworten pro Tag Intervall-Einstellungen - Fortschritts-Einstellungen + Fortschritt Keine Suche Stelle ein, welche Sprachen du in der App verwenden möchtest. Sprachen, die nicht aktiviert sind, werden in dieser App nicht angezeigt. Du kannst auch deine eigene Sprache zur Liste hinzufügen oder eine vorhandene Sprache (Region/Locale) ändern. @@ -898,7 +898,7 @@ Los Sortieren nach Zurücksetzen - Filter Cards + Karten Filtern Organisiere deinen Wortschatz in Gruppen Extrahiere ein neues Wort in deine Liste Nach oben scrollen diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index d561011..4217e32 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -832,7 +832,7 @@ Início Sair do App Configurações de Intervalo - Configurações de Progresso + Progresso Nenhum Buscar 1. Escolha quais idiomas você quer usar no app. Idiomas que não estiverem ativados não vão aparecer aqui. Você também pode adicionar o seu próprio idioma à lista ou mudar um idioma existente (região/localidade) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2c2ff21..847ed7b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1114,4 +1114,5 @@ Search cards learned All Categories + Show More