Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Unreleased]

## [1.2.10] - 2026-09-12

### Fixed

- The graph has its time axis back. The labels sat flush against the bottom edge of the image, and the car screen crops the top and bottom of that image to fit its own slot, so on most screens they were cut away entirely and the graph showed no times at all. They now sit far enough inside the image to survive the crop
- The graph no longer falls behind the reading beside it. How often it may be redrawn was measured from when the last image happened to be drawn rather than from the readings themselves, and since a refresh can notice a reading up to a minute after it arrived, every redraw pushed the next one further past the interval — holding readings back for an extra refresh and sometimes skipping one outright. It now follows the readings' own timestamps, so each reading a sensor sends reaches the graph

## [1.2.9] - 2026-09-12

### Removed
Expand Down
4 changes: 2 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ android {
applicationId = "de.autosugar"
minSdk = 26
targetSdk = 37
versionCode = 14
versionName = "1.2.9"
versionCode = 15
versionName = "1.2.10"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

Expand Down
21 changes: 19 additions & 2 deletions app/src/main/java/de/autosugar/car/GlucoseGraphRenderer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,23 @@ import kotlin.math.roundToInt
*/
private const val DROP_PINS_ENABLED = false

/**
* How far above the bottom of the plot the time labels sit.
*
* The host scales this image into a slot whose aspect ratio it chooses, and when that slot
* is wider than the bitmap it crops the top and bottom rather than letterboxing them. The
* labels sat four pixels off the plot's bottom edge — with the font's descent that put them
* flush against the image edge — and were cropped away entirely, taking the whole time axis
* with them. The band's lower edge was visibly flush with the image edge in cases where the
* scale says it should not have been, which is what gave the crop away.
*
* At 32 the baseline lands 48px above a 400px-tall bitmap, so the labels survive a bottom
* crop of up to about 12% of the image. This bounds the time labels only: the band, the
* trace and the gridlines still use the full plot, since losing a few pixels off the peak of
* the line is cosmetic where losing the axis is not.
*/
private const val SAFE_INSET_V = 32f

/**
* Renders the 3-hour glucose history graph as a [CarIcon] bitmap.
*
Expand Down Expand Up @@ -167,7 +184,7 @@ private fun drawTimeLabelsAndDropPins(
labelPaint.textAlign = Paint.Align.CENTER
canvas.drawText(
"%02d:30".format(Locale.US, cal.get(java.util.Calendar.HOUR_OF_DAY)),
xOf(tHalf), pad + plotH - 4f, labelPaint,
xOf(tHalf), pad + plotH - SAFE_INSET_V, labelPaint,
)
tHalf += msPerHour
}
Expand Down Expand Up @@ -220,7 +237,7 @@ private fun drawDropPinsAndHourLines(
labelPaint.textAlign = Paint.Align.CENTER
canvas.drawText(
"%02d:00".format(Locale.US, cal.get(java.util.Calendar.HOUR_OF_DAY)),
x, pad + plotH - 4f, labelPaint,
x, pad + plotH - SAFE_INSET_V, labelPaint,
)
}

Expand Down
60 changes: 43 additions & 17 deletions app/src/main/java/de/autosugar/car/GlucoseScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,21 @@ import kotlinx.coroutines.launch
private const val TREND_ARROW_ENABLED = false

/**
* Shortest interval at which the graph image may be redrawn.
* How much newer the latest reading must be than the one the graph currently shows before
* the image is redrawn.
*
* The graph is the one image the Android for Cars guidelines allow on the car screen, and
* they allow it as a *static* image for content context. Holding the bitmap for five
* minutes keeps that true no matter how fast the source reports: readings normally arrive
* at sensor cadence, but some uploaders push every minute, which would otherwise redraw the
* image on every poll and turn it into a live view. The reading, delta and timestamps
* beside it are text and keep updating as soon as data arrives.
* they allow it as a *static* image for content context. Throttling redraws keeps that true
* no matter how fast the source reports: readings normally arrive at sensor cadence, but
* some uploaders push every minute, which would otherwise redraw the image on every poll and
* turn it into a live view. The reading, delta and timestamps beside it are text and keep
* updating as soon as data arrives.
*
* Measured against the readings' own timestamps rather than the wall clock, and set just
* under the five minutes a sensor reports at so ordinary jitter — a reading landing 4:52
* after the last one rather than 5:00 — doesn't push it over the line and cost a redraw.
*/
private const val MIN_GRAPH_REDRAW_MS = 5 * 60_000L
private const val MIN_GRAPH_ADVANCE_MS = 4 * 60_000L + 30_000L

class GlucoseScreen(
carContext: CarContext,
Expand Down Expand Up @@ -88,10 +93,22 @@ class GlucoseScreen(
val bgTargetTop: Float,
val bgLow: Int,
val bgHigh: Int,
)
) {
/**
* True when the two keys draw on the same scale, i.e. they differ only in the readings
* themselves. A unit or threshold change is a user or server action rather than data
* churn, so it is not what the redraw interval exists to throttle and redraws at once.
*/
fun sameScaleAs(other: GraphCacheKey): Boolean =
unit == other.unit &&
bgTargetBottom == other.bgTargetBottom &&
bgTargetTop == other.bgTargetTop &&
bgLow == other.bgLow &&
bgHigh == other.bgHigh
}
private var cachedGraphKey: GraphCacheKey? = null
private var cachedGraphIcon: CarIcon? = null
private var lastGraphRenderMs = 0L
private var renderedNewestReadingMs = 0L

// onGetTemplate can fire frequently; cache the small generated bitmaps so they are
// not re-rendered on the main thread on every rebuild.
Expand Down Expand Up @@ -346,18 +363,27 @@ class GlucoseScreen(
bgLow = thresholds.bgLow,
bgHigh = thresholds.bgHigh,
)
// Draw immediately when there is nothing on screen yet (first reading, or a
// profile switch); otherwise hold the current image for MIN_GRAPH_REDRAW_MS even
// if newer readings have arrived. The stale key is kept, not overwritten, so the
// next rebuild after the hold expires picks the newer data up.
if (cachedGraphIcon == null ||
(cachedGraphKey != key && now - lastGraphRenderMs >= MIN_GRAPH_REDRAW_MS)
// The throttle measures reading time, not wall-clock time between renders. A
// poll can notice a reading up to a full interval after it arrived, so stamping
// the clock at render time left the next redraw permanently under the floor:
// every reading was held for an extra poll and some were skipped outright, for
// no gain — the image still only ever changed once per reading.
//
// Draw at once when there is nothing on screen yet (first reading, or a profile
// switch) or when the scale itself changed; otherwise wait for a reading at
// least MIN_GRAPH_ADVANCE_MS newer than the one drawn. The stale key is kept
// rather than overwritten, so the next rebuild picks the newer data up.
val newestReadingMs = history.last().dateMs
val previous = cachedGraphKey
if (previous == null || cachedGraphIcon == null ||
!previous.sameScaleAs(key) ||
newestReadingMs - renderedNewestReadingMs >= MIN_GRAPH_ADVANCE_MS
) {
cachedGraphIcon = glucoseGraphIcon(
history, unit, key.bgTargetBottom, key.bgTargetTop, key.bgLow, key.bgHigh,
)
cachedGraphKey = key
lastGraphRenderMs = now
renderedNewestReadingMs = newestReadingMs
}
pane.setImage(cachedGraphIcon!!)
}
Expand Down Expand Up @@ -394,7 +420,7 @@ class GlucoseScreen(
isLoading = true
cachedGraphKey = null
cachedGraphIcon = null
lastGraphRenderMs = 0L
renderedNewestReadingMs = 0L
invalidate()
lifecycleScope.launch { fetch() }
}
Expand Down
1 change: 1 addition & 0 deletions docs/whatsnew/1.2.10-ar
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
يعرض الرسم البياني الأوقات في الأسفل من جديد بعد أن كانت شاشة السيارة تقتطعها. كما صار يواكب القراءات الجديدة بشكل صحيح؛ إذ كان يتأخر تحديثًا كاملاً أو يتخطى قراءة. أما القيمة المجاورة له فكانت محدّثة دائمًا.
1 change: 1 addition & 0 deletions docs/whatsnew/1.2.10-de-DE
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Die Grafik zeigt die Uhrzeiten am unteren Rand wieder an; sie wurden vom Autobildschirm abgeschnitten. Außerdem hält sie richtig mit neuen Messwerten Schritt – zuvor konnte sie eine Aktualisierung hinterherhinken oder einen Wert überspringen. Der Wert daneben war immer aktuell.
1 change: 1 addition & 0 deletions docs/whatsnew/1.2.10-en-US
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The graph shows its times along the bottom again; they were being cut off by the car screen. It also keeps up with new readings properly — it could previously lag a refresh behind or skip one altogether. The reading beside it was always up to date.
1 change: 1 addition & 0 deletions docs/whatsnew/1.2.10-es-ES
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
El gráfico vuelve a mostrar las horas en la parte inferior; la pantalla del coche las recortaba. Además, ahora sigue bien las nuevas lecturas: antes podía ir un refresco por detrás o saltarse una. El valor que aparece al lado siempre estuvo actualizado.
1 change: 1 addition & 0 deletions docs/whatsnew/1.2.10-fr-FR
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Le graphique réaffiche les heures en bas ; elles étaient coupées par l'écran de la voiture. Il suit aussi correctement les nouvelles mesures : il pouvait auparavant avoir un rafraîchissement de retard, voire en sauter une. La valeur affichée à côté, elle, était toujours à jour.
1 change: 1 addition & 0 deletions docs/whatsnew/1.2.10-hi-IN
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ग्राफ़ नीचे के समय फिर से दिखाता है; कार स्क्रीन उन्हें काट रही थी। यह नई रीडिंग के साथ भी सही ढंग से चलता है — पहले यह एक रिफ़्रेश पीछे रह सकता था या कोई रीडिंग छोड़ सकता था। बगल में दिख रहा मान हमेशा ताज़ा रहता था।
1 change: 1 addition & 0 deletions docs/whatsnew/1.2.10-it-IT
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Il grafico mostra di nuovo gli orari in basso: venivano tagliati dalla schermata dell'auto. Inoltre ora sta al passo con le nuove letture; prima poteva restare indietro di un aggiornamento o saltarne una. Il valore accanto è sempre stato aggiornato.
1 change: 1 addition & 0 deletions docs/whatsnew/1.2.10-ja-JP
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
グラフの下部に時刻が再び表示されます。これまでは車の画面で切り取られていました。新しい測定値にも正しく追従するようになり、更新が1回遅れたり測定値を飛ばしたりしなくなります。隣に表示される数値は以前から常に最新でした。
1 change: 1 addition & 0 deletions docs/whatsnew/1.2.10-nl-NL
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
De grafiek toont de tijden onderaan weer; ze werden door het autoscherm afgesneden. Ook houdt hij nu goed gelijke tred met nieuwe metingen: eerder kon hij een verversing achterlopen of er een overslaan. De waarde ernaast was altijd actueel.
1 change: 1 addition & 0 deletions docs/whatsnew/1.2.10-pt-PT
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
O gráfico volta a mostrar as horas na parte inferior; estavam a ser cortadas pelo ecrã do carro. Além disso, acompanha agora corretamente as novas leituras: antes podia ficar uma atualização atrasado ou saltar uma. O valor ao lado esteve sempre atualizado.
1 change: 1 addition & 0 deletions docs/whatsnew/1.2.10-zh-CN
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
图表底部的时间刻度重新显示出来了,此前会被车机屏幕裁掉。图表现在也能正确跟上新读数,不再出现晚一次刷新或漏掉一个读数的情况。旁边的读数一直都是最新的。
Loading