Refactor hint management by replacing @Composable lambda hint content with a structured Hint type and updating UI components to support it.

This commit is contained in:
jonasgaudian
2026-02-17 14:57:56 +01:00
parent d14940ed11
commit 85c407481d
12 changed files with 81 additions and 67 deletions

View File

@@ -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,13 +142,15 @@ fun AppAlertDialog(
)
if (showBottomSheet) {
hintContent?.let {
HintBottomSheet(
onDismissRequest = { showBottomSheet = false },
sheetState = sheetState,
content = hintContent
content = it
)
}
}
}
/**
* Standard Dialog Header for BottomSheets.
@@ -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.") }
)
}

View File

@@ -115,18 +115,19 @@ fun AppTopAppBar(
)
if (showBottomSheet) {
hintContent?.let {
HintBottomSheet(
onDismissRequest = {
@Suppress("AssignedValueIsNeverRead")
showBottomSheet = false
},
sheetState = sheetState,
content = {
hintContent?.Render()
}
content = it
)
}
}
}
/**
* A composable that acts as a TopAppBar, containing a back navigation icon

View File

@@ -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 ---

View File

@@ -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

View File

@@ -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),

View File

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

View File

@@ -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) {
hintContent?.let {
HintBottomSheet(
onDismissRequest = {
@Suppress("AssignedValueIsNeverRead")
showBottomSheet = false
},
sheetState = sheetState,
hintContent
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))
}
}
}

View File

@@ -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()

View File

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

View File

@@ -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)) {

View File

@@ -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)) {

View File

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