From 49e8eb25f1cfecdf374629dd5345e879b7ada75d Mon Sep 17 00:00:00 2001 From: Davide Ferrari Date: Mon, 5 Jan 2026 17:24:20 +0100 Subject: [PATCH] feat(stats): improve AC/DC ratio bar visualization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Change colors: green for AC, yellow/amber for DC - Make bar thicker (20dp height with rounded corners) - Calculate ratios based on charged kWh instead of charge count - Show energy values (kWh) in the stats row - Show charge counts in small text below each bar side - Remove percentage numbers from display Added new DAO queries to sum energy by AC/DC type: - sumAcChargeEnergy / sumAcChargeEnergyInRange - sumDcChargeEnergy / sumDcChargeEnergyInRange Added acChargeEnergyKwh and dcChargeEnergyKwh fields to DeepStats. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../matedroid/data/local/dao/AggregateDao.kt | 32 +++++++ .../data/repository/StatsRepository.kt | 4 + .../com/matedroid/domain/model/CarStats.kt | 2 + .../matedroid/ui/screens/stats/StatsScreen.kt | 87 +++++++++++++------ 4 files changed, 100 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/com/matedroid/data/local/dao/AggregateDao.kt b/app/src/main/java/com/matedroid/data/local/dao/AggregateDao.kt index 402a225..61211e0 100644 --- a/app/src/main/java/com/matedroid/data/local/dao/AggregateDao.kt +++ b/app/src/main/java/com/matedroid/data/local/dao/AggregateDao.kt @@ -279,6 +279,38 @@ interface AggregateDao { @Query("SELECT chargeId FROM charge_detail_aggregates WHERE carId = :carId AND isFastCharger = 1") suspend fun getDcChargeIds(carId: Int): List + // Sum of energy added for AC charges (join with summary to get energyAdded) + @Query(""" + SELECT COALESCE(SUM(c.energyAdded), 0.0) FROM charge_detail_aggregates a + JOIN charges_summary c ON a.chargeId = c.chargeId + WHERE a.carId = :carId AND a.isFastCharger = 0 + """) + suspend fun sumAcChargeEnergy(carId: Int): Double + + @Query(""" + SELECT COALESCE(SUM(c.energyAdded), 0.0) FROM charge_detail_aggregates a + JOIN charges_summary c ON a.chargeId = c.chargeId + WHERE a.carId = :carId AND a.isFastCharger = 0 + AND c.startDate >= :startDate AND c.startDate < :endDate + """) + suspend fun sumAcChargeEnergyInRange(carId: Int, startDate: String, endDate: String): Double + + // Sum of energy added for DC charges + @Query(""" + SELECT COALESCE(SUM(c.energyAdded), 0.0) FROM charge_detail_aggregates a + JOIN charges_summary c ON a.chargeId = c.chargeId + WHERE a.carId = :carId AND a.isFastCharger = 1 + """) + suspend fun sumDcChargeEnergy(carId: Int): Double + + @Query(""" + SELECT COALESCE(SUM(c.energyAdded), 0.0) FROM charge_detail_aggregates a + JOIN charges_summary c ON a.chargeId = c.chargeId + WHERE a.carId = :carId AND a.isFastCharger = 1 + AND c.startDate >= :startDate AND c.startDate < :endDate + """) + suspend fun sumDcChargeEnergyInRange(carId: Int, startDate: String, endDate: String): Double + // Get all processed charge IDs (for checking if we have aggregate data) @Query("SELECT chargeId FROM charge_detail_aggregates WHERE carId = :carId") suspend fun getAllProcessedChargeIds(carId: Int): List diff --git a/app/src/main/java/com/matedroid/data/repository/StatsRepository.kt b/app/src/main/java/com/matedroid/data/repository/StatsRepository.kt index 38c4f26..b86d8ed 100644 --- a/app/src/main/java/com/matedroid/data/repository/StatsRepository.kt +++ b/app/src/main/java/com/matedroid/data/repository/StatsRepository.kt @@ -238,6 +238,8 @@ class StatsRepository @Inject constructor( acChargeCount = aggregateDao.countAcCharges(carId), dcChargeCount = aggregateDao.countDcCharges(carId), + acChargeEnergyKwh = aggregateDao.sumAcChargeEnergy(carId), + dcChargeEnergyKwh = aggregateDao.sumDcChargeEnergy(carId), driveDetailsProcessed = driveCount, chargeDetailsProcessed = chargeCount @@ -341,6 +343,8 @@ class StatsRepository @Inject constructor( acChargeCount = aggregateDao.countAcChargesInRange(carId, startDate, endDate), dcChargeCount = aggregateDao.countDcChargesInRange(carId, startDate, endDate), + acChargeEnergyKwh = aggregateDao.sumAcChargeEnergyInRange(carId, startDate, endDate), + dcChargeEnergyKwh = aggregateDao.sumDcChargeEnergyInRange(carId, startDate, endDate), driveDetailsProcessed = driveCount, chargeDetailsProcessed = chargeCount diff --git a/app/src/main/java/com/matedroid/domain/model/CarStats.kt b/app/src/main/java/com/matedroid/domain/model/CarStats.kt index 95d4ec2..4ed9a50 100644 --- a/app/src/main/java/com/matedroid/domain/model/CarStats.kt +++ b/app/src/main/java/com/matedroid/domain/model/CarStats.kt @@ -98,6 +98,8 @@ data class DeepStats( // === AC/DC Ratio === val acChargeCount: Int, val dcChargeCount: Int, + val acChargeEnergyKwh: Double, + val dcChargeEnergyKwh: Double, // === Sync Progress === val driveDetailsProcessed: Int, diff --git a/app/src/main/java/com/matedroid/ui/screens/stats/StatsScreen.kt b/app/src/main/java/com/matedroid/ui/screens/stats/StatsScreen.kt index 617392a..680a3dd 100644 --- a/app/src/main/java/com/matedroid/ui/screens/stats/StatsScreen.kt +++ b/app/src/main/java/com/matedroid/ui/screens/stats/StatsScreen.kt @@ -1,8 +1,10 @@ package com.matedroid.ui.screens.stats +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row @@ -16,6 +18,7 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight @@ -46,6 +49,8 @@ import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color import androidx.compose.foundation.lazy.LazyColumn as LogLazyColumn import androidx.compose.foundation.lazy.items as logItems import androidx.compose.foundation.rememberScrollState @@ -714,53 +719,85 @@ private fun ChargingPowerCard(deepStats: DeepStats, palette: CarColorPalette) { @Composable private fun AcDcRatioCard(deepStats: DeepStats, palette: CarColorPalette) { - val total = deepStats.acChargeCount + deepStats.dcChargeCount - if (total == 0) { + val totalEnergy = deepStats.acChargeEnergyKwh + deepStats.dcChargeEnergyKwh + if (totalEnergy <= 0) { return // No charge data } - val acPercent = (deepStats.acChargeCount * 100f / total).toInt() - val dcPercent = (deepStats.dcChargeCount * 100f / total).toInt() + val acRatio = (deepStats.acChargeEnergyKwh / totalEnergy).toFloat() + val acColor = Color(0xFF4CAF50) // Green + val dcColor = Color(0xFFFFC107) // Yellow/Amber StatsCard( title = "AC/DC Charging Ratio", icon = Icons.Default.BatteryChargingFull, palette = palette ) { + // Energy stats row Row(modifier = Modifier.fillMaxWidth()) { StatItem( - label = "AC Charges", - value = "${deepStats.acChargeCount} ($acPercent%)", + label = "AC Energy", + value = "%.1f kWh".format(deepStats.acChargeEnergyKwh), modifier = Modifier.weight(1f) ) StatItem( - label = "DC Charges", - value = "${deepStats.dcChargeCount} ($dcPercent%)", + label = "DC Energy", + value = "%.1f kWh".format(deepStats.dcChargeEnergyKwh), modifier = Modifier.weight(1f) ) } + Spacer(modifier = Modifier.height(12.dp)) - // Progress bar showing ratio - LinearProgressIndicator( - progress = { acPercent / 100f }, - modifier = Modifier.fillMaxWidth(), - color = MaterialTheme.colorScheme.primary, - trackColor = MaterialTheme.colorScheme.tertiary - ) + + // Custom ratio bar (thicker, green/yellow) + Box( + modifier = Modifier + .fillMaxWidth() + .height(20.dp) + .clip(RoundedCornerShape(10.dp)) + .background(dcColor) + ) { + Box( + modifier = Modifier + .fillMaxHeight() + .fillMaxWidth(acRatio) + .background(acColor) + ) + } + + Spacer(modifier = Modifier.height(4.dp)) + + // Labels with counts below Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween ) { - Text( - text = "AC (Home/Destination)", - style = MaterialTheme.typography.labelSmall, - color = MaterialTheme.colorScheme.primary - ) - Text( - text = "DC (Supercharger)", - style = MaterialTheme.typography.labelSmall, - color = MaterialTheme.colorScheme.tertiary - ) + Column { + Text( + text = "AC", + style = MaterialTheme.typography.labelSmall, + fontWeight = FontWeight.Bold, + color = acColor + ) + Text( + text = "${deepStats.acChargeCount} charges", + style = MaterialTheme.typography.labelSmall, + color = palette.onSurfaceVariant + ) + } + Column(horizontalAlignment = Alignment.End) { + Text( + text = "DC", + style = MaterialTheme.typography.labelSmall, + fontWeight = FontWeight.Bold, + color = dcColor + ) + Text( + text = "${deepStats.dcChargeCount} charges", + style = MaterialTheme.typography.labelSmall, + color = palette.onSurfaceVariant + ) + } } } }