diff --git a/.gitignore b/.gitignore index aa724b7..0588d3a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ .externalNativeBuild .cxx local.properties +app/firebase-key.json +firebase-key.json \ No newline at end of file diff --git a/.idea/.name b/.idea/.name index ceab9ac..9040ef8 100644 --- a/.idea/.name +++ b/.idea/.name @@ -1 +1 @@ -coloricam \ No newline at end of file +scan-wich \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index b2c751a..991a888 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,7 @@ + - + diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 39fb6ba..6369fc3 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,14 +1,17 @@ +import java.util.Properties + plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.compose) alias(libs.plugins.ksp) alias(libs.plugins.secrets) alias(libs.plugins.google.services) + alias(libs.plugins.firebase.appdistribution) } android { namespace = "com.example.scanwich" - compileSdk = 35 + compileSdk = 36 defaultConfig { applicationId = "com.example.scanwich" @@ -21,23 +24,56 @@ android { } 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") { storeFile = file(System.getProperty("user.home") + "/.android/debug.keystore") - storePassword = "android" - keyAlias = "androiddebugkey" - keyPassword = "android" + getByName("debug").storePassword = "android" + getByName("debug").keyAlias = "androiddebugkey" + getByName("debug").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 { + val keyFile = project.file("firebase-key.json") + release { isMinifyEnabled = false - signingConfig = signingConfigs.getByName("debug") + signingConfig = signingConfigs.getByName("release") proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) + + configure { + artifactType = "APK" + if (keyFile.exists()) { + serviceCredentialsFile = keyFile.absolutePath + } + groups = "internal-user" + } + } + + debug { + configure { + artifactType = "APK" + if (keyFile.exists()) { + serviceCredentialsFile = keyFile.absolutePath + } + groups = "internal-user" + releaseNotes = "Version de développement" + } } } compileOptions { @@ -51,7 +87,6 @@ android { } secrets { - // A list of keys that should be ignored by the plugin by default. ignoreList.add("properties") } @@ -64,28 +99,23 @@ dependencies { implementation(libs.androidx.compose.ui.graphics) implementation(libs.androidx.compose.ui.tooling.preview) implementation(libs.androidx.compose.material3) + implementation(libs.androidx.compose.material.icons.extended) implementation(libs.google.generativeai) implementation(libs.coil.compose) implementation(libs.androidx.exifinterface) - - // Navigation implementation(libs.androidx.navigation.compose) - // Room implementation(libs.androidx.room.runtime) implementation(libs.androidx.room.ktx) ksp(libs.androidx.room.compiler) - // Network & Strava Auth implementation(libs.retrofit.core) implementation(libs.retrofit.gson) implementation(libs.okhttp.logging) implementation(libs.androidx.browser) - // Google Sign-In implementation(libs.play.services.auth) - // Firebase implementation(platform(libs.firebase.bom)) implementation(libs.firebase.analytics) diff --git a/app/google-services.json b/app/google-services.json index e9b8783..1098f98 100644 --- a/app/google-services.json +++ b/app/google-services.json @@ -21,6 +21,14 @@ "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_type": 3 diff --git a/app/release/app-release.apk b/app/release/app-release.apk index aa61d63..bbfc722 100644 Binary files a/app/release/app-release.apk and b/app/release/app-release.apk differ diff --git a/app/release/baselineProfiles/0/app-release.dm b/app/release/baselineProfiles/0/app-release.dm index 8867e9e..5e8f2a8 100644 Binary files a/app/release/baselineProfiles/0/app-release.dm and b/app/release/baselineProfiles/0/app-release.dm differ diff --git a/app/release/baselineProfiles/1/app-release.dm b/app/release/baselineProfiles/1/app-release.dm index 8af9ce4..dc729d0 100644 Binary files a/app/release/baselineProfiles/1/app-release.dm and b/app/release/baselineProfiles/1/app-release.dm differ diff --git a/app/src/main/java/com/example/coloricam/Database.kt b/app/src/main/java/com/example/coloricam/Database.kt deleted file mode 100644 index 8762c2f..0000000 --- a/app/src/main/java/com/example/coloricam/Database.kt +++ /dev/null @@ -1 +0,0 @@ -// Ce fichier est obsolète. Utilisez celui dans le package com.example.scanwich. diff --git a/app/src/main/java/com/example/coloricam/MainActivity.kt b/app/src/main/java/com/example/coloricam/MainActivity.kt deleted file mode 100644 index 8762c2f..0000000 --- a/app/src/main/java/com/example/coloricam/MainActivity.kt +++ /dev/null @@ -1 +0,0 @@ -// Ce fichier est obsolète. Utilisez celui dans le package com.example.scanwich. diff --git a/app/src/main/java/com/example/scanwich/MainActivity.kt b/app/src/main/java/com/example/scanwich/MainActivity.kt index bd752fe..37b6992 100644 --- a/app/src/main/java/com/example/scanwich/MainActivity.kt +++ b/app/src/main/java/com/example/scanwich/MainActivity.kt @@ -76,7 +76,7 @@ data class N8nMealRequest( ) interface N8nApi { - @POST("webhook-test/v1/gemini-proxy") + @POST("webhook/v1/gemini-proxy") suspend fun analyzeMeal( @Header("X-API-KEY") apiKey: String, @Body request: N8nMealRequest @@ -278,7 +278,7 @@ 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 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." 12500 -> "Erreur 12500 : Problème de configuration Google Play Services." else -> "Erreur Google (Code ${e.statusCode})." @@ -333,7 +333,7 @@ fun AccessDeniedScreen(onLogout: () -> Unit) { Spacer(Modifier.height(16.dp)) Text("Accès Refusé", style = MaterialTheme.typography.headlineMedium, color = MaterialTheme.colorScheme.error) 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)) Button(onClick = onLogout) { Text("Changer de compte") } } @@ -471,7 +471,7 @@ fun SetupScreen(prefs: SharedPreferences, onComplete: () -> Unit) { 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 -> Row( Modifier @@ -607,7 +607,7 @@ fun CaptureScreen(dao: AppDao, prefs: SharedPreferences, isDiabetic: Boolean) { currentMealData = data showBottomSheet = true } 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) } catch (e: Exception) { @@ -665,7 +665,7 @@ fun CaptureScreen(dao: AppDao, prefs: SharedPreferences, isDiabetic: Boolean) { OutlinedTextField( value = editableDesc, onValueChange = { editableDesc = it }, - label = { Text("Description / Précisions pour l'IA") }, + label = { Text("Description / Précisions pour l\u0027IA") }, modifier = Modifier.fillMaxWidth(), minLines = 3 ) @@ -686,7 +686,7 @@ fun CaptureScreen(dao: AppDao, prefs: SharedPreferences, isDiabetic: Boolean) { ) { Icon(Icons.Default.Refresh, null) Spacer(Modifier.width(8.dp)) - Text("Ressoumettre à l'IA") + Text("Ressoumettre à l\u0027IA") } Spacer(Modifier.height(16.dp)) @@ -771,7 +771,7 @@ fun CaptureScreen(dao: AppDao, prefs: SharedPreferences, isDiabetic: Boolean) { Spacer(Modifier.height(32.dp)) Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxWidth()) { 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( value = manualMealName, 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") }, modifier = Modifier.fillMaxWidth() ) @@ -869,6 +869,7 @@ fun HistoryScreen(dao: AppDao, prefs: SharedPreferences) { val isDiabetic = prefs.getBoolean("is_diabetic", false) var selectedMealForDetail by remember { mutableStateOf(null) } val context = LocalContext.current + val coroutineScope = rememberCoroutineScope() val tCal = prefs.getString("target_calories", "2000")?.toIntOrNull() ?: 2000 val tCarb = prefs.getString("target_carbs", "250")?.toIntOrNull() ?: 250 @@ -972,7 +973,15 @@ fun HistoryScreen(dao: AppDao, prefs: SharedPreferences) { if (isDiabetic && beforeGly != null) { item { 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) }, supportingContent = { Text("${meal.totalCalories} kcal - G:${meal.carbs}g P:${meal.protein}g L:${meal.fat}g") }, 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 } ) @@ -991,7 +1005,15 @@ fun HistoryScreen(dao: AppDao, prefs: SharedPreferences) { if (isDiabetic && afterGly != null) { item { 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) { "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) { - "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 { - "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 { diff --git a/app/src/main/java/com/example/coloricam/ui/theme/Color.kt b/app/src/main/java/com/example/scanwich/ui/theme/Color.kt similarity index 100% rename from app/src/main/java/com/example/coloricam/ui/theme/Color.kt rename to app/src/main/java/com/example/scanwich/ui/theme/Color.kt diff --git a/app/src/main/java/com/example/coloricam/ui/theme/Theme.kt b/app/src/main/java/com/example/scanwich/ui/theme/Theme.kt similarity index 98% rename from app/src/main/java/com/example/coloricam/ui/theme/Theme.kt rename to app/src/main/java/com/example/scanwich/ui/theme/Theme.kt index c598e02..a99e0df 100644 --- a/app/src/main/java/com/example/coloricam/ui/theme/Theme.kt +++ b/app/src/main/java/com/example/scanwich/ui/theme/Theme.kt @@ -1,6 +1,5 @@ package com.example.scanwich.ui.theme -import android.app.Activity import android.os.Build import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.MaterialTheme diff --git a/app/src/main/java/com/example/coloricam/ui/theme/Type.kt b/app/src/main/java/com/example/scanwich/ui/theme/Type.kt similarity index 100% rename from app/src/main/java/com/example/coloricam/ui/theme/Type.kt rename to app/src/main/java/com/example/scanwich/ui/theme/Type.kt diff --git a/build.gradle.kts b/build.gradle.kts index 0330749..631610b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,4 +5,5 @@ plugins { alias(libs.plugins.ksp) apply false alias(libs.plugins.secrets) apply false alias(libs.plugins.google.services) apply false + alias(libs.plugins.firebase.appdistribution) apply false } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9ac1ab9..6e24664 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,26 +1,27 @@ [versions] agp = "9.0.1" -coreKtx = "1.10.1" +coreKtx = "1.17.0" junit = "4.13.2" -junitVersion = "1.1.5" -espressoCore = "3.5.1" -lifecycleRuntimeKtx = "2.6.1" -activityCompose = "1.8.0" +junitVersion = "1.3.0" +espressoCore = "3.7.0" +lifecycleRuntimeKtx = "2.10.0" +activityCompose = "1.12.4" kotlin = "2.0.21" -composeBom = "2024.09.00" +composeBom = "2026.02.00" generativeai = "0.9.0" coil = "2.7.0" room = "2.8.4" -navigation = "2.7.7" +navigation = "2.9.7" ksp = "2.0.21-1.0.27" -retrofit = "2.9.0" -okhttp = "4.12.0" -browser = "1.8.0" -exifinterface = "1.3.7" +retrofit = "3.0.0" +okhttp = "5.3.2" +browser = "1.9.0" +exifinterface = "1.4.2" secretsPlugin = "2.0.1" -playServicesAuth = "21.2.0" -googleServices = "4.4.2" +playServicesAuth = "21.5.1" +googleServices = "4.4.4" firebaseBom = "34.9.0" +firebaseAppDistribution = "5.2.1" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -37,6 +38,7 @@ androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "u androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" } +androidx-compose-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" } google-generativeai = { group = "com.google.ai.client.generativeai", name = "generativeai", version.ref = "generativeai" } coil-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coil" } androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" } @@ -58,3 +60,4 @@ kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "ko ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } secrets = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin", version.ref = "secretsPlugin" } google-services = { id = "com.google.gms.google-services", version.ref = "googleServices" } +firebase-appdistribution = { id = "com.google.firebase.appdistribution", version.ref = "firebaseAppDistribution" } diff --git a/settings.gradle.kts b/settings.gradle.kts index c6e9c2a..8c781e8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -22,5 +22,5 @@ dependencyResolutionManagement { } } -rootProject.name = "coloricam" +rootProject.name = "scan-wich" include(":app")