From f737657cdb91b1f4b0a498e453839c293f47c7f2 Mon Sep 17 00:00:00 2001 From: jonasgaudian <43753916+jonasgaudian@users.noreply.github.com> Date: Thu, 19 Feb 2026 15:24:27 +0100 Subject: [PATCH] refactor UI components and layout in `NewWordScreen` and `HomeScreen` using new reusable composables: `AppActionCard`, `AppIconContainer`, `AppTextField`, and `LabeledSection`. --- .../main/assets/hints/explore_packs_hint.md | 2 +- .../view/composable/AppActionCard.kt | 124 ++++++++++ .../view/composable/AppIconContainer.kt | 81 +++++++ .../view/composable/AppOutlinedTextField.kt | 2 +- .../view/composable/AppTextField.kt | 74 ++++++ .../view/dialogs/RequestMorePackDialog.kt | 15 +- .../translator/view/home/HomeScreen.kt | 22 +- .../view/vocabulary/ExplorePacksScreen.kt | 7 +- .../view/vocabulary/NewWordScreen.kt | 225 +++++++++--------- app/src/main/res/values/strings.xml | 2 + 10 files changed, 408 insertions(+), 146 deletions(-) create mode 100644 app/src/main/java/eu/gaudian/translator/view/composable/AppActionCard.kt create mode 100644 app/src/main/java/eu/gaudian/translator/view/composable/AppIconContainer.kt create mode 100644 app/src/main/java/eu/gaudian/translator/view/composable/AppTextField.kt diff --git a/app/src/main/assets/hints/explore_packs_hint.md b/app/src/main/assets/hints/explore_packs_hint.md index d9bc3a7..54f2f0b 100644 --- a/app/src/main/assets/hints/explore_packs_hint.md +++ b/app/src/main/assets/hints/explore_packs_hint.md @@ -1,4 +1,4 @@ -All vocabulary lists in this section were generated using AI. While I strive for accuracy and quality, please keep in mind that these are machine-generated collections. +All vocabulary lists in this section were generated automatically. While I strive for accuracy and quality, please keep in mind that these are machine-generated collections. I'm a single developer building and maintaining this app in my spare time. I'm passionate about creating tools that help people learn languages, but I have limited resources and time. diff --git a/app/src/main/java/eu/gaudian/translator/view/composable/AppActionCard.kt b/app/src/main/java/eu/gaudian/translator/view/composable/AppActionCard.kt new file mode 100644 index 0000000..a98e4d5 --- /dev/null +++ b/app/src/main/java/eu/gaudian/translator/view/composable/AppActionCard.kt @@ -0,0 +1,124 @@ +package eu.gaudian.translator.view.composable + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +/** + * A compact action card with an icon and label, designed for use in rows or grids. + * Used for quick action buttons like "Explore Packs", "Import CSV", etc. + * + * @param label The text label below the icon + * @param icon The icon to display + * @param onClick Callback when the card is clicked + * @param modifier Modifier for the card + * @param height The height of the card (default 120.dp) + */ +@Composable +fun AppActionCard( + label: String, + icon: ImageVector, + onClick: () -> Unit, + modifier: Modifier = Modifier, + height: Dp = 120.dp, + iconContainerSize: Dp = 48.dp, + iconSize: Dp = 24.dp +) { + AppCard( + modifier = modifier.height(height), + onClick = onClick + ) { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + CircularIconContainer( + imageVector = icon, + size = iconContainerSize, + iconSize = iconSize + ) + Spacer(modifier = Modifier.height(12.dp)) + Text( + text = label, + style = MaterialTheme.typography.labelLarge, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurface, + textAlign = TextAlign.Center + ) + } + } +} + +/** + * A section header label with consistent styling. + * Used for section titles like "Recently Added", etc. + * + * @param text The section title text + * @param modifier Modifier for the text + */ +@Composable +fun SectionLabel( + text: String, + modifier: Modifier = Modifier +) { + Text( + text = text, + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold, + modifier = modifier + ) +} + +/** + * A labeled section with an optional action button. + * Provides consistent header styling for sections with a title and optional action. + * + * @param title The section title + * @param modifier Modifier for the section header + * @param actionLabel Optional label for the action button + * @param onActionClick Optional callback for the action button + * @param content The content below the header + */ +@Composable +fun LabeledSection( + title: String, + modifier: Modifier = Modifier, + actionLabel: String? = null, + onActionClick: (() -> Unit)? = null, + content: @Composable () -> Unit +) { + Column(modifier = modifier) { + // Header row with title and optional action + if (actionLabel != null && onActionClick != null) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + SectionLabel(text = title) + androidx.compose.material3.TextButton(onClick = onActionClick) { + Text(actionLabel) + } + } + } else { + SectionLabel(text = title) + } + Spacer(modifier = Modifier.height(12.dp)) + content() + } +} diff --git a/app/src/main/java/eu/gaudian/translator/view/composable/AppIconContainer.kt b/app/src/main/java/eu/gaudian/translator/view/composable/AppIconContainer.kt new file mode 100644 index 0000000..c57ea6c --- /dev/null +++ b/app/src/main/java/eu/gaudian/translator/view/composable/AppIconContainer.kt @@ -0,0 +1,81 @@ +package eu.gaudian.translator.view.composable + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +/** + * A reusable icon container that displays an icon inside a shaped background. + * Used throughout the app for consistent icon presentation in cards, buttons, and action items. + * + * @param imageVector The icon to display + * @param modifier Modifier to be applied to the container + * @param size The size of the container (default 40.dp) + * @param iconSize The size of the icon itself (default 24.dp) + * @param shape The shape of the container (default RoundedCornerShape(12.dp)) + * @param backgroundColor Background color of the container + * @param iconTint Tint color for the icon + */ +@Composable +fun AppIconContainer( + imageVector: ImageVector, + modifier: Modifier = Modifier, + size: Dp = 40.dp, + iconSize: Dp = 24.dp, + shape: androidx.compose.ui.graphics.Shape = RoundedCornerShape(12.dp), + backgroundColor: androidx.compose.ui.graphics.Color = MaterialTheme.colorScheme.surfaceVariant, + iconTint: androidx.compose.ui.graphics.Color = MaterialTheme.colorScheme.primary, + contentDescription: String? = null +) { + Box( + modifier = modifier + .size(size) + .clip(shape) + .background(backgroundColor), + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = imageVector, + contentDescription = contentDescription, + tint = iconTint, + modifier = Modifier.size(iconSize) + ) + } +} + +/** + * A circular variant of AppIconContainer. + * Convenience wrapper for circular icon containers. + */ +@Composable +fun CircularIconContainer( + imageVector: ImageVector, + modifier: Modifier = Modifier, + size: Dp = 48.dp, + iconSize: Dp = 24.dp, + backgroundColor: androidx.compose.ui.graphics.Color = MaterialTheme.colorScheme.surfaceVariant, + iconTint: androidx.compose.ui.graphics.Color = MaterialTheme.colorScheme.primary, + contentDescription: String? = null +) { + AppIconContainer( + imageVector = imageVector, + modifier = modifier, + size = size, + iconSize = iconSize, + shape = CircleShape, + backgroundColor = backgroundColor, + iconTint = iconTint, + contentDescription = contentDescription + ) +} \ No newline at end of file diff --git a/app/src/main/java/eu/gaudian/translator/view/composable/AppOutlinedTextField.kt b/app/src/main/java/eu/gaudian/translator/view/composable/AppOutlinedTextField.kt index 916d34a..fd5e0f7 100644 --- a/app/src/main/java/eu/gaudian/translator/view/composable/AppOutlinedTextField.kt +++ b/app/src/main/java/eu/gaudian/translator/view/composable/AppOutlinedTextField.kt @@ -115,7 +115,7 @@ fun AppOutlinedTextField( OutlinedTextField( value = value, onValueChange = onValueChange, - modifier = modifier.fillMaxWidth(), + modifier = modifier, label = label, trailingIcon = finalTrailingIcon, shape = ComponentDefaults.DefaultShape, diff --git a/app/src/main/java/eu/gaudian/translator/view/composable/AppTextField.kt b/app/src/main/java/eu/gaudian/translator/view/composable/AppTextField.kt new file mode 100644 index 0000000..f6dc47f --- /dev/null +++ b/app/src/main/java/eu/gaudian/translator/view/composable/AppTextField.kt @@ -0,0 +1,74 @@ +package eu.gaudian.translator.view.composable + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp + +/** + * A styled filled text input field. + * Different from AppOutlinedTextField - this uses a filled background style. + * + * @param value The input text to be shown in the text field. + * @param onValueChange The callback that is triggered when the input service updates the text. + * @param modifier The modifier to be applied to the text field. + * @param placeholder The placeholder text to display when the field is empty. + * @param enabled Whether the text field is enabled. + * @param readOnly Whether the text field is read-only. + * @param singleLine Whether the text field is single line. + * @param minLines Minimum number of lines. + * @param maxLines Maximum number of lines. + */ +@Composable +fun AppTextField( + value: String, + onValueChange: (String) -> Unit, + modifier: Modifier = Modifier, + placeholder: String? = null, + enabled: Boolean = true, + readOnly: Boolean = false, + singleLine: Boolean = true, + minLines: Int = 1, + maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE, + leadingIcon: @Composable (() -> Unit)? = null, + trailingIcon: @Composable (() -> Unit)? = null, +) { + val cornerRadius = 12.dp + + TextField( + value = value, + onValueChange = onValueChange, + modifier = modifier.fillMaxWidth(), + placeholder = placeholder?.let { + { + Text( + text = it, + color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f) + ) + } + }, + shape = RoundedCornerShape(cornerRadius), + colors = TextFieldDefaults.colors( + focusedContainerColor = MaterialTheme.colorScheme.surface, + unfocusedContainerColor = MaterialTheme.colorScheme.surface, + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + cursorColor = MaterialTheme.colorScheme.primary, + focusedTextColor = MaterialTheme.colorScheme.onSurface, + unfocusedTextColor = MaterialTheme.colorScheme.onSurface + ), + singleLine = singleLine, + minLines = minLines, + maxLines = maxLines, + enabled = enabled, + readOnly = readOnly, + leadingIcon = leadingIcon, + trailingIcon = trailingIcon + ) +} \ No newline at end of file diff --git a/app/src/main/java/eu/gaudian/translator/view/dialogs/RequestMorePackDialog.kt b/app/src/main/java/eu/gaudian/translator/view/dialogs/RequestMorePackDialog.kt index 349d8ea..d8cec16 100644 --- a/app/src/main/java/eu/gaudian/translator/view/dialogs/RequestMorePackDialog.kt +++ b/app/src/main/java/eu/gaudian/translator/view/dialogs/RequestMorePackDialog.kt @@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable @@ -24,6 +23,7 @@ import androidx.core.net.toUri import eu.gaudian.translator.R import eu.gaudian.translator.ui.theme.ThemePreviews import eu.gaudian.translator.view.composable.AppDialog +import eu.gaudian.translator.view.composable.AppOutlinedTextField import eu.gaudian.translator.view.composable.AppSlider import kotlin.math.roundToInt @@ -55,7 +55,7 @@ fun RequestMorePackDialog( style = MaterialTheme.typography.labelLarge, fontWeight = FontWeight.SemiBold ) - OutlinedTextField( + AppOutlinedTextField( value = topic, onValueChange = { topic = it }, placeholder = { Text("e.g. Travel, Business, Cooking…") }, @@ -78,20 +78,21 @@ fun RequestMorePackDialog( color = MaterialTheme.colorScheme.onSurfaceVariant ) } - Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { - OutlinedTextField( + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + AppOutlinedTextField( value = langFrom, onValueChange = { langFrom = it }, placeholder = { Text(stringResource(R.string.label_from)) }, - label = { Text(stringResource(R.string.label_from)) }, singleLine = true, modifier = Modifier.weight(1f) ) - OutlinedTextField( + AppOutlinedTextField( value = langTo, onValueChange = { langTo = it }, placeholder = { Text(stringResource(R.string.label_to)) }, - label = { Text(stringResource(R.string.label_to)) }, singleLine = true, modifier = Modifier.weight(1f) ) diff --git a/app/src/main/java/eu/gaudian/translator/view/home/HomeScreen.kt b/app/src/main/java/eu/gaudian/translator/view/home/HomeScreen.kt index 2df4384..43a7078 100644 --- a/app/src/main/java/eu/gaudian/translator/view/home/HomeScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/home/HomeScreen.kt @@ -27,7 +27,6 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -47,6 +46,7 @@ import eu.gaudian.translator.R import eu.gaudian.translator.utils.findActivity import eu.gaudian.translator.view.NavigationRoutes import eu.gaudian.translator.view.composable.AppCard +import eu.gaudian.translator.view.composable.LabeledSection import eu.gaudian.translator.view.composable.Screen import eu.gaudian.translator.view.settings.SettingsRoutes import eu.gaudian.translator.view.stats.widgets.WeeklyActivityChartWidget @@ -356,20 +356,12 @@ fun WeeklyProgressSection( val viewModel: ProgressViewModel = hiltViewModel(activity) val weeklyActivityStats by viewModel.weeklyActivityStats.collectAsState() - Column(modifier = modifier) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text(text = stringResource(R.string.label_weekly_progress), style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) - TextButton(onClick = { navController.navigate(NavigationRoutes.STATS_VOCABULARY_HEATMAP) }) { - Text(stringResource(R.string.label_see_history), softWrap = false) - } - } - - Spacer(modifier = Modifier.height(8.dp)) - + LabeledSection( + title = stringResource(R.string.label_weekly_progress), + modifier = modifier, + actionLabel = stringResource(R.string.label_see_history), + onActionClick = { navController.navigate(NavigationRoutes.STATS_VOCABULARY_HEATMAP) } + ) { AppCard( modifier = Modifier.fillMaxWidth(), onClick = { navController.navigate(NavigationRoutes.STATS_VOCABULARY_HEATMAP) } diff --git a/app/src/main/java/eu/gaudian/translator/view/vocabulary/ExplorePacksScreen.kt b/app/src/main/java/eu/gaudian/translator/view/vocabulary/ExplorePacksScreen.kt index 7cc6e56..d052d52 100644 --- a/app/src/main/java/eu/gaudian/translator/view/vocabulary/ExplorePacksScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/vocabulary/ExplorePacksScreen.kt @@ -80,6 +80,7 @@ import eu.gaudian.translator.utils.findActivity import eu.gaudian.translator.view.composable.AppAlertDialog import eu.gaudian.translator.view.composable.AppDialog import eu.gaudian.translator.view.composable.AppTopAppBar +import eu.gaudian.translator.view.composable.SectionLabel import eu.gaudian.translator.view.dialogs.RequestMorePackDialog import eu.gaudian.translator.view.hints.HintDefinition import eu.gaudian.translator.view.translation.LanguageSelectorBar @@ -365,11 +366,7 @@ fun ExplorePacksScreen( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { - Text( - stringResource(R.string.label_available_collections), - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.Bold - ) + SectionLabel(text = stringResource(R.string.label_available_collections)) if (!isLoadingManifest && packs.isNotEmpty()) { Text( stringResource(R.string.label_d_packs, filteredPacks.size), 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 9985538..be3e163 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 @@ -16,7 +16,6 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons @@ -31,8 +30,6 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TextButton -import androidx.compose.material3.TextField -import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -46,7 +43,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringArrayResource import androidx.compose.ui.res.stringResource @@ -61,13 +57,17 @@ import eu.gaudian.translator.utils.StatusMessageId import eu.gaudian.translator.utils.StatusMessageService import eu.gaudian.translator.utils.findActivity import eu.gaudian.translator.view.NavigationRoutes +import eu.gaudian.translator.view.composable.AppActionCard import eu.gaudian.translator.view.composable.AppButton import eu.gaudian.translator.view.composable.AppCard +import eu.gaudian.translator.view.composable.AppIconContainer import eu.gaudian.translator.view.composable.AppIcons import eu.gaudian.translator.view.composable.AppOutlinedButton +import eu.gaudian.translator.view.composable.AppOutlinedTextField import eu.gaudian.translator.view.composable.AppSlider import eu.gaudian.translator.view.composable.AppTopAppBar import eu.gaudian.translator.view.composable.InspiringSearchField +import eu.gaudian.translator.view.composable.LabeledSection import eu.gaudian.translator.view.composable.Screen import eu.gaudian.translator.view.composable.SourceLanguageDropdown import eu.gaudian.translator.view.composable.TargetLanguageDropdown @@ -226,6 +226,14 @@ fun NewWordScreen( Spacer(modifier = Modifier.height(16.dp)) + // Explore Packs - Prominent full-width card at top + ExplorePacksProminentCard( + onClick = { navController.navigate(NavigationRoutes.EXPLORE_PACKS) } + ) + + Spacer(modifier = Modifier.height(24.dp)) + + // AI Generator Card AIGeneratorCard( category = category, onCategoryChange = { category = it }, @@ -245,6 +253,7 @@ fun NewWordScreen( Spacer(modifier = Modifier.height(24.dp)) + // Add Manually Card AddManuallyCard( languageViewModel = languageViewModel, vocabularyViewModel = vocabularyViewModel, @@ -252,11 +261,9 @@ fun NewWordScreen( Spacer(modifier = Modifier.height(24.dp)) - BottomActionCardsRow( - onExplorePsClick = { - navController.navigate(NavigationRoutes.EXPLORE_PACKS) - }, - onImportCsvClick = { + // Import CSV - Full width card at bottom + ImportCsvCard( + onClick = { navController.navigate("settings_vocabulary_repository_options") } ) @@ -464,7 +471,11 @@ fun AIGeneratorCard( ) Spacer(modifier = Modifier.height(8.dp)) Row( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(16.dp)) + .background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.35f)) + .padding(12.dp), horizontalArrangement = Arrangement.spacedBy(12.dp) ) { SourceLanguageDropdown( @@ -556,26 +567,16 @@ fun AddManuallyCard( modifier = modifier.fillMaxWidth(), ) { Column(modifier = Modifier.padding(24.dp)) { - // Header Row + // Header Row - Using reusable AppIconContainer Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { Row(verticalAlignment = Alignment.CenterVertically) { - Box( - modifier = Modifier - .size(40.dp) - .clip(RoundedCornerShape(12.dp)) - .background(MaterialTheme.colorScheme.surfaceVariant), - contentAlignment = Alignment.Center - ) { - Icon( - imageVector = Icons.Default.EditNote, - contentDescription = null, - tint = MaterialTheme.colorScheme.primary - ) - } + AppIconContainer( + imageVector = Icons.Default.EditNote + ) Spacer(modifier = Modifier.width(16.dp)) Text( text = stringResource(R.string.label_add_vocabulary), @@ -588,37 +589,19 @@ fun AddManuallyCard( Spacer(modifier = Modifier.height(24.dp)) - // Input Fields - TextField( + // Input Fields - Using AppOutlinedTextField + AppOutlinedTextField( value = wordText, onValueChange = { wordText = it }, - placeholder = { Text(stringResource(R.string.text_label_word), color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f)) }, - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(12.dp), - colors = TextFieldDefaults.colors( - focusedContainerColor = MaterialTheme.colorScheme.surface, // Very dark background - unfocusedContainerColor = MaterialTheme.colorScheme.surface, - focusedIndicatorColor = Color.Transparent, - unfocusedIndicatorColor = Color.Transparent - ), - singleLine = true + placeholder = { Text(stringResource(R.string.text_label_word)) } ) Spacer(modifier = Modifier.height(16.dp)) - TextField( + AppOutlinedTextField( value = translationText, onValueChange = { translationText = it }, - placeholder = { Text(stringResource(R.string.text_translation), color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f)) }, - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(12.dp), - colors = TextFieldDefaults.colors( - focusedContainerColor = MaterialTheme.colorScheme.surface, - unfocusedContainerColor = MaterialTheme.colorScheme.surface, - focusedIndicatorColor = Color.Transparent, - unfocusedIndicatorColor = Color.Transparent - ), - singleLine = true + placeholder = { Text(stringResource(R.string.text_translation)) } ) Spacer(modifier = Modifier.height(16.dp)) @@ -653,7 +636,7 @@ fun AddManuallyCard( Spacer(modifier = Modifier.height(24.dp)) - // Add to List Button (Darker variant) + // Add to List Button AppButton( onClick = { val newItem = VocabularyItem( @@ -682,84 +665,92 @@ fun AddManuallyCard( } } +// --- Explore Packs Prominent Card (Full width at top) --- @Composable -fun BottomActionCardsRow( - modifier: Modifier = Modifier, - onExplorePsClick: () -> Unit, - onImportCsvClick: () -> Unit +fun ExplorePacksProminentCard( + onClick: () -> Unit, + modifier: Modifier = Modifier ) { - Row( + AppCard( modifier = modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(16.dp) + onClick = onClick ) { - // Explore Packs Card - AppCard( + Row( modifier = Modifier - .weight(1f) - .height(120.dp), - onClick = onExplorePsClick + .fillMaxWidth() + .padding(20.dp), + verticalAlignment = Alignment.CenterVertically ) { - Column( - modifier = Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center - ) { - Box( - modifier = Modifier - .size(48.dp) - .clip(CircleShape) - .background(MaterialTheme.colorScheme.surfaceVariant), - contentAlignment = Alignment.Center - ) { - Icon( - imageVector = AppIcons.Vocabulary, - contentDescription = null, - tint = MaterialTheme.colorScheme.primary - ) - } - Spacer(modifier = Modifier.height(12.dp)) - @Suppress("HardCodedStringLiteral") + AppIconContainer( + imageVector = AppIcons.Vocabulary, + size = 56.dp, + iconSize = 28.dp + ) + Spacer(modifier = Modifier.width(16.dp)) + Column(modifier = Modifier.weight(1f)) { Text( - text = "Explore Packs", - style = MaterialTheme.typography.labelLarge, - fontWeight = FontWeight.Bold, - color = MaterialTheme.colorScheme.onSurface - ) - } - } - - // Import CSV Card - AppCard( - modifier = Modifier - .weight(1f) - .height(120.dp), - onClick = onImportCsvClick - ) { - Column( - modifier = Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center - ) { - Box( - modifier = Modifier - .size(48.dp) - .clip(CircleShape) - .background(MaterialTheme.colorScheme.surfaceVariant), - contentAlignment = Alignment.Center - ) { - Icon( - imageVector = Icons.Default.DriveFolderUpload, - contentDescription = null, - tint = MaterialTheme.colorScheme.primary - ) - } - Spacer(modifier = Modifier.height(12.dp)) - Text( - text = "Import Lists or CSV", - style = MaterialTheme.typography.labelLarge, + text = stringResource(R.string.title_explore_packs), + style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = stringResource(R.string.desc_explore_packs), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) } + Icon( + imageVector = Icons.Default.DriveFolderUpload, + contentDescription = null, + modifier = Modifier.size(24.dp), + tint = MaterialTheme.colorScheme.primary + ) } } -} \ No newline at end of file +} + +// --- Import CSV Card (Full width at bottom) --- +@Composable +fun ImportCsvCard( + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + AppCard( + modifier = modifier.fillMaxWidth(), + onClick = onClick + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(20.dp), + verticalAlignment = Alignment.CenterVertically + ) { + AppIconContainer( + imageVector = Icons.Default.DriveFolderUpload, + size = 56.dp, + iconSize = 28.dp + ) + Spacer(modifier = Modifier.width(16.dp)) + Column(modifier = Modifier.weight(1f)) { + Text( + text = stringResource(R.string.label_import_csv), + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = stringResource(R.string.desc_import_csv), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + Icon( + imageVector = Icons.Default.DriveFolderUpload, + contentDescription = null, + modifier = Modifier.size(24.dp), + tint = MaterialTheme.colorScheme.primary + ) + } + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6fd7c47..9b8f5f9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -77,6 +77,8 @@ %1$d words need attention Expand your vocabulary + Discover curated vocabulary packs + Import words from CSV or lists Description