feat: add region flag emojis to shopping lists UI, implement save actions in settings screens, add collapsible preview card in sort screen
- Display region flag emoji next to list name in `ShoppingListCard` with mapping for all supported countries - Show region flag and name subtitle in `ListSettingsScreen` settings tile when region is set - Replace bottom save button with top-right check icon in `ListNameImageScreen` and `ListRegionScreen` - Add `onSave` callbacks to persist list updates via `update
This commit is contained in:
parent
ab1bf189b3
commit
2cef0e399c
@ -316,8 +316,28 @@ private fun ShoppingListCard(
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
// List name
|
||||
val regionFlagEmoji = item.list.region?.let { code ->
|
||||
when (code) {
|
||||
"de" -> "🇩🇪"
|
||||
"au" -> "🇦🇺"
|
||||
"at" -> "🇦🇹"
|
||||
"ca" -> "🇨🇦"
|
||||
"es" -> "🇪🇸"
|
||||
"fr" -> "🇫🇷"
|
||||
"hu" -> "🇭🇺"
|
||||
"it" -> "🇮🇹"
|
||||
"no" -> "🇳🇴"
|
||||
"nl" -> "🇳🇱"
|
||||
"pl" -> "🇵🇱"
|
||||
"pt" -> "🇵🇹"
|
||||
"gb" -> "🇬🇧"
|
||||
"ru" -> "🇷🇺"
|
||||
"ch_de", "ch_fr" -> "🇨🇭"
|
||||
else -> ""
|
||||
}
|
||||
} ?: ""
|
||||
Text(
|
||||
text = item.list.name,
|
||||
text = "$regionFlagEmoji ${item.list.name}".trim(),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = Color.White,
|
||||
fontWeight = FontWeight.Bold,
|
||||
|
||||
@ -66,6 +66,17 @@ fun ListNameImageScreen(
|
||||
mutableStateOf(listData?.list?.backgroundResName)
|
||||
}
|
||||
|
||||
val onSave = {
|
||||
listData?.let {
|
||||
val updated = it.list.copy(
|
||||
name = listName.ifBlank { it.list.name },
|
||||
backgroundResName = selectedBg
|
||||
)
|
||||
viewModel.updateList(updated)
|
||||
}
|
||||
onBack()
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
@ -74,6 +85,14 @@ fun ListNameImageScreen(
|
||||
IconButton(onClick = onBack) {
|
||||
Icon(Icons.Filled.ArrowBack, contentDescription = stringResource(R.string.action_back))
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = onSave) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Check,
|
||||
contentDescription = stringResource(R.string.action_save)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -153,7 +172,9 @@ fun ListNameImageScreen(
|
||||
contentPadding = PaddingValues(vertical = 8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f)
|
||||
) {
|
||||
items(allListBackgrounds) { bg ->
|
||||
val isSelected = selectedBg == bg.resName
|
||||
@ -199,23 +220,6 @@ fun ListNameImageScreen(
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
val updated = listData.list.copy(
|
||||
name = listName.ifBlank { listData.list.name },
|
||||
backgroundResName = selectedBg
|
||||
)
|
||||
// TODO: update via viewmodel/usecase
|
||||
onBack()
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
Text(stringResource(R.string.action_save))
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ 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.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
@ -17,6 +18,7 @@ import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
@ -40,23 +42,25 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.safebite.app.R
|
||||
import com.safebite.app.presentation.screen.lists.ListsViewModel
|
||||
|
||||
private data class Region(val name: String, val code: String, val flag: String)
|
||||
|
||||
private val availableRegions = listOf(
|
||||
"Allemagne" to "de",
|
||||
"Australie" to "au",
|
||||
"Autriche" to "at",
|
||||
"Canada" to "ca",
|
||||
"Espagne" to "es",
|
||||
"France" to "fr",
|
||||
"Hongrie" to "hu",
|
||||
"Italie" to "it",
|
||||
"Norvège" to "no",
|
||||
"Pays-Bas" to "nl",
|
||||
"Pologne" to "pl",
|
||||
"Portugal" to "pt",
|
||||
"Royaume-Uni" to "gb",
|
||||
"Russie" to "ru",
|
||||
"Suisse (Allemand)" to "ch_de",
|
||||
"Suisse (français)" to "ch_fr"
|
||||
Region("Allemagne", "de", "🇩🇪"),
|
||||
Region("Australie", "au", "🇦🇺"),
|
||||
Region("Autriche", "at", "🇦🇹"),
|
||||
Region("Canada", "ca", "🇨🇦"),
|
||||
Region("Espagne", "es", "🇪🇸"),
|
||||
Region("France", "fr", "🇫🇷"),
|
||||
Region("Hongrie", "hu", "🇭🇺"),
|
||||
Region("Italie", "it", "🇮🇹"),
|
||||
Region("Norvège", "no", "🇳🇴"),
|
||||
Region("Pays-Bas", "nl", "🇳🇱"),
|
||||
Region("Pologne", "pl", "🇵🇱"),
|
||||
Region("Portugal", "pt", "🇵🇹"),
|
||||
Region("Royaume-Uni", "gb", "🇬🇧"),
|
||||
Region("Russie", "ru", "🇷🇺"),
|
||||
Region("Suisse (Allemand)", "ch_de", "🇨🇭"),
|
||||
Region("Suisse (français)", "ch_fr", "🇨🇭")
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@ -72,6 +76,13 @@ fun ListRegionScreen(
|
||||
mutableStateOf(listData?.list?.region)
|
||||
}
|
||||
|
||||
val onSave = {
|
||||
listData?.let {
|
||||
viewModel.updateList(it.list.copy(region = selectedRegion))
|
||||
}
|
||||
onBack()
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
@ -80,6 +91,14 @@ fun ListRegionScreen(
|
||||
IconButton(onClick = onBack) {
|
||||
Icon(Icons.Filled.ArrowBack, contentDescription = stringResource(R.string.action_back))
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = onSave) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Check,
|
||||
contentDescription = stringResource(R.string.action_save)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -99,24 +118,21 @@ fun ListRegionScreen(
|
||||
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(vertical = 8.dp),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f)
|
||||
) {
|
||||
items(availableRegions) { (name, code) ->
|
||||
val isSelected = selectedRegion == code
|
||||
items(availableRegions) { region ->
|
||||
val isSelected = selectedRegion == region.code
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
selectedRegion = code
|
||||
listData?.let {
|
||||
// TODO: persist via viewmodel/usecase
|
||||
}
|
||||
}
|
||||
.clickable { selectedRegion = region.code }
|
||||
.padding(vertical = 14.dp, horizontal = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = name,
|
||||
text = "${region.flag} ${region.name}",
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
@ -139,6 +155,8 @@ fun ListRegionScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,9 +154,15 @@ fun ListSettingsScreen(
|
||||
)
|
||||
}
|
||||
item {
|
||||
val regionCode = listData.list.region
|
||||
val regionSubtitle = if (regionCode != null) {
|
||||
val (flag, name) = regionFlagAndName(regionCode)
|
||||
"$flag $name"
|
||||
} else null
|
||||
SettingsTile(
|
||||
icon = Icons.Filled.Language,
|
||||
label = stringResource(R.string.list_region_language),
|
||||
subtitle = regionSubtitle,
|
||||
onClick = onOpenRegion
|
||||
)
|
||||
}
|
||||
@ -201,10 +207,31 @@ fun ListSettingsScreen(
|
||||
}
|
||||
}
|
||||
|
||||
private fun regionFlagAndName(code: String): Pair<String, String> = when (code) {
|
||||
"de" -> "🇩🇪" to "Allemagne"
|
||||
"au" -> "🇦🇺" to "Australie"
|
||||
"at" -> "🇦🇹" to "Autriche"
|
||||
"ca" -> "🇨🇦" to "Canada"
|
||||
"es" -> "🇪🇸" to "Espagne"
|
||||
"fr" -> "🇫🇷" to "France"
|
||||
"hu" -> "🇭🇺" to "Hongrie"
|
||||
"it" -> "🇮🇹" to "Italie"
|
||||
"no" -> "🇳🇴" to "Norvège"
|
||||
"nl" -> "🇳🇱" to "Pays-Bas"
|
||||
"pl" -> "🇵🇱" to "Pologne"
|
||||
"pt" -> "🇵🇹" to "Portugal"
|
||||
"gb" -> "🇬🇧" to "Royaume-Uni"
|
||||
"ru" -> "🇷🇺" to "Russie"
|
||||
"ch_de" -> "🇨🇭" to "Suisse (Allemand)"
|
||||
"ch_fr" -> "🇨🇭" to "Suisse (français)"
|
||||
else -> "" to code
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SettingsTile(
|
||||
icon: androidx.compose.ui.graphics.vector.ImageVector,
|
||||
label: String,
|
||||
subtitle: String? = null,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
Card(
|
||||
@ -238,6 +265,16 @@ private fun SettingsTile(
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
if (subtitle != null) {
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Text(
|
||||
text = subtitle,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
package com.safebite.app.presentation.screen.lists.settings
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
|
||||
@ -23,6 +26,8 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.Sort
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.DragHandle
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
||||
import androidx.compose.material.icons.filled.Visibility
|
||||
import androidx.compose.material.icons.filled.VisibilityOff
|
||||
import androidx.compose.material3.Card
|
||||
@ -85,6 +90,7 @@ fun ListSortScreen(
|
||||
|
||||
var draggedIndex by remember { mutableStateOf<Int?>(null) }
|
||||
var dragOffsetY by remember { mutableFloatStateOf(0f) }
|
||||
var previewExpanded by remember { mutableStateOf(true) }
|
||||
val itemHeight = 56.dp
|
||||
val itemPx = with(LocalContext.current.resources.displayMetrics) { itemHeight.value * density }
|
||||
|
||||
@ -134,23 +140,22 @@ fun ListSortScreen(
|
||||
modifier = Modifier.padding(vertical = 8.dp)
|
||||
)
|
||||
|
||||
// Preview card
|
||||
// Preview card (collapsible)
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp),
|
||||
.padding(vertical = 8.dp)
|
||||
.clickable { previewExpanded = !previewExpanded },
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f)
|
||||
)
|
||||
) {
|
||||
Column(
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
@ -162,11 +167,25 @@ fun ListSortScreen(
|
||||
Text(
|
||||
text = stringResource(R.string.list_sort_preview),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
Icon(
|
||||
imageVector = if (previewExpanded) Icons.Filled.KeyboardArrowUp else Icons.Filled.KeyboardArrowDown,
|
||||
contentDescription = if (previewExpanded) "Réduire" else "Développer"
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
AnimatedVisibility(
|
||||
visible = previewExpanded,
|
||||
enter = expandVertically(),
|
||||
exit = shrinkVertically()
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
orderedCategories.forEach { category ->
|
||||
val isVisible = category in visibleCategories
|
||||
Row(
|
||||
@ -189,6 +208,7 @@ fun ListSortScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
HorizontalDivider()
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
MAJOR=1
|
||||
MINOR=16
|
||||
PATCH=5
|
||||
CODE=25
|
||||
PATCH=7
|
||||
CODE=27
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user