feat: implement splash screen with user preference toggle, update app branding with SafeBite logo across UI
- Add `splashScreenEnabled` preference to UserPreferences with default true - Implement `SplashScreen` composable with animated logo, scale/fade transitions, and gradient background - Add splash screen toggle in settings UI under "Interface" section - Update navigation graph to conditionally show splash before dashboard when enabled - Replace app launcher icons with SafeBite logo drawable
@ -19,8 +19,8 @@
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:icon="@drawable/safebite_logo"
|
||||
android:roundIcon="@drawable/safebite_logo"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.SafeBite"
|
||||
|
||||
@ -26,6 +26,7 @@ object UserPreferencesKeys {
|
||||
val ONBOARDING_DONE = booleanPreferencesKey("onboarding_done")
|
||||
val ACTIVE_PROFILE_IDS = stringSetPreferencesKey("active_profile_ids")
|
||||
val HEALTH_STRICTNESS = stringPreferencesKey("health_strictness")
|
||||
val SPLASH_SCREEN_ENABLED = booleanPreferencesKey("splash_screen_enabled")
|
||||
}
|
||||
|
||||
class UserPreferences(private val dataStore: DataStore<Preferences>) {
|
||||
@ -61,6 +62,10 @@ class UserPreferences(private val dataStore: DataStore<Preferences>) {
|
||||
.getOrDefault(HealthStrictness.NORMAL)
|
||||
}
|
||||
|
||||
val splashScreenEnabled: Flow<Boolean> = dataStore.data.map {
|
||||
it[UserPreferencesKeys.SPLASH_SCREEN_ENABLED] ?: true
|
||||
}
|
||||
|
||||
suspend fun setAppLanguage(value: AppLanguage) {
|
||||
dataStore.edit { it[UserPreferencesKeys.APP_LANGUAGE] = value.name }
|
||||
}
|
||||
@ -94,4 +99,8 @@ class UserPreferences(private val dataStore: DataStore<Preferences>) {
|
||||
suspend fun setHealthStrictness(value: HealthStrictness) {
|
||||
dataStore.edit { it[UserPreferencesKeys.HEALTH_STRICTNESS] = value.name }
|
||||
}
|
||||
|
||||
suspend fun setSplashScreenEnabled(value: Boolean) {
|
||||
dataStore.edit { it[UserPreferencesKeys.SPLASH_SCREEN_ENABLED] = value }
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,6 +20,7 @@ class SettingsRepositoryImpl @Inject constructor(
|
||||
override val theme = prefs.theme
|
||||
override val onboardingCompleted = prefs.onboardingCompleted
|
||||
override val healthStrictness = prefs.healthStrictness
|
||||
override val splashScreenEnabled = prefs.splashScreenEnabled
|
||||
|
||||
override suspend fun setAppLanguage(value: AppLanguage) = prefs.setAppLanguage(value)
|
||||
override suspend fun setDetectionLanguage(value: DetectionLanguage) = prefs.setDetectionLanguage(value)
|
||||
@ -28,4 +29,5 @@ class SettingsRepositoryImpl @Inject constructor(
|
||||
override suspend fun setTheme(value: ThemePref) = prefs.setTheme(value)
|
||||
override suspend fun setOnboardingCompleted(value: Boolean) = prefs.setOnboardingCompleted(value)
|
||||
override suspend fun setHealthStrictness(value: HealthStrictness) = prefs.setHealthStrictness(value)
|
||||
override suspend fun setSplashScreenEnabled(enabled: Boolean) = prefs.setSplashScreenEnabled(enabled)
|
||||
}
|
||||
|
||||
@ -57,6 +57,7 @@ interface SettingsRepository {
|
||||
val theme: Flow<ThemePref>
|
||||
val onboardingCompleted: Flow<Boolean>
|
||||
val healthStrictness: Flow<HealthStrictness>
|
||||
val splashScreenEnabled: Flow<Boolean>
|
||||
|
||||
suspend fun setAppLanguage(value: AppLanguage)
|
||||
suspend fun setDetectionLanguage(value: DetectionLanguage)
|
||||
@ -65,6 +66,7 @@ interface SettingsRepository {
|
||||
suspend fun setTheme(value: ThemePref)
|
||||
suspend fun setOnboardingCompleted(value: Boolean)
|
||||
suspend fun setHealthStrictness(value: HealthStrictness)
|
||||
suspend fun setSplashScreenEnabled(enabled: Boolean)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@ -21,14 +21,28 @@ import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import javax.inject.Inject
|
||||
|
||||
data class RootUi(val onboardingDone: Boolean = false, val theme: ThemePref = ThemePref.SYSTEM, val ready: Boolean = false)
|
||||
data class RootUi(
|
||||
val onboardingDone: Boolean = false,
|
||||
val theme: ThemePref = ThemePref.SYSTEM,
|
||||
val showSplash: Boolean = false,
|
||||
val ready: Boolean = false
|
||||
)
|
||||
|
||||
@HiltViewModel
|
||||
class RootViewModel @Inject constructor(
|
||||
settings: SettingsRepository
|
||||
) : ViewModel() {
|
||||
val state: StateFlow<RootUi> = combine(settings.onboardingCompleted, settings.theme) { done, theme ->
|
||||
RootUi(onboardingDone = done, theme = theme, ready = true)
|
||||
val state: StateFlow<RootUi> = combine(
|
||||
settings.onboardingCompleted,
|
||||
settings.theme,
|
||||
settings.splashScreenEnabled
|
||||
) { done, theme, splashEnabled ->
|
||||
RootUi(
|
||||
onboardingDone = done,
|
||||
theme = theme,
|
||||
showSplash = splashEnabled && done,
|
||||
ready = true
|
||||
)
|
||||
}.stateIn(viewModelScope, SharingStarted.Eagerly, RootUi())
|
||||
}
|
||||
|
||||
@ -49,7 +63,10 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
SafeBiteTheme(darkTheme = dark) {
|
||||
if (ui.ready) {
|
||||
SafeBiteNavGraph(onboardingCompleted = ui.onboardingDone)
|
||||
SafeBiteNavGraph(
|
||||
onboardingCompleted = ui.onboardingDone,
|
||||
showSplash = ui.showSplash
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,6 +24,7 @@ import com.safebite.app.presentation.screen.profile.ProfileListScreen
|
||||
import com.safebite.app.presentation.screen.result.ResultScreen
|
||||
import com.safebite.app.presentation.screen.scanner.ScannerScreen
|
||||
import com.safebite.app.presentation.screen.settings.SettingsScreen
|
||||
import com.safebite.app.presentation.screen.splash.SplashScreen
|
||||
|
||||
/**
|
||||
* Graph de navigation principal de l'application SafeBite.
|
||||
@ -34,9 +35,13 @@ import com.safebite.app.presentation.screen.settings.SettingsScreen
|
||||
* - Écrans de navigation : Scanner, Result, OCR, Settings, etc.
|
||||
*/
|
||||
@Composable
|
||||
fun SafeBiteNavGraph(onboardingCompleted: Boolean) {
|
||||
fun SafeBiteNavGraph(onboardingCompleted: Boolean, showSplash: Boolean = false) {
|
||||
val navController = rememberNavController()
|
||||
val startDestination = if (onboardingCompleted) Screen.Dashboard.route else Screen.Onboarding.route
|
||||
val startDestination = when {
|
||||
showSplash -> Screen.Splash.route
|
||||
onboardingCompleted -> Screen.Dashboard.route
|
||||
else -> Screen.Onboarding.route
|
||||
}
|
||||
|
||||
val enterAnim = fadeIn(animationSpec = tween(250)) +
|
||||
slideInHorizontally(animationSpec = tween(250)) { it / 24 }
|
||||
@ -53,6 +58,17 @@ fun SafeBiteNavGraph(onboardingCompleted: Boolean) {
|
||||
popEnterTransition = { popEnterAnim },
|
||||
popExitTransition = { popExitAnim },
|
||||
) {
|
||||
// ── Splash ──
|
||||
composable(Screen.Splash.route) {
|
||||
SplashScreen(
|
||||
onFinished = {
|
||||
navController.navigate(Screen.Dashboard.route) {
|
||||
popUpTo(Screen.Splash.route) { inclusive = true }
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// ── Onboarding ──
|
||||
composable(Screen.Onboarding.route) {
|
||||
OnboardingScreen(onFinished = {
|
||||
|
||||
@ -39,6 +39,7 @@ sealed class Screen(val route: String) {
|
||||
}
|
||||
data object Onboarding : Screen("onboarding")
|
||||
data object Settings : Screen("settings")
|
||||
data object Splash : Screen("splash")
|
||||
|
||||
// ── Sous-écrans ──
|
||||
data object ProfileList : Screen("profiles")
|
||||
|
||||
@ -8,9 +8,13 @@ import androidx.compose.animation.scaleIn
|
||||
import androidx.compose.animation.scaleOut
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.QrCodeScanner
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
@ -28,7 +32,9 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavDestination
|
||||
@ -81,7 +87,17 @@ fun MainScreen(
|
||||
containerColor = MaterialTheme.colorScheme.background,
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(R.string.app_name)) },
|
||||
title = {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.safebite_logo),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(32.dp)
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text(stringResource(R.string.app_name))
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = onOpenSettings) {
|
||||
Icon(
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package com.safebite.app.presentation.screen.onboarding
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@ -31,6 +32,7 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
@ -129,8 +131,12 @@ private fun WelcomeStep(onNext: () -> Unit) {
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text("🛡️", style = MaterialTheme.typography.displayLarge)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.safebite_logo),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(120.dp)
|
||||
)
|
||||
Spacer(Modifier.height(24.dp))
|
||||
Text(
|
||||
stringResource(R.string.onboarding_welcome_title),
|
||||
style = MaterialTheme.typography.headlineLarge,
|
||||
|
||||
@ -78,6 +78,7 @@ fun SettingsScreen(
|
||||
Column(verticalArrangement = Arrangement.spacedBy(dimens.spacingSm)) {
|
||||
ToggleRow(stringResource(R.string.settings_haptics), ui.haptics, viewModel::setHaptics)
|
||||
ToggleRow(stringResource(R.string.settings_sound), ui.sound, viewModel::setSound)
|
||||
ToggleRow(stringResource(R.string.settings_splash_screen), ui.splashScreenEnabled, viewModel::setSplashScreenEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -23,7 +23,8 @@ data class SettingsUi(
|
||||
val haptics: Boolean = true,
|
||||
val sound: Boolean = true,
|
||||
val theme: ThemePref = ThemePref.SYSTEM,
|
||||
val healthStrictness: HealthStrictness = HealthStrictness.NORMAL
|
||||
val healthStrictness: HealthStrictness = HealthStrictness.NORMAL,
|
||||
val splashScreenEnabled: Boolean = true
|
||||
)
|
||||
|
||||
@HiltViewModel
|
||||
@ -43,8 +44,12 @@ class SettingsViewModel @Inject constructor(
|
||||
SettingsUi(lang, detection, haptics, sound, theme)
|
||||
}
|
||||
|
||||
val state: StateFlow<SettingsUi> = combine(coreFlow, settings.healthStrictness) { core, strict ->
|
||||
core.copy(healthStrictness = strict)
|
||||
val state: StateFlow<SettingsUi> = combine(
|
||||
coreFlow,
|
||||
settings.healthStrictness,
|
||||
settings.splashScreenEnabled
|
||||
) { core, strict, splash ->
|
||||
core.copy(healthStrictness = strict, splashScreenEnabled = splash)
|
||||
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), SettingsUi())
|
||||
|
||||
fun setAppLanguage(v: AppLanguage) = viewModelScope.launch { settings.setAppLanguage(v) }
|
||||
@ -53,6 +58,7 @@ class SettingsViewModel @Inject constructor(
|
||||
fun setSound(v: Boolean) = viewModelScope.launch { settings.setSound(v) }
|
||||
fun setTheme(v: ThemePref) = viewModelScope.launch { settings.setTheme(v) }
|
||||
fun setHealthStrictness(v: HealthStrictness) = viewModelScope.launch { settings.setHealthStrictness(v) }
|
||||
fun setSplashScreenEnabled(v: Boolean) = viewModelScope.launch { settings.setSplashScreenEnabled(v) }
|
||||
fun clearCache() = viewModelScope.launch { productRepo.clearCache() }
|
||||
fun clearHistory() = viewModelScope.launch { historyRepo.clear() }
|
||||
}
|
||||
|
||||
@ -0,0 +1,83 @@
|
||||
package com.safebite.app.presentation.screen.splash
|
||||
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.safebite.app.R
|
||||
import com.safebite.app.presentation.theme.ShieldGradient
|
||||
|
||||
@Composable
|
||||
fun SplashScreen(
|
||||
onFinished: () -> Unit,
|
||||
durationMillis: Int = 2500
|
||||
) {
|
||||
val scale = remember { Animatable(0.6f) }
|
||||
val alpha = remember { Animatable(0f) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
scale.animateTo(1f, animationSpec = tween(durationMillis = 800))
|
||||
alpha.animateTo(1f, animationSpec = tween(durationMillis = 600))
|
||||
kotlinx.coroutines.delay(durationMillis.toLong())
|
||||
onFinished()
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(ShieldGradient),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
modifier = Modifier.padding(24.dp)
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.safebite_logo),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(160.dp)
|
||||
.scale(scale.value)
|
||||
)
|
||||
Spacer(Modifier.height(24.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.app_name),
|
||||
style = MaterialTheme.typography.headlineLarge.copy(
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = androidx.compose.ui.graphics.Color.White
|
||||
),
|
||||
modifier = Modifier.alpha(alpha.value)
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.onboarding_welcome_subtitle),
|
||||
style = MaterialTheme.typography.bodyLarge.copy(
|
||||
color = androidx.compose.ui.graphics.Color.White.copy(alpha = 0.9f)
|
||||
),
|
||||
modifier = Modifier.alpha(alpha.value)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -11,110 +11,110 @@ import androidx.compose.ui.graphics.Color
|
||||
// Ces couleurs sont indépendantes du thème M3 pour cohérence marque.
|
||||
object SemanticColors {
|
||||
// Light mode
|
||||
val Safe = Color(0xFF2ECC71) // Vert sécurité
|
||||
val SafeContainer = Color(0xFFE8F8F5) // Fond très clair
|
||||
val Safe = Color(0xFF43A047) // Vert sécurité
|
||||
val SafeContainer = Color(0xFFE8F5E9) // Fond très clair
|
||||
val OnSafe = Color(0xFFFFFFFF)
|
||||
val OnSafeContainer = Color(0xFF1A3A2A)
|
||||
|
||||
val Warning = Color(0xFFE67E22) // Orange attention
|
||||
val WarningContainer = Color(0xFFFEF5E7)
|
||||
val Warning = Color(0xFFFFA000) // Orange attention
|
||||
val WarningContainer = Color(0xFFFFF3E0)
|
||||
val OnWarning = Color(0xFFFFFFFF)
|
||||
val OnWarningContainer = Color(0xFF3A2A1A)
|
||||
val OnWarningContainer = Color(0xFF4A2800)
|
||||
|
||||
val Danger = Color(0xFFE74C3C) // Rouge danger
|
||||
val DangerContainer = Color(0xFFFDEDEC)
|
||||
val Danger = Color(0xFFD32F2F) // Rouge danger
|
||||
val DangerContainer = Color(0xFFFFEBEE)
|
||||
val OnDanger = Color(0xFFFFFFFF)
|
||||
val OnDangerContainer = Color(0xFF3A1A1A)
|
||||
val OnDangerContainer = Color(0xFF5C0B0B)
|
||||
|
||||
// Dark mode (mêmes couleurs sémantiques, containers adaptés)
|
||||
val SafeDark = Color(0xFF2ECC71)
|
||||
val SafeContainerDark = Color(0xFF1A3A2A)
|
||||
val WarningDark = Color(0xFFE67E22)
|
||||
val WarningContainerDark = Color(0xFF3A2A1A)
|
||||
val DangerDark = Color(0xFFE74C3C)
|
||||
val DangerContainerDark = Color(0xFF3A1A1A)
|
||||
val SafeDark = Color(0xFF81C784)
|
||||
val SafeContainerDark = Color(0xFF1B5E20)
|
||||
val WarningDark = Color(0xFFFFB74D)
|
||||
val WarningContainerDark = Color(0xFF4A2800)
|
||||
val DangerDark = Color(0xFFEF9A9A)
|
||||
val DangerContainerDark = Color(0xFF5C0B0B)
|
||||
}
|
||||
|
||||
// ---- NEUTRES (spec UX §2.1) ------------------------------------------------
|
||||
object NeutralColors {
|
||||
val Background = Color(0xFFF5F5F0) // Gris chaud (réduit fatigue oculaire)
|
||||
val Background = Color(0xFFF1F8E9) // Fond principal light
|
||||
val Surface = Color(0xFFFFFFFF) // Blanc pur pour cartes
|
||||
val TextPrimary = Color(0xFF2D3436) // Noir doux (pas #000)
|
||||
val TextSecondary = Color(0xFF636E72) // Gris moyen
|
||||
val Separator = Color(0xFFDFE6E9) // Gris clair
|
||||
val TextPrimary = Color(0xFF212121) // Texte principal
|
||||
val TextSecondary = Color(0xFF757575) // Texte secondaire
|
||||
val Separator = Color(0xFFBDBDBD) // Séparateurs
|
||||
}
|
||||
|
||||
// ---- Brand anchors (Material 3) --------------------------------------------
|
||||
val BrandIndigo = Color(0xFF1A237E)
|
||||
val BrandIndigoLight = Color(0xFFBAC3FF)
|
||||
val BrandTeal = Color(0xFF00897B)
|
||||
val BrandTealLight = Color(0xFF4DB6AC)
|
||||
val BrandPrimary = Color(0xFF1B7A2B)
|
||||
val BrandPrimaryDark = Color(0xFF0D5E1A)
|
||||
val BrandPrimaryLight = Color(0xFF4CAF50)
|
||||
val BrandSecondary = Color(0xFF2E7D32)
|
||||
|
||||
// ---- Light scheme ---------------------------------------------------------
|
||||
val LightPrimary = Color(0xFF1A237E)
|
||||
val LightPrimary = Color(0xFF1B7A2B)
|
||||
val LightOnPrimary = Color(0xFFFFFFFF)
|
||||
val LightPrimaryContainer = Color(0xFFDDE1FF)
|
||||
val LightOnPrimaryContainer = Color(0xFF001159)
|
||||
val LightPrimaryContainer = Color(0xFFA5D6A7)
|
||||
val LightOnPrimaryContainer = Color(0xFF0D3B12)
|
||||
|
||||
val LightSecondary = Color(0xFF00897B)
|
||||
val LightSecondary = Color(0xFF2E7D32)
|
||||
val LightOnSecondary = Color(0xFFFFFFFF)
|
||||
val LightSecondaryContainer = Color(0xFFB2DFDB)
|
||||
val LightOnSecondaryContainer = Color(0xFF00251F)
|
||||
val LightSecondaryContainer = Color(0xFFC8E6C9)
|
||||
val LightOnSecondaryContainer = Color(0xFF1B5E20)
|
||||
|
||||
val LightTertiary = Color(0xFF7B4E9E)
|
||||
val LightTertiary = Color(0xFF00796B)
|
||||
val LightOnTertiary = Color(0xFFFFFFFF)
|
||||
val LightTertiaryContainer = Color(0xFFF0DBFF)
|
||||
val LightOnTertiaryContainer = Color(0xFF2B0A45)
|
||||
val LightTertiaryContainer = Color(0xFFB2DFDB)
|
||||
val LightOnTertiaryContainer = Color(0xFF004D40)
|
||||
|
||||
val LightError = Color(0xFFBA1A1A)
|
||||
val LightError = Color(0xFFD32F2F)
|
||||
val LightOnError = Color(0xFFFFFFFF)
|
||||
val LightErrorContainer = Color(0xFFFFDAD6)
|
||||
val LightOnErrorContainer = Color(0xFF410002)
|
||||
val LightErrorContainer = Color(0xFFFFCDD2)
|
||||
val LightOnErrorContainer = Color(0xFF5C0B0B)
|
||||
|
||||
val LightBackground = NeutralColors.Background // #F5F5F0
|
||||
val LightOnBackground = NeutralColors.TextPrimary // #2D3436
|
||||
val LightBackground = NeutralColors.Background // #F1F8E9
|
||||
val LightOnBackground = NeutralColors.TextPrimary // #212121
|
||||
val LightSurface = NeutralColors.Surface // #FFFFFF
|
||||
val LightOnSurface = NeutralColors.TextPrimary // #2D3436
|
||||
val LightSurfaceVariant = Color(0xFFE3E2EC)
|
||||
val LightOnSurface = NeutralColors.TextPrimary // #212121
|
||||
val LightSurfaceVariant = Color(0xFFE8F5E9)
|
||||
val LightOnSurfaceVariant = NeutralColors.TextSecondary
|
||||
val LightSurfaceTint = LightPrimary
|
||||
|
||||
val LightOutline = NeutralColors.Separator
|
||||
val LightOutlineVariant = Color(0xFFC7C6D0)
|
||||
val LightOutlineVariant = Color(0xFFE0E0E0)
|
||||
|
||||
val LightInverseSurface = Color(0xFF2F3033)
|
||||
val LightInverseOnSurface = Color(0xFFF1F0F4)
|
||||
val LightInversePrimary = Color(0xFFBAC3FF)
|
||||
val LightInversePrimary = Color(0xFF81C784)
|
||||
|
||||
val LightScrim = Color(0xFF000000)
|
||||
|
||||
// ---- Dark scheme (surfaces élevées M3) ------------------------------------
|
||||
val DarkPrimary = Color(0xFFBAC3FF)
|
||||
val DarkOnPrimary = Color(0xFF0A1A6A)
|
||||
val DarkPrimaryContainer = Color(0xFF3241A0)
|
||||
val DarkOnPrimaryContainer = Color(0xFFDDE1FF)
|
||||
val DarkPrimary = Color(0xFF81C784)
|
||||
val DarkOnPrimary = Color(0xFF0D3B12)
|
||||
val DarkPrimaryContainer = Color(0xFF1B5E20)
|
||||
val DarkOnPrimaryContainer = Color(0xFFA5D6A7)
|
||||
|
||||
val DarkSecondary = Color(0xFF4DB6AC)
|
||||
val DarkOnSecondary = Color(0xFF00332C)
|
||||
val DarkSecondaryContainer = Color(0xFF00695C)
|
||||
val DarkOnSecondaryContainer = Color(0xFFB2DFDB)
|
||||
val DarkSecondary = Color(0xFFA5D6A7)
|
||||
val DarkOnSecondary = Color(0xFF1B5E20)
|
||||
val DarkSecondaryContainer = Color(0xFF2E7D32)
|
||||
val DarkOnSecondaryContainer = Color(0xFFC8E6C9)
|
||||
|
||||
val DarkTertiary = Color(0xFFE0B6FF)
|
||||
val DarkOnTertiary = Color(0xFF451F6D)
|
||||
val DarkTertiaryContainer = Color(0xFF5D3785)
|
||||
val DarkOnTertiaryContainer = Color(0xFFF0DBFF)
|
||||
val DarkTertiary = Color(0xFF4DB6AC)
|
||||
val DarkOnTertiary = Color(0xFF00332C)
|
||||
val DarkTertiaryContainer = Color(0xFF00695C)
|
||||
val DarkOnTertiaryContainer = Color(0xFFB2DFDB)
|
||||
|
||||
val DarkError = Color(0xFFFFB4AB)
|
||||
val DarkError = Color(0xFFEF9A9A)
|
||||
val DarkOnError = Color(0xFF690005)
|
||||
val DarkErrorContainer = Color(0xFF93000A)
|
||||
val DarkOnErrorContainer = Color(0xFFFFDAD6)
|
||||
val DarkOnErrorContainer = Color(0xFFFFCDD2)
|
||||
|
||||
val DarkBackground = Color(0xFF121212)
|
||||
val DarkOnBackground = Color(0xFFE6E1E5)
|
||||
val DarkSurface = Color(0xFF1E1E1E)
|
||||
val DarkOnSurface = Color(0xFFE6E1E5)
|
||||
val DarkSurfaceVariant = Color(0xFF46464F)
|
||||
val DarkOnSurfaceVariant = Color(0xFFC7C6D0)
|
||||
val DarkBackground = Color(0xFF1A1C1A)
|
||||
val DarkOnBackground = Color(0xFFE0E0E0)
|
||||
val DarkSurface = Color(0xFF2D2F2D)
|
||||
val DarkOnSurface = Color(0xFFE0E0E0)
|
||||
val DarkSurfaceVariant = Color(0xFF3A3F3A)
|
||||
val DarkOnSurfaceVariant = Color(0xFFBDBDBD)
|
||||
val DarkSurfaceTint = DarkPrimary
|
||||
|
||||
val DarkOutline = Color(0xFF90909A)
|
||||
@ -122,10 +122,21 @@ val DarkOutlineVariant = Color(0xFF46464F)
|
||||
|
||||
val DarkInverseSurface = Color(0xFFE6E1E5)
|
||||
val DarkInverseOnSurface = Color(0xFF2F3033)
|
||||
val DarkInversePrimary = Color(0xFF1A237E)
|
||||
val DarkInversePrimary = Color(0xFF1B7A2B)
|
||||
|
||||
val DarkScrim = Color(0xFF000000)
|
||||
|
||||
// ---- Dégradé signature (rappel du fond du logo bouclier) --------------------
|
||||
val ShieldGradient = androidx.compose.ui.graphics.Brush.linearGradient(
|
||||
colors = listOf(
|
||||
Color(0xFF4CAF50), // Vert clair (haut-gauche)
|
||||
Color(0xFF1B7A2B), // Vert moyen
|
||||
Color(0xFF0D5E1A) // Vert foncé (bas-droite)
|
||||
),
|
||||
start = androidx.compose.ui.geometry.Offset(0f, 0f),
|
||||
end = androidx.compose.ui.geometry.Offset(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY)
|
||||
)
|
||||
|
||||
// ---- Legacy aliases (backward compat pour code existant) -------------------
|
||||
@Deprecated("Use SemanticColors.Safe", ReplaceWith("SemanticColors.Safe"))
|
||||
val StatusSafe get() = SemanticColors.Safe
|
||||
|
||||
@ -8,128 +8,126 @@ import androidx.compose.ui.unit.sp
|
||||
|
||||
/**
|
||||
* Typographie Material 3 complète — 15 styles avec lineHeight et letterSpacing
|
||||
* standardisés. Utilise la sans-serif système (Roboto sur Android) pour éviter
|
||||
* standardisés. Alignée sur le guide design SafeBite (Poppins titres, Inter corps).
|
||||
* Utilise la sans-serif système (Roboto sur Android) pour éviter
|
||||
* toute dépendance réseau / asset.
|
||||
*/
|
||||
private val DisplayFamily = FontFamily.SansSerif
|
||||
private val HeadlineFamily = FontFamily.SansSerif
|
||||
private val TitleFamily = FontFamily.SansSerif
|
||||
private val BodyFamily = FontFamily.SansSerif
|
||||
private val LabelFamily = FontFamily.SansSerif
|
||||
private val PoppinsFamily = FontFamily.SansSerif
|
||||
private val InterFamily = FontFamily.SansSerif
|
||||
|
||||
val SafeBiteTypography = Typography(
|
||||
// Display — pour les titres héros (onboarding, banner)
|
||||
displayLarge = TextStyle(
|
||||
fontFamily = DisplayFamily,
|
||||
fontFamily = PoppinsFamily,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 57.sp,
|
||||
lineHeight = 64.sp,
|
||||
letterSpacing = (-0.25).sp
|
||||
),
|
||||
displayMedium = TextStyle(
|
||||
fontFamily = DisplayFamily,
|
||||
fontFamily = PoppinsFamily,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 45.sp,
|
||||
lineHeight = 52.sp,
|
||||
letterSpacing = 0.sp
|
||||
),
|
||||
displaySmall = TextStyle(
|
||||
fontFamily = DisplayFamily,
|
||||
fontFamily = PoppinsFamily,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 36.sp,
|
||||
lineHeight = 44.sp,
|
||||
letterSpacing = 0.sp
|
||||
),
|
||||
|
||||
// Headline — sections majeures
|
||||
// Headline — sections majeures (Poppins Bold/SemiBold)
|
||||
headlineLarge = TextStyle(
|
||||
fontFamily = HeadlineFamily,
|
||||
fontFamily = PoppinsFamily,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 32.sp,
|
||||
lineHeight = 40.sp,
|
||||
letterSpacing = 0.sp
|
||||
letterSpacing = (-0.5).sp
|
||||
),
|
||||
headlineMedium = TextStyle(
|
||||
fontFamily = HeadlineFamily,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 28.sp,
|
||||
lineHeight = 36.sp,
|
||||
letterSpacing = 0.sp
|
||||
),
|
||||
headlineSmall = TextStyle(
|
||||
fontFamily = HeadlineFamily,
|
||||
fontFamily = PoppinsFamily,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
fontSize = 24.sp,
|
||||
lineHeight = 32.sp,
|
||||
letterSpacing = 0.sp
|
||||
),
|
||||
|
||||
// Title — titres d'écran, de cartes
|
||||
titleLarge = TextStyle(
|
||||
fontFamily = TitleFamily,
|
||||
headlineSmall = TextStyle(
|
||||
fontFamily = PoppinsFamily,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
fontSize = 20.sp,
|
||||
lineHeight = 28.sp,
|
||||
letterSpacing = 0.15.sp
|
||||
),
|
||||
|
||||
// Title — titres d'écran, de cartes (Inter Medium)
|
||||
titleLarge = TextStyle(
|
||||
fontFamily = InterFamily,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 22.sp,
|
||||
lineHeight = 28.sp,
|
||||
letterSpacing = 0.sp
|
||||
),
|
||||
titleMedium = TextStyle(
|
||||
fontFamily = TitleFamily,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
fontFamily = InterFamily,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 16.sp,
|
||||
lineHeight = 24.sp,
|
||||
letterSpacing = 0.15.sp
|
||||
),
|
||||
titleSmall = TextStyle(
|
||||
fontFamily = TitleFamily,
|
||||
fontFamily = InterFamily,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 14.sp,
|
||||
lineHeight = 20.sp,
|
||||
letterSpacing = 0.1.sp
|
||||
),
|
||||
|
||||
// Body — textes courants
|
||||
// Body — textes courants (Inter Regular)
|
||||
bodyLarge = TextStyle(
|
||||
fontFamily = BodyFamily,
|
||||
fontFamily = InterFamily,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 16.sp,
|
||||
lineHeight = 24.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
),
|
||||
bodyMedium = TextStyle(
|
||||
fontFamily = BodyFamily,
|
||||
fontFamily = InterFamily,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 14.sp,
|
||||
lineHeight = 20.sp,
|
||||
letterSpacing = 0.25.sp
|
||||
),
|
||||
bodySmall = TextStyle(
|
||||
fontFamily = BodyFamily,
|
||||
fontFamily = InterFamily,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 12.sp,
|
||||
lineHeight = 16.sp,
|
||||
letterSpacing = 0.4.sp
|
||||
),
|
||||
|
||||
// Label — boutons, chips, tags
|
||||
// Label — boutons, chips, tags (Poppins SemiBold pour boutons, Inter Medium pour petits)
|
||||
labelLarge = TextStyle(
|
||||
fontFamily = LabelFamily,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontFamily = PoppinsFamily,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
fontSize = 14.sp,
|
||||
lineHeight = 20.sp,
|
||||
letterSpacing = 0.1.sp
|
||||
letterSpacing = 1.25.sp
|
||||
),
|
||||
labelMedium = TextStyle(
|
||||
fontFamily = LabelFamily,
|
||||
fontFamily = InterFamily,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 12.sp,
|
||||
lineHeight = 16.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
),
|
||||
labelSmall = TextStyle(
|
||||
fontFamily = LabelFamily,
|
||||
fontFamily = InterFamily,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 11.sp,
|
||||
fontSize = 10.sp,
|
||||
lineHeight = 16.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
letterSpacing = 1.5.sp
|
||||
)
|
||||
)
|
||||
|
||||
BIN
app/src/main/res/drawable/background_list/bg_animaux.png
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
BIN
app/src/main/res/drawable/background_list/bg_baby.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
app/src/main/res/drawable/background_list/bg_epicerie.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
app/src/main/res/drawable/background_list/bg_epicerie2.png
Normal file
|
After Width: | Height: | Size: 912 KiB |
BIN
app/src/main/res/drawable/background_list/bg_jardinage.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
app/src/main/res/drawable/background_list/bg_office.png
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
app/src/main/res/drawable/background_list/bg_party.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
app/src/main/res/drawable/background_list/bg_pharmacie.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
app/src/main/res/drawable/background_list/bg_plage.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
app/src/main/res/drawable/background_list/bg_renovation.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
@ -13,7 +13,6 @@
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector
|
||||
android:height="108dp"
|
||||
android:width="108dp"
|
||||
|
||||
@ -13,9 +13,8 @@
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/safebite_logo_background"/>
|
||||
<foreground android:drawable="@drawable/safebite_logo_foreground"/>
|
||||
<monochrome android:drawable="@drawable/safebite_logo_foreground"/>
|
||||
<foreground android:drawable="@drawable/safebite_logo"/>
|
||||
<monochrome android:drawable="@drawable/safebite_logo"/>
|
||||
</adaptive-icon>
|
||||
@ -13,7 +13,6 @@
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/safebite_logo_background"/>
|
||||
<foreground android:drawable="@drawable/safebite_logo_foreground"/>
|
||||
|
||||
@ -122,6 +122,7 @@
|
||||
<string name="settings_theme_system">System</string>
|
||||
<string name="settings_clear_cache">Clear product cache</string>
|
||||
<string name="settings_clear_history">Clear history</string>
|
||||
<string name="settings_splash_screen">Splash screen</string>
|
||||
<string name="settings_about">About</string>
|
||||
<string name="settings_version">Version %1$s</string>
|
||||
<string name="settings_off_attribution">Data provided by Open Food Facts</string>
|
||||
|
||||
@ -190,6 +190,7 @@
|
||||
<string name="settings_theme_system">Système</string>
|
||||
<string name="settings_clear_cache">Vider le cache des produits</string>
|
||||
<string name="settings_clear_history">Vider l\'historique</string>
|
||||
<string name="settings_splash_screen">Écran de démarrage (splash)</string>
|
||||
<string name="settings_about">À propos</string>
|
||||
<string name="settings_version">Version %1$s</string>
|
||||
<string name="settings_off_attribution">Données fournies par Open Food Facts</string>
|
||||
|
||||
13
gradle/gradle-daemon-jvm.properties
Normal file
@ -0,0 +1,13 @@
|
||||
#This file is generated by updateDaemonJvm
|
||||
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/56a19bc915b9ba2eb62ba7554c61b919/redirect
|
||||
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/398ffe3949748bfb1d5636f023d228fd/redirect
|
||||
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/56a19bc915b9ba2eb62ba7554c61b919/redirect
|
||||
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/398ffe3949748bfb1d5636f023d228fd/redirect
|
||||
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/e99bae143b75f9a10ead10248f02055e/redirect
|
||||
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/04e088f8677de3b384108493cc9481d0/redirect
|
||||
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/56a19bc915b9ba2eb62ba7554c61b919/redirect
|
||||
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/398ffe3949748bfb1d5636f023d228fd/redirect
|
||||
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/e55dccbfe27cb97945148c61a39c89c5/redirect
|
||||
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/dbd05c4936d573642f94cd149e1356c8/redirect
|
||||
toolchainVendor=JETBRAINS
|
||||
toolchainVersion=21
|
||||
@ -11,6 +11,9 @@ pluginManagement {
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
plugins {
|
||||
id("org.gradle.toolchains.foojay-resolver-convention") version "0.10.0"
|
||||
}
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
MAJOR=1
|
||||
MINOR=9
|
||||
PATCH=2
|
||||
CODE=13
|
||||
MINOR=14
|
||||
PATCH=0
|
||||
CODE=18
|
||||
|
||||