test
This commit is contained in:
@@ -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...")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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).
|
||||||
|
|||||||
Reference in New Issue
Block a user