diff --git a/CHANGELOG.md b/CHANGELOG.md index fd07da8..a04232a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index cc9cfcf..42962bc 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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" } diff --git a/app/src/main/java/de/autosugar/car/GlucoseGraphRenderer.kt b/app/src/main/java/de/autosugar/car/GlucoseGraphRenderer.kt index 8046b56..d4189bb 100644 --- a/app/src/main/java/de/autosugar/car/GlucoseGraphRenderer.kt +++ b/app/src/main/java/de/autosugar/car/GlucoseGraphRenderer.kt @@ -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. * @@ -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 } @@ -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, ) } diff --git a/app/src/main/java/de/autosugar/car/GlucoseScreen.kt b/app/src/main/java/de/autosugar/car/GlucoseScreen.kt index 2193df0..079c351 100644 --- a/app/src/main/java/de/autosugar/car/GlucoseScreen.kt +++ b/app/src/main/java/de/autosugar/car/GlucoseScreen.kt @@ -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, @@ -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. @@ -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!!) } @@ -394,7 +420,7 @@ class GlucoseScreen( isLoading = true cachedGraphKey = null cachedGraphIcon = null - lastGraphRenderMs = 0L + renderedNewestReadingMs = 0L invalidate() lifecycleScope.launch { fetch() } } diff --git a/docs/whatsnew/1.2.10-ar b/docs/whatsnew/1.2.10-ar new file mode 100644 index 0000000..13488db --- /dev/null +++ b/docs/whatsnew/1.2.10-ar @@ -0,0 +1 @@ +يعرض الرسم البياني الأوقات في الأسفل من جديد بعد أن كانت شاشة السيارة تقتطعها. كما صار يواكب القراءات الجديدة بشكل صحيح؛ إذ كان يتأخر تحديثًا كاملاً أو يتخطى قراءة. أما القيمة المجاورة له فكانت محدّثة دائمًا. diff --git a/docs/whatsnew/1.2.10-de-DE b/docs/whatsnew/1.2.10-de-DE new file mode 100644 index 0000000..e8008f4 --- /dev/null +++ b/docs/whatsnew/1.2.10-de-DE @@ -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. diff --git a/docs/whatsnew/1.2.10-en-US b/docs/whatsnew/1.2.10-en-US new file mode 100644 index 0000000..c3f2f34 --- /dev/null +++ b/docs/whatsnew/1.2.10-en-US @@ -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. diff --git a/docs/whatsnew/1.2.10-es-ES b/docs/whatsnew/1.2.10-es-ES new file mode 100644 index 0000000..129ebe7 --- /dev/null +++ b/docs/whatsnew/1.2.10-es-ES @@ -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. diff --git a/docs/whatsnew/1.2.10-fr-FR b/docs/whatsnew/1.2.10-fr-FR new file mode 100644 index 0000000..4193384 --- /dev/null +++ b/docs/whatsnew/1.2.10-fr-FR @@ -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. diff --git a/docs/whatsnew/1.2.10-hi-IN b/docs/whatsnew/1.2.10-hi-IN new file mode 100644 index 0000000..8f0f5a1 --- /dev/null +++ b/docs/whatsnew/1.2.10-hi-IN @@ -0,0 +1 @@ +ग्राफ़ नीचे के समय फिर से दिखाता है; कार स्क्रीन उन्हें काट रही थी। यह नई रीडिंग के साथ भी सही ढंग से चलता है — पहले यह एक रिफ़्रेश पीछे रह सकता था या कोई रीडिंग छोड़ सकता था। बगल में दिख रहा मान हमेशा ताज़ा रहता था। diff --git a/docs/whatsnew/1.2.10-it-IT b/docs/whatsnew/1.2.10-it-IT new file mode 100644 index 0000000..0e2c41d --- /dev/null +++ b/docs/whatsnew/1.2.10-it-IT @@ -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. diff --git a/docs/whatsnew/1.2.10-ja-JP b/docs/whatsnew/1.2.10-ja-JP new file mode 100644 index 0000000..06b6d83 --- /dev/null +++ b/docs/whatsnew/1.2.10-ja-JP @@ -0,0 +1 @@ +グラフの下部に時刻が再び表示されます。これまでは車の画面で切り取られていました。新しい測定値にも正しく追従するようになり、更新が1回遅れたり測定値を飛ばしたりしなくなります。隣に表示される数値は以前から常に最新でした。 diff --git a/docs/whatsnew/1.2.10-nl-NL b/docs/whatsnew/1.2.10-nl-NL new file mode 100644 index 0000000..05674f1 --- /dev/null +++ b/docs/whatsnew/1.2.10-nl-NL @@ -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. diff --git a/docs/whatsnew/1.2.10-pt-PT b/docs/whatsnew/1.2.10-pt-PT new file mode 100644 index 0000000..efff54b --- /dev/null +++ b/docs/whatsnew/1.2.10-pt-PT @@ -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. diff --git a/docs/whatsnew/1.2.10-zh-CN b/docs/whatsnew/1.2.10-zh-CN new file mode 100644 index 0000000..f566efd --- /dev/null +++ b/docs/whatsnew/1.2.10-zh-CN @@ -0,0 +1 @@ +图表底部的时间刻度重新显示出来了,此前会被车机屏幕裁掉。图表现在也能正确跟上新读数,不再出现晚一次刷新或漏掉一个读数的情况。旁边的读数一直都是最新的。