Fix image picker crash, add 7-day bar chart, bump to v1.2
All checks were successful
Android CI / build (push) Successful in 1m58s
Web CI / test (push) Successful in 49s
Android Release / release-apk (push) Successful in 1m35s

- Image reading moved to IO thread with runCatching to prevent main thread crash and OOM on large gallery images
- 7-day calorie bar chart added to dashboard using Canvas
- Version bumped to 1.2 (code 3)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Daniel 2026-05-20 06:10:54 +02:00
parent 709d6c1f6f
commit af35b70399
4 changed files with 55 additions and 4 deletions

View file

@ -12,8 +12,8 @@ android {
applicationId 'com.danvics.calorieai'
minSdk 26
targetSdk 35
versionCode 2
versionName '1.1'
versionCode 3
versionName '1.2'
}
buildFeatures {

View file

@ -52,8 +52,19 @@ private fun AppRoot(repo: ApiRepository) {
var syncing by remember { mutableStateOf(false) }
val imagePicker = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
if (uri != null) selectedImage = context.contentResolver.openInputStream(uri)?.use { stream ->
ImagePayload.fromBytes(uri.lastPathSegment ?: "image", context.contentResolver.getType(uri) ?: "image/jpeg", stream.readBytes())
if (uri != null) {
scope.launch(Dispatchers.IO) {
val payload = runCatching {
context.contentResolver.openInputStream(uri)?.use { stream ->
ImagePayload.fromBytes(
uri.lastPathSegment ?: "image",
context.contentResolver.getType(uri) ?: "image/jpeg",
stream.readBytes()
)
}
}.getOrNull()
withContext(Dispatchers.Main) { if (payload != null) selectedImage = payload }
}
}
}
val camera = rememberLauncherForActivityResult(ActivityResultContracts.TakePicturePreview()) { bitmap ->

View file

@ -1,12 +1,18 @@
package com.danvics.calorieai.ui
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.danvics.calorieai.data.DayTotals
@Composable
fun SectionCard(title: String, modifier: Modifier = Modifier, content: @Composable ColumnScope.() -> Unit) {
@ -78,6 +84,38 @@ fun MacroBar(label: String, grams: Int, totalGrams: Int, color: Color) {
}
}
@Composable
fun WeekBarChart(week: List<DayTotals>, modifier: Modifier = Modifier) {
val primary = MaterialTheme.colorScheme.primary
val track = MaterialTheme.colorScheme.surfaceVariant
val maxCal = week.maxOfOrNull { it.calories }.takeIf { it != null && it > 0 } ?: 1
Column(modifier) {
Canvas(modifier = Modifier.fillMaxWidth().height(96.dp)) {
val slotW = size.width / week.size
val barW = slotW * 0.55f
val maxH = size.height
week.forEachIndexed { i, day ->
val x = i * slotW + (slotW - barW) / 2
val fillH = (day.calories.toFloat() / maxCal) * maxH
drawRoundRect(track, Offset(x, 0f), Size(barW, maxH), CornerRadius(6f))
if (fillH > 0f) drawRoundRect(primary, Offset(x, maxH - fillH), Size(barW, fillH), CornerRadius(6f))
}
}
Spacer(Modifier.height(4.dp))
Row(Modifier.fillMaxWidth()) {
week.forEach { day ->
Text(
day.label,
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center,
modifier = Modifier.weight(1f)
)
}
}
}
}
@Composable
fun CalorieGoalBar(consumed: Int, target: Int) {
val fraction = (consumed.toFloat() / target).coerceIn(0f, 1f)

View file

@ -66,6 +66,8 @@ fun DashboardScreen(entries: List<MealEntry>, settings: ServerSettings, onLog: (
if (week.any { it.calories > 0 }) {
SectionCard("Last 7 days") {
WeekBarChart(week, Modifier.fillMaxWidth())
HorizontalDivider(thickness = 0.5.dp, color = MaterialTheme.colorScheme.outlineVariant)
week.reversed().forEach { day ->
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Text(day.label, style = MaterialTheme.typography.bodyMedium)