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