changes #3

Merged
macharest merged 3 commits from changes into master 2026-02-22 21:24:26 -05:00
15 changed files with 84 additions and 34 deletions
Showing only changes of commit 670880d197 - Show all commits

2
.idea/.name generated
View File

@@ -1 +1 @@
coloricam scan-wich

View File

@@ -1,9 +1,12 @@
import java.util.Properties
plugins { plugins {
alias(libs.plugins.android.application) alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.compose) alias(libs.plugins.kotlin.compose)
alias(libs.plugins.ksp) alias(libs.plugins.ksp)
alias(libs.plugins.secrets) alias(libs.plugins.secrets)
alias(libs.plugins.google.services) alias(libs.plugins.google.services)
alias(libs.plugins.firebase.appdistribution)
} }
android { android {
@@ -21,23 +24,45 @@ android {
} }
signingConfigs { signingConfigs {
// On configure la release pour utiliser la même clé que le debug pour l'instant val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("local.properties")
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(keystorePropertiesFile.inputStream())
}
getByName("debug") { getByName("debug") {
storeFile = file(System.getProperty("user.home") + "/.android/debug.keystore") storeFile = file(System.getProperty("user.home") + "/.android/debug.keystore")
storePassword = "android" storePassword = "android"
keyAlias = "androiddebugkey" keyAlias = "androiddebugkey"
keyPassword = "android" keyPassword = "android"
} }
create("release") {
storeFile = file("C:\\Users\\mac\\keys\\keys")
storePassword = keystoreProperties.getProperty("RELEASE_STORE_PASSWORD")
keyAlias = keystoreProperties.getProperty("RELEASE_KEY_ALIAS") ?: "key0"
keyPassword = keystoreProperties.getProperty("RELEASE_KEY_PASSWORD")
}
} }
buildTypes { buildTypes {
release { release {
isMinifyEnabled = false isMinifyEnabled = false
signingConfig = signingConfigs.getByName("debug") signingConfig = signingConfigs.getByName("release")
proguardFiles( proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"), getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro" "proguard-rules.pro"
) )
configure<com.google.firebase.appdistribution.gradle.AppDistributionExtension> {
artifactType = "APK"
}
}
debug {
configure<com.google.firebase.appdistribution.gradle.AppDistributionExtension> {
artifactType = "APK"
}
} }
} }
compileOptions { compileOptions {
@@ -51,7 +76,6 @@ android {
} }
secrets { secrets {
// A list of keys that should be ignored by the plugin by default.
ignoreList.add("properties") ignoreList.add("properties")
} }
@@ -68,24 +92,19 @@ dependencies {
implementation(libs.coil.compose) implementation(libs.coil.compose)
implementation(libs.androidx.exifinterface) implementation(libs.androidx.exifinterface)
// Navigation
implementation(libs.androidx.navigation.compose) implementation(libs.androidx.navigation.compose)
// Room
implementation(libs.androidx.room.runtime) implementation(libs.androidx.room.runtime)
implementation(libs.androidx.room.ktx) implementation(libs.androidx.room.ktx)
ksp(libs.androidx.room.compiler) ksp(libs.androidx.room.compiler)
// Network & Strava Auth
implementation(libs.retrofit.core) implementation(libs.retrofit.core)
implementation(libs.retrofit.gson) implementation(libs.retrofit.gson)
implementation(libs.okhttp.logging) implementation(libs.okhttp.logging)
implementation(libs.androidx.browser) implementation(libs.androidx.browser)
// Google Sign-In
implementation(libs.play.services.auth) implementation(libs.play.services.auth)
// Firebase
implementation(platform(libs.firebase.bom)) implementation(platform(libs.firebase.bom))
implementation(libs.firebase.analytics) implementation(libs.firebase.analytics)

View File

@@ -21,6 +21,14 @@
"certificate_hash": "2c39e4131dcac8a1d4257b804718ac113f855b04" "certificate_hash": "2c39e4131dcac8a1d4257b804718ac113f855b04"
} }
}, },
{
"client_id": "652626507041-i6ne7rt1b711gfpbc5f5hd1q60kd0ntv.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.example.scanwich",
"certificate_hash": "ebcc060f9a1fdeb1186536d3828574b42cefa03c"
}
},
{ {
"client_id": "652626507041-5n42q37adh1guuv9gibfcf5uvekgunbe.apps.googleusercontent.com", "client_id": "652626507041-5n42q37adh1guuv9gibfcf5uvekgunbe.apps.googleusercontent.com",
"client_type": 3 "client_type": 3

Binary file not shown.

View File

@@ -1 +0,0 @@
// Ce fichier est obsolète. Utilisez celui dans le package com.example.scanwich.

View File

@@ -1 +0,0 @@
// Ce fichier est obsolète. Utilisez celui dans le package com.example.scanwich.

View File

@@ -76,7 +76,7 @@ data class N8nMealRequest(
) )
interface N8nApi { interface N8nApi {
@POST("webhook-test/v1/gemini-proxy") @POST("webhook/v1/gemini-proxy")
suspend fun analyzeMeal( suspend fun analyzeMeal(
@Header("X-API-KEY") apiKey: String, @Header("X-API-KEY") apiKey: String,
@Body request: N8nMealRequest @Body request: N8nMealRequest
@@ -278,7 +278,7 @@ 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 dans Firebase. Assurez-vous d\u0027avoir ajouté le SHA-1 de TOUTES vos clés de signature."
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." 12500 -> "Erreur 12500 : Problème de configuration Google Play Services."
else -> "Erreur Google (Code ${e.statusCode})." else -> "Erreur Google (Code ${e.statusCode})."
@@ -333,7 +333,7 @@ fun AccessDeniedScreen(onLogout: () -> Unit) {
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))
Text("Accès Refusé", style = MaterialTheme.typography.headlineMedium, color = MaterialTheme.colorScheme.error) Text("Accès Refusé", style = MaterialTheme.typography.headlineMedium, color = MaterialTheme.colorScheme.error)
Spacer(Modifier.height(8.dp)) Spacer(Modifier.height(8.dp))
Text("Votre compte n'est pas autorisé à utiliser cette application.", style = MaterialTheme.typography.bodyLarge) Text("Votre compte n\u0027est pas autorisé à utiliser cette application.", style = MaterialTheme.typography.bodyLarge)
Spacer(Modifier.height(32.dp)) Spacer(Modifier.height(32.dp))
Button(onClick = onLogout) { Text("Changer de compte") } Button(onClick = onLogout) { Text("Changer de compte") }
} }
@@ -471,7 +471,7 @@ fun SetupScreen(prefs: SharedPreferences, onComplete: () -> Unit) {
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))
Text("Niveau d'activité :", style = MaterialTheme.typography.titleMedium, modifier = Modifier.align(Alignment.Start)) Text("Niveau d\u0027activité :", style = MaterialTheme.typography.titleMedium, modifier = Modifier.align(Alignment.Start))
activityLevels.forEach { level -> activityLevels.forEach { level ->
Row( Row(
Modifier Modifier
@@ -607,7 +607,7 @@ fun CaptureScreen(dao: AppDao, prefs: SharedPreferences, isDiabetic: Boolean) {
currentMealData = data currentMealData = data
showBottomSheet = true showBottomSheet = true
} else { } else {
Toast.makeText(context, "L'IA n'a pas pu identifier le repas.", Toast.LENGTH_LONG).show() Toast.makeText(context, "L\u0027IA n\u0027a pas pu identifier le repas.", Toast.LENGTH_LONG).show()
} }
}, coroutineScope) }, coroutineScope)
} catch (e: Exception) { } catch (e: Exception) {
@@ -665,7 +665,7 @@ fun CaptureScreen(dao: AppDao, prefs: SharedPreferences, isDiabetic: Boolean) {
OutlinedTextField( OutlinedTextField(
value = editableDesc, value = editableDesc,
onValueChange = { editableDesc = it }, onValueChange = { editableDesc = it },
label = { Text("Description / Précisions pour l'IA") }, label = { Text("Description / Précisions pour l\u0027IA") },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
minLines = 3 minLines = 3
) )
@@ -686,7 +686,7 @@ fun CaptureScreen(dao: AppDao, prefs: SharedPreferences, isDiabetic: Boolean) {
) { ) {
Icon(Icons.Default.Refresh, null) Icon(Icons.Default.Refresh, null)
Spacer(Modifier.width(8.dp)) Spacer(Modifier.width(8.dp))
Text("Ressoumettre à l'IA") Text("Ressoumettre à l\u0027IA")
} }
Spacer(Modifier.height(16.dp)) Spacer(Modifier.height(16.dp))
@@ -771,7 +771,7 @@ fun CaptureScreen(dao: AppDao, prefs: SharedPreferences, isDiabetic: Boolean) {
Spacer(Modifier.height(32.dp)) Spacer(Modifier.height(32.dp))
Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxWidth()) { Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxWidth()) {
CircularProgressIndicator() CircularProgressIndicator()
Text("Analyse par l'IA en cours...", modifier = Modifier.padding(top = 8.dp)) Text("Analyse par l\u0027IA en cours...", modifier = Modifier.padding(top = 8.dp))
} }
} }
@@ -783,7 +783,7 @@ fun CaptureScreen(dao: AppDao, prefs: SharedPreferences, isDiabetic: Boolean) {
OutlinedTextField( OutlinedTextField(
value = manualMealName, value = manualMealName,
onValueChange = { manualMealName = it }, onValueChange = { manualMealName = it },
label = { Text("Qu'avez-vous mangé ?") }, label = { Text("Qu\u0027avez-vous mangé ?") },
placeholder = { Text("ex: Un sandwich au poulet et une pomme") }, placeholder = { Text("ex: Un sandwich au poulet et une pomme") },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
@@ -869,6 +869,7 @@ fun HistoryScreen(dao: AppDao, prefs: SharedPreferences) {
val isDiabetic = prefs.getBoolean("is_diabetic", false) val isDiabetic = prefs.getBoolean("is_diabetic", false)
var selectedMealForDetail by remember { mutableStateOf<Meal?>(null) } var selectedMealForDetail by remember { mutableStateOf<Meal?>(null) }
val context = LocalContext.current val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val tCal = prefs.getString("target_calories", "2000")?.toIntOrNull() ?: 2000 val tCal = prefs.getString("target_calories", "2000")?.toIntOrNull() ?: 2000
val tCarb = prefs.getString("target_carbs", "250")?.toIntOrNull() ?: 250 val tCarb = prefs.getString("target_carbs", "250")?.toIntOrNull() ?: 250
@@ -972,7 +973,15 @@ fun HistoryScreen(dao: AppDao, prefs: SharedPreferences) {
if (isDiabetic && beforeGly != null) { if (isDiabetic && beforeGly != null) {
item { item {
Surface(color = Color.Blue.copy(alpha = 0.1f), modifier = Modifier.fillMaxWidth().clip(MaterialTheme.shapes.small)) { Surface(color = Color.Blue.copy(alpha = 0.1f), modifier = Modifier.fillMaxWidth().clip(MaterialTheme.shapes.small)) {
Text("🩸 Glycémie Avant: ${beforeGly.value} mmol/L", modifier = Modifier.padding(8.dp), style = MaterialTheme.typography.bodyMedium) Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(8.dp)) {
Text("🩸 Glycémie Avant: ${beforeGly.value} mmol/L", modifier = Modifier.weight(1f), style = MaterialTheme.typography.bodyMedium)
IconButton(onClick = {
coroutineScope.launch {
dao.deleteGlycemia(beforeGly)
Toast.makeText(context, "Glycémie supprimée", Toast.LENGTH_SHORT).show()
}
}) { Icon(Icons.Default.Delete, null, modifier = Modifier.size(20.dp)) }
}
} }
} }
} }
@@ -982,7 +991,12 @@ fun HistoryScreen(dao: AppDao, prefs: SharedPreferences) {
headlineContent = { Text(meal.name) }, headlineContent = { Text(meal.name) },
supportingContent = { Text("${meal.totalCalories} kcal - G:${meal.carbs}g P:${meal.protein}g L:${meal.fat}g") }, supportingContent = { Text("${meal.totalCalories} kcal - G:${meal.carbs}g P:${meal.protein}g L:${meal.fat}g") },
trailingContent = { trailingContent = {
IconButton(onClick = { /* TODO */ }) { Icon(Icons.Default.Delete, null) } IconButton(onClick = {
coroutineScope.launch {
dao.deleteMeal(meal)
Toast.makeText(context, "Repas supprimé", Toast.LENGTH_SHORT).show()
}
}) { Icon(Icons.Default.Delete, null) }
}, },
modifier = Modifier.clickable { selectedMealForDetail = meal } modifier = Modifier.clickable { selectedMealForDetail = meal }
) )
@@ -991,7 +1005,15 @@ fun HistoryScreen(dao: AppDao, prefs: SharedPreferences) {
if (isDiabetic && afterGly != null) { if (isDiabetic && afterGly != null) {
item { item {
Surface(color = Color.Green.copy(alpha = 0.1f), modifier = Modifier.fillMaxWidth().clip(MaterialTheme.shapes.small)) { Surface(color = Color.Green.copy(alpha = 0.1f), modifier = Modifier.fillMaxWidth().clip(MaterialTheme.shapes.small)) {
Text("🩸 Glycémie Après: ${afterGly.value} mmol/L", modifier = Modifier.padding(8.dp), style = MaterialTheme.typography.bodyMedium) Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(8.dp)) {
Text("🩸 Glycémie Après: ${afterGly.value} mmol/L", modifier = Modifier.weight(1f), style = MaterialTheme.typography.bodyMedium)
IconButton(onClick = {
coroutineScope.launch {
dao.deleteGlycemia(afterGly)
Toast.makeText(context, "Glycémie supprimée", Toast.LENGTH_SHORT).show()
}
}) { Icon(Icons.Default.Delete, null, modifier = Modifier.size(20.dp)) }
}
} }
} }
} }
@@ -1033,9 +1055,9 @@ private fun analyzeImage(
val prompt = if (bitmap != null && textDescription == null) { val prompt = if (bitmap != null && textDescription == null) {
"Analyze this food image in FRENCH. Provide ONLY: 1. Name, 2. Summary description in FRENCH, 3. Macros. Format EXACTLY as: {\"name\": \"...\", \"description\": \"...\", \"calories\": int, \"carbs\": int, \"protein\": int, \"fat\": int}" "Analyze this food image in FRENCH. Provide ONLY: 1. Name, 2. Summary description in FRENCH, 3. Macros. Format EXACTLY as: {\"name\": \"...\", \"description\": \"...\", \"calories\": int, \"carbs\": int, \"protein\": int, \"fat\": int}"
} else if (bitmap != null && textDescription != null) { } else if (bitmap != null && textDescription != null) {
"Analyze this food image in FRENCH, taking into account these corrections or details: '$textDescription'. Provide ONLY: 1. Name, 2. Summary description in FRENCH, 3. Macros. Format EXACTLY as: {\"name\": \"...\", \"description\": \"...\", \"calories\": int, \"carbs\": int, \"protein\": int, \"fat\": int}" "Analyze this food image in FRENCH, taking into account these corrections or details: \u0027$textDescription\u0027. Provide ONLY: 1. Name, 2. Summary description in FRENCH, 3. Macros. Format EXACTLY as: {\"name\": \"...\", \"description\": \"...\", \"calories\": int, \"carbs\": int, \"protein\": int, \"fat\": int}"
} else { } else {
"Analyze this meal description in FRENCH: '$textDescription'. Estimate the macros. Provide ONLY a JSON object. Format EXACTLY as: {\"name\": \"...\", \"description\": \"...\", \"calories\": int, \"carbs\": int, \"protein\": int, \"fat\": int}" "Analyze this meal description in FRENCH: \u0027$textDescription\u0027. Estimate the macros. Provide ONLY a JSON object. Format EXACTLY as: {\"name\": \"...\", \"description\": \"...\", \"calories\": int, \"carbs\": int, \"protein\": int, \"fat\": int}"
} }
scope.launch { scope.launch {

View File

@@ -5,4 +5,5 @@ plugins {
alias(libs.plugins.ksp) apply false alias(libs.plugins.ksp) apply false
alias(libs.plugins.secrets) apply false alias(libs.plugins.secrets) apply false
alias(libs.plugins.google.services) apply false alias(libs.plugins.google.services) apply false
alias(libs.plugins.firebase.appdistribution) apply false
} }

View File

@@ -1,26 +1,27 @@
[versions] [versions]
agp = "9.0.1" agp = "9.0.1"
coreKtx = "1.10.1" coreKtx = "1.12.0"
junit = "4.13.2" junit = "4.13.2"
junitVersion = "1.1.5" junitVersion = "1.2.1"
espressoCore = "3.5.1" espressoCore = "3.6.1"
lifecycleRuntimeKtx = "2.6.1" lifecycleRuntimeKtx = "2.8.7"
activityCompose = "1.8.0" activityCompose = "1.10.0"
kotlin = "2.0.21" kotlin = "2.0.21"
composeBom = "2024.09.00" composeBom = "2025.02.00"
generativeai = "0.9.0" generativeai = "0.9.0"
coil = "2.7.0" coil = "2.7.0"
room = "2.8.4" room = "2.8.4"
navigation = "2.7.7" navigation = "2.8.7"
ksp = "2.0.21-1.0.27" ksp = "2.0.21-1.0.27"
retrofit = "2.9.0" retrofit = "2.9.0"
okhttp = "4.12.0" okhttp = "4.12.0"
browser = "1.8.0" browser = "1.8.0"
exifinterface = "1.3.7" exifinterface = "1.3.7"
secretsPlugin = "2.0.1" secretsPlugin = "2.0.1"
playServicesAuth = "21.2.0" playServicesAuth = "21.3.0"
googleServices = "4.4.2" googleServices = "4.4.2"
firebaseBom = "34.9.0" firebaseBom = "34.9.0"
firebaseAppDistribution = "5.2.1"
[libraries] [libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -58,3 +59,4 @@ kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "ko
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
secrets = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin", version.ref = "secretsPlugin" } secrets = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin", version.ref = "secretsPlugin" }
google-services = { id = "com.google.gms.google-services", version.ref = "googleServices" } google-services = { id = "com.google.gms.google-services", version.ref = "googleServices" }
firebase-appdistribution = { id = "com.google.firebase.appdistribution", version.ref = "firebaseAppDistribution" }

View File

@@ -22,5 +22,5 @@ dependencyResolutionManagement {
} }
} }
rootProject.name = "coloricam" rootProject.name = "scan-wich"
include(":app") include(":app")