changes #6

Merged
macharest merged 3 commits from changes into master 2026-02-24 12:57:45 -05:00
3 changed files with 122 additions and 50 deletions
Showing only changes of commit 93c8814b84 - Show all commits

View File

@@ -41,10 +41,11 @@ import androidx.security.crypto.MasterKey
import com.example.scanwich.LoginScreen import com.example.scanwich.LoginScreen
import com.example.scanwich.MainApp import com.example.scanwich.MainApp
import com.example.scanwich.AccessDeniedScreen import com.example.scanwich.AccessDeniedScreen
import com.google.firebase.firestore.FirebaseFirestore
@Composable @Composable
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
fun AuthWrapper(dao: AppDao, ) { fun AuthWrapper(dao: AppDao) {
val context = LocalContext.current val context = LocalContext.current
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val auth = remember { FirebaseAuth.getInstance() } val auth = remember { FirebaseAuth.getInstance() }
@@ -56,8 +57,8 @@ fun AuthWrapper(dao: AppDao, ) {
} }
val googleSignInClient = remember { GoogleSignIn.getClient(context, gso) } val googleSignInClient = remember { GoogleSignIn.getClient(context, gso) }
var firebaseUser by remember { mutableStateOf<FirebaseUser?>(auth.currentUser) } var firebaseUser by remember { mutableStateOf<FirebaseUser?>(auth.currentUser) }
val allowedEmails = listOf("marcandre.charest@gmail.com", "everousseau07@gmail.com") var isAuthorized by remember { mutableStateOf<Boolean?>(null) }
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> val launcher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
val task = GoogleSignIn.getSignedInAccountFromIntent(result.data) val task = GoogleSignIn.getSignedInAccountFromIntent(result.data)
@@ -68,7 +69,6 @@ fun AuthWrapper(dao: AppDao, ) {
try { try {
val authResult = auth.signInWithCredential(credential).await() val authResult = auth.signInWithCredential(credential).await()
firebaseUser = authResult.user firebaseUser = authResult.user
Log.d("Auth", "Connecté à Firebase avec : ${firebaseUser?.email}")
} catch (e: Exception) { } catch (e: Exception) {
Log.e("Auth", "Erreur Firebase Auth : ${e.message}") Log.e("Auth", "Erreur Firebase Auth : ${e.message}")
Toast.makeText(context, "Erreur de synchronisation Firebase.", Toast.LENGTH_LONG).show() Toast.makeText(context, "Erreur de synchronisation Firebase.", Toast.LENGTH_LONG).show()
@@ -77,9 +77,8 @@ fun AuthWrapper(dao: AppDao, ) {
} catch (e: ApiException) { } catch (e: ApiException) {
Log.e("Auth", "Erreur Google Sign-In : ${e.statusCode}") Log.e("Auth", "Erreur Google Sign-In : ${e.statusCode}")
val msg = when (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." 7 -> "Erreur 7 : Problème de réseau."
12500 -> "Erreur 12500 : Problème de configuration Google Play Services."
else -> "Erreur Google (Code ${e.statusCode})." else -> "Erreur Google (Code ${e.statusCode})."
} }
Toast.makeText(context, msg, Toast.LENGTH_LONG).show() Toast.makeText(context, msg, Toast.LENGTH_LONG).show()
@@ -90,17 +89,52 @@ fun AuthWrapper(dao: AppDao, ) {
auth.signOut() auth.signOut()
googleSignInClient.signOut().addOnCompleteListener { googleSignInClient.signOut().addOnCompleteListener {
firebaseUser = null 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) { if (firebaseUser == null) {
LoginScreen { launcher.launch(googleSignInClient.signInIntent) } LoginScreen { launcher.launch(googleSignInClient.signInIntent) }
} else { } else {
val userEmail = firebaseUser?.email?.lowercase() ?: "" when (isAuthorized) {
if (allowedEmails.contains(userEmail)) { true -> MainApp(dao = dao, onLogout = onLogout, userId = firebaseUser!!.uid)
MainApp(dao = dao, onLogout = onLogout, userId = firebaseUser!!.uid) false -> AccessDeniedScreen(onLogout)
} else { null -> {
AccessDeniedScreen(onLogout) Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
CircularProgressIndicator()
Spacer(modifier = Modifier.height(8.dp))
Text("Vérification de l'accès...")
}
}
} }
} }
} }

View File

@@ -1,24 +1,23 @@
package com.example.scanwich package com.example.scanwich
import android.content.SharedPreferences import android.content.SharedPreferences
import android.util.Log
import android.widget.Toast
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.input.KeyboardType 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.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 androidx.core.content.edit
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.firestore.FirebaseFirestore
@Composable @Composable
fun SetupScreen(prefs: SharedPreferences, onComplete: () -> Unit) { 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 goal by remember { mutableStateOf(prefs.getString("goal", "Maintenir le poids") ?: "Maintenir le poids") }
var isDiabetic by remember { mutableStateOf(prefs.getBoolean("is_diabetic", false)) } 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 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") val goals = listOf("Maintenir le poids", "Perdre du poids")
@@ -139,6 +139,12 @@ fun SetupScreen(prefs: SharedPreferences, onComplete: () -> Unit) {
Button( Button(
onClick = { 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 ageInt = age.toIntOrNull() ?: 0
val height = heightCm.toDoubleOrNull() ?: 0.0 val height = heightCm.toDoubleOrNull() ?: 0.0
var weightKg = weight.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 targetProtein = (targetCals * 0.2 / 4).toInt()
val targetFat = (targetCals * 0.3 / 9).toInt() val targetFat = (targetCals * 0.3 / 9).toInt()
prefs.edit { val userProfile = hashMapOf(
putString("target_calories", targetCals.toString()) "age" to ageInt,
putString("target_carbs", targetCarbs.toString()) "height_cm" to height,
putString("target_protein", targetProtein.toString()) "weight_kg" to weightKg,
putString("target_fat", targetFat.toString()) "is_lbs" to isLbs,
putString("weight_kg", weightKg.toString()) "gender" to gender,
putString("weight_display", weightDisplay) "activity_level" to activityLevel,
putBoolean("is_lbs", isLbs) "goal" to goal,
putString("height_cm", heightCm) "is_diabetic" to isDiabetic,
putBoolean("is_diabetic", isDiabetic) "target_calories" to targetCals,
putInt("age", ageInt) "target_carbs" to targetCarbs,
putString("gender", gender) "target_protein" to targetProtein,
putString("activity_level", activityLevel) "target_fat" to targetFat
putString("goal", goal) )
}
onComplete() // 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 modifier = Modifier
.fillMaxWidth() .fillMaxWidth()

View File

@@ -1,40 +1,46 @@
📝 Notes de version - Scan-Wich 📝 Notes de version - Scan-Wich
Dernières mises à jour : **Changements majeurs de la version actuelle :**
🛠️ Correctifs et Améliorations Strava :
- Résolution d'un problème de compilation bloquant sur l'écran des sports. 🛡️ **Refonte de l'Architecture de Sécurité et de Données :**
- 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. - **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.
- Amélioration de la fiabilité du parsing des dates d'activités Strava. - **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. - 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. - 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. - 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. - 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. - 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. - 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. - 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. - 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. - 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. - Optimisation pour Android 36.
- Correction de bugs majeurs sur le stockage sécurisé des préférences (MasterKey) et la synchronisation Firebase (collectAsState). - Correction de bugs majeurs sur le stockage sécurisé des préférences (MasterKey) et la synchronisation Firebase (collectAsState).