Fix image picker crash, add 7-day bar chart, bump to v1.2
- 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:
parent
709d6c1f6f
commit
af35b70399
4 changed files with 55 additions and 4 deletions
|
|
@ -12,8 +12,8 @@ android {
|
||||||
applicationId 'com.danvics.calorieai'
|
applicationId 'com.danvics.calorieai'
|
||||||
minSdk 26
|
minSdk 26
|
||||||
targetSdk 35
|
targetSdk 35
|
||||||
versionCode 2
|
versionCode 3
|
||||||
versionName '1.1'
|
versionName '1.2'
|
||||||
}
|
}
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
|
|
|
||||||
|
|
@ -52,8 +52,19 @@ private fun AppRoot(repo: ApiRepository) {
|
||||||
var syncing by remember { mutableStateOf(false) }
|
var syncing by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val imagePicker = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
|
val imagePicker = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
|
||||||
if (uri != null) selectedImage = context.contentResolver.openInputStream(uri)?.use { stream ->
|
if (uri != null) {
|
||||||
ImagePayload.fromBytes(uri.lastPathSegment ?: "image", context.contentResolver.getType(uri) ?: "image/jpeg", stream.readBytes())
|
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 ->
|
val camera = rememberLauncherForActivityResult(ActivityResultContracts.TakePicturePreview()) { bitmap ->
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,18 @@
|
||||||
package com.danvics.calorieai.ui
|
package com.danvics.calorieai.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Canvas
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Modifier
|
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.graphics.Color
|
||||||
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.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.danvics.calorieai.data.DayTotals
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SectionCard(title: String, modifier: Modifier = Modifier, content: @Composable ColumnScope.() -> Unit) {
|
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
|
@Composable
|
||||||
fun CalorieGoalBar(consumed: Int, target: Int) {
|
fun CalorieGoalBar(consumed: Int, target: Int) {
|
||||||
val fraction = (consumed.toFloat() / target).coerceIn(0f, 1f)
|
val fraction = (consumed.toFloat() / target).coerceIn(0f, 1f)
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,8 @@ fun DashboardScreen(entries: List<MealEntry>, settings: ServerSettings, onLog: (
|
||||||
|
|
||||||
if (week.any { it.calories > 0 }) {
|
if (week.any { it.calories > 0 }) {
|
||||||
SectionCard("Last 7 days") {
|
SectionCard("Last 7 days") {
|
||||||
|
WeekBarChart(week, Modifier.fillMaxWidth())
|
||||||
|
HorizontalDivider(thickness = 0.5.dp, color = MaterialTheme.colorScheme.outlineVariant)
|
||||||
week.reversed().forEach { day ->
|
week.reversed().forEach { day ->
|
||||||
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||||
Text(day.label, style = MaterialTheme.typography.bodyMedium)
|
Text(day.label, style = MaterialTheme.typography.bodyMedium)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue