diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c0566a2..b9493b5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -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" diff --git a/app/src/main/java/com/safebite/app/data/local/datastore/UserPreferences.kt b/app/src/main/java/com/safebite/app/data/local/datastore/UserPreferences.kt index 6c0f13f..195a6e7 100644 --- a/app/src/main/java/com/safebite/app/data/local/datastore/UserPreferences.kt +++ b/app/src/main/java/com/safebite/app/data/local/datastore/UserPreferences.kt @@ -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) { @@ -61,6 +62,10 @@ class UserPreferences(private val dataStore: DataStore) { .getOrDefault(HealthStrictness.NORMAL) } + val splashScreenEnabled: Flow = 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) { 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 } + } } diff --git a/app/src/main/java/com/safebite/app/data/repository/SettingsRepositoryImpl.kt b/app/src/main/java/com/safebite/app/data/repository/SettingsRepositoryImpl.kt index 74b97c3..98c74a1 100644 --- a/app/src/main/java/com/safebite/app/data/repository/SettingsRepositoryImpl.kt +++ b/app/src/main/java/com/safebite/app/data/repository/SettingsRepositoryImpl.kt @@ -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) } diff --git a/app/src/main/java/com/safebite/app/domain/repository/Repositories.kt b/app/src/main/java/com/safebite/app/domain/repository/Repositories.kt index f41c297..ebc9be9 100644 --- a/app/src/main/java/com/safebite/app/domain/repository/Repositories.kt +++ b/app/src/main/java/com/safebite/app/domain/repository/Repositories.kt @@ -57,6 +57,7 @@ interface SettingsRepository { val theme: Flow val onboardingCompleted: Flow val healthStrictness: Flow + val splashScreenEnabled: Flow 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) } // ============================================================================= diff --git a/app/src/main/java/com/safebite/app/presentation/MainActivity.kt b/app/src/main/java/com/safebite/app/presentation/MainActivity.kt index 8c8da3e..e06bf5e 100644 --- a/app/src/main/java/com/safebite/app/presentation/MainActivity.kt +++ b/app/src/main/java/com/safebite/app/presentation/MainActivity.kt @@ -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 = combine(settings.onboardingCompleted, settings.theme) { done, theme -> - RootUi(onboardingDone = done, theme = theme, ready = true) + val state: StateFlow = 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 + ) } } } diff --git a/app/src/main/java/com/safebite/app/presentation/navigation/NavGraph.kt b/app/src/main/java/com/safebite/app/presentation/navigation/NavGraph.kt index cc799b2..90f7e1e 100644 --- a/app/src/main/java/com/safebite/app/presentation/navigation/NavGraph.kt +++ b/app/src/main/java/com/safebite/app/presentation/navigation/NavGraph.kt @@ -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 = { diff --git a/app/src/main/java/com/safebite/app/presentation/navigation/Screen.kt b/app/src/main/java/com/safebite/app/presentation/navigation/Screen.kt index e92b360..13b0f0c 100644 --- a/app/src/main/java/com/safebite/app/presentation/navigation/Screen.kt +++ b/app/src/main/java/com/safebite/app/presentation/navigation/Screen.kt @@ -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") diff --git a/app/src/main/java/com/safebite/app/presentation/screen/main/MainScreen.kt b/app/src/main/java/com/safebite/app/presentation/screen/main/MainScreen.kt index 037647c..2a58143 100644 --- a/app/src/main/java/com/safebite/app/presentation/screen/main/MainScreen.kt +++ b/app/src/main/java/com/safebite/app/presentation/screen/main/MainScreen.kt @@ -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( diff --git a/app/src/main/java/com/safebite/app/presentation/screen/onboarding/OnboardingScreen.kt b/app/src/main/java/com/safebite/app/presentation/screen/onboarding/OnboardingScreen.kt index 059df16..c20780b 100644 --- a/app/src/main/java/com/safebite/app/presentation/screen/onboarding/OnboardingScreen.kt +++ b/app/src/main/java/com/safebite/app/presentation/screen/onboarding/OnboardingScreen.kt @@ -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, diff --git a/app/src/main/java/com/safebite/app/presentation/screen/settings/SettingsScreen.kt b/app/src/main/java/com/safebite/app/presentation/screen/settings/SettingsScreen.kt index 01d0ebb..f7c7687 100644 --- a/app/src/main/java/com/safebite/app/presentation/screen/settings/SettingsScreen.kt +++ b/app/src/main/java/com/safebite/app/presentation/screen/settings/SettingsScreen.kt @@ -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) } } diff --git a/app/src/main/java/com/safebite/app/presentation/screen/settings/SettingsViewModel.kt b/app/src/main/java/com/safebite/app/presentation/screen/settings/SettingsViewModel.kt index 0ae6083..0e400f6 100644 --- a/app/src/main/java/com/safebite/app/presentation/screen/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/safebite/app/presentation/screen/settings/SettingsViewModel.kt @@ -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 = combine(coreFlow, settings.healthStrictness) { core, strict -> - core.copy(healthStrictness = strict) + val state: StateFlow = 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() } } diff --git a/app/src/main/java/com/safebite/app/presentation/screen/splash/SplashScreen.kt b/app/src/main/java/com/safebite/app/presentation/screen/splash/SplashScreen.kt new file mode 100644 index 0000000..1b2d458 --- /dev/null +++ b/app/src/main/java/com/safebite/app/presentation/screen/splash/SplashScreen.kt @@ -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) + ) + } + } +} diff --git a/app/src/main/java/com/safebite/app/presentation/theme/Color.kt b/app/src/main/java/com/safebite/app/presentation/theme/Color.kt index 51d16eb..bb7185e 100644 --- a/app/src/main/java/com/safebite/app/presentation/theme/Color.kt +++ b/app/src/main/java/com/safebite/app/presentation/theme/Color.kt @@ -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 diff --git a/app/src/main/java/com/safebite/app/presentation/theme/Type.kt b/app/src/main/java/com/safebite/app/presentation/theme/Type.kt index 32a0b91..001931f 100644 --- a/app/src/main/java/com/safebite/app/presentation/theme/Type.kt +++ b/app/src/main/java/com/safebite/app/presentation/theme/Type.kt @@ -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 ) ) diff --git a/app/src/main/res/drawable/background_list/bg_animaux.png b/app/src/main/res/drawable/background_list/bg_animaux.png new file mode 100644 index 0000000..0b1e450 Binary files /dev/null and b/app/src/main/res/drawable/background_list/bg_animaux.png differ diff --git a/app/src/main/res/drawable/background_list/bg_baby.png b/app/src/main/res/drawable/background_list/bg_baby.png new file mode 100644 index 0000000..8f75be4 Binary files /dev/null and b/app/src/main/res/drawable/background_list/bg_baby.png differ diff --git a/app/src/main/res/drawable/background_list/bg_epicerie.png b/app/src/main/res/drawable/background_list/bg_epicerie.png new file mode 100644 index 0000000..11adcf2 Binary files /dev/null and b/app/src/main/res/drawable/background_list/bg_epicerie.png differ diff --git a/app/src/main/res/drawable/background_list/bg_epicerie2.png b/app/src/main/res/drawable/background_list/bg_epicerie2.png new file mode 100644 index 0000000..5f4e414 Binary files /dev/null and b/app/src/main/res/drawable/background_list/bg_epicerie2.png differ diff --git a/app/src/main/res/drawable/background_list/bg_jardinage.png b/app/src/main/res/drawable/background_list/bg_jardinage.png new file mode 100644 index 0000000..a6711b5 Binary files /dev/null and b/app/src/main/res/drawable/background_list/bg_jardinage.png differ diff --git a/app/src/main/res/drawable/background_list/bg_office.png b/app/src/main/res/drawable/background_list/bg_office.png new file mode 100644 index 0000000..fc1fb5b Binary files /dev/null and b/app/src/main/res/drawable/background_list/bg_office.png differ diff --git a/app/src/main/res/drawable/background_list/bg_party.png b/app/src/main/res/drawable/background_list/bg_party.png new file mode 100644 index 0000000..e65ed5b Binary files /dev/null and b/app/src/main/res/drawable/background_list/bg_party.png differ diff --git a/app/src/main/res/drawable/background_list/bg_pharmacie.png b/app/src/main/res/drawable/background_list/bg_pharmacie.png new file mode 100644 index 0000000..8977757 Binary files /dev/null and b/app/src/main/res/drawable/background_list/bg_pharmacie.png differ diff --git a/app/src/main/res/drawable/background_list/bg_plage.png b/app/src/main/res/drawable/background_list/bg_plage.png new file mode 100644 index 0000000..f519ed1 Binary files /dev/null and b/app/src/main/res/drawable/background_list/bg_plage.png differ diff --git a/app/src/main/res/drawable/background_list/bg_renovation.png b/app/src/main/res/drawable/background_list/bg_renovation.png new file mode 100644 index 0000000..af3b7f8 Binary files /dev/null and b/app/src/main/res/drawable/background_list/bg_renovation.png differ diff --git a/app/src/main/res/drawable/safebite_logo_background.xml b/app/src/main/res/drawable/safebite_logo_background.xml index 5df29e7..eae2f6c 100644 --- a/app/src/main/res/drawable/safebite_logo_background.xml +++ b/app/src/main/res/drawable/safebite_logo_background.xml @@ -13,7 +13,6 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - - - - + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/safebite_logo_round.xml b/app/src/main/res/mipmap-anydpi-v26/safebite_logo_round.xml index 7e20d08..05fb77d 100644 --- a/app/src/main/res/mipmap-anydpi-v26/safebite_logo_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/safebite_logo_round.xml @@ -13,7 +13,6 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index 01969be..66fa793 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -122,6 +122,7 @@ System Clear product cache Clear history + Splash screen About Version %1$s Data provided by Open Food Facts diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0796dfe..e92ad60 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -190,6 +190,7 @@ Système Vider le cache des produits Vider l\'historique + Écran de démarrage (splash) À propos Version %1$s Données fournies par Open Food Facts diff --git a/gradle/gradle-daemon-jvm.properties b/gradle/gradle-daemon-jvm.properties new file mode 100644 index 0000000..5c34300 --- /dev/null +++ b/gradle/gradle-daemon-jvm.properties @@ -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 diff --git a/settings.gradle.kts b/settings.gradle.kts index e3a542a..c028bef 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -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) diff --git a/version.properties b/version.properties index 68f23bd..b2080a1 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ MAJOR=1 -MINOR=9 -PATCH=2 -CODE=13 +MINOR=14 +PATCH=0 +CODE=18