feat: implement center-crop image processing for custom items, enhance onboarding UI with numbered step cards and allergen level selection
- Add `saveCroppedImage` helper to center-crop photos to square, resize to 512px, and compress to JPEG - Apply cropping to both camera capture and gallery picker in `ListDetailScreen` and `ItemDetailSheet` - Display custom item photos in tiles with `AsyncImage` and camera badge overlay - Show full-width photos in item detail sheet - Redesign onboarding "How it works" step
This commit is contained in:
parent
4ac951cf6e
commit
48a9266942
@ -78,13 +78,16 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import coil.compose.AsyncImage
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.draw.rotate
|
import androidx.compose.ui.draw.rotate
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
@ -150,11 +153,7 @@ fun ListDetailScreen(
|
|||||||
ActivityResultContracts.TakePicturePreview()
|
ActivityResultContracts.TakePicturePreview()
|
||||||
) { bitmap: Bitmap? ->
|
) { bitmap: Bitmap? ->
|
||||||
bitmap?.let {
|
bitmap?.let {
|
||||||
val file = File(context.cacheDir, "sb_photo_${System.currentTimeMillis()}.jpg")
|
selectedImageUri = saveCroppedImage(context, it)
|
||||||
file.outputStream().use { out ->
|
|
||||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, out)
|
|
||||||
}
|
|
||||||
selectedImageUri = Uri.fromFile(file).toString()
|
|
||||||
showDescriptionDialog = true
|
showDescriptionDialog = true
|
||||||
}
|
}
|
||||||
showPhotoPicker = false
|
showPhotoPicker = false
|
||||||
@ -164,8 +163,13 @@ fun ListDetailScreen(
|
|||||||
ActivityResultContracts.GetContent()
|
ActivityResultContracts.GetContent()
|
||||||
) { uri: Uri? ->
|
) { uri: Uri? ->
|
||||||
uri?.let {
|
uri?.let {
|
||||||
selectedImageUri = it.toString()
|
val bmp = context.contentResolver.openInputStream(it)?.use { stream ->
|
||||||
showDescriptionDialog = true
|
BitmapFactory.decodeStream(stream)
|
||||||
|
}
|
||||||
|
bmp?.let { bitmap ->
|
||||||
|
selectedImageUri = saveCroppedImage(context, bitmap)
|
||||||
|
showDescriptionDialog = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
showPhotoPicker = false
|
showPhotoPicker = false
|
||||||
}
|
}
|
||||||
@ -349,6 +353,26 @@ fun ListDetailScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Croppe au centre en carré, redimensionne à [maxSize] et sauvegarde en JPEG dans le cache. */
|
||||||
|
private fun saveCroppedImage(context: android.content.Context, bitmap: Bitmap, maxSize: Int = 512): String? {
|
||||||
|
return try {
|
||||||
|
val w = bitmap.width
|
||||||
|
val h = bitmap.height
|
||||||
|
val side = kotlin.math.min(w, h)
|
||||||
|
val x = (w - side) / 2
|
||||||
|
val y = (h - side) / 2
|
||||||
|
val cropped = Bitmap.createBitmap(bitmap, x, y, side, side)
|
||||||
|
val scaled = Bitmap.createScaledBitmap(cropped, maxSize, maxSize, true)
|
||||||
|
val file = File(context.cacheDir, "item_${System.currentTimeMillis()}.jpg")
|
||||||
|
file.outputStream().use { out ->
|
||||||
|
scaled.compress(Bitmap.CompressFormat.JPEG, 85, out)
|
||||||
|
}
|
||||||
|
Uri.fromFile(file).toString()
|
||||||
|
} catch (_: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
// Contenu principal scrollable
|
// Contenu principal scrollable
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
@ -588,12 +612,25 @@ private fun Tile(
|
|||||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
Text(
|
if (!data.imageUrl.isNullOrBlank()) {
|
||||||
text = data.emoji,
|
AsyncImage(
|
||||||
style = MaterialTheme.typography.headlineMedium,
|
model = data.imageUrl,
|
||||||
modifier = Modifier.padding(top = 4.dp)
|
contentDescription = null,
|
||||||
)
|
modifier = Modifier
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
.fillMaxWidth()
|
||||||
|
.weight(1f)
|
||||||
|
.padding(top = 4.dp)
|
||||||
|
.clip(RoundedCornerShape(8.dp)),
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
text = data.emoji,
|
||||||
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
|
modifier = Modifier.padding(top = 4.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
Text(
|
Text(
|
||||||
text = data.label,
|
text = data.label,
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
@ -624,6 +661,18 @@ private fun Tile(
|
|||||||
.size(16.dp)
|
.size(16.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (!data.imageUrl.isNullOrBlank()) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.CameraAlt,
|
||||||
|
contentDescription = "Photo",
|
||||||
|
tint = content,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.BottomEnd)
|
||||||
|
.size(18.dp)
|
||||||
|
.background(container.copy(alpha = 0.8f), CircleShape)
|
||||||
|
.padding(2.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
if (!data.tag.isNullOrBlank()) {
|
if (!data.tag.isNullOrBlank()) {
|
||||||
val tagColor = when (data.tag.lowercase()) {
|
val tagColor = when (data.tag.lowercase()) {
|
||||||
"urgent" -> statusColors.danger
|
"urgent" -> statusColors.danger
|
||||||
@ -896,6 +945,7 @@ private fun ItemDetailSheet(
|
|||||||
onOpenProduct: (() -> Unit)?
|
onOpenProduct: (() -> Unit)?
|
||||||
) {
|
) {
|
||||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||||
|
val context = LocalContext.current
|
||||||
var note by remember(item.id) { mutableStateOf(item.note.orEmpty()) }
|
var note by remember(item.id) { mutableStateOf(item.note.orEmpty()) }
|
||||||
var currentTag by remember(item.id) { mutableStateOf(item.tag) }
|
var currentTag by remember(item.id) { mutableStateOf(item.tag) }
|
||||||
var showCategoryPicker by remember { mutableStateOf(false) }
|
var showCategoryPicker by remember { mutableStateOf(false) }
|
||||||
@ -906,7 +956,14 @@ private fun ItemDetailSheet(
|
|||||||
val photoPickerLauncher = rememberLauncherForActivityResult(
|
val photoPickerLauncher = rememberLauncherForActivityResult(
|
||||||
contract = ActivityResultContracts.GetContent()
|
contract = ActivityResultContracts.GetContent()
|
||||||
) { uri: Uri? ->
|
) { uri: Uri? ->
|
||||||
uri?.let { onUpdateImage(it.toString()) }
|
uri?.let {
|
||||||
|
val bmp = context.contentResolver.openInputStream(it)?.use { stream ->
|
||||||
|
BitmapFactory.decodeStream(stream)
|
||||||
|
}
|
||||||
|
bmp?.let { bitmap ->
|
||||||
|
onUpdateImage(saveCroppedImage(context, bitmap))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ModalBottomSheet(
|
ModalBottomSheet(
|
||||||
@ -950,6 +1007,19 @@ private fun ItemDetailSheet(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Photo de l'article
|
||||||
|
if (!item.imageUrl.isNullOrBlank()) {
|
||||||
|
AsyncImage(
|
||||||
|
model = item.imageUrl,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(180.dp)
|
||||||
|
.clip(RoundedCornerShape(12.dp)),
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Quantité / description
|
// Quantité / description
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = note,
|
value = note,
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package com.safebite.app.presentation.screen.onboarding
|
package com.safebite.app.presentation.screen.onboarding
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@ -8,11 +9,16 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.LazyRow
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CardDefaults
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@ -23,6 +29,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@ -38,13 +45,15 @@ import com.safebite.app.domain.model.DietaryRestriction
|
|||||||
import com.safebite.app.presentation.common.components.PrimaryButton
|
import com.safebite.app.presentation.common.components.PrimaryButton
|
||||||
import com.safebite.app.presentation.common.components.StandardTextField
|
import com.safebite.app.presentation.common.components.StandardTextField
|
||||||
import com.safebite.app.presentation.common.components.TertiaryButton
|
import com.safebite.app.presentation.common.components.TertiaryButton
|
||||||
import com.safebite.app.presentation.screen.profile.AllergenGrid
|
import com.safebite.app.presentation.common.components.AllergenLevel
|
||||||
|
import com.safebite.app.presentation.common.components.AllergenSelectionGrid
|
||||||
import com.safebite.app.presentation.screen.profile.CustomItemAdder
|
import com.safebite.app.presentation.screen.profile.CustomItemAdder
|
||||||
import com.safebite.app.presentation.screen.profile.CustomItemsList
|
import com.safebite.app.presentation.screen.profile.CustomItemsList
|
||||||
import com.google.accompanist.permissions.shouldShowRationale
|
import com.google.accompanist.permissions.shouldShowRationale
|
||||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
import androidx.compose.foundation.layout.FlowRow
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
import androidx.compose.material3.FilterChip
|
import androidx.compose.material3.FilterChip
|
||||||
|
import androidx.compose.material3.Switch
|
||||||
|
|
||||||
@OptIn(ExperimentalPermissionsApi::class)
|
@OptIn(ExperimentalPermissionsApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
@ -55,8 +64,8 @@ fun OnboardingScreen(
|
|||||||
var step by rememberSaveable { mutableStateOf(0) }
|
var step by rememberSaveable { mutableStateOf(0) }
|
||||||
var name by rememberSaveable { mutableStateOf("") }
|
var name by rememberSaveable { mutableStateOf("") }
|
||||||
var avatar by rememberSaveable { mutableStateOf("🙂") }
|
var avatar by rememberSaveable { mutableStateOf("🙂") }
|
||||||
val severe = remember { mutableStateOf<Set<AllergenType>>(emptySet()) }
|
var isDefault by rememberSaveable { mutableStateOf(true) }
|
||||||
val moderate = remember { mutableStateOf<Set<AllergenType>>(emptySet()) }
|
val allergenLevels = remember { mutableStateOf<Map<AllergenType, AllergenLevel>>(emptyMap()) }
|
||||||
val restrictions = remember { mutableStateOf<Set<DietaryRestriction>>(emptySet()) }
|
val restrictions = remember { mutableStateOf<Set<DietaryRestriction>>(emptySet()) }
|
||||||
val customItems = remember { mutableStateOf<List<CustomDietItem>>(emptyList()) }
|
val customItems = remember { mutableStateOf<List<CustomDietItem>>(emptyList()) }
|
||||||
|
|
||||||
@ -71,13 +80,15 @@ fun OnboardingScreen(
|
|||||||
onNameChange = { name = it },
|
onNameChange = { name = it },
|
||||||
avatar = avatar,
|
avatar = avatar,
|
||||||
onAvatarChange = { avatar = it },
|
onAvatarChange = { avatar = it },
|
||||||
severe = severe.value,
|
isDefault = isDefault,
|
||||||
onToggleSevere = { a ->
|
onSetDefault = { isDefault = it },
|
||||||
severe.value = if (a in severe.value) severe.value - a else severe.value + a
|
allergenLevels = allergenLevels.value,
|
||||||
},
|
onSetAllergenLevel = { a, level ->
|
||||||
moderate = moderate.value,
|
allergenLevels.value = if (level == AllergenLevel.NONE) {
|
||||||
onToggleModerate = { a ->
|
allergenLevels.value - a
|
||||||
moderate.value = if (a in moderate.value) moderate.value - a else moderate.value + a
|
} else {
|
||||||
|
allergenLevels.value + (a to level)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
restrictions = restrictions.value,
|
restrictions = restrictions.value,
|
||||||
onToggleRestriction = { r ->
|
onToggleRestriction = { r ->
|
||||||
@ -91,7 +102,9 @@ fun OnboardingScreen(
|
|||||||
customItems.value = customItems.value - item
|
customItems.value = customItems.value - item
|
||||||
},
|
},
|
||||||
onNext = {
|
onNext = {
|
||||||
viewModel.createProfile(name, avatar, severe.value, moderate.value, restrictions.value, customItems.value)
|
val severe = allergenLevels.value.filterValues { it == AllergenLevel.SEVERE }.keys
|
||||||
|
val moderate = allergenLevels.value.filterValues { it == AllergenLevel.TRACE }.keys
|
||||||
|
viewModel.createProfile(name, avatar, severe, moderate, restrictions.value, customItems.value)
|
||||||
step = 3
|
step = 3
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -143,17 +156,72 @@ private fun WelcomeStep(onNext: () -> Unit) {
|
|||||||
private fun HowStep(onNext: () -> Unit) {
|
private fun HowStep(onNext: () -> Unit) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(20.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
stringResource(R.string.onboarding_how_title),
|
stringResource(R.string.onboarding_how_title),
|
||||||
style = MaterialTheme.typography.headlineMedium,
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
color = MaterialTheme.colorScheme.onBackground
|
color = MaterialTheme.colorScheme.onBackground
|
||||||
)
|
)
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.onboarding_how_subtitle),
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
Spacer(Modifier.height(8.dp))
|
Spacer(Modifier.height(8.dp))
|
||||||
Text("👤 " + stringResource(R.string.onboarding_how_step1), style = MaterialTheme.typography.bodyLarge)
|
|
||||||
Text("📷 " + stringResource(R.string.onboarding_how_step2), style = MaterialTheme.typography.bodyLarge)
|
val steps = listOf(
|
||||||
Text("✅ " + stringResource(R.string.onboarding_how_step3), style = MaterialTheme.typography.bodyLarge)
|
Triple("1", "👤", stringResource(R.string.onboarding_how_step1)),
|
||||||
|
Triple("2", "📷", stringResource(R.string.onboarding_how_step2)),
|
||||||
|
Triple("3", "✅", stringResource(R.string.onboarding_how_step3))
|
||||||
|
)
|
||||||
|
|
||||||
|
for ((number, emoji, label) in steps) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
shape = RoundedCornerShape(16.dp),
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(48.dp)
|
||||||
|
.background(
|
||||||
|
MaterialTheme.colorScheme.primaryContainer,
|
||||||
|
CircleShape
|
||||||
|
),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = number,
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
text = emoji,
|
||||||
|
style = MaterialTheme.typography.headlineSmall
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = label,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Spacer(Modifier.weight(1f))
|
Spacer(Modifier.weight(1f))
|
||||||
PrimaryButton(
|
PrimaryButton(
|
||||||
text = stringResource(R.string.action_continue),
|
text = stringResource(R.string.action_continue),
|
||||||
@ -171,10 +239,10 @@ private fun CreateProfileStep(
|
|||||||
onNameChange: (String) -> Unit,
|
onNameChange: (String) -> Unit,
|
||||||
avatar: String,
|
avatar: String,
|
||||||
onAvatarChange: (String) -> Unit,
|
onAvatarChange: (String) -> Unit,
|
||||||
severe: Set<AllergenType>,
|
isDefault: Boolean,
|
||||||
onToggleSevere: (AllergenType) -> Unit,
|
onSetDefault: (Boolean) -> Unit,
|
||||||
moderate: Set<AllergenType>,
|
allergenLevels: Map<AllergenType, AllergenLevel>,
|
||||||
onToggleModerate: (AllergenType) -> Unit,
|
onSetAllergenLevel: (AllergenType, AllergenLevel) -> Unit,
|
||||||
restrictions: Set<DietaryRestriction>,
|
restrictions: Set<DietaryRestriction>,
|
||||||
onToggleRestriction: (DietaryRestriction) -> Unit,
|
onToggleRestriction: (DietaryRestriction) -> Unit,
|
||||||
customItems: List<CustomDietItem>,
|
customItems: List<CustomDietItem>,
|
||||||
@ -183,10 +251,11 @@ private fun CreateProfileStep(
|
|||||||
onNext: () -> Unit
|
onNext: () -> Unit
|
||||||
) {
|
) {
|
||||||
val avatars = listOf("🙂", "😀", "👧", "👦", "👨", "👩", "👵", "👴", "🧑", "👶", "🧒", "🧓", "🍽️", "🛒", "🥗", "🍎")
|
val avatars = listOf("🙂", "😀", "👧", "👦", "👨", "👩", "👵", "👴", "🧑", "👶", "🧒", "🧓", "🍽️", "🛒", "🥗", "🍎")
|
||||||
|
val dimens = com.safebite.app.presentation.theme.LocalDimens.current
|
||||||
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
verticalArrangement = Arrangement.spacedBy(dimens.spacingMd)
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
Text(
|
Text(
|
||||||
@ -222,18 +291,24 @@ private fun CreateProfileStep(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
Text(stringResource(R.string.profile_allergies), style = MaterialTheme.typography.titleMedium)
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
Text(stringResource(R.string.profile_allergies_help), color = MaterialTheme.colorScheme.onSurfaceVariant)
|
Switch(checked = isDefault, onCheckedChange = onSetDefault)
|
||||||
|
Spacer(Modifier.width(8.dp))
|
||||||
|
Text(stringResource(R.string.profile_set_default))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
item { AllergenGrid(selected = severe, onToggle = onToggleSevere) }
|
|
||||||
|
|
||||||
item {
|
item {
|
||||||
Text(stringResource(R.string.profile_intolerances), style = MaterialTheme.typography.titleMedium)
|
Text(stringResource(R.string.profile_allergies), style = MaterialTheme.typography.titleMedium)
|
||||||
Text(stringResource(R.string.profile_intolerances_help), color = MaterialTheme.colorScheme.onSurfaceVariant)
|
Text(stringResource(R.string.profile_allergies_3states_help), color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
AllergenSelectionGrid(
|
||||||
|
selectedAllergens = allergenLevels,
|
||||||
|
onLevelChanged = onSetAllergenLevel
|
||||||
|
)
|
||||||
}
|
}
|
||||||
item { AllergenGrid(selected = moderate, onToggle = onToggleModerate) }
|
|
||||||
|
|
||||||
item {
|
item {
|
||||||
Text(stringResource(R.string.profile_restrictions), style = MaterialTheme.typography.titleMedium)
|
Text(stringResource(R.string.profile_restrictions), style = MaterialTheme.typography.titleMedium)
|
||||||
@ -262,7 +337,7 @@ private fun CreateProfileStep(
|
|||||||
|
|
||||||
item {
|
item {
|
||||||
PrimaryButton(
|
PrimaryButton(
|
||||||
text = stringResource(R.string.action_continue),
|
text = stringResource(R.string.action_save),
|
||||||
onClick = onNext,
|
onClick = onNext,
|
||||||
enabled = name.isNotBlank(),
|
enabled = name.isNotBlank(),
|
||||||
large = true,
|
large = true,
|
||||||
|
|||||||
@ -19,6 +19,7 @@
|
|||||||
<string name="onboarding_welcome_title">Welcome to SafeBite</string>
|
<string name="onboarding_welcome_title">Welcome to SafeBite</string>
|
||||||
<string name="onboarding_welcome_subtitle">Scan, verify, eat safely.</string>
|
<string name="onboarding_welcome_subtitle">Scan, verify, eat safely.</string>
|
||||||
<string name="onboarding_how_title">How it works</string>
|
<string name="onboarding_how_title">How it works</string>
|
||||||
|
<string name="onboarding_how_subtitle">In 3 simple steps, scan your products and eat safely.</string>
|
||||||
<string name="onboarding_how_step1">1. Create an allergy profile</string>
|
<string name="onboarding_how_step1">1. Create an allergy profile</string>
|
||||||
<string name="onboarding_how_step2">2. Scan the product barcode</string>
|
<string name="onboarding_how_step2">2. Scan the product barcode</string>
|
||||||
<string name="onboarding_how_step3">3. Get an instant verdict</string>
|
<string name="onboarding_how_step3">3. Get an instant verdict</string>
|
||||||
|
|||||||
@ -21,6 +21,7 @@
|
|||||||
<string name="onboarding_welcome_title">Bienvenue sur SafeBite</string>
|
<string name="onboarding_welcome_title">Bienvenue sur SafeBite</string>
|
||||||
<string name="onboarding_welcome_subtitle">Scannez, vérifiez, mangez en toute sécurité.</string>
|
<string name="onboarding_welcome_subtitle">Scannez, vérifiez, mangez en toute sécurité.</string>
|
||||||
<string name="onboarding_how_title">Comment ça fonctionne</string>
|
<string name="onboarding_how_title">Comment ça fonctionne</string>
|
||||||
|
<string name="onboarding_how_subtitle">En 3 étapes simples, scannez vos produits et mangez en toute sécurité.</string>
|
||||||
<string name="onboarding_how_step1">1. Créez un profil d\'allergies</string>
|
<string name="onboarding_how_step1">1. Créez un profil d\'allergies</string>
|
||||||
<string name="onboarding_how_step2">2. Scannez le code-barres du produit</string>
|
<string name="onboarding_how_step2">2. Scannez le code-barres du produit</string>
|
||||||
<string name="onboarding_how_step3">3. Obtenez un verdict instantané</string>
|
<string name="onboarding_how_step3">3. Obtenez un verdict instantané</string>
|
||||||
|
|||||||
@ -17,6 +17,7 @@ dependencyResolutionManagement {
|
|||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
maven { url = uri("https://jitpack.io") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
MAJOR=1
|
MAJOR=1
|
||||||
MINOR=8
|
MINOR=9
|
||||||
PATCH=0
|
PATCH=0
|
||||||
CODE=10
|
CODE=11
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user