Compare commits
2 Commits
c9d05be4b1
...
2bec3bc681
| Author | SHA1 | Date | |
|---|---|---|---|
| 2bec3bc681 | |||
| 9b87930e9a |
4
.idea/deploymentTargetSelector.xml
generated
4
.idea/deploymentTargetSelector.xml
generated
@@ -4,10 +4,10 @@
|
|||||||
<selectionStates>
|
<selectionStates>
|
||||||
<SelectionState runConfigName="app">
|
<SelectionState runConfigName="app">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
<DropdownSelection timestamp="2026-02-20T21:41:35.128842100Z">
|
<DropdownSelection timestamp="2026-03-10T00:31:28.813056900Z">
|
||||||
<Target type="DEFAULT_BOOT">
|
<Target type="DEFAULT_BOOT">
|
||||||
<handle>
|
<handle>
|
||||||
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\marca\.android\avd\Medium_Phone.avd" />
|
<DeviceId pluginId="PhysicalDevice" identifier="serial=58051FDCG0038S" />
|
||||||
</handle>
|
</handle>
|
||||||
</Target>
|
</Target>
|
||||||
</DropdownSelection>
|
</DropdownSelection>
|
||||||
|
|||||||
2
.idea/vcs.xml
generated
2
.idea/vcs.xml
generated
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
<mapping directory="" vcs="Git" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@@ -18,6 +18,7 @@ android {
|
|||||||
minSdk = 24
|
minSdk = 24
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
|
|
||||||
|
// Incrémentation automatique du versionCode basé sur le temps
|
||||||
versionCode = (System.currentTimeMillis() / 60000).toInt()
|
versionCode = (System.currentTimeMillis() / 60000).toInt()
|
||||||
versionName = "1.0." + (System.currentTimeMillis() / 3600000).toString().takeLast(3)
|
versionName = "1.0." + (System.currentTimeMillis() / 3600000).toString().takeLast(3)
|
||||||
|
|
||||||
@@ -25,6 +26,7 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
|
// Chargement des propriétés depuis local.properties
|
||||||
val keystoreProperties = Properties()
|
val keystoreProperties = Properties()
|
||||||
val keystorePropertiesFile = rootProject.file("local.properties")
|
val keystorePropertiesFile = rootProject.file("local.properties")
|
||||||
if (keystorePropertiesFile.exists()) {
|
if (keystorePropertiesFile.exists()) {
|
||||||
@@ -33,13 +35,14 @@ android {
|
|||||||
|
|
||||||
getByName("debug") {
|
getByName("debug") {
|
||||||
storeFile = file(System.getProperty("user.home") + "/.android/debug.keystore")
|
storeFile = file(System.getProperty("user.home") + "/.android/debug.keystore")
|
||||||
storePassword = "android"
|
getByName("debug").storePassword = "android"
|
||||||
keyAlias = "androiddebugkey"
|
getByName("debug").keyAlias = "androiddebugkey"
|
||||||
keyPassword = "android"
|
getByName("debug").keyPassword = "android"
|
||||||
}
|
}
|
||||||
|
|
||||||
create("release") {
|
create("release") {
|
||||||
storeFile = file("C:\\Users\\mac\\keys\\keys")
|
// Utilisation d'un chemin relatif au dossier Utilisateur pour fonctionner sur tous les PC
|
||||||
|
storeFile = file("${System.getProperty("user.home")}/keys/keys")
|
||||||
storePassword = keystoreProperties.getProperty("RELEASE_STORE_PASSWORD")
|
storePassword = keystoreProperties.getProperty("RELEASE_STORE_PASSWORD")
|
||||||
keyAlias = keystoreProperties.getProperty("RELEASE_KEY_ALIAS") ?: "key0"
|
keyAlias = keystoreProperties.getProperty("RELEASE_KEY_ALIAS") ?: "key0"
|
||||||
keyPassword = keystoreProperties.getProperty("RELEASE_KEY_PASSWORD")
|
keyPassword = keystoreProperties.getProperty("RELEASE_KEY_PASSWORD")
|
||||||
@@ -51,7 +54,7 @@ android {
|
|||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
isMinifyEnabled = true
|
isMinifyEnabled = true // Activer l'offuscation
|
||||||
signingConfig = signingConfigs.getByName("release")
|
signingConfig = signingConfigs.getByName("release")
|
||||||
proguardFiles(
|
proguardFiles(
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
@@ -109,7 +112,9 @@ dependencies {
|
|||||||
implementation(libs.androidx.compose.material3)
|
implementation(libs.androidx.compose.material3)
|
||||||
implementation(libs.androidx.compose.material.icons.extended)
|
implementation(libs.androidx.compose.material.icons.extended)
|
||||||
|
|
||||||
|
// SDK Firebase App Distribution COMPLET (API + Implémentation)
|
||||||
implementation(libs.firebase.appdistribution)
|
implementation(libs.firebase.appdistribution)
|
||||||
|
|
||||||
implementation(libs.google.generativeai)
|
implementation(libs.google.generativeai)
|
||||||
implementation(libs.coil.compose)
|
implementation(libs.coil.compose)
|
||||||
implementation(libs.androidx.exifinterface)
|
implementation(libs.androidx.exifinterface)
|
||||||
@@ -129,13 +134,21 @@ dependencies {
|
|||||||
implementation(libs.firebase.firestore)
|
implementation(libs.firebase.firestore)
|
||||||
implementation(libs.firebase.appcheck.playintegrity)
|
implementation(libs.firebase.appcheck.playintegrity)
|
||||||
|
|
||||||
|
// On met le debug provider en implementation pour qu'il soit disponible à la compilation en Release
|
||||||
|
// (le code MainActivity utilise un check BuildConfig.DEBUG pour ne pas l'utiliser en prod)
|
||||||
|
implementation(libs.firebase.appcheck.debug)
|
||||||
|
|
||||||
|
// Barcode Scanning & Camera
|
||||||
implementation(libs.mlkit.barcode.scanning)
|
implementation(libs.mlkit.barcode.scanning)
|
||||||
implementation(libs.androidx.camera.core)
|
implementation(libs.androidx.camera.core)
|
||||||
implementation(libs.androidx.camera.camera2)
|
implementation(libs.androidx.camera.camera2)
|
||||||
implementation(libs.androidx.camera.lifecycle)
|
implementation(libs.androidx.camera.lifecycle)
|
||||||
implementation(libs.androidx.camera.view)
|
implementation(libs.androidx.camera.view)
|
||||||
|
|
||||||
|
// PDF generation
|
||||||
implementation(libs.itext7.core)
|
implementation(libs.itext7.core)
|
||||||
|
|
||||||
|
// Security
|
||||||
implementation(libs.androidx.security.crypto)
|
implementation(libs.androidx.security.crypto)
|
||||||
|
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
@@ -145,5 +158,4 @@ dependencies {
|
|||||||
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
|
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
|
||||||
debugImplementation(libs.androidx.compose.ui.tooling)
|
debugImplementation(libs.androidx.compose.ui.tooling)
|
||||||
debugImplementation(libs.androidx.compose.ui.test.manifest)
|
debugImplementation(libs.androidx.compose.ui.test.manifest)
|
||||||
debugImplementation(libs.firebase.appcheck.debug)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,14 @@
|
|||||||
"certificate_hash": "ebcc060f9a1fdeb1186536d3828574b42cefa03c"
|
"certificate_hash": "ebcc060f9a1fdeb1186536d3828574b42cefa03c"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"client_id": "652626507041-i928hstoseh72dta5d0lokm9c55tma2p.apps.googleusercontent.com",
|
||||||
|
"client_type": 1,
|
||||||
|
"android_info": {
|
||||||
|
"package_name": "com.example.scanwich",
|
||||||
|
"certificate_hash": "6f363d957ca44b3ca607c29f58f575d0ae71571d"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"client_id": "652626507041-5n42q37adh1guuv9gibfcf5uvekgunbe.apps.googleusercontent.com",
|
"client_id": "652626507041-5n42q37adh1guuv9gibfcf5uvekgunbe.apps.googleusercontent.com",
|
||||||
"client_type": 3
|
"client_type": 3
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
|
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
@@ -14,6 +17,17 @@
|
|||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.Coloricam">
|
android:theme="@style/Theme.Coloricam">
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name=".MealReminderReceiver"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
|
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
import com.example.scanwich.ui.theme.ReadableAmber
|
import com.example.scanwich.ui.theme.ReadableAmber
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -329,13 +330,38 @@ fun HistoryScreen(dao: AppDao, prefs: android.content.SharedPreferences) {
|
|||||||
|
|
||||||
val totalIn = meals.sumOf { it.totalCalories }
|
val totalIn = meals.sumOf { it.totalCalories }
|
||||||
val totalOut = sports.sumOf { it.calories?.toInt() ?: 0 }
|
val totalOut = sports.sumOf { it.calories?.toInt() ?: 0 }
|
||||||
|
val netTotal = totalIn - totalOut
|
||||||
val totalCarbs = meals.sumOf { it.carbs }
|
val totalCarbs = meals.sumOf { it.carbs }
|
||||||
val totalProt = meals.sumOf { it.protein }
|
val totalProt = meals.sumOf { it.protein }
|
||||||
val totalFat = meals.sumOf { it.fat }
|
val totalFat = meals.sumOf { it.fat }
|
||||||
|
|
||||||
Card(modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant)) {
|
Card(modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant)) {
|
||||||
Column(modifier = Modifier.padding(16.dp)) {
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
Text("Objectifs Quotidiens", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold)
|
Text("Résumé Calorique", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold)
|
||||||
|
Spacer(Modifier.height(8.dp))
|
||||||
|
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
Text("Mangé", style = MaterialTheme.typography.labelMedium, color = Color.Gray)
|
||||||
|
Text("$totalIn", style = MaterialTheme.typography.titleLarge, color = calorieColor, fontWeight = FontWeight.Bold)
|
||||||
|
}
|
||||||
|
Text("-", style = MaterialTheme.typography.headlineMedium, color = Color.Gray)
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
Text("Dépensé", style = MaterialTheme.typography.labelMedium, color = Color.Gray)
|
||||||
|
Text("$totalOut", style = MaterialTheme.typography.titleLarge, color = MaterialTheme.colorScheme.primary, fontWeight = FontWeight.Bold)
|
||||||
|
}
|
||||||
|
Text("=", style = MaterialTheme.typography.headlineMedium, color = Color.Gray)
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
Text("Total Net", style = MaterialTheme.typography.labelMedium, color = Color.Gray)
|
||||||
|
Text("$netTotal", style = MaterialTheme.typography.titleLarge, color = if(netTotal <= tCal) calorieColor else Color.Red, fontWeight = FontWeight.ExtraBold)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Text("kcal", style = MaterialTheme.typography.labelSmall, modifier = Modifier.align(Alignment.End), color = Color.Gray)
|
||||||
|
|
||||||
|
Spacer(Modifier.height(12.dp))
|
||||||
|
HorizontalDivider()
|
||||||
|
Spacer(Modifier.height(12.dp))
|
||||||
|
|
||||||
|
Text("Objectifs Quotidiens", style = MaterialTheme.typography.titleSmall, fontWeight = FontWeight.Bold)
|
||||||
Spacer(Modifier.height(12.dp))
|
Spacer(Modifier.height(12.dp))
|
||||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) {
|
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) {
|
||||||
DailyGoalChart("Calories", totalIn, tCal, calorieColor)
|
DailyGoalChart("Calories", totalIn, tCal, calorieColor)
|
||||||
@@ -343,13 +369,6 @@ fun HistoryScreen(dao: AppDao, prefs: android.content.SharedPreferences) {
|
|||||||
DailyGoalChart("Protéines", totalProt, tProt, proteinColor)
|
DailyGoalChart("Protéines", totalProt, tProt, proteinColor)
|
||||||
DailyGoalChart("Lipides", totalFat, tFat, fatColor)
|
DailyGoalChart("Lipides", totalFat, tFat, fatColor)
|
||||||
}
|
}
|
||||||
Spacer(Modifier.height(12.dp))
|
|
||||||
HorizontalDivider()
|
|
||||||
Spacer(Modifier.height(8.dp))
|
|
||||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
|
||||||
Text("Sport (Brûlées):")
|
|
||||||
Text("$totalOut kcal", fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.primary)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
package com.example.scanwich
|
package com.example.scanwich
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import com.example.scanwich.ui.theme.ScanwichTheme
|
import com.example.scanwich.ui.theme.ScanwichTheme
|
||||||
import com.google.firebase.Firebase
|
import com.google.firebase.Firebase
|
||||||
import com.google.firebase.appcheck.appCheck
|
import com.google.firebase.appcheck.appCheck
|
||||||
@@ -20,11 +25,26 @@ import kotlinx.coroutines.launch
|
|||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
|
private val requestPermissionLauncher = registerForActivityResult(
|
||||||
|
ActivityResultContracts.RequestPermission()
|
||||||
|
) { isGranted: Boolean ->
|
||||||
|
if (isGranted) {
|
||||||
|
NotificationHelper.scheduleReminders(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
checkNotificationPermission()
|
||||||
|
NotificationHelper.scheduleReminders(this)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Firebase.initialize(this)
|
Firebase.initialize(this)
|
||||||
val appCheckFactory = if (BuildConfig.DEBUG) {
|
|
||||||
|
// On utilise explicitement le package name pour BuildConfig car l'import peut échouer
|
||||||
|
val appCheckFactory = if (com.example.scanwich.BuildConfig.DEBUG) {
|
||||||
DebugAppCheckProviderFactory.getInstance()
|
DebugAppCheckProviderFactory.getInstance()
|
||||||
} else {
|
} else {
|
||||||
PlayIntegrityAppCheckProviderFactory.getInstance()
|
PlayIntegrityAppCheckProviderFactory.getInstance()
|
||||||
@@ -48,6 +68,16 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun checkNotificationPermission() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) !=
|
||||||
|
PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onNewIntent(intent: Intent) {
|
override fun onNewIntent(intent: Intent) {
|
||||||
super.onNewIntent(intent)
|
super.onNewIntent(intent)
|
||||||
handleStravaCallback(intent)
|
handleStravaCallback(intent)
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package com.example.scanwich
|
||||||
|
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
|
||||||
|
class MealReminderReceiver : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
|
||||||
|
NotificationHelper.scheduleReminders(context)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val mealType = intent.getStringExtra("meal_type") ?: "repas"
|
||||||
|
showNotification(context, mealType)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showNotification(context: Context, mealType: String) {
|
||||||
|
val channelId = "meal_reminders"
|
||||||
|
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
val channel = NotificationChannel(
|
||||||
|
channelId,
|
||||||
|
"Rappels de repas",
|
||||||
|
NotificationManager.IMPORTANCE_DEFAULT
|
||||||
|
).apply {
|
||||||
|
description = "Notifications pour ne pas oublier d'entrer vos repas"
|
||||||
|
}
|
||||||
|
notificationManager.createNotificationChannel(channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
val activityIntent = Intent(context, MainActivity::class.java).apply {
|
||||||
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||||
|
}
|
||||||
|
val pendingIntent = PendingIntent.getActivity(
|
||||||
|
context, 0, activityIntent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
|
||||||
|
val notification = NotificationCompat.Builder(context, channelId)
|
||||||
|
.setSmallIcon(android.R.drawable.ic_dialog_info) // À remplacer par l'icône de l'app si dispo
|
||||||
|
.setContentTitle("N'oubliez pas votre $mealType !")
|
||||||
|
.setContentText("Prenez un moment pour enregistrer ce que vous avez mangé.")
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||||
|
.setContentIntent(pendingIntent)
|
||||||
|
.setAutoCancel(true)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val notificationId = when(mealType.lowercase()) {
|
||||||
|
"déjeuner" -> 1
|
||||||
|
"dîner" -> 2
|
||||||
|
"souper" -> 3
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
|
notificationManager.notify(notificationId, notification)
|
||||||
|
}
|
||||||
|
}
|
||||||
48
app/src/main/java/com/example/scanwich/NotificationHelper.kt
Normal file
48
app/src/main/java/com/example/scanwich/NotificationHelper.kt
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package com.example.scanwich
|
||||||
|
|
||||||
|
import android.app.AlarmManager
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
object NotificationHelper {
|
||||||
|
fun scheduleReminders(context: Context) {
|
||||||
|
scheduleMealAlarm(context, 8, 30, "Déjeuner", 101)
|
||||||
|
scheduleMealAlarm(context, 12, 30, "Dîner", 102)
|
||||||
|
scheduleMealAlarm(context, 19, 30, "Souper", 103)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun scheduleMealAlarm(context: Context, hour: Int, minute: Int, mealType: String, requestCode: Int) {
|
||||||
|
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||||
|
val intent = Intent(context, MealReminderReceiver::class.java).apply {
|
||||||
|
putExtra("meal_type", mealType)
|
||||||
|
}
|
||||||
|
|
||||||
|
val pendingIntent = PendingIntent.getBroadcast(
|
||||||
|
context,
|
||||||
|
requestCode,
|
||||||
|
intent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
|
||||||
|
val calendar = Calendar.getInstance().apply {
|
||||||
|
timeInMillis = System.currentTimeMillis()
|
||||||
|
set(Calendar.HOUR_OF_DAY, hour)
|
||||||
|
set(Calendar.MINUTE, minute)
|
||||||
|
set(Calendar.SECOND, 0)
|
||||||
|
|
||||||
|
// Si l'heure est déjà passée, on programme pour demain
|
||||||
|
if (before(Calendar.getInstance())) {
|
||||||
|
add(Calendar.DATE, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
alarmManager.setInexactRepeating(
|
||||||
|
AlarmManager.RTC_WAKEUP,
|
||||||
|
calendar.timeInMillis,
|
||||||
|
AlarmManager.INTERVAL_DAY,
|
||||||
|
pendingIntent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -72,7 +72,15 @@ fun analyzeImage(
|
|||||||
bitmap?.let { getOptimizedImageBase64(it) }
|
bitmap?.let { getOptimizedImageBase64(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
val data = hashMapOf("imageBase64" to base64, "mealName" to textDescription)
|
// Instruction pour que l'IA se concentre uniquement sur la nourriture (sans qualificatifs ni environnement)
|
||||||
|
val aiInstruction = "Focus seulement sur la nourriture, pas de qualificatif, pas son environnement, seulement la nourriture."
|
||||||
|
val mealDescriptionForAI = if (textDescription.isNullOrBlank()) {
|
||||||
|
aiInstruction
|
||||||
|
} else {
|
||||||
|
"$textDescription. $aiInstruction"
|
||||||
|
}
|
||||||
|
|
||||||
|
val data = hashMapOf("imageBase64" to base64, "mealName" to mealDescriptionForAI)
|
||||||
|
|
||||||
Firebase.functions("us-central1")
|
Firebase.functions("us-central1")
|
||||||
.getHttpsCallable("analyzeMealProxy")
|
.getHttpsCallable("analyzeMealProxy")
|
||||||
|
|||||||
@@ -1,50 +1,43 @@
|
|||||||
📝 Notes de version - Scan-Wich
|
📝 Notes de version - Scan-Wich
|
||||||
|
|
||||||
**Changements majeurs de la version actuelle :**
|
**Nouveautés de la version actuelle :**
|
||||||
|
|
||||||
|
🍲 **Focus Nourriture Pur (IA) :**
|
||||||
|
- **Analyse sélective :** L'intelligence artificielle se concentre désormais exclusivement sur la nourriture. Les qualificatifs, les descriptions de l'environnement ou les éléments de décor sont ignorés pour ne garder que l'essentiel nutritionnel.
|
||||||
|
|
||||||
|
🔔 **Rappels de Repas :**
|
||||||
|
- **Notifications intelligentes :** Ne manquez plus un enregistrement ! Des rappels automatiques ont été ajoutés pour le déjeuner (08h30), le dîner (12h30) et le souper (19h30).
|
||||||
|
- **Relance automatique :** Les notifications se réactivent automatiquement même après un redémarrage du téléphone.
|
||||||
|
|
||||||
|
📊 **Résumé Calorique Avancé :**
|
||||||
|
- **Bilan Net :** L'historique affiche maintenant le calcul "Mangé - Dépensé (Sport) = Total Net" pour une vision précise de votre équilibre journalier.
|
||||||
|
- **Alertes visuelles :** Le total net passe en rouge si vous dépassez vos objectifs quotidiens.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Changements majeurs précédents :**
|
||||||
|
|
||||||
🇫🇷 **Expérience 100% en Français :**
|
🇫🇷 **Expérience 100% en Français :**
|
||||||
- **IA Francophone :** Les analyses de repas (caméra et manuel) sont désormais exclusivement en français grâce à des instructions renforcées côté serveur.
|
- **IA Francophone :** Les analyses de repas (caméra et manuel) sont désormais exclusivement en français.
|
||||||
- **Scanner Localisé :** Utilisation forcée de la base de données française d'Open Food Facts pour le scan de codes-barres, garantissant des noms de produits familiers.
|
- **Scanner Localisé :** Utilisation de la base de données française d'Open Food Facts.
|
||||||
|
|
||||||
🤖 **Analyse IA plus Robuste :**
|
🤖 **Analyse IA plus Robuste :**
|
||||||
- **Correctif d'analyse :** Résolution du bug "Erreur IA" lié au formatage des réponses du modèle.
|
- **Fiabilité accrue :** Nettoyage automatique des données nutritionnelles côté serveur.
|
||||||
- **Fiabilité accrue :** Nettoyage automatique des données nutritionnelles côté serveur (Cloud Functions).
|
|
||||||
|
|
||||||
🚀 **Connexion Strava 100% Automatique :**
|
🚀 **Connexion Strava 100% Automatique :**
|
||||||
- **Simplicité totale :** Connexion en un seul clic sans saisie d'identifiants techniques.
|
- **Simplicité totale :** Connexion en un seul clic sans saisie d'identifiants techniques.
|
||||||
- **Sécurité Maximale :** Utilisation de Google Cloud Secret Manager pour la protection des clés API Strava.
|
|
||||||
|
|
||||||
🎨 **Améliorations UI/UX :**
|
🎨 **Améliorations UI/UX :**
|
||||||
- **Interface épurée :** Suppression des réglages superflus.
|
|
||||||
- **Correction du Thème :** Lisibilité parfaite sur l'écran de profil.
|
- **Correction du Thème :** Lisibilité parfaite sur l'écran de profil.
|
||||||
|
|
||||||
🛡️ **Architecture Cloud :**
|
🛡️ **Architecture Cloud :**
|
||||||
- **Gestion des accès :** Contrôle dynamique des utilisateurs autorisés via Firestore.
|
|
||||||
- **Profils Cloud :** Synchronisation automatique de vos données personnelles.
|
- **Profils Cloud :** Synchronisation automatique de vos données personnelles.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Mises à jour précédentes :**
|
🛠️ **Historique technique :**
|
||||||
|
- Export PDF Professionnel (Repas, sport, glycémie).
|
||||||
🛠️ **Correctifs et Améliorations Strava :**
|
- Suivi Diabétique complet.
|
||||||
- Résolution de bugs de compilation et amélioration du parsing des dates.
|
- Intégration Firebase App Check (Play Integrity).
|
||||||
- Nouvel algorithme d'estimation des calories basé sur les MET.
|
- Algorithme d'estimation MET pour le sport.
|
||||||
|
- Optimisation pour Android 36.
|
||||||
🛡️ **Sécurité renforcée :**
|
|
||||||
- Intégration de Firebase App Check (Play Integrity).
|
|
||||||
- Migration des clés vers Secret Manager.
|
|
||||||
|
|
||||||
⚡ **Analyse Ultra-Rapide :**
|
|
||||||
- Nouveau moteur de compression d'image intelligent.
|
|
||||||
|
|
||||||
📄 **Export PDF Professionnel :**
|
|
||||||
- Exportation de l'historique complet (repas, sport, glycémie).
|
|
||||||
|
|
||||||
🩸 **Suivi Diabétique :**
|
|
||||||
- Visualisation de la glycémie directement dans l'historique.
|
|
||||||
|
|
||||||
⭐ **Gestion des Favoris :**
|
|
||||||
- Nouvelle interface d'ajout rapide.
|
|
||||||
|
|
||||||
🔧 **Stabilité :**
|
|
||||||
- Optimisation pour Android 36 et corrections diverses.
|
|
||||||
|
|||||||
Reference in New Issue
Block a user