Files
calorie/app/src/main/java/com/example/scanwich/Utils.kt
2026-03-09 20:55:48 -04:00

116 lines
4.4 KiB
Kotlin

package com.example.scanwich
import android.graphics.Bitmap
import android.util.Base64
import com.google.firebase.Firebase
import com.google.firebase.functions.functions
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.ByteArrayOutputStream
import java.text.SimpleDateFormat
import java.util.*
fun Float.format(digits: Int) = "%.${digits}f".format(this)
fun getOptimizedImageBase64(bitmap: Bitmap): String {
val outputStream = ByteArrayOutputStream()
val width = bitmap.width
val height = bitmap.height
val maxSize = 1024
val (newWidth, newHeight) = if (width > maxSize || height > maxSize) {
val ratio = width.toFloat() / height.toFloat()
if (width > height) {
maxSize to (maxSize / ratio).toInt()
} else {
(maxSize * ratio).toInt() to maxSize
}
} else {
width to height
}
val resized = Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true)
resized.compress(Bitmap.CompressFormat.JPEG, 70, outputStream)
return Base64.encodeToString(outputStream.toByteArray(), Base64.NO_WRAP)
}
fun parseStravaDate(dateStr: String): Long {
return try {
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)
inputFormat.timeZone = TimeZone.getTimeZone("UTC")
inputFormat.parse(dateStr)?.time ?: 0L
} catch (_: Exception) { 0L }
}
fun estimateCaloriesFromDb(activity: SportActivity, weightKg: Double): Int {
if (activity.calories != null && activity.calories > 0) return activity.calories.toInt()
val met = when (activity.type.lowercase()) {
"run" -> 10.0
"ride" -> 8.0
"walk" -> 3.5
"hike" -> 6.0
"swim" -> 7.0
"weighttraining" -> 5.0
"workout" -> 4.5
else -> 5.0
}
val durationHours = activity.movingTime / 3600.0
return (met * weightKg * durationHours).toInt()
}
fun analyzeImage(
bitmap: Bitmap?,
textDescription: String?,
setAnalyzing: (Boolean) -> Unit,
onResult: (Triple<String, String, List<Int>>?, String?) -> Unit,
scope: CoroutineScope
) {
setAnalyzing(true)
scope.launch {
try {
val base64 = withContext(Dispatchers.Default) {
bitmap?.let { getOptimizedImageBase64(it) }
}
// 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")
.getHttpsCallable("analyzeMealProxy")
.call(data)
.addOnSuccessListener { result ->
try {
val responseData = result.data as? Map<*, *>
if (responseData != null) {
onResult(Triple(
(responseData["name"] as? String) ?: textDescription ?: "Repas",
(responseData["description"] as? String) ?: "Analyse réussie",
listOf(
(responseData["calories"] as? Number)?.toInt() ?: 0,
(responseData["carbs"] as? Number)?.toInt() ?: 0,
(responseData["protein"] as? Number)?.toInt() ?: 0,
(responseData["fat"] as? Number)?.toInt() ?: 0
)
), null)
} else { onResult(null, "Format invalide") }
} catch (e: Exception) { onResult(null, e.message) }
setAnalyzing(false)
}
.addOnFailureListener { e ->
onResult(null, e.message)
setAnalyzing(false)
}
} catch (e: Exception) {
onResult(null, e.localizedMessage)
setAnalyzing(false)
}
}
}