116 lines
4.4 KiB
Kotlin
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)
|
|
}
|
|
}
|
|
}
|