refactor UI components and layout in NewWordScreen and HomeScreen using new reusable composables: AppActionCard, AppIconContainer, AppTextField, and LabeledSection.
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
@@ -115,7 +115,7 @@ fun AppOutlinedTextField(
|
||||
OutlinedTextField(
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
modifier = modifier,
|
||||
label = label,
|
||||
trailingIcon = finalTrailingIcon,
|
||||
shape = ComponentDefaults.DefaultShape,
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
@@ -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) }
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- 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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,8 @@
|
||||
|
||||
<string name="desc_daily_review_due">%1$d words need attention</string>
|
||||
<string name="desc_expand_your_vocabulary">Expand your vocabulary</string>
|
||||
<string name="desc_explore_packs">Discover curated vocabulary packs</string>
|
||||
<string name="desc_import_csv">Import words from CSV or lists</string>
|
||||
|
||||
<string name="description">Description</string>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user