From 85c407481dda64ae758e6f37a7dfb0bcb2370074 Mon Sep 17 00:00:00 2001 From: jonasgaudian <43753916+jonasgaudian@users.noreply.github.com> Date: Tue, 17 Feb 2026 14:57:56 +0100 Subject: [PATCH] Refactor hint management by replacing `@Composable` lambda hint content with a structured `Hint` type and updating UI components to support it. --- .../translator/view/composable/AppDialogs.kt | 18 +++++----- .../view/composable/AppTopAppBar.kt | 21 +++++------ .../view/composable/ComponentLibrary.kt | 35 ++++++++++++++++++- .../view/dialogs/ImportVocabularyDialog.kt | 2 +- .../gaudian/translator/view/hints/AllHints.kt | 2 +- .../translator/view/hints/HintBottomSheet.kt | 4 +-- .../translator/view/hints/HintComposable.kt | 35 ++++++------------- .../view/hints/HintsOverviewScreen.kt | 2 +- .../view/settings/SettingsNavGraph.kt | 2 +- .../view/translation/LanguageSelectorBar.kt | 3 +- .../view/translation/MainTranslationScreen.kt | 2 +- .../view/vocabulary/NewWordScreen.kt | 22 ++++-------- 12 files changed, 81 insertions(+), 67 deletions(-) diff --git a/app/src/main/java/eu/gaudian/translator/view/composable/AppDialogs.kt b/app/src/main/java/eu/gaudian/translator/view/composable/AppDialogs.kt index c171753..99e9235 100644 --- a/app/src/main/java/eu/gaudian/translator/view/composable/AppDialogs.kt +++ b/app/src/main/java/eu/gaudian/translator/view/composable/AppDialogs.kt @@ -115,7 +115,7 @@ fun AppAlertDialog( title: @Composable (() -> Unit)? = null, text: @Composable (() -> Unit)? = null, properties: DialogProperties = DialogProperties(), - hintContent: @Composable (() -> Unit)? = null, + hintContent:Hint? = null, ) { val sheetState = rememberModalBottomSheetState() var showBottomSheet by remember { mutableStateOf(false) } @@ -142,11 +142,13 @@ fun AppAlertDialog( ) if (showBottomSheet) { - HintBottomSheet( - onDismissRequest = { showBottomSheet = false }, - sheetState = sheetState, - content = hintContent - ) + hintContent?.let { + HintBottomSheet( + onDismissRequest = { showBottomSheet = false }, + sheetState = sheetState, + content = it + ) + } } } @@ -212,7 +214,7 @@ private fun DialogHeader( @Composable private fun DialogTitleWithHint( title: @Composable () -> Unit, - hintContent: @Composable (() -> Unit)?, + hintContent: Hint? = null, onHintClick: () -> Unit ) { val showHints = LocalShowHints.current @@ -424,7 +426,6 @@ fun AppAlertDialogPreview() { }, title = { Text("Alert Dialog Title") }, text = { Text("This is the alert dialog text.") }, - hintContent = { Text("This is a hint for the alert dialog.") } ) } @@ -492,7 +493,6 @@ fun AppAlertDialogLongTextPreview() { Text("Third paragraph with additional information that users need to be aware of.") } }, - hintContent = { Text("This hint explains the terms in more detail.") } ) } diff --git a/app/src/main/java/eu/gaudian/translator/view/composable/AppTopAppBar.kt b/app/src/main/java/eu/gaudian/translator/view/composable/AppTopAppBar.kt index 3595ee5..412f7a7 100644 --- a/app/src/main/java/eu/gaudian/translator/view/composable/AppTopAppBar.kt +++ b/app/src/main/java/eu/gaudian/translator/view/composable/AppTopAppBar.kt @@ -115,16 +115,17 @@ fun AppTopAppBar( ) if (showBottomSheet) { - HintBottomSheet( - onDismissRequest = { - @Suppress("AssignedValueIsNeverRead") - showBottomSheet = false - }, - sheetState = sheetState, - content = { - hintContent?.Render() - } - ) + hintContent?.let { + HintBottomSheet( + onDismissRequest = { + @Suppress("AssignedValueIsNeverRead") + showBottomSheet = false + }, + sheetState = sheetState, + content = it + + ) + } } } diff --git a/app/src/main/java/eu/gaudian/translator/view/composable/ComponentLibrary.kt b/app/src/main/java/eu/gaudian/translator/view/composable/ComponentLibrary.kt index 3c771b6..9f0443a 100644 --- a/app/src/main/java/eu/gaudian/translator/view/composable/ComponentLibrary.kt +++ b/app/src/main/java/eu/gaudian/translator/view/composable/ComponentLibrary.kt @@ -28,6 +28,7 @@ import androidx.compose.material3.CardDefaults import androidx.compose.material3.Checkbox import androidx.compose.material3.CheckboxDefaults import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedCard @@ -36,6 +37,7 @@ import androidx.compose.material3.Switch import androidx.compose.material3.SwitchDefaults import androidx.compose.material3.Text import androidx.compose.material3.TextButton +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -56,6 +58,9 @@ import eu.gaudian.translator.R import eu.gaudian.translator.ui.theme.ThemePreviews import eu.gaudian.translator.ui.theme.semanticColors import eu.gaudian.translator.view.composable.ComponentDefaults.DefaultElevation +import eu.gaudian.translator.view.hints.Hint +import eu.gaudian.translator.view.hints.HintBottomSheet +import eu.gaudian.translator.view.hints.LocalShowHints object ComponentDefaults { @@ -97,14 +102,16 @@ object ComponentDefaults { fun AppCard( modifier: Modifier = Modifier, title: String? = null, - icon: ImageVector? = null, // New optional icon parameter + icon: ImageVector? = null, text: String? = null, expandable: Boolean = false, initiallyExpanded: Boolean = false, onClick: (() -> Unit)? = null, + hintContent : Hint? = null, content: @Composable ColumnScope.() -> Unit, ) { var isExpanded by remember { mutableStateOf(initiallyExpanded) } + val showHints = LocalShowHints.current val rotationState by animateFloatAsState( targetValue = if (isExpanded) 180f else 0f, @@ -116,6 +123,20 @@ fun AppCard( val hasHeader = title != null || text != null || expandable || icon != null val canClickHeader = expandable || onClick != null + var showBottomSheet by remember { mutableStateOf(false) } + + if (showBottomSheet) { + hintContent?.let { + HintBottomSheet( + onDismissRequest = { showBottomSheet = false }, + content = it, + sheetState = rememberModalBottomSheetState( + skipPartiallyExpanded = true + ) + ) + } + } + Surface( modifier = modifier .fillMaxWidth() @@ -146,6 +167,7 @@ fun AppCard( ) { // 1. Optional Icon on the left if (icon != null) { + Spacer(modifier = Modifier.width(8.dp)) Icon( imageVector = icon, contentDescription = null, @@ -179,6 +201,16 @@ fun AppCard( } } + if (showHints && hintContent != null) { + IconButton(onClick = { showBottomSheet = true }) { + Icon( + imageVector = AppIcons.Help, + contentDescription = stringResource(R.string.show_hint), + tint = MaterialTheme.colorScheme.secondary + ) + } + } + // 3. Expand Chevron (Far right) if (expandable) { Icon( @@ -189,6 +221,7 @@ fun AppCard( ) } } + } // --- Content Area --- diff --git a/app/src/main/java/eu/gaudian/translator/view/dialogs/ImportVocabularyDialog.kt b/app/src/main/java/eu/gaudian/translator/view/dialogs/ImportVocabularyDialog.kt index 9347b93..0d0e4b4 100644 --- a/app/src/main/java/eu/gaudian/translator/view/dialogs/ImportVocabularyDialog.kt +++ b/app/src/main/java/eu/gaudian/translator/view/dialogs/ImportVocabularyDialog.kt @@ -109,7 +109,7 @@ fun ImportDialogContent( AppDialog( onDismissRequest = onDismiss, title = { Text(descriptionText) }, - hintContent = HintDefinition.IMPORT.hint(), + hintContent = HintDefinition.VOCABULARY_GENERATE_AI.hint(), content = { Column( modifier = Modifier diff --git a/app/src/main/java/eu/gaudian/translator/view/hints/AllHints.kt b/app/src/main/java/eu/gaudian/translator/view/hints/AllHints.kt index fb5d4fc..ccc5e52 100644 --- a/app/src/main/java/eu/gaudian/translator/view/hints/AllHints.kt +++ b/app/src/main/java/eu/gaudian/translator/view/hints/AllHints.kt @@ -21,7 +21,7 @@ enum class HintDefinition( CATEGORY("category_hint", R.string.category_hint_intro), DICTIONARY_OPTIONS("dictionary_hint", R.string.label_dictionary_options), EXERCISE("exercise_hint", R.string.label_exercise), - IMPORT("import_hint", R.string.hint_how_to_generate_vocabulary_with_ai), + VOCABULARY_GENERATE_AI("import_hint", R.string.hint_how_to_generate_vocabulary_with_ai), LEARNING_STAGES("learning_stages_hint", R.string.learning_stages_title), REVIEW("review_hint", R.string.review_intro), SORTING("sorting_hint", R.string.sorting_hint_title), diff --git a/app/src/main/java/eu/gaudian/translator/view/hints/HintBottomSheet.kt b/app/src/main/java/eu/gaudian/translator/view/hints/HintBottomSheet.kt index 28b02ba..e5e2337 100644 --- a/app/src/main/java/eu/gaudian/translator/view/hints/HintBottomSheet.kt +++ b/app/src/main/java/eu/gaudian/translator/view/hints/HintBottomSheet.kt @@ -32,7 +32,7 @@ import kotlinx.coroutines.launch fun HintBottomSheet( onDismissRequest: () -> Unit, sheetState: SheetState, - content: @Composable (() -> Unit)? + content: Hint, ) { val scope = rememberCoroutineScope() ModalBottomSheet( @@ -50,7 +50,7 @@ fun HintBottomSheet( .weight(1f, fill = false) .verticalScroll(rememberScrollState()) ) { - content?.invoke() + content.Render() } Spacer(modifier = Modifier.height(16.dp)) diff --git a/app/src/main/java/eu/gaudian/translator/view/hints/HintComposable.kt b/app/src/main/java/eu/gaudian/translator/view/hints/HintComposable.kt index c6b2e84..e78cde8 100644 --- a/app/src/main/java/eu/gaudian/translator/view/hints/HintComposable.kt +++ b/app/src/main/java/eu/gaudian/translator/view/hints/HintComposable.kt @@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.compositionLocalOf @@ -16,7 +15,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import eu.gaudian.translator.R import eu.gaudian.translator.view.composable.AppIcons @@ -39,8 +37,8 @@ val LocalShowHints = compositionLocalOf { false } */ @Composable fun WithHint( - hintContent: @Composable () -> Unit, modifier: Modifier = Modifier, + hintContent: Hint? = null, content: @Composable () -> Unit ) { val showHints = LocalShowHints.current @@ -69,27 +67,16 @@ fun WithHint( } if (showBottomSheet) { - HintBottomSheet( - onDismissRequest = { - @Suppress("AssignedValueIsNeverRead") - showBottomSheet = false - }, - sheetState = sheetState, - hintContent - ) + hintContent?.let { + HintBottomSheet( + onDismissRequest = { + @Suppress("AssignedValueIsNeverRead") + showBottomSheet = false + }, + sheetState = sheetState, + content = it, + ) + } } } -@Preview -@Composable -fun WithHintPreview() { - androidx.compose.runtime.CompositionLocalProvider(LocalShowHints provides true) { - WithHint( - hintContent = { - Text(stringResource(R.string.this_is_a_hint)) - } - ) { - Text(stringResource(R.string.this_is_the_main_content)) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/eu/gaudian/translator/view/hints/HintsOverviewScreen.kt b/app/src/main/java/eu/gaudian/translator/view/hints/HintsOverviewScreen.kt index 3d53ac2..f402ea4 100644 --- a/app/src/main/java/eu/gaudian/translator/view/hints/HintsOverviewScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/hints/HintsOverviewScreen.kt @@ -40,7 +40,7 @@ fun HintsOverviewScreen( val showExperimental = LocalShowExperimentalFeatures.current // Get hints using the new function-based approach - val importHint = HintDefinition.IMPORT.hint() + val importHint = HintDefinition.VOCABULARY_GENERATE_AI.hint() val addModelScanHint = HintDefinition.ADD_MODEL_SCAN.hint() val dictionaryOptionsHint = HintDefinition.DICTIONARY_OPTIONS.hint() val translationScreenHint = HintDefinition.TRANSLATION.hint() diff --git a/app/src/main/java/eu/gaudian/translator/view/settings/SettingsNavGraph.kt b/app/src/main/java/eu/gaudian/translator/view/settings/SettingsNavGraph.kt index 5be6023..04c7f27 100644 --- a/app/src/main/java/eu/gaudian/translator/view/settings/SettingsNavGraph.kt +++ b/app/src/main/java/eu/gaudian/translator/view/settings/SettingsNavGraph.kt @@ -113,7 +113,7 @@ fun NavGraphBuilder.settingsGraph(navController: NavController) { HintScreen(navController, HintDefinition.DICTIONARY_OPTIONS) } composable(SettingsRoutes.HINTS_IMPORT) { - HintScreen(navController, HintDefinition.IMPORT) + HintScreen(navController, HintDefinition.VOCABULARY_GENERATE_AI) } composable(SettingsRoutes.HINTS_SORTING) { HintScreen(navController, HintDefinition.SORTING) diff --git a/app/src/main/java/eu/gaudian/translator/view/translation/LanguageSelectorBar.kt b/app/src/main/java/eu/gaudian/translator/view/translation/LanguageSelectorBar.kt index ca5dd4c..f398366 100644 --- a/app/src/main/java/eu/gaudian/translator/view/translation/LanguageSelectorBar.kt +++ b/app/src/main/java/eu/gaudian/translator/view/translation/LanguageSelectorBar.kt @@ -37,6 +37,7 @@ import eu.gaudian.translator.R import eu.gaudian.translator.view.composable.AppIcons import eu.gaudian.translator.view.composable.SourceLanguageDropdown import eu.gaudian.translator.view.composable.TargetLanguageDropdown +import eu.gaudian.translator.view.hints.Hint import eu.gaudian.translator.view.hints.WithHint import eu.gaudian.translator.viewmodel.LanguageViewModel @@ -71,7 +72,7 @@ fun TopBarActions( languageViewModel: LanguageViewModel, onSettingsClick: () -> Unit, onNavigateBack: (() -> Unit)? = null, - hintContent: (@Composable () -> Unit)? = null + hintContent: Hint? = null ) { ActionBar(modifier = Modifier.padding(horizontal = 8.dp, vertical = 8.dp)) { diff --git a/app/src/main/java/eu/gaudian/translator/view/translation/MainTranslationScreen.kt b/app/src/main/java/eu/gaudian/translator/view/translation/MainTranslationScreen.kt index a8f02bb..14866ad 100644 --- a/app/src/main/java/eu/gaudian/translator/view/translation/MainTranslationScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/translation/MainTranslationScreen.kt @@ -177,7 +177,7 @@ private fun LoadedTranslationContent( languageViewModel = languageViewModel, onSettingsClick = onSettingsClick, onNavigateBack = onNavigateBack, - hintContent = { HintDefinition.TRANSLATION.Render() } + hintContent = HintDefinition.TRANSLATION.hint() ) AppCard(modifier = Modifier.padding(8.dp, end = 8.dp, bottom = 8.dp, top = 0.dp)) { diff --git a/app/src/main/java/eu/gaudian/translator/view/vocabulary/NewWordScreen.kt b/app/src/main/java/eu/gaudian/translator/view/vocabulary/NewWordScreen.kt index df86c91..badea30 100644 --- a/app/src/main/java/eu/gaudian/translator/view/vocabulary/NewWordScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/vocabulary/NewWordScreen.kt @@ -70,6 +70,7 @@ import eu.gaudian.translator.view.composable.AppTopAppBar import eu.gaudian.translator.view.composable.InspiringSearchField import eu.gaudian.translator.view.composable.SourceLanguageDropdown import eu.gaudian.translator.view.composable.TargetLanguageDropdown +import eu.gaudian.translator.view.hints.HintDefinition import eu.gaudian.translator.view.library.VocabularyCard import eu.gaudian.translator.viewmodel.LanguageViewModel import eu.gaudian.translator.viewmodel.VocabularyViewModel @@ -445,26 +446,17 @@ fun AIGeneratorCard( onGenerate: () -> Unit, modifier: Modifier = Modifier ) { + + val icon = Icons.Default.AutoAwesome val hints = stringArrayResource(R.array.vocabulary_hints) AppCard( modifier = modifier.fillMaxWidth(), + title = "AI Generator", + icon = icon, + hintContent = HintDefinition.VOCABULARY_GENERATE_AI.hint(), ) { - Column(modifier = Modifier.padding(24.dp)) { - Row(verticalAlignment = Alignment.CenterVertically) { - Icon( - imageVector = Icons.Default.AutoAwesome, - contentDescription = null, - tint = MaterialTheme.colorScheme.primary - ) - Spacer(modifier = Modifier.width(8.dp)) - Text( - text = "AI Generator", - style = MaterialTheme.typography.titleLarge, - fontWeight = FontWeight.Bold - ) - } + Column(modifier = Modifier.padding(8.dp)) { - Spacer(modifier = Modifier.height(24.dp)) Text( text = stringResource(R.string.text_search_term), style = MaterialTheme.typography.labelLarge,