diff --git a/app/src/main/java/com/example/scanwich/AuthWrapper.kt b/app/src/main/java/com/example/scanwich/AuthWrapper.kt index 1b07d63..21b5cd7 100644 --- a/app/src/main/java/com/example/scanwich/AuthWrapper.kt +++ b/app/src/main/java/com/example/scanwich/AuthWrapper.kt @@ -41,10 +41,11 @@ import androidx.security.crypto.MasterKey import com.example.scanwich.LoginScreen import com.example.scanwich.MainApp import com.example.scanwich.AccessDeniedScreen +import com.google.firebase.firestore.FirebaseFirestore @Composable @Suppress("DEPRECATION") -fun AuthWrapper(dao: AppDao, ) { +fun AuthWrapper(dao: AppDao) { val context = LocalContext.current val coroutineScope = rememberCoroutineScope() val auth = remember { FirebaseAuth.getInstance() } @@ -56,8 +57,8 @@ fun AuthWrapper(dao: AppDao, ) { } val googleSignInClient = remember { GoogleSignIn.getClient(context, gso) } var firebaseUser by remember { mutableStateOf(auth.currentUser) } - - val allowedEmails = listOf("marcandre.charest@gmail.com", "everousseau07@gmail.com") + + var isAuthorized by remember { mutableStateOf(null) } val launcher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> val task = GoogleSignIn.getSignedInAccountFromIntent(result.data) @@ -68,7 +69,6 @@ fun AuthWrapper(dao: AppDao, ) { try { val authResult = auth.signInWithCredential(credential).await() firebaseUser = authResult.user - Log.d("Auth", "Connecté à Firebase avec : ${firebaseUser?.email}") } catch (e: Exception) { Log.e("Auth", "Erreur Firebase Auth : ${e.message}") Toast.makeText(context, "Erreur de synchronisation Firebase.", Toast.LENGTH_LONG).show() @@ -77,9 +77,8 @@ fun AuthWrapper(dao: AppDao, ) { } catch (e: ApiException) { Log.e("Auth", "Erreur Google Sign-In : ${e.statusCode}") val msg = when (e.statusCode) { - 10 -> "Erreur 10 : SHA-1 non reconnu dans Firebase. Assurez-vous d'avoir ajouté le SHA-1 de TOUTES vos clés de signature." + 10 -> "Erreur 10 : SHA-1 non reconnu. Assurez-vous d'avoir ajouté le SHA-1 de VOS clés." 7 -> "Erreur 7 : Problème de réseau." - 12500 -> "Erreur 12500 : Problème de configuration Google Play Services." else -> "Erreur Google (Code ${e.statusCode})." } Toast.makeText(context, msg, Toast.LENGTH_LONG).show() @@ -90,17 +89,52 @@ fun AuthWrapper(dao: AppDao, ) { auth.signOut() googleSignInClient.signOut().addOnCompleteListener { firebaseUser = null + isAuthorized = null + } + } + + LaunchedEffect(firebaseUser) { + isAuthorized = null + val email = firebaseUser?.email?.trim()?.lowercase() + + if (email != null && email.isNotEmpty()) { + Log.d("Auth", "Vérification de l'autorisation pour l'email: '$email'") + try { + // On spécifie explicitement la base de données "scan-wich" + val db = FirebaseFirestore.getInstance("scan-wich") + val docRef = db.collection("authorized_users").document(email) + val document = docRef.get().await() + + if (document.exists()) { + Log.d("Auth", "Accès AUTORISÉ pour '$email'. Document trouvé.") + isAuthorized = true + } else { + Log.w("Auth", "Accès REFUSÉ pour '$email'. Document NON trouvé.") + isAuthorized = false + } + } catch (e: Exception) { + Log.e("Auth", "Erreur critique Firestore. Vérifiez les règles de sécurité.", e) + isAuthorized = false + } + } else if (firebaseUser != null) { + Log.w("Auth", "L'utilisateur est connecté mais son email est vide.") + isAuthorized = false } } if (firebaseUser == null) { LoginScreen { launcher.launch(googleSignInClient.signInIntent) } } else { - val userEmail = firebaseUser?.email?.lowercase() ?: "" - if (allowedEmails.contains(userEmail)) { - MainApp(dao = dao, onLogout = onLogout, userId = firebaseUser!!.uid) - } else { - AccessDeniedScreen(onLogout) + when (isAuthorized) { + true -> MainApp(dao = dao, onLogout = onLogout, userId = firebaseUser!!.uid) + false -> AccessDeniedScreen(onLogout) + null -> { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + CircularProgressIndicator() + Spacer(modifier = Modifier.height(8.dp)) + Text("Vérification de l'accès...") + } + } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/example/scanwich/SetupScreen.kt b/app/src/main/java/com/example/scanwich/SetupScreen.kt index e993d4f..a49cc7a 100644 --- a/app/src/main/java/com/example/scanwich/SetupScreen.kt +++ b/app/src/main/java/com/example/scanwich/SetupScreen.kt @@ -1,24 +1,23 @@ package com.example.scanwich import android.content.SharedPreferences +import android.util.Log +import android.widget.Toast import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.ui.unit.dp -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.foundation.background -import androidx.compose.runtime.Composable import androidx.core.content.edit +import com.google.firebase.auth.FirebaseAuth +import com.google.firebase.firestore.FirebaseFirestore @Composable fun SetupScreen(prefs: SharedPreferences, onComplete: () -> Unit) { @@ -31,6 +30,7 @@ fun SetupScreen(prefs: SharedPreferences, onComplete: () -> Unit) { var goal by remember { mutableStateOf(prefs.getString("goal", "Maintenir le poids") ?: "Maintenir le poids") } var isDiabetic by remember { mutableStateOf(prefs.getBoolean("is_diabetic", false)) } + val context = LocalContext.current val activityLevels = listOf("Sédentaire", "Légèrement actif", "Modérément actif", "Très actif", "Extrêmement actif") val goals = listOf("Maintenir le poids", "Perdre du poids") @@ -139,6 +139,12 @@ fun SetupScreen(prefs: SharedPreferences, onComplete: () -> Unit) { Button( onClick = { + val currentUser = FirebaseAuth.getInstance().currentUser + if (currentUser == null) { + Toast.makeText(context, "Erreur : Vous devez être connecté pour sauvegarder.", Toast.LENGTH_LONG).show() + return@Button + } + val ageInt = age.toIntOrNull() ?: 0 val height = heightCm.toDoubleOrNull() ?: 0.0 var weightKg = weight.toDoubleOrNull() ?: 0.0 @@ -160,22 +166,48 @@ fun SetupScreen(prefs: SharedPreferences, onComplete: () -> Unit) { val targetProtein = (targetCals * 0.2 / 4).toInt() val targetFat = (targetCals * 0.3 / 9).toInt() - prefs.edit { - putString("target_calories", targetCals.toString()) - putString("target_carbs", targetCarbs.toString()) - putString("target_protein", targetProtein.toString()) - putString("target_fat", targetFat.toString()) - putString("weight_kg", weightKg.toString()) - putString("weight_display", weightDisplay) - putBoolean("is_lbs", isLbs) - putString("height_cm", heightCm) - putBoolean("is_diabetic", isDiabetic) - putInt("age", ageInt) - putString("gender", gender) - putString("activity_level", activityLevel) - putString("goal", goal) - } - onComplete() + val userProfile = hashMapOf( + "age" to ageInt, + "height_cm" to height, + "weight_kg" to weightKg, + "is_lbs" to isLbs, + "gender" to gender, + "activity_level" to activityLevel, + "goal" to goal, + "is_diabetic" to isDiabetic, + "target_calories" to targetCals, + "target_carbs" to targetCarbs, + "target_protein" to targetProtein, + "target_fat" to targetFat + ) + + // On spécifie explicitement la base de données "scan-wich" + FirebaseFirestore.getInstance("scan-wich").collection("users").document(currentUser.uid) + .set(userProfile) + .addOnSuccessListener { + Log.d("SetupScreen", "User profile saved to Firestore.") + prefs.edit { + putString("target_calories", targetCals.toString()) + putString("target_carbs", targetCarbs.toString()) + putString("target_protein", targetProtein.toString()) + putString("target_fat", targetFat.toString()) + putString("weight_kg", weightKg.toString()) + putString("weight_display", weightDisplay) + putBoolean("is_lbs", isLbs) + putString("height_cm", heightCm) + putBoolean("is_diabetic", isDiabetic) + putInt("age", ageInt) + putString("gender", gender) + putString("activity_level", activityLevel) + putString("goal", goal) + } + Toast.makeText(context, "Profil sauvegardé sur votre compte !", Toast.LENGTH_SHORT).show() + onComplete() + } + .addOnFailureListener { e -> + Log.w("SetupScreen", "Error writing user profile to Firestore", e) + Toast.makeText(context, "Erreur de sauvegarde du profil: ${e.message}", Toast.LENGTH_LONG).show() + } }, modifier = Modifier .fillMaxWidth() diff --git a/release-notes.txt b/release-notes.txt index dd0563b..d2dd64d 100644 --- a/release-notes.txt +++ b/release-notes.txt @@ -1,40 +1,46 @@ 📝 Notes de version - Scan-Wich -Dernières mises à jour : -🛠️ Correctifs et Améliorations Strava : -- Résolution d'un problème de compilation bloquant sur l'écran des sports. -- Intégration d'un nouvel algorithme d'estimation des calories basé sur les MET (Metabolic Equivalent of Task) pour une précision accrue des activités Strava sans données de calories natives. -- Amélioration de la fiabilité du parsing des dates d'activités Strava. +**Changements majeurs de la version actuelle :** + +🛡️ **Refonte de l'Architecture de Sécurité et de Données :** +- **Gestion des accès centralisée :** L'ancien système d'utilisateurs autorisés codé en dur dans l'application a été supprimé. L'accès est désormais contrôlé de manière sécurisée et dynamique via une liste d'autorisation sur le serveur Firebase Firestore. Cela ouvre la voie à la gestion d'abonnements. +- **Profils Utilisateurs dans le Cloud :** Les données de profil (poids, objectifs, etc.) sont maintenant synchronisées avec le compte Firebase de l'utilisateur, permettant une expérience cohérente sur plusieurs appareils. +- **Correctif Critique de Connexion :** Résolution d'un bug majeur qui empêchait l'application de se connecter correctement à la base de données Firestore, causant des accès refusés inattendus pour les utilisateurs légitimes. L'application est désormais compatible avec les bases de données Firestore nommées. --- -Nouveautés et Améliorations précédentes : +**Mises à jour précédentes :** -🛡️ Sécurité renforcée : +🛠️ **Correctifs et Améliorations Strava :** +- Résolution d'un problème de compilation bloquant sur l'écran des sports. +- Intégration d'un nouvel algorithme d'estimation des calories basé sur les MET (Metabolic Equivalent of Task) pour une précision accrue. +- Amélioration de la fiabilité du parsing des dates d'activités Strava. + +🛡️ **Sécurité renforcée :** - Intégration de Firebase App Check (Play Integrity) pour protéger l'API contre les accès non autorisés. - Migration de la clé API vers Google Cloud Secret Manager, supprimant toute information sensible du code source. -⚡ Analyse Ultra-Rapide : +⚡ **Analyse Ultra-Rapide :** - Nouveau moteur de compression d'image intelligent (réduction de ~2.2 Mo à 150 Ko par scan), accélérant drastiquement l'analyse IA. -🤖 IA Sécurisée : +🤖 **IA Sécurisée :** - Migration de la logique d'analyse (prompts) vers des Cloud Functions pour garantir des résultats plus fiables et protégés. -📄 Export PDF Professionnel : +📄 **Export PDF Professionnel :** - Nouvelle fonctionnalité d'exportation de l'historique. Générez un rapport PDF complet incluant vos repas, activités sportives et suivis de glycémie. -🩸 Suivi Diabétique complet : +🩸 **Suivi Diabétique complet :** - Possibilité d'enregistrer et de visualiser sa glycémie avant/après chaque catégorie de repas directement dans l'historique. -🚴 Synchronisation Strava : +🚴 **Synchronisation Strava :** - Connexion directe à Strava pour importer vos activités et calculer précisément les calories brûlées. -⭐ Gestion des Favoris : +⭐ **Gestion des Favoris :** - Refonte de l'interface des favoris avec une nouvelle fenêtre modale pour un ajout rapide de vos repas récurrents. -🔍 Scanner de Code-barres : +🔍 **Scanner de Code-barres :** - Intégration d'Open Food Facts pour identifier instantanément les produits industriels via leur code-barres. -🔧 Stabilité et Modernisation : +🔧 **Stabilité et Modernisation :** - Optimisation pour Android 36. - Correction de bugs majeurs sur le stockage sécurisé des préférences (MasterKey) et la synchronisation Firebase (collectAsState).