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:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@drawable/safebite_logo"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@drawable/safebite_logo"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.SafeBite"
|
android:theme="@style/Theme.SafeBite"
|
||||||
|
|||||||
@ -26,6 +26,7 @@ object UserPreferencesKeys {
|
|||||||
val ONBOARDING_DONE = booleanPreferencesKey("onboarding_done")
|
val ONBOARDING_DONE = booleanPreferencesKey("onboarding_done")
|
||||||
val ACTIVE_PROFILE_IDS = stringSetPreferencesKey("active_profile_ids")
|
val ACTIVE_PROFILE_IDS = stringSetPreferencesKey("active_profile_ids")
|
||||||
val HEALTH_STRICTNESS = stringPreferencesKey("health_strictness")
|
val HEALTH_STRICTNESS = stringPreferencesKey("health_strictness")
|
||||||
|
val SPLASH_SCREEN_ENABLED = booleanPreferencesKey("splash_screen_enabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
class UserPreferences(private val dataStore: DataStore<Preferences>) {
|
class UserPreferences(private val dataStore: DataStore<Preferences>) {
|
||||||
@ -61,6 +62,10 @@ class UserPreferences(private val dataStore: DataStore<Preferences>) {
|
|||||||
.getOrDefault(HealthStrictness.NORMAL)
|
.getOrDefault(HealthStrictness.NORMAL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val splashScreenEnabled: Flow<Boolean> = dataStore.data.map {
|
||||||
|
it[UserPreferencesKeys.SPLASH_SCREEN_ENABLED] ?: true
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun setAppLanguage(value: AppLanguage) {
|
suspend fun setAppLanguage(value: AppLanguage) {
|
||||||
dataStore.edit { it[UserPreferencesKeys.APP_LANGUAGE] = value.name }
|
dataStore.edit { it[UserPreferencesKeys.APP_LANGUAGE] = value.name }
|
||||||
}
|
}
|
||||||
@ -94,4 +99,8 @@ class UserPreferences(private val dataStore: DataStore<Preferences>) {
|
|||||||
suspend fun setHealthStrictness(value: HealthStrictness) {
|
suspend fun setHealthStrictness(value: HealthStrictness) {
|
||||||
dataStore.edit { it[UserPreferencesKeys.HEALTH_STRICTNESS] = value.name }
|
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 theme = prefs.theme
|
||||||
override val onboardingCompleted = prefs.onboardingCompleted
|
override val onboardingCompleted = prefs.onboardingCompleted
|
||||||
override val healthStrictness = prefs.healthStrictness
|
override val healthStrictness = prefs.healthStrictness
|
||||||
|
override val splashScreenEnabled = prefs.splashScreenEnabled
|
||||||
|
|
||||||
override suspend fun setAppLanguage(value: AppLanguage) = prefs.setAppLanguage(value)
|
override suspend fun setAppLanguage(value: AppLanguage) = prefs.setAppLanguage(value)
|
||||||
override suspend fun setDetectionLanguage(value: DetectionLanguage) = prefs.setDetectionLanguage(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 setTheme(value: ThemePref) = prefs.setTheme(value)
|
||||||
override suspend fun setOnboardingCompleted(value: Boolean) = prefs.setOnboardingCompleted(value)
|
override suspend fun setOnboardingCompleted(value: Boolean) = prefs.setOnboardingCompleted(value)
|
||||||
override suspend fun setHealthStrictness(value: HealthStrictness) = prefs.setHealthStrictness(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 theme: Flow<ThemePref>
|
||||||
val onboardingCompleted: Flow<Boolean>
|
val onboardingCompleted: Flow<Boolean>
|
||||||
val healthStrictness: Flow<HealthStrictness>
|
val healthStrictness: Flow<HealthStrictness>
|
||||||
|
val splashScreenEnabled: Flow<Boolean>
|
||||||
|
|
||||||
suspend fun setAppLanguage(value: AppLanguage)
|
suspend fun setAppLanguage(value: AppLanguage)
|
||||||
suspend fun setDetectionLanguage(value: DetectionLanguage)
|
suspend fun setDetectionLanguage(value: DetectionLanguage)
|
||||||
@ -65,6 +66,7 @@ interface SettingsRepository {
|
|||||||
suspend fun setTheme(value: ThemePref)
|
suspend fun setTheme(value: ThemePref)
|
||||||
suspend fun setOnboardingCompleted(value: Boolean)
|
suspend fun setOnboardingCompleted(value: Boolean)
|
||||||
suspend fun setHealthStrictness(value: HealthStrictness)
|
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 kotlinx.coroutines.flow.stateIn
|
||||||
import javax.inject.Inject
|
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
|
@HiltViewModel
|
||||||
class RootViewModel @Inject constructor(
|
class RootViewModel @Inject constructor(
|
||||||
settings: SettingsRepository
|
settings: SettingsRepository
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
val state: StateFlow<RootUi> = combine(settings.onboardingCompleted, settings.theme) { done, theme ->
|
val state: StateFlow<RootUi> = combine(
|
||||||
RootUi(onboardingDone = done, theme = theme, ready = true)
|
settings.onboardingCompleted,
|
||||||
|
settings.theme,
|
||||||
|
settings.splashScreenEnabled
|
||||||
|
) { done, theme, splashEnabled ->
|
||||||
|
RootUi(
|
||||||
|
onboardingDone = done,
|
||||||
|
theme = theme,
|
||||||
|
showSplash = splashEnabled && done,
|
||||||
|
ready = true
|
||||||
|
)
|
||||||
}.stateIn(viewModelScope, SharingStarted.Eagerly, RootUi())
|
}.stateIn(viewModelScope, SharingStarted.Eagerly, RootUi())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,7 +63,10 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
SafeBiteTheme(darkTheme = dark) {
|
SafeBiteTheme(darkTheme = dark) {
|
||||||
if (ui.ready) {
|
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.result.ResultScreen
|
||||||
import com.safebite.app.presentation.screen.scanner.ScannerScreen
|
import com.safebite.app.presentation.screen.scanner.ScannerScreen
|
||||||
import com.safebite.app.presentation.screen.settings.SettingsScreen
|
import com.safebite.app.presentation.screen.settings.SettingsScreen
|
||||||
|
import com.safebite.app.presentation.screen.splash.SplashScreen
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Graph de navigation principal de l'application SafeBite.
|
* 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.
|
* - Écrans de navigation : Scanner, Result, OCR, Settings, etc.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun SafeBiteNavGraph(onboardingCompleted: Boolean) {
|
fun SafeBiteNavGraph(onboardingCompleted: Boolean, showSplash: Boolean = false) {
|
||||||
val navController = rememberNavController()
|
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)) +
|
val enterAnim = fadeIn(animationSpec = tween(250)) +
|
||||||
slideInHorizontally(animationSpec = tween(250)) { it / 24 }
|
slideInHorizontally(animationSpec = tween(250)) { it / 24 }
|
||||||
@ -53,6 +58,17 @@ fun SafeBiteNavGraph(onboardingCompleted: Boolean) {
|
|||||||
popEnterTransition = { popEnterAnim },
|
popEnterTransition = { popEnterAnim },
|
||||||
popExitTransition = { popExitAnim },
|
popExitTransition = { popExitAnim },
|
||||||
) {
|
) {
|
||||||
|
// ── Splash ──
|
||||||
|
composable(Screen.Splash.route) {
|
||||||
|
SplashScreen(
|
||||||
|
onFinished = {
|
||||||
|
navController.navigate(Screen.Dashboard.route) {
|
||||||
|
popUpTo(Screen.Splash.route) { inclusive = true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// ── Onboarding ──
|
// ── Onboarding ──
|
||||||
composable(Screen.Onboarding.route) {
|
composable(Screen.Onboarding.route) {
|
||||||
OnboardingScreen(onFinished = {
|
OnboardingScreen(onFinished = {
|
||||||
|
|||||||
@ -39,6 +39,7 @@ sealed class Screen(val route: String) {
|
|||||||
}
|
}
|
||||||
data object Onboarding : Screen("onboarding")
|
data object Onboarding : Screen("onboarding")
|
||||||
data object Settings : Screen("settings")
|
data object Settings : Screen("settings")
|
||||||
|
data object Splash : Screen("splash")
|
||||||
|
|
||||||
// ── Sous-écrans ──
|
// ── Sous-écrans ──
|
||||||
data object ProfileList : Screen("profiles")
|
data object ProfileList : Screen("profiles")
|
||||||
|
|||||||
@ -8,9 +8,13 @@ import androidx.compose.animation.scaleIn
|
|||||||
import androidx.compose.animation.scaleOut
|
import androidx.compose.animation.scaleOut
|
||||||
import androidx.compose.animation.slideInVertically
|
import androidx.compose.animation.slideInVertically
|
||||||
import androidx.compose.animation.slideOutVertically
|
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.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.QrCodeScanner
|
import androidx.compose.material.icons.filled.QrCodeScanner
|
||||||
import androidx.compose.material.icons.filled.Settings
|
import androidx.compose.material.icons.filled.Settings
|
||||||
@ -28,7 +32,9 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.navigation.NavDestination
|
import androidx.navigation.NavDestination
|
||||||
@ -81,7 +87,17 @@ fun MainScreen(
|
|||||||
containerColor = MaterialTheme.colorScheme.background,
|
containerColor = MaterialTheme.colorScheme.background,
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
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 = {
|
actions = {
|
||||||
IconButton(onClick = onOpenSettings) {
|
IconButton(onClick = onOpenSettings) {
|
||||||
Icon(
|
Icon(
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package com.safebite.app.presentation.screen.onboarding
|
package com.safebite.app.presentation.screen.onboarding
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
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
|
||||||
@ -31,6 +32,7 @@ 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.text.font.FontWeight
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
@ -129,8 +131,12 @@ private fun WelcomeStep(onNext: () -> Unit) {
|
|||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.Center
|
verticalArrangement = Arrangement.Center
|
||||||
) {
|
) {
|
||||||
Text("🛡️", style = MaterialTheme.typography.displayLarge)
|
Image(
|
||||||
Spacer(Modifier.height(16.dp))
|
painter = painterResource(id = R.drawable.safebite_logo),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(120.dp)
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(24.dp))
|
||||||
Text(
|
Text(
|
||||||
stringResource(R.string.onboarding_welcome_title),
|
stringResource(R.string.onboarding_welcome_title),
|
||||||
style = MaterialTheme.typography.headlineLarge,
|
style = MaterialTheme.typography.headlineLarge,
|
||||||
|
|||||||
@ -78,6 +78,7 @@ fun SettingsScreen(
|
|||||||
Column(verticalArrangement = Arrangement.spacedBy(dimens.spacingSm)) {
|
Column(verticalArrangement = Arrangement.spacedBy(dimens.spacingSm)) {
|
||||||
ToggleRow(stringResource(R.string.settings_haptics), ui.haptics, viewModel::setHaptics)
|
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_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 haptics: Boolean = true,
|
||||||
val sound: Boolean = true,
|
val sound: Boolean = true,
|
||||||
val theme: ThemePref = ThemePref.SYSTEM,
|
val theme: ThemePref = ThemePref.SYSTEM,
|
||||||
val healthStrictness: HealthStrictness = HealthStrictness.NORMAL
|
val healthStrictness: HealthStrictness = HealthStrictness.NORMAL,
|
||||||
|
val splashScreenEnabled: Boolean = true
|
||||||
)
|
)
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
@ -43,8 +44,12 @@ class SettingsViewModel @Inject constructor(
|
|||||||
SettingsUi(lang, detection, haptics, sound, theme)
|
SettingsUi(lang, detection, haptics, sound, theme)
|
||||||
}
|
}
|
||||||
|
|
||||||
val state: StateFlow<SettingsUi> = combine(coreFlow, settings.healthStrictness) { core, strict ->
|
val state: StateFlow<SettingsUi> = combine(
|
||||||
core.copy(healthStrictness = strict)
|
coreFlow,
|
||||||
|
settings.healthStrictness,
|
||||||
|
settings.splashScreenEnabled
|
||||||
|
) { core, strict, splash ->
|
||||||
|
core.copy(healthStrictness = strict, splashScreenEnabled = splash)
|
||||||
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), SettingsUi())
|
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), SettingsUi())
|
||||||
|
|
||||||
fun setAppLanguage(v: AppLanguage) = viewModelScope.launch { settings.setAppLanguage(v) }
|
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 setSound(v: Boolean) = viewModelScope.launch { settings.setSound(v) }
|
||||||
fun setTheme(v: ThemePref) = viewModelScope.launch { settings.setTheme(v) }
|
fun setTheme(v: ThemePref) = viewModelScope.launch { settings.setTheme(v) }
|
||||||
fun setHealthStrictness(v: HealthStrictness) = viewModelScope.launch { settings.setHealthStrictness(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 clearCache() = viewModelScope.launch { productRepo.clearCache() }
|
||||||
fun clearHistory() = viewModelScope.launch { historyRepo.clear() }
|
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.
|
// Ces couleurs sont indépendantes du thème M3 pour cohérence marque.
|
||||||
object SemanticColors {
|
object SemanticColors {
|
||||||
// Light mode
|
// Light mode
|
||||||
val Safe = Color(0xFF2ECC71) // Vert sécurité
|
val Safe = Color(0xFF43A047) // Vert sécurité
|
||||||
val SafeContainer = Color(0xFFE8F8F5) // Fond très clair
|
val SafeContainer = Color(0xFFE8F5E9) // Fond très clair
|
||||||
val OnSafe = Color(0xFFFFFFFF)
|
val OnSafe = Color(0xFFFFFFFF)
|
||||||
val OnSafeContainer = Color(0xFF1A3A2A)
|
val OnSafeContainer = Color(0xFF1A3A2A)
|
||||||
|
|
||||||
val Warning = Color(0xFFE67E22) // Orange attention
|
val Warning = Color(0xFFFFA000) // Orange attention
|
||||||
val WarningContainer = Color(0xFFFEF5E7)
|
val WarningContainer = Color(0xFFFFF3E0)
|
||||||
val OnWarning = Color(0xFFFFFFFF)
|
val OnWarning = Color(0xFFFFFFFF)
|
||||||
val OnWarningContainer = Color(0xFF3A2A1A)
|
val OnWarningContainer = Color(0xFF4A2800)
|
||||||
|
|
||||||
val Danger = Color(0xFFE74C3C) // Rouge danger
|
val Danger = Color(0xFFD32F2F) // Rouge danger
|
||||||
val DangerContainer = Color(0xFFFDEDEC)
|
val DangerContainer = Color(0xFFFFEBEE)
|
||||||
val OnDanger = Color(0xFFFFFFFF)
|
val OnDanger = Color(0xFFFFFFFF)
|
||||||
val OnDangerContainer = Color(0xFF3A1A1A)
|
val OnDangerContainer = Color(0xFF5C0B0B)
|
||||||
|
|
||||||
// Dark mode (mêmes couleurs sémantiques, containers adaptés)
|
// Dark mode (mêmes couleurs sémantiques, containers adaptés)
|
||||||
val SafeDark = Color(0xFF2ECC71)
|
val SafeDark = Color(0xFF81C784)
|
||||||
val SafeContainerDark = Color(0xFF1A3A2A)
|
val SafeContainerDark = Color(0xFF1B5E20)
|
||||||
val WarningDark = Color(0xFFE67E22)
|
val WarningDark = Color(0xFFFFB74D)
|
||||||
val WarningContainerDark = Color(0xFF3A2A1A)
|
val WarningContainerDark = Color(0xFF4A2800)
|
||||||
val DangerDark = Color(0xFFE74C3C)
|
val DangerDark = Color(0xFFEF9A9A)
|
||||||
val DangerContainerDark = Color(0xFF3A1A1A)
|
val DangerContainerDark = Color(0xFF5C0B0B)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- NEUTRES (spec UX §2.1) ------------------------------------------------
|
// ---- NEUTRES (spec UX §2.1) ------------------------------------------------
|
||||||
object NeutralColors {
|
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 Surface = Color(0xFFFFFFFF) // Blanc pur pour cartes
|
||||||
val TextPrimary = Color(0xFF2D3436) // Noir doux (pas #000)
|
val TextPrimary = Color(0xFF212121) // Texte principal
|
||||||
val TextSecondary = Color(0xFF636E72) // Gris moyen
|
val TextSecondary = Color(0xFF757575) // Texte secondaire
|
||||||
val Separator = Color(0xFFDFE6E9) // Gris clair
|
val Separator = Color(0xFFBDBDBD) // Séparateurs
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Brand anchors (Material 3) --------------------------------------------
|
// ---- Brand anchors (Material 3) --------------------------------------------
|
||||||
val BrandIndigo = Color(0xFF1A237E)
|
val BrandPrimary = Color(0xFF1B7A2B)
|
||||||
val BrandIndigoLight = Color(0xFFBAC3FF)
|
val BrandPrimaryDark = Color(0xFF0D5E1A)
|
||||||
val BrandTeal = Color(0xFF00897B)
|
val BrandPrimaryLight = Color(0xFF4CAF50)
|
||||||
val BrandTealLight = Color(0xFF4DB6AC)
|
val BrandSecondary = Color(0xFF2E7D32)
|
||||||
|
|
||||||
// ---- Light scheme ---------------------------------------------------------
|
// ---- Light scheme ---------------------------------------------------------
|
||||||
val LightPrimary = Color(0xFF1A237E)
|
val LightPrimary = Color(0xFF1B7A2B)
|
||||||
val LightOnPrimary = Color(0xFFFFFFFF)
|
val LightOnPrimary = Color(0xFFFFFFFF)
|
||||||
val LightPrimaryContainer = Color(0xFFDDE1FF)
|
val LightPrimaryContainer = Color(0xFFA5D6A7)
|
||||||
val LightOnPrimaryContainer = Color(0xFF001159)
|
val LightOnPrimaryContainer = Color(0xFF0D3B12)
|
||||||
|
|
||||||
val LightSecondary = Color(0xFF00897B)
|
val LightSecondary = Color(0xFF2E7D32)
|
||||||
val LightOnSecondary = Color(0xFFFFFFFF)
|
val LightOnSecondary = Color(0xFFFFFFFF)
|
||||||
val LightSecondaryContainer = Color(0xFFB2DFDB)
|
val LightSecondaryContainer = Color(0xFFC8E6C9)
|
||||||
val LightOnSecondaryContainer = Color(0xFF00251F)
|
val LightOnSecondaryContainer = Color(0xFF1B5E20)
|
||||||
|
|
||||||
val LightTertiary = Color(0xFF7B4E9E)
|
val LightTertiary = Color(0xFF00796B)
|
||||||
val LightOnTertiary = Color(0xFFFFFFFF)
|
val LightOnTertiary = Color(0xFFFFFFFF)
|
||||||
val LightTertiaryContainer = Color(0xFFF0DBFF)
|
val LightTertiaryContainer = Color(0xFFB2DFDB)
|
||||||
val LightOnTertiaryContainer = Color(0xFF2B0A45)
|
val LightOnTertiaryContainer = Color(0xFF004D40)
|
||||||
|
|
||||||
val LightError = Color(0xFFBA1A1A)
|
val LightError = Color(0xFFD32F2F)
|
||||||
val LightOnError = Color(0xFFFFFFFF)
|
val LightOnError = Color(0xFFFFFFFF)
|
||||||
val LightErrorContainer = Color(0xFFFFDAD6)
|
val LightErrorContainer = Color(0xFFFFCDD2)
|
||||||
val LightOnErrorContainer = Color(0xFF410002)
|
val LightOnErrorContainer = Color(0xFF5C0B0B)
|
||||||
|
|
||||||
val LightBackground = NeutralColors.Background // #F5F5F0
|
val LightBackground = NeutralColors.Background // #F1F8E9
|
||||||
val LightOnBackground = NeutralColors.TextPrimary // #2D3436
|
val LightOnBackground = NeutralColors.TextPrimary // #212121
|
||||||
val LightSurface = NeutralColors.Surface // #FFFFFF
|
val LightSurface = NeutralColors.Surface // #FFFFFF
|
||||||
val LightOnSurface = NeutralColors.TextPrimary // #2D3436
|
val LightOnSurface = NeutralColors.TextPrimary // #212121
|
||||||
val LightSurfaceVariant = Color(0xFFE3E2EC)
|
val LightSurfaceVariant = Color(0xFFE8F5E9)
|
||||||
val LightOnSurfaceVariant = NeutralColors.TextSecondary
|
val LightOnSurfaceVariant = NeutralColors.TextSecondary
|
||||||
val LightSurfaceTint = LightPrimary
|
val LightSurfaceTint = LightPrimary
|
||||||
|
|
||||||
val LightOutline = NeutralColors.Separator
|
val LightOutline = NeutralColors.Separator
|
||||||
val LightOutlineVariant = Color(0xFFC7C6D0)
|
val LightOutlineVariant = Color(0xFFE0E0E0)
|
||||||
|
|
||||||
val LightInverseSurface = Color(0xFF2F3033)
|
val LightInverseSurface = Color(0xFF2F3033)
|
||||||
val LightInverseOnSurface = Color(0xFFF1F0F4)
|
val LightInverseOnSurface = Color(0xFFF1F0F4)
|
||||||
val LightInversePrimary = Color(0xFFBAC3FF)
|
val LightInversePrimary = Color(0xFF81C784)
|
||||||
|
|
||||||
val LightScrim = Color(0xFF000000)
|
val LightScrim = Color(0xFF000000)
|
||||||
|
|
||||||
// ---- Dark scheme (surfaces élevées M3) ------------------------------------
|
// ---- Dark scheme (surfaces élevées M3) ------------------------------------
|
||||||
val DarkPrimary = Color(0xFFBAC3FF)
|
val DarkPrimary = Color(0xFF81C784)
|
||||||
val DarkOnPrimary = Color(0xFF0A1A6A)
|
val DarkOnPrimary = Color(0xFF0D3B12)
|
||||||
val DarkPrimaryContainer = Color(0xFF3241A0)
|
val DarkPrimaryContainer = Color(0xFF1B5E20)
|
||||||
val DarkOnPrimaryContainer = Color(0xFFDDE1FF)
|
val DarkOnPrimaryContainer = Color(0xFFA5D6A7)
|
||||||
|
|
||||||
val DarkSecondary = Color(0xFF4DB6AC)
|
val DarkSecondary = Color(0xFFA5D6A7)
|
||||||
val DarkOnSecondary = Color(0xFF00332C)
|
val DarkOnSecondary = Color(0xFF1B5E20)
|
||||||
val DarkSecondaryContainer = Color(0xFF00695C)
|
val DarkSecondaryContainer = Color(0xFF2E7D32)
|
||||||
val DarkOnSecondaryContainer = Color(0xFFB2DFDB)
|
val DarkOnSecondaryContainer = Color(0xFFC8E6C9)
|
||||||
|
|
||||||
val DarkTertiary = Color(0xFFE0B6FF)
|
val DarkTertiary = Color(0xFF4DB6AC)
|
||||||
val DarkOnTertiary = Color(0xFF451F6D)
|
val DarkOnTertiary = Color(0xFF00332C)
|
||||||
val DarkTertiaryContainer = Color(0xFF5D3785)
|
val DarkTertiaryContainer = Color(0xFF00695C)
|
||||||
val DarkOnTertiaryContainer = Color(0xFFF0DBFF)
|
val DarkOnTertiaryContainer = Color(0xFFB2DFDB)
|
||||||
|
|
||||||
val DarkError = Color(0xFFFFB4AB)
|
val DarkError = Color(0xFFEF9A9A)
|
||||||
val DarkOnError = Color(0xFF690005)
|
val DarkOnError = Color(0xFF690005)
|
||||||
val DarkErrorContainer = Color(0xFF93000A)
|
val DarkErrorContainer = Color(0xFF93000A)
|
||||||
val DarkOnErrorContainer = Color(0xFFFFDAD6)
|
val DarkOnErrorContainer = Color(0xFFFFCDD2)
|
||||||
|
|
||||||
val DarkBackground = Color(0xFF121212)
|
val DarkBackground = Color(0xFF1A1C1A)
|
||||||
val DarkOnBackground = Color(0xFFE6E1E5)
|
val DarkOnBackground = Color(0xFFE0E0E0)
|
||||||
val DarkSurface = Color(0xFF1E1E1E)
|
val DarkSurface = Color(0xFF2D2F2D)
|
||||||
val DarkOnSurface = Color(0xFFE6E1E5)
|
val DarkOnSurface = Color(0xFFE0E0E0)
|
||||||
val DarkSurfaceVariant = Color(0xFF46464F)
|
val DarkSurfaceVariant = Color(0xFF3A3F3A)
|
||||||
val DarkOnSurfaceVariant = Color(0xFFC7C6D0)
|
val DarkOnSurfaceVariant = Color(0xFFBDBDBD)
|
||||||
val DarkSurfaceTint = DarkPrimary
|
val DarkSurfaceTint = DarkPrimary
|
||||||
|
|
||||||
val DarkOutline = Color(0xFF90909A)
|
val DarkOutline = Color(0xFF90909A)
|
||||||
@ -122,10 +122,21 @@ val DarkOutlineVariant = Color(0xFF46464F)
|
|||||||
|
|
||||||
val DarkInverseSurface = Color(0xFFE6E1E5)
|
val DarkInverseSurface = Color(0xFFE6E1E5)
|
||||||
val DarkInverseOnSurface = Color(0xFF2F3033)
|
val DarkInverseOnSurface = Color(0xFF2F3033)
|
||||||
val DarkInversePrimary = Color(0xFF1A237E)
|
val DarkInversePrimary = Color(0xFF1B7A2B)
|
||||||
|
|
||||||
val DarkScrim = Color(0xFF000000)
|
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) -------------------
|
// ---- Legacy aliases (backward compat pour code existant) -------------------
|
||||||
@Deprecated("Use SemanticColors.Safe", ReplaceWith("SemanticColors.Safe"))
|
@Deprecated("Use SemanticColors.Safe", ReplaceWith("SemanticColors.Safe"))
|
||||||
val StatusSafe get() = 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
|
* 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.
|
* toute dépendance réseau / asset.
|
||||||
*/
|
*/
|
||||||
private val DisplayFamily = FontFamily.SansSerif
|
private val PoppinsFamily = FontFamily.SansSerif
|
||||||
private val HeadlineFamily = FontFamily.SansSerif
|
private val InterFamily = FontFamily.SansSerif
|
||||||
private val TitleFamily = FontFamily.SansSerif
|
|
||||||
private val BodyFamily = FontFamily.SansSerif
|
|
||||||
private val LabelFamily = FontFamily.SansSerif
|
|
||||||
|
|
||||||
val SafeBiteTypography = Typography(
|
val SafeBiteTypography = Typography(
|
||||||
// Display — pour les titres héros (onboarding, banner)
|
// Display — pour les titres héros (onboarding, banner)
|
||||||
displayLarge = TextStyle(
|
displayLarge = TextStyle(
|
||||||
fontFamily = DisplayFamily,
|
fontFamily = PoppinsFamily,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
fontSize = 57.sp,
|
fontSize = 57.sp,
|
||||||
lineHeight = 64.sp,
|
lineHeight = 64.sp,
|
||||||
letterSpacing = (-0.25).sp
|
letterSpacing = (-0.25).sp
|
||||||
),
|
),
|
||||||
displayMedium = TextStyle(
|
displayMedium = TextStyle(
|
||||||
fontFamily = DisplayFamily,
|
fontFamily = PoppinsFamily,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
fontSize = 45.sp,
|
fontSize = 45.sp,
|
||||||
lineHeight = 52.sp,
|
lineHeight = 52.sp,
|
||||||
letterSpacing = 0.sp
|
letterSpacing = 0.sp
|
||||||
),
|
),
|
||||||
displaySmall = TextStyle(
|
displaySmall = TextStyle(
|
||||||
fontFamily = DisplayFamily,
|
fontFamily = PoppinsFamily,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
fontSize = 36.sp,
|
fontSize = 36.sp,
|
||||||
lineHeight = 44.sp,
|
lineHeight = 44.sp,
|
||||||
letterSpacing = 0.sp
|
letterSpacing = 0.sp
|
||||||
),
|
),
|
||||||
|
|
||||||
// Headline — sections majeures
|
// Headline — sections majeures (Poppins Bold/SemiBold)
|
||||||
headlineLarge = TextStyle(
|
headlineLarge = TextStyle(
|
||||||
fontFamily = HeadlineFamily,
|
fontFamily = PoppinsFamily,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
fontSize = 32.sp,
|
fontSize = 32.sp,
|
||||||
lineHeight = 40.sp,
|
lineHeight = 40.sp,
|
||||||
letterSpacing = 0.sp
|
letterSpacing = (-0.5).sp
|
||||||
),
|
),
|
||||||
headlineMedium = TextStyle(
|
headlineMedium = TextStyle(
|
||||||
fontFamily = HeadlineFamily,
|
fontFamily = PoppinsFamily,
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
fontSize = 28.sp,
|
|
||||||
lineHeight = 36.sp,
|
|
||||||
letterSpacing = 0.sp
|
|
||||||
),
|
|
||||||
headlineSmall = TextStyle(
|
|
||||||
fontFamily = HeadlineFamily,
|
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
fontSize = 24.sp,
|
fontSize = 24.sp,
|
||||||
lineHeight = 32.sp,
|
lineHeight = 32.sp,
|
||||||
letterSpacing = 0.sp
|
letterSpacing = 0.sp
|
||||||
),
|
),
|
||||||
|
headlineSmall = TextStyle(
|
||||||
// Title — titres d'écran, de cartes
|
fontFamily = PoppinsFamily,
|
||||||
titleLarge = TextStyle(
|
|
||||||
fontFamily = TitleFamily,
|
|
||||||
fontWeight = FontWeight.SemiBold,
|
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,
|
fontSize = 22.sp,
|
||||||
lineHeight = 28.sp,
|
lineHeight = 28.sp,
|
||||||
letterSpacing = 0.sp
|
letterSpacing = 0.sp
|
||||||
),
|
),
|
||||||
titleMedium = TextStyle(
|
titleMedium = TextStyle(
|
||||||
fontFamily = TitleFamily,
|
fontFamily = InterFamily,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.Medium,
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
lineHeight = 24.sp,
|
lineHeight = 24.sp,
|
||||||
letterSpacing = 0.15.sp
|
letterSpacing = 0.15.sp
|
||||||
),
|
),
|
||||||
titleSmall = TextStyle(
|
titleSmall = TextStyle(
|
||||||
fontFamily = TitleFamily,
|
fontFamily = InterFamily,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Medium,
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
lineHeight = 20.sp,
|
lineHeight = 20.sp,
|
||||||
letterSpacing = 0.1.sp
|
letterSpacing = 0.1.sp
|
||||||
),
|
),
|
||||||
|
|
||||||
// Body — textes courants
|
// Body — textes courants (Inter Regular)
|
||||||
bodyLarge = TextStyle(
|
bodyLarge = TextStyle(
|
||||||
fontFamily = BodyFamily,
|
fontFamily = InterFamily,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal,
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
lineHeight = 24.sp,
|
lineHeight = 24.sp,
|
||||||
letterSpacing = 0.5.sp
|
letterSpacing = 0.5.sp
|
||||||
),
|
),
|
||||||
bodyMedium = TextStyle(
|
bodyMedium = TextStyle(
|
||||||
fontFamily = BodyFamily,
|
fontFamily = InterFamily,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal,
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
lineHeight = 20.sp,
|
lineHeight = 20.sp,
|
||||||
letterSpacing = 0.25.sp
|
letterSpacing = 0.25.sp
|
||||||
),
|
),
|
||||||
bodySmall = TextStyle(
|
bodySmall = TextStyle(
|
||||||
fontFamily = BodyFamily,
|
fontFamily = InterFamily,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal,
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
lineHeight = 16.sp,
|
lineHeight = 16.sp,
|
||||||
letterSpacing = 0.4.sp
|
letterSpacing = 0.4.sp
|
||||||
),
|
),
|
||||||
|
|
||||||
// Label — boutons, chips, tags
|
// Label — boutons, chips, tags (Poppins SemiBold pour boutons, Inter Medium pour petits)
|
||||||
labelLarge = TextStyle(
|
labelLarge = TextStyle(
|
||||||
fontFamily = LabelFamily,
|
fontFamily = PoppinsFamily,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.SemiBold,
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
lineHeight = 20.sp,
|
lineHeight = 20.sp,
|
||||||
letterSpacing = 0.1.sp
|
letterSpacing = 1.25.sp
|
||||||
),
|
),
|
||||||
labelMedium = TextStyle(
|
labelMedium = TextStyle(
|
||||||
fontFamily = LabelFamily,
|
fontFamily = InterFamily,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Medium,
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
lineHeight = 16.sp,
|
lineHeight = 16.sp,
|
||||||
letterSpacing = 0.5.sp
|
letterSpacing = 0.5.sp
|
||||||
),
|
),
|
||||||
labelSmall = TextStyle(
|
labelSmall = TextStyle(
|
||||||
fontFamily = LabelFamily,
|
fontFamily = InterFamily,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Medium,
|
||||||
fontSize = 11.sp,
|
fontSize = 10.sp,
|
||||||
lineHeight = 16.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
|
~ See the License for the specific language governing permissions and
|
||||||
~ limitations under the License.
|
~ limitations under the License.
|
||||||
-->
|
-->
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<vector
|
<vector
|
||||||
android:height="108dp"
|
android:height="108dp"
|
||||||
android:width="108dp"
|
android:width="108dp"
|
||||||
|
|||||||
@ -13,9 +13,8 @@
|
|||||||
~ See the License for the specific language governing permissions and
|
~ See the License for the specific language governing permissions and
|
||||||
~ limitations under the License.
|
~ limitations under the License.
|
||||||
-->
|
-->
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/safebite_logo_background"/>
|
<background android:drawable="@drawable/safebite_logo_background"/>
|
||||||
<foreground android:drawable="@drawable/safebite_logo_foreground"/>
|
<foreground android:drawable="@drawable/safebite_logo"/>
|
||||||
<monochrome android:drawable="@drawable/safebite_logo_foreground"/>
|
<monochrome android:drawable="@drawable/safebite_logo"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
@ -13,7 +13,6 @@
|
|||||||
~ See the License for the specific language governing permissions and
|
~ See the License for the specific language governing permissions and
|
||||||
~ limitations under the License.
|
~ limitations under the License.
|
||||||
-->
|
-->
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/safebite_logo_background"/>
|
<background android:drawable="@drawable/safebite_logo_background"/>
|
||||||
<foreground android:drawable="@drawable/safebite_logo_foreground"/>
|
<foreground android:drawable="@drawable/safebite_logo_foreground"/>
|
||||||
|
|||||||
@ -122,6 +122,7 @@
|
|||||||
<string name="settings_theme_system">System</string>
|
<string name="settings_theme_system">System</string>
|
||||||
<string name="settings_clear_cache">Clear product cache</string>
|
<string name="settings_clear_cache">Clear product cache</string>
|
||||||
<string name="settings_clear_history">Clear history</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_about">About</string>
|
||||||
<string name="settings_version">Version %1$s</string>
|
<string name="settings_version">Version %1$s</string>
|
||||||
<string name="settings_off_attribution">Data provided by Open Food Facts</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_theme_system">Système</string>
|
||||||
<string name="settings_clear_cache">Vider le cache des produits</string>
|
<string name="settings_clear_cache">Vider le cache des produits</string>
|
||||||
<string name="settings_clear_history">Vider l\'historique</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_about">À propos</string>
|
||||||
<string name="settings_version">Version %1$s</string>
|
<string name="settings_version">Version %1$s</string>
|
||||||
<string name="settings_off_attribution">Données fournies par Open Food Facts</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()
|
gradlePluginPortal()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
plugins {
|
||||||
|
id("org.gradle.toolchains.foojay-resolver-convention") version "0.10.0"
|
||||||
|
}
|
||||||
|
|
||||||
dependencyResolutionManagement {
|
dependencyResolutionManagement {
|
||||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
MAJOR=1
|
MAJOR=1
|
||||||
MINOR=9
|
MINOR=14
|
||||||
PATCH=2
|
PATCH=0
|
||||||
CODE=13
|
CODE=18
|
||||||
|
|||||||