Fix camera, add photo preview, native date/time pickers, auto-navigate dashboard after save

- Replace TakePicturePreview with TakePicture + FileProvider for reliable full-res camera capture
- Add FileProvider to manifest with cache-path for camera photos
- Show selected photo as a thumbnail preview in the Log screen
- Replace plain date/time text fields with clickable fields that open native Android pickers
- After AI analysis or manual edit save, auto-navigate to Dashboard so results are visible immediately
- Clear editing/image state after successful save so Log screen resets for next entry

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Daniel 2026-05-20 15:18:26 +02:00
parent af35b70399
commit b93e479be4
5 changed files with 209 additions and 52 deletions

View file

@ -17,5 +17,14 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application> </application>
</manifest> </manifest>

View file

@ -1,5 +1,7 @@
package com.danvics.calorieai package com.danvics.calorieai
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
@ -8,6 +10,7 @@ import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.core.content.FileProvider
import com.danvics.calorieai.ai.ImagePayload import com.danvics.calorieai.ai.ImagePayload
import com.danvics.calorieai.ai.NutritionParser import com.danvics.calorieai.ai.NutritionParser
import com.danvics.calorieai.data.* import com.danvics.calorieai.data.*
@ -19,6 +22,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import java.io.File
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalTime import java.time.LocalTime
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
@ -38,6 +42,11 @@ private fun AppRoot(repo: ApiRepository) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val parser = remember { NutritionParser() } val parser = remember { NutritionParser() }
val cameraImageFile = remember { File(context.cacheDir, "cam_capture.jpg") }
val cameraImageUri: Uri = remember {
FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", cameraImageFile)
}
var connected by remember { mutableStateOf(repo.isConnected()) } var connected by remember { mutableStateOf(repo.isConnected()) }
var connectError by remember { mutableStateOf("") } var connectError by remember { mutableStateOf("") }
var connecting by remember { mutableStateOf(false) } var connecting by remember { mutableStateOf(false) }
@ -45,30 +54,48 @@ private fun AppRoot(repo: ApiRepository) {
var appState by remember { mutableStateOf(AppState()) } var appState by remember { mutableStateOf(AppState()) }
var editing by remember { mutableStateOf<MealEntry?>(null) } var editing by remember { mutableStateOf<MealEntry?>(null) }
var selectedImage by remember { mutableStateOf<ImagePayload?>(null) } var selectedImage by remember { mutableStateOf<ImagePayload?>(null) }
var selectedImageBitmap by remember { mutableStateOf<Bitmap?>(null) }
var status by remember { mutableStateOf("") } var status by remember { mutableStateOf("") }
var busy by remember { mutableStateOf(false) } var busy by remember { mutableStateOf(false) }
var planStatus by remember { mutableStateOf("") } var planStatus by remember { mutableStateOf("") }
var planBusy by remember { mutableStateOf(false) } var planBusy by remember { mutableStateOf(false) }
var syncing by remember { mutableStateOf(false) } var syncing by remember { mutableStateOf(false) }
var mealSaveCount by remember { mutableStateOf(0) }
val imagePicker = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? -> val imagePicker = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
if (uri != null) { if (uri != null) {
scope.launch(Dispatchers.IO) { scope.launch(Dispatchers.IO) {
val payload = runCatching { val bytes = context.contentResolver.openInputStream(uri)?.use { it.readBytes() }
context.contentResolver.openInputStream(uri)?.use { stream -> if (bytes != null) {
ImagePayload.fromBytes( val bitmap = decodeSampledBitmap(bytes)
uri.lastPathSegment ?: "image", val payload = ImagePayload.fromBytes(
context.contentResolver.getType(uri) ?: "image/jpeg", uri.lastPathSegment ?: "image",
stream.readBytes() context.contentResolver.getType(uri) ?: "image/jpeg",
) bytes
)
withContext(Dispatchers.Main) {
selectedImage = payload
selectedImageBitmap = bitmap
} }
}.getOrNull() }
withContext(Dispatchers.Main) { if (payload != null) selectedImage = payload }
} }
} }
} }
val camera = rememberLauncherForActivityResult(ActivityResultContracts.TakePicturePreview()) { bitmap ->
if (bitmap != null) selectedImage = ImagePayload.fromBitmap("camera photo", bitmap) val camera = rememberLauncherForActivityResult(ActivityResultContracts.TakePicture()) { success ->
if (success) {
scope.launch(Dispatchers.IO) {
val bytes = cameraImageFile.readBytes()
if (bytes.isNotEmpty()) {
val bitmap = decodeSampledBitmap(bytes)
val payload = ImagePayload.fromBytes("photo.jpg", "image/jpeg", bytes)
withContext(Dispatchers.Main) {
selectedImage = payload
selectedImageBitmap = bitmap
}
}
}
}
} }
fun handleUnauth() { connected = false; appState = AppState() } fun handleUnauth() { connected = false; appState = AppState() }
@ -88,21 +115,21 @@ private fun AppRoot(repo: ApiRepository) {
if (!connected) { if (!connected) {
CalorieTheme { CalorieTheme {
ConnectScreen(error = connectError, connecting = connecting) { url, user, pass -> ConnectScreen(error = connectError, connecting = connecting) { url, user, pass ->
connecting = true connecting = true
connectError = "" connectError = ""
scope.launch(Dispatchers.IO) { scope.launch(Dispatchers.IO) {
runCatching { repo.login(url, user, pass) } runCatching { repo.login(url, user, pass) }
.onSuccess { cookie -> .onSuccess { cookie ->
repo.saveConfig(url, cookie) repo.saveConfig(url, cookie)
withContext(Dispatchers.Main) { connecting = false; connected = true } withContext(Dispatchers.Main) { connecting = false; connected = true }
} }
.onFailure { e -> .onFailure { e ->
withContext(Dispatchers.Main) { connecting = false; connectError = e.message ?: "Connection failed" } withContext(Dispatchers.Main) { connecting = false; connectError = e.message ?: "Connection failed" }
} }
}
} }
} }
}
return return
} }
@ -113,13 +140,20 @@ private fun AppRoot(repo: ApiRepository) {
busy = busy, busy = busy,
editing = editing, editing = editing,
selectedImageName = selectedImage?.name.orEmpty(), selectedImageName = selectedImage?.name.orEmpty(),
selectedImageBitmap = selectedImageBitmap,
planStatus = planStatus, planStatus = planStatus,
planBusy = planBusy, planBusy = planBusy,
syncing = syncing, syncing = syncing,
mealSaveCount = mealSaveCount,
onSync = { syncState() }, onSync = { syncState() },
onPickImage = { imagePicker.launch("image/*") }, onPickImage = { imagePicker.launch("image/*") },
onTakePhoto = { camera.launch(null) }, onTakePhoto = { camera.launch(cameraImageUri) },
onCancelEdit = { editing = null; selectedImage = null; status = "" }, onCancelEdit = {
editing = null
selectedImage = null
selectedImageBitmap = null
status = ""
},
onSaveManualEdit = { updated -> onSaveManualEdit = { updated ->
scope.launch(Dispatchers.IO) { scope.launch(Dispatchers.IO) {
runCatching { repo.upsertEntry(updated) } runCatching { repo.upsertEntry(updated) }
@ -127,7 +161,10 @@ private fun AppRoot(repo: ApiRepository) {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
appState = appState.copy(entries = entries, trash = trash) appState = appState.copy(entries = entries, trash = trash)
editing = null editing = null
selectedImage = null
selectedImageBitmap = null
status = "Meal updated." status = "Meal updated."
mealSaveCount++
} }
} }
.onFailure { e -> withContext(Dispatchers.Main) { if (e is UnauthorizedException) handleUnauth() else status = "Save failed: ${e.message}" } } .onFailure { e -> withContext(Dispatchers.Main) { if (e is UnauthorizedException) handleUnauth() else status = "Save failed: ${e.message}" } }
@ -158,9 +195,11 @@ private fun AppRoot(repo: ApiRepository) {
val (entries, trash) = pair val (entries, trash) = pair
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
appState = appState.copy(entries = entries, trash = trash) appState = appState.copy(entries = entries, trash = trash)
editing = meal editing = null
selectedImage = null selectedImage = null
selectedImageBitmap = null
status = "Saved. ${meal.estimate.mealName} · ${meal.estimate.calories} kcal" status = "Saved. ${meal.estimate.mealName} · ${meal.estimate.calories} kcal"
mealSaveCount++
busy = false busy = false
} }
}.onFailure { e -> }.onFailure { e ->
@ -289,6 +328,14 @@ private fun AppRoot(repo: ApiRepository) {
) )
} }
private fun decodeSampledBitmap(bytes: ByteArray, maxWidth: Int = 600): Bitmap {
val opts = BitmapFactory.Options().apply { inJustDecodeBounds = true }
BitmapFactory.decodeByteArray(bytes, 0, bytes.size, opts)
var sample = 1
while (opts.outWidth / sample > maxWidth) sample *= 2
return BitmapFactory.decodeByteArray(bytes, 0, bytes.size, BitmapFactory.Options().apply { inSampleSize = sample })!!
}
private fun buildVisionBody(model: String, description: String, measure: String, image: ImagePayload): JSONObject = private fun buildVisionBody(model: String, description: String, measure: String, image: ImagePayload): JSONObject =
JSONObject().put("model", model).put("temperature", 0.15) JSONObject().put("model", model).put("temperature", 0.15)
.put("messages", JSONArray().put(JSONObject().put("role", "user").put("content", .put("messages", JSONArray().put(JSONObject().put("role", "user").put("content",

View file

@ -1,5 +1,6 @@
package com.danvics.calorieai.ui package com.danvics.calorieai.ui
import android.graphics.Bitmap
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
@ -29,9 +30,11 @@ fun CalorieAiApp(
busy: Boolean, busy: Boolean,
editing: MealEntry?, editing: MealEntry?,
selectedImageName: String, selectedImageName: String,
selectedImageBitmap: Bitmap?,
planStatus: String, planStatus: String,
planBusy: Boolean, planBusy: Boolean,
syncing: Boolean, syncing: Boolean,
mealSaveCount: Int,
onSync: () -> Unit, onSync: () -> Unit,
onPickImage: () -> Unit, onPickImage: () -> Unit,
onTakePhoto: () -> Unit, onTakePhoto: () -> Unit,
@ -51,6 +54,11 @@ fun CalorieAiApp(
) { ) {
CalorieTheme { CalorieTheme {
var screen by remember { mutableStateOf(Screen.Dashboard) } var screen by remember { mutableStateOf(Screen.Dashboard) }
LaunchedEffect(mealSaveCount) {
if (mealSaveCount > 0) screen = Screen.Dashboard
}
Scaffold( Scaffold(
topBar = { topBar = {
TopAppBar( TopAppBar(
@ -88,7 +96,18 @@ fun CalorieAiApp(
Box(Modifier.padding(padding).fillMaxSize()) { Box(Modifier.padding(padding).fillMaxSize()) {
when (screen) { when (screen) {
Screen.Dashboard -> DashboardScreen(appState.entries, appState.settings, onLog = { screen = Screen.Log }) Screen.Dashboard -> DashboardScreen(appState.entries, appState.settings, onLog = { screen = Screen.Log })
Screen.Log -> LogMealScreen(editing, selectedImageName, status, busy, onPickImage, onTakePhoto, onAnalyze, onSaveManualEdit, onCancelEdit) Screen.Log -> LogMealScreen(
editing = editing,
selectedImageName = selectedImageName,
selectedImageBitmap = selectedImageBitmap,
status = status,
busy = busy,
onPickImage = onPickImage,
onTakePhoto = onTakePhoto,
onAnalyze = onAnalyze,
onSaveManualEdit = onSaveManualEdit,
onCancelEdit = onCancelEdit
)
Screen.Diary -> DiaryScreen( Screen.Diary -> DiaryScreen(
entries = appState.entries, entries = appState.entries,
trash = appState.trash, trash = appState.trash,

View file

@ -1,20 +1,31 @@
package com.danvics.calorieai.ui package com.danvics.calorieai.ui
import android.app.DatePickerDialog
import android.app.TimePickerDialog
import android.graphics.Bitmap
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CalendarToday
import androidx.compose.material.icons.filled.CameraAlt import androidx.compose.material.icons.filled.CameraAlt
import androidx.compose.material.icons.filled.PhotoLibrary import androidx.compose.material.icons.filled.PhotoLibrary
import androidx.compose.material.icons.filled.Schedule
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.danvics.calorieai.data.MealEntry import com.danvics.calorieai.data.MealEntry
import com.danvics.calorieai.data.NutritionEstimate import com.danvics.calorieai.data.NutritionEstimate
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalTime import java.time.LocalTime
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.util.Calendar
private val MEAL_TYPES = listOf("Breakfast", "Lunch", "Dinner", "Snack", "Other") private val MEAL_TYPES = listOf("Breakfast", "Lunch", "Dinner", "Snack", "Other")
@ -23,6 +34,7 @@ private val MEAL_TYPES = listOf("Breakfast", "Lunch", "Dinner", "Snack", "Other"
fun LogMealScreen( fun LogMealScreen(
editing: MealEntry?, editing: MealEntry?,
selectedImageName: String, selectedImageName: String,
selectedImageBitmap: Bitmap?,
status: String, status: String,
busy: Boolean, busy: Boolean,
onPickImage: () -> Unit, onPickImage: () -> Unit,
@ -31,6 +43,8 @@ fun LogMealScreen(
onSaveManualEdit: (MealEntry) -> Unit, onSaveManualEdit: (MealEntry) -> Unit,
onCancelEdit: () -> Unit onCancelEdit: () -> Unit
) = Page { ) = Page {
val context = LocalContext.current
var date by remember(editing?.id) { mutableStateOf(editing?.date ?: LocalDate.now().toString()) } var date by remember(editing?.id) { mutableStateOf(editing?.date ?: LocalDate.now().toString()) }
var time by remember(editing?.id) { mutableStateOf(editing?.time ?: LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm"))) } var time by remember(editing?.id) { mutableStateOf(editing?.time ?: LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm"))) }
var mealType by remember(editing?.id) { mutableStateOf(editing?.mealType ?: "Breakfast") } var mealType by remember(editing?.id) { mutableStateOf(editing?.mealType ?: "Breakfast") }
@ -38,6 +52,41 @@ fun LogMealScreen(
var measure by remember(editing?.id) { mutableStateOf(editing?.measure ?: "") } var measure by remember(editing?.id) { mutableStateOf(editing?.measure ?: "") }
var estimate by remember(editing?.id) { mutableStateOf(editing?.estimate ?: NutritionEstimate()) } var estimate by remember(editing?.id) { mutableStateOf(editing?.estimate ?: NutritionEstimate()) }
var showImageSheet by remember { mutableStateOf(false) } var showImageSheet by remember { mutableStateOf(false) }
var showDatePicker by remember { mutableStateOf(false) }
var showTimePicker by remember { mutableStateOf(false) }
if (showDatePicker) {
val cal = Calendar.getInstance()
runCatching {
val d = LocalDate.parse(date)
cal.set(d.year, d.monthValue - 1, d.dayOfMonth)
}
DisposableEffect(Unit) {
val dialog = DatePickerDialog(
context,
{ _, year, month, day -> date = "%04d-%02d-%02d".format(year, month + 1, day) },
cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH)
)
dialog.setOnDismissListener { showDatePicker = false }
dialog.show()
onDispose { if (dialog.isShowing) dialog.dismiss() }
}
}
if (showTimePicker) {
val h = time.split(":").getOrNull(0)?.toIntOrNull() ?: LocalTime.now().hour
val m = time.split(":").getOrNull(1)?.toIntOrNull() ?: LocalTime.now().minute
DisposableEffect(Unit) {
val dialog = TimePickerDialog(
context,
{ _, hour, minute -> time = "%02d:%02d".format(hour, minute) },
h, m, true
)
dialog.setOnDismissListener { showTimePicker = false }
dialog.show()
onDispose { if (dialog.isShowing) dialog.dismiss() }
}
}
if (showImageSheet) { if (showImageSheet) {
ModalBottomSheet(onDismissRequest = { showImageSheet = false }) { ModalBottomSheet(onDismissRequest = { showImageSheet = false }) {
@ -51,18 +100,12 @@ fun LogMealScreen(
ListItem( ListItem(
headlineContent = { Text("Take photo") }, headlineContent = { Text("Take photo") },
leadingContent = { Icon(Icons.Default.CameraAlt, contentDescription = null) }, leadingContent = { Icon(Icons.Default.CameraAlt, contentDescription = null) },
modifier = Modifier.clickable { modifier = Modifier.clickable { showImageSheet = false; onTakePhoto() }
showImageSheet = false
onTakePhoto()
}
) )
ListItem( ListItem(
headlineContent = { Text("Choose from gallery") }, headlineContent = { Text("Choose from gallery") },
leadingContent = { Icon(Icons.Default.PhotoLibrary, contentDescription = null) }, leadingContent = { Icon(Icons.Default.PhotoLibrary, contentDescription = null) },
modifier = Modifier.clickable { modifier = Modifier.clickable { showImageSheet = false; onPickImage() }
showImageSheet = false
onPickImage()
}
) )
} }
} }
@ -70,12 +113,42 @@ fun LogMealScreen(
SectionCard("Meal details") { SectionCard("Meal details") {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
Field("Date", date, { date = it }, modifier = Modifier.weight(1f), placeholder = "YYYY-MM-DD") Box(Modifier.weight(1f)) {
Field("Time", time, { time = it }, modifier = Modifier.weight(1f), placeholder = "HH:MM") OutlinedTextField(
value = date,
onValueChange = {},
readOnly = true,
label = { Text("Date") },
trailingIcon = { Icon(Icons.Default.CalendarToday, contentDescription = null, modifier = Modifier.size(18.dp)) },
modifier = Modifier.fillMaxWidth()
)
Box(Modifier.matchParentSize().clickable { showDatePicker = true })
}
Box(Modifier.weight(1f)) {
OutlinedTextField(
value = time,
onValueChange = {},
readOnly = true,
label = { Text("Time") },
trailingIcon = { Icon(Icons.Default.Schedule, contentDescription = null, modifier = Modifier.size(18.dp)) },
modifier = Modifier.fillMaxWidth()
)
Box(Modifier.matchParentSize().clickable { showTimePicker = true })
}
} }
DropdownField("Meal type", MEAL_TYPES, mealType, { mealType = it }) DropdownField("Meal type", MEAL_TYPES, mealType, { mealType = it })
Field("Description", description, { description = it }, singleLine = false, placeholder = "e.g. grilled salmon, rice, broccoli") Field("Description", description, { description = it }, singleLine = false, placeholder = "e.g. grilled salmon, rice, broccoli")
Field("Portion or measure", measure, { measure = it }, placeholder = "e.g. one plate, 450g") Field("Portion or measure", measure, { measure = it }, placeholder = "e.g. one plate, 450g")
if (selectedImageBitmap != null) {
Image(
bitmap = selectedImageBitmap.asImageBitmap(),
contentDescription = "Meal photo preview",
modifier = Modifier.fillMaxWidth().height(180.dp).clip(MaterialTheme.shapes.medium),
contentScale = ContentScale.Crop
)
}
OutlinedButton( OutlinedButton(
onClick = { showImageSheet = true }, onClick = { showImageSheet = true },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
@ -83,31 +156,26 @@ fun LogMealScreen(
Icon( Icon(
if (selectedImageName.isNotBlank()) Icons.Default.CameraAlt else Icons.Default.PhotoLibrary, if (selectedImageName.isNotBlank()) Icons.Default.CameraAlt else Icons.Default.PhotoLibrary,
contentDescription = null, contentDescription = null,
modifier = Modifier.size(18.dp).padding(end = 0.dp) modifier = Modifier.size(18.dp)
) )
Spacer(Modifier.width(8.dp)) Spacer(Modifier.width(8.dp))
Text(if (selectedImageName.isNotBlank()) "Change photo" else "Add photo") Text(if (selectedImageName.isNotBlank()) "Change photo" else "Add photo")
} }
if (selectedImageName.isNotBlank()) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
selectedImageName,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.weight(1f)
)
}
}
Button( Button(
enabled = !busy && (description.isNotBlank() || selectedImageName.isNotBlank()), enabled = !busy && (description.isNotBlank() || selectedImageName.isNotBlank()),
onClick = { onAnalyze(buildDraft(editing, date, time, mealType, description, measure, estimate)) }, onClick = { onAnalyze(buildDraft(editing, date, time, mealType, description, measure, estimate)) },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { Text(if (busy) "Analyzing..." else if (editing == null) "Analyze and save" else "Analyze again") } ) { Text(if (busy) "Analyzing..." else if (editing == null) "Analyze and save" else "Analyze again") }
if (status.isNotBlank()) { if (status.isNotBlank()) {
Text( Text(
status, status,
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = if (status.startsWith("Analysis failed") || status.startsWith("No AI")) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.primary color = if (status.startsWith("Analysis failed") || status.startsWith("No AI") || status.startsWith("Save failed"))
MaterialTheme.colorScheme.error
else
MaterialTheme.colorScheme.primary
) )
} }
} }
@ -140,5 +208,15 @@ fun LogMealScreen(
} }
} }
private fun buildDraft(editing: MealEntry?, date: String, time: String, mealType: String, description: String, measure: String, estimate: NutritionEstimate) = private fun buildDraft(
MealEntry(editing?.id.orEmpty(), date, time, mealType, description, measure, editing?.imageIncluded ?: false, editing?.imageName.orEmpty(), editing?.visionEstimate.orEmpty(), estimate) editing: MealEntry?,
date: String,
time: String,
mealType: String,
description: String,
measure: String,
estimate: NutritionEstimate
) = MealEntry(
editing?.id.orEmpty(), date, time, mealType, description, measure,
editing?.imageIncluded ?: false, editing?.imageName.orEmpty(), editing?.visionEstimate.orEmpty(), estimate
)

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<cache-path name="camera_photos" path="." />
</paths>