From b19d5a566eed8dc92862b3f022d0a526b3e39b35 Mon Sep 17 00:00:00 2001 From: Nico Wiedemann Date: Fri, 24 Jul 2026 19:54:04 +0200 Subject: [PATCH 1/5] fix(car): close remaining race window in profile-switch fetch guard switchTo() updated activeProfileId synchronously but only launched a new fetch(), which bumps fetchGeneration itself once its coroutine starts. An in-flight fetch for the profile being switched away from could still resolve in that gap, pass the stale gen check, and get evaluated/alerted under the newly-active profile's name with the old profile's data. fetchGeneration is now bumped synchronously in switchTo() itself, closing the window regardless of when the new fetch's coroutine is scheduled. Co-Authored-By: Claude Sonnet 5 --- CHANGELOG.md | 4 ++++ app/src/main/java/de/autosugar/car/GlucoseScreen.kt | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d729da..adc0182 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased] +### Fixed + +- Closed a narrow race where switching profiles right as an in-flight fetch for the previous profile resolved could show that profile's reading (and fire its alert) under the new profile's name; the fetch-generation guard is now bumped synchronously the moment a switch starts instead of only once the new fetch's coroutine runs + ## [1.2.4] - 2026-07-23 ### Security diff --git a/app/src/main/java/de/autosugar/car/GlucoseScreen.kt b/app/src/main/java/de/autosugar/car/GlucoseScreen.kt index 6c6b00d..f6e3eb3 100644 --- a/app/src/main/java/de/autosugar/car/GlucoseScreen.kt +++ b/app/src/main/java/de/autosugar/car/GlucoseScreen.kt @@ -412,6 +412,12 @@ class GlucoseScreen( private fun switchTo(profileId: String) { if (profileId == activeProfileId) return + // Invalidate any in-flight fetch for the old profile immediately, synchronously — + // otherwise it could still be awaiting its network call when activeProfileId flips, + // and fetch()'s own generation bump (which only happens once its coroutine actually + // starts running) may not have happened yet, letting the old profile's data slip + // through the gen check and get evaluated/alerted under the new profile's name. + fetchGeneration++ activeProfileId = profileId repository.setActiveProfile(profileId) entry = null From f5f2db556a7fa914ac6ff405a808aa62da3bec0a Mon Sep 17 00:00:00 2001 From: Nico Wiedemann Date: Fri, 24 Jul 2026 20:15:31 +0200 Subject: [PATCH 2/5] fix(car): mirror glucose alerts onto the Android Auto screen GlucoseAlertManager posted notifications via the plain NotificationManager, which only ever reaches the phone's notification tray. The Car App Library requires posting through CarNotificationManager (which also auto-extends the notification with CarAppExtender) before Android Auto will mirror it onto the car screen, and requires the automotive_app_desc.xml capability descriptor to declare the `notification` capability. buildNotification() now returns the NotificationCompat.Builder instead of a built Notification, and post() calls CarNotificationManager.notify() with it. Co-Authored-By: Claude Sonnet 5 --- CHANGELOG.md | 1 + .../main/java/de/autosugar/car/GlucoseAlertManager.kt | 9 ++++++--- app/src/main/res/xml/automotive_app_desc.xml | 1 + 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index adc0182..dcad0c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Fixed - Closed a narrow race where switching profiles right as an in-flight fetch for the previous profile resolved could show that profile's reading (and fire its alert) under the new profile's name; the fetch-generation guard is now bumped synchronously the moment a switch starts instead of only once the new fetch's coroutine runs +- Glucose alerts now appear on the Android Auto car screen instead of only the phone's notification tray: notifications are posted through `CarNotificationManager` and the app now declares the `notification` capability it requires ## [1.2.4] - 2026-07-23 diff --git a/app/src/main/java/de/autosugar/car/GlucoseAlertManager.kt b/app/src/main/java/de/autosugar/car/GlucoseAlertManager.kt index 51bcfa5..544b2a9 100644 --- a/app/src/main/java/de/autosugar/car/GlucoseAlertManager.kt +++ b/app/src/main/java/de/autosugar/car/GlucoseAlertManager.kt @@ -3,6 +3,7 @@ package de.autosugar.car import android.app.NotificationChannel import android.app.NotificationManager import android.content.Context +import androidx.car.app.notification.CarNotificationManager import androidx.core.app.NotificationCompat import de.autosugar.R import de.autosugar.data.model.GlucoseUnit @@ -29,6 +30,7 @@ class GlucoseAlertManager(private val context: Context) { } private val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + private val carNm = CarNotificationManager.from(context) init { nm.createNotificationChannel( @@ -92,18 +94,19 @@ class GlucoseAlertManager(private val context: Context) { return "$value $label" } - internal open fun buildNotification(title: String, text: String) = + internal open fun buildNotification(title: String, text: String): NotificationCompat.Builder = NotificationCompat.Builder(context, CHANNEL_ID) .setSmallIcon(R.drawable.ic_profile_medical) .setContentTitle(title) .setContentText(text) .setPriority(NotificationCompat.PRIORITY_HIGH) .setAutoCancel(true) - .build() + // Posted via CarNotificationManager (not the plain NotificationManager) so the alert is + // mirrored onto the Android Auto screen, not just the phone's notification tray. internal open fun post(id: Int, title: String, text: String) { try { - nm.notify(id, buildNotification(title, text)) + carNm.notify(id, buildNotification(title, text)) } catch (_: SecurityException) { // POST_NOTIFICATIONS not granted on Android 13+ — alerts silently suppressed } diff --git a/app/src/main/res/xml/automotive_app_desc.xml b/app/src/main/res/xml/automotive_app_desc.xml index 0fb852c..8b46ed0 100644 --- a/app/src/main/res/xml/automotive_app_desc.xml +++ b/app/src/main/res/xml/automotive_app_desc.xml @@ -1,4 +1,5 @@ + From b0d2ea75257b78b1c11786e34e13a0a1073070d1 Mon Sep 17 00:00:00 2001 From: Nico Wiedemann Date: Sat, 25 Jul 2026 02:08:40 +0200 Subject: [PATCH 3/5] feat(car): monitor all alert-enabled profiles in the background MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only the currently displayed profile ever had its data fetched and its alert thresholds checked; a profile with alertsEnabled=true that wasn't the active tab was never evaluated at all. Adds BackgroundAlertMonitor, which independently fetches and checks every alert-enabled profile from repository.profilesFlow, keyed throughout by profile id (own cooldown maps per alert type), so profiles can't cross-contaminate each other's data. AutoSugarSession starts it once carContext is available and polls at the configured refresh interval via lifecycleScope, which is cancelled automatically when the session's lifecycle is destroyed (Android Auto disconnects) — no separate Service, no polling while the app isn't in use. GlucoseScreen.checkAlerts()/alertManager/cooldown maps are removed since the monitor now covers the active profile too; keeping both would double- evaluate it against two independent cooldowns. Co-Authored-By: Claude Sonnet 5 --- CHANGELOG.md | 4 + .../java/de/autosugar/car/AutoSugarSession.kt | 32 ++++++- .../autosugar/car/BackgroundAlertMonitor.kt | 96 +++++++++++++++++++ .../java/de/autosugar/car/GlucoseScreen.kt | 68 +------------ 4 files changed, 132 insertions(+), 68 deletions(-) create mode 100644 app/src/main/java/de/autosugar/car/BackgroundAlertMonitor.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index dcad0c4..bbe5c94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased] +### Added + +- Alert-enabled profiles are now monitored in the background for as long as Android Auto is connected, not only the profile currently shown on screen; a profile that has alerts enabled but isn't the active tab now still gets checked and can still alert + ### Fixed - Closed a narrow race where switching profiles right as an in-flight fetch for the previous profile resolved could show that profile's reading (and fire its alert) under the new profile's name; the fetch-generation guard is now bumped synchronously the moment a switch starts instead of only once the new fetch's coroutine runs diff --git a/app/src/main/java/de/autosugar/car/AutoSugarSession.kt b/app/src/main/java/de/autosugar/car/AutoSugarSession.kt index b9484bb..c9b3f19 100644 --- a/app/src/main/java/de/autosugar/car/AutoSugarSession.kt +++ b/app/src/main/java/de/autosugar/car/AutoSugarSession.kt @@ -3,14 +3,42 @@ package de.autosugar.car import android.content.Intent import androidx.car.app.Screen import androidx.car.app.Session +import androidx.lifecycle.lifecycleScope import de.autosugar.data.repository.NightscoutRepository import de.autosugar.data.storage.AppPreferencesDataStore +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch class AutoSugarSession( private val repository: NightscoutRepository, private val appPrefs: AppPreferencesDataStore, ) : Session() { - override fun onCreateScreen(intent: Intent): Screen = - LoadingScreen(carContext, repository, appPrefs) + private var monitorStarted = false + + override fun onCreateScreen(intent: Intent): Screen { + if (!monitorStarted) { + monitorStarted = true + startBackgroundAlertMonitor() + } + return LoadingScreen(carContext, repository, appPrefs) + } + + // Started once carContext is available and runs only while Android Auto is connected: + // lifecycleScope is cancelled automatically when the session's lifecycle is destroyed + // (car disconnected), so alert-enabled profiles are checked even when they aren't the + // one currently shown on screen, without any polling happening while the app isn't in use. + private fun startBackgroundAlertMonitor() { + val monitor = BackgroundAlertMonitor(carContext, repository) + lifecycleScope.launch { + appPrefs.refreshIntervalSeconds.collectLatest { intervalSeconds -> + while (isActive) { + monitor.checkAll() + delay(intervalSeconds * 1000L) + } + } + } + } } diff --git a/app/src/main/java/de/autosugar/car/BackgroundAlertMonitor.kt b/app/src/main/java/de/autosugar/car/BackgroundAlertMonitor.kt new file mode 100644 index 0000000..f05fb96 --- /dev/null +++ b/app/src/main/java/de/autosugar/car/BackgroundAlertMonitor.kt @@ -0,0 +1,96 @@ +package de.autosugar.car + +import androidx.car.app.CarContext +import de.autosugar.data.model.GlucoseEntry +import de.autosugar.data.model.NightscoutProfile +import de.autosugar.data.repository.NightscoutRepository +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.first + +/** + * Evaluates alert thresholds for every alert-enabled profile, independent of which profile is + * currently shown on the Android Auto screen. Keyed throughout by each profile's own id, so + * (unlike the screen, which only ever tracks the single active profile) a profile that isn't + * on screen still gets its alerts checked, and profiles can never cross-contaminate each other's + * data or cooldowns. + */ +class BackgroundAlertMonitor( + carContext: CarContext, + private val repository: NightscoutRepository, +) { + private val alertManager = GlucoseAlertManager(carContext) + private val alertCooldownMs = 15 * 60_000L + + // A reading older than this is considered stale (≥2 missed 5-min CGM readings) and never + // triggers an alert, since acting on outdated glucose data is worse than not alerting. + private val staleAfterMs = 12 * 60_000L + + private val lastHighAlertMs = mutableMapOf() + private val lastLowAlertMs = mutableMapOf() + private val lastPredictedHighAlertMs = mutableMapOf() + private val lastPredictedLowAlertMs = mutableMapOf() + + // Recent history per profile, kept only to derive that profile's own reading cadence for + // the 15-minute-ahead projection. + private val historyByProfile = mutableMapOf>() + + suspend fun checkAll() = coroutineScope { + val profiles = repository.profilesFlow.first().filter { it.alertsEnabled } + profiles.map { profile -> async { checkProfile(profile) } }.awaitAll() + } + + private suspend fun checkProfile(profile: NightscoutProfile) = coroutineScope { + val entryResult = async { repository.getCurrentEntry(profile.id) } + val thresholdsResult = async { repository.getThresholds(profile.id) } + val historyResult = async { repository.getHistory(profile.id, count = 36) } + + val entry = entryResult.await().getOrNull() ?: return@coroutineScope + val thresholds = thresholdsResult.await().getOrNull() ?: return@coroutineScope + historyResult.await().getOrNull()?.let { h -> + historyByProfile[profile.id] = h.sortedBy { it.dateMs } + } + + val sgv = entry.sgv + val now = System.currentTimeMillis() + if (now - entry.dateMs > staleAfterMs) return@coroutineScope + + val id = profile.id + if (sgv >= thresholds.bgHigh && now - (lastHighAlertMs[id] ?: 0L) > alertCooldownMs) { + alertManager.sendHighAlert(id, profile.displayName, sgv, profile.unit) + lastHighAlertMs[id] = now + } + if (sgv <= thresholds.bgLow && now - (lastLowAlertMs[id] ?: 0L) > alertCooldownMs) { + alertManager.sendLowAlert(id, profile.displayName, sgv, profile.unit) + lastLowAlertMs[id] = now + } + + val delta = entry.delta ?: return@coroutineScope + // delta is the change over one reading interval; project 15 minutes ahead using + // the profile's actual sampling cadence rather than assuming a fixed 5-minute interval. + val projected15 = sgv + delta * projectionSteps(id) + + if (projected15 > thresholds.bgHigh && sgv < thresholds.bgHigh && + now - (lastPredictedHighAlertMs[id] ?: 0L) > alertCooldownMs + ) { + alertManager.sendPredictedHighAlert(id, profile.displayName, projected15, profile.unit) + lastPredictedHighAlertMs[id] = now + } + if (projected15 < thresholds.bgLow && sgv > thresholds.bgLow && + now - (lastPredictedLowAlertMs[id] ?: 0L) > alertCooldownMs + ) { + alertManager.sendPredictedLowAlert(id, profile.displayName, projected15, profile.unit) + lastPredictedLowAlertMs[id] = now + } + } + + private fun projectionSteps(profileId: String): Double { + val history = historyByProfile[profileId] ?: emptyList() + val gaps = history.zipWithNext { a, b -> b.dateMs - a.dateMs } + .filter { it in 60_000L..15 * 60_000L } + .sorted() + val intervalMs = if (gaps.isEmpty()) 5 * 60_000L else gaps[gaps.size / 2] + return 15 * 60_000.0 / intervalMs + } +} diff --git a/app/src/main/java/de/autosugar/car/GlucoseScreen.kt b/app/src/main/java/de/autosugar/car/GlucoseScreen.kt index f6e3eb3..416de58 100644 --- a/app/src/main/java/de/autosugar/car/GlucoseScreen.kt +++ b/app/src/main/java/de/autosugar/car/GlucoseScreen.kt @@ -74,19 +74,9 @@ class GlucoseScreen( private val trendIconCache = mutableMapOf() private val numberIconCache = mutableMapOf, CarIcon>() - private val alertManager = GlucoseAlertManager(carContext) - private val alertCooldownMs = 15 * 60_000L - - // A reading older than this is considered stale (≥2 missed 5-min CGM readings): - // it is labelled as such in the UI and never triggers alerts, since acting on - // outdated glucose data is worse than not alerting at all. + // A reading older than this is considered stale (≥2 missed 5-min CGM readings) and is + // labelled as such in the UI. Alerting itself is handled by BackgroundAlertMonitor. private val staleAfterMs = 12 * 60_000L - // Cooldown timers are keyed by profile id so switching profiles does not let one - // profile's recent alert suppress a genuine alert for another profile. - private val lastHighAlertMs = mutableMapOf() - private val lastLowAlertMs = mutableMapOf() - private val lastPredictedHighAlertMs = mutableMapOf() - private val lastPredictedLowAlertMs = mutableMapOf() init { lifecycleScope.launch { @@ -152,63 +142,9 @@ class GlucoseScreen( .onSuccess { t -> thresholds = t } } if (gen != fetchGeneration) return - checkAlerts() invalidate() } - private fun checkAlerts() { - val currentEntry = entry ?: return - val profile = profiles.find { it.id == activeProfileId } ?: return - if (!profile.alertsEnabled) return - - val sgv = currentEntry.sgv - val now = System.currentTimeMillis() - - // Never alert on stale data — the true current value is unknown. - if (now - currentEntry.dateMs > staleAfterMs) return - - val id = profile.id - if (sgv >= thresholds.bgHigh && now - (lastHighAlertMs[id] ?: 0L) > alertCooldownMs) { - alertManager.sendHighAlert(id, profile.displayName, sgv, profile.unit) - lastHighAlertMs[id] = now - } - if (sgv <= thresholds.bgLow && now - (lastLowAlertMs[id] ?: 0L) > alertCooldownMs) { - alertManager.sendLowAlert(id, profile.displayName, sgv, profile.unit) - lastLowAlertMs[id] = now - } - - val delta = currentEntry.delta ?: return - // delta is the change over one reading interval; project 15 minutes ahead using - // the actual sampling cadence rather than assuming a fixed 5-minute interval. - val projected15 = sgv + delta * projectionSteps() - - if (projected15 > thresholds.bgHigh && sgv < thresholds.bgHigh && - now - (lastPredictedHighAlertMs[id] ?: 0L) > alertCooldownMs - ) { - alertManager.sendPredictedHighAlert(id, profile.displayName, projected15, profile.unit) - lastPredictedHighAlertMs[id] = now - } - if (projected15 < thresholds.bgLow && sgv > thresholds.bgLow && - now - (lastPredictedLowAlertMs[id] ?: 0L) > alertCooldownMs - ) { - alertManager.sendPredictedLowAlert(id, profile.displayName, projected15, profile.unit) - lastPredictedLowAlertMs[id] = now - } - } - - /** - * How many reading intervals fit into a 15-minute look-ahead, derived from the median - * gap between recent history points. Falls back to a 5-minute cadence when unknown, so - * sources posting at 1- or 15-minute intervals project correctly instead of over/under. - */ - private fun projectionSteps(): Double { - val gaps = history.zipWithNext { a, b -> b.dateMs - a.dateMs } - .filter { it in 60_000L..15 * 60_000L } - .sorted() - val intervalMs = if (gaps.isEmpty()) 5 * 60_000L else gaps[gaps.size / 2] - return 15 * 60_000.0 / intervalMs - } - override fun onGetTemplate(): Template { return if (carContext.carAppApiLevel >= 6 && profiles.size in 2..4) { buildTabTemplate() From 24a6ff2563e562bb574f4fac53212c5a81c340d9 Mon Sep 17 00:00:00 2001 From: Nico Wiedemann Date: Sat, 25 Jul 2026 03:15:04 +0200 Subject: [PATCH 4/5] docs(release): verify every locale's what's-new stays under 500 chars MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The release skill only capped the English draft at 300 chars and assumed translations would stay under Google Play's 500-char per-locale limit as a result. Translation length varies significantly by language, so a short English draft doesn't guarantee every translation stays in bounds. Adds a mandatory step: after writing/translating each docs/whatsnew/X.Y.Z-{LOCALE} file, check that specific file's actual character count (wc -m, not wc -c, to handle multi-byte UTF-8 locales) and re-trim if over 500 — for every locale, not just English. Co-Authored-By: Claude Sonnet 5 --- .claude/commands/release.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.claude/commands/release.md b/.claude/commands/release.md index 32baed1..bfaa158 100644 --- a/.claude/commands/release.md +++ b/.claude/commands/release.md @@ -58,7 +58,9 @@ Create `docs/whatsnew/X.Y.Z-{LOCALE}` files for all supported locales. Each file Only include changes that are visible or relevant to the user of the app itself. Exclude anything related to CI/CD workflows, GitHub Actions, GitHub Pages, the website, internal tooling, or other infrastructure — users don't see these. -**Translation note:** Keep English concise (max 300 chars) so translations don't exceed GitHub's release notes limits (500 chars per language). Asian languages are more compact; Romance/Germanic languages expand. +**Translation note:** Keep English concise (max 300 chars) as a starting point, but the 300/500 numbers are guidance for the *English draft* only, not a guarantee for the rest. Translation length varies a lot by language (Romance/Germanic languages tend to expand, CJK languages tend to compress) — a translation that started from an in-limit English draft can still end up over the limit. + +**Mandatory per-locale check — do this for every locale, not just English:** after writing (or translating) each `docs/whatsnew/X.Y.Z-{LOCALE}` file, check that specific file's character count (e.g. `wc -m`, not `wc -c`, since `wc -c` undercounts multi-byte UTF-8 locales like `ar`/`hi-IN`/`ja-JP`/`zh-CN`). No locale file may exceed 500 characters — Google Play's hard "what's new" limit is per-locale, not per-release. If any file is over, re-trim that locale's text (summarize/combine bullets) and re-check — do not assume it's fine because the English source was short. **4b. Update `app/build.gradle.kts`** From eb5023bbf39b8f808ebd0ca8c3f1241a225e3015 Mon Sep 17 00:00:00 2001 From: Nico Wiedemann Date: Sat, 25 Jul 2026 08:15:50 +0200 Subject: [PATCH 5/5] chore(release): bump version to 1.2.5 Co-Authored-By: Claude Sonnet 5 --- CHANGELOG.md | 2 ++ app/build.gradle.kts | 4 ++-- docs/whatsnew/1.2.5-ar | 3 +++ docs/whatsnew/1.2.5-de-DE | 3 +++ docs/whatsnew/1.2.5-en-US | 3 +++ docs/whatsnew/1.2.5-es-ES | 3 +++ docs/whatsnew/1.2.5-fr-FR | 3 +++ docs/whatsnew/1.2.5-hi-IN | 3 +++ docs/whatsnew/1.2.5-it-IT | 3 +++ docs/whatsnew/1.2.5-ja-JP | 3 +++ docs/whatsnew/1.2.5-nl-NL | 3 +++ docs/whatsnew/1.2.5-pt-PT | 3 +++ docs/whatsnew/1.2.5-zh-CN | 3 +++ 13 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 docs/whatsnew/1.2.5-ar create mode 100644 docs/whatsnew/1.2.5-de-DE create mode 100644 docs/whatsnew/1.2.5-en-US create mode 100644 docs/whatsnew/1.2.5-es-ES create mode 100644 docs/whatsnew/1.2.5-fr-FR create mode 100644 docs/whatsnew/1.2.5-hi-IN create mode 100644 docs/whatsnew/1.2.5-it-IT create mode 100644 docs/whatsnew/1.2.5-ja-JP create mode 100644 docs/whatsnew/1.2.5-nl-NL create mode 100644 docs/whatsnew/1.2.5-pt-PT create mode 100644 docs/whatsnew/1.2.5-zh-CN diff --git a/CHANGELOG.md b/CHANGELOG.md index bbe5c94..85fe028 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased] +## [1.2.5] - 2026-07-25 + ### Added - Alert-enabled profiles are now monitored in the background for as long as Android Auto is connected, not only the profile currently shown on screen; a profile that has alerts enabled but isn't the active tab now still gets checked and can still alert diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ceb8232..b447b6b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -14,8 +14,8 @@ android { applicationId = "de.autosugar" minSdk = 26 targetSdk = 36 - versionCode = 9 - versionName = "1.2.4" + versionCode = 10 + versionName = "1.2.5" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } diff --git a/docs/whatsnew/1.2.5-ar b/docs/whatsnew/1.2.5-ar new file mode 100644 index 0000000..f7ec0e7 --- /dev/null +++ b/docs/whatsnew/1.2.5-ar @@ -0,0 +1,3 @@ +• التنبيهات الآن لكل مصدر، بذكر اسمه، ولا تُطلق خطأً على بيانات قديمة، ولا تعطل بعضها، ولا تُظهر (نادرًا) اسم مصدر خاطئ +• تظهر التنبيهات أيضًا على شاشة Android Auto، وتُفحص كل المصادر المفعّلة في الخلفية +• الوضع الداكن، ألوان الرسم حسب حدودك، وروابط Nightscout محلية/LAN diff --git a/docs/whatsnew/1.2.5-de-DE b/docs/whatsnew/1.2.5-de-DE new file mode 100644 index 0000000..226d55a --- /dev/null +++ b/docs/whatsnew/1.2.5-de-DE @@ -0,0 +1,3 @@ +• Alarme sind jetzt pro Quelle, nennen die Quelle und lösen nicht mehr fälschlich bei veralteten Daten aus, blockieren sich nicht mehr gegenseitig und zeigen (selten) nicht mehr die falsche Quelle +• Alarme erscheinen jetzt auch auf dem Android-Auto-Bildschirm, und jede aktivierte Quelle wird im Hintergrund geprüft +• Dunkelmodus, Graphenfarben nach deinen Grenzwerten und lokale/LAN-Nightscout-URLs diff --git a/docs/whatsnew/1.2.5-en-US b/docs/whatsnew/1.2.5-en-US new file mode 100644 index 0000000..a9e859d --- /dev/null +++ b/docs/whatsnew/1.2.5-en-US @@ -0,0 +1,3 @@ +• Alerts are now per-source, name the source, and no longer misfire on stale data, block each other, or (rarely) show the wrong source +• Alerts also appear on the Android Auto screen now, and every enabled source is checked in the background +• Dark mode, graph colours matching your thresholds, and local/LAN Nightscout URLs diff --git a/docs/whatsnew/1.2.5-es-ES b/docs/whatsnew/1.2.5-es-ES new file mode 100644 index 0000000..ccb9051 --- /dev/null +++ b/docs/whatsnew/1.2.5-es-ES @@ -0,0 +1,3 @@ +• Las alertas ahora son por fuente, indican la fuente y ya no fallan con datos antiguos, se bloquean entre sí ni (raramente) muestran la fuente equivocada +• Las alertas también aparecen ahora en la pantalla de Android Auto, y cada fuente activa se comprueba en segundo plano +• Modo oscuro, colores del gráfico según tus umbrales y URLs de Nightscout en local/LAN diff --git a/docs/whatsnew/1.2.5-fr-FR b/docs/whatsnew/1.2.5-fr-FR new file mode 100644 index 0000000..4b0d850 --- /dev/null +++ b/docs/whatsnew/1.2.5-fr-FR @@ -0,0 +1,3 @@ +• Les alertes sont désormais par source, nomment la source et ne se déclenchent plus à tort sur des données anciennes, ne se bloquent plus entre elles et n'affichent (rarement) plus la mauvaise source +• Les alertes s'affichent aussi maintenant sur l'écran Android Auto, et chaque source activée est vérifiée en arrière-plan +• Mode sombre, couleurs du graphique selon vos seuils, et URL Nightscout en local/LAN diff --git a/docs/whatsnew/1.2.5-hi-IN b/docs/whatsnew/1.2.5-hi-IN new file mode 100644 index 0000000..828121a --- /dev/null +++ b/docs/whatsnew/1.2.5-hi-IN @@ -0,0 +1,3 @@ +• अलर्ट अब प्रति-स्रोत, नाम सहित; पुराना डेटा, आपसी रोक, गलत नाम — अब नहीं +• Android Auto स्क्रीन पर भी अलर्ट; बैकग्राउंड में हर सक्षम स्रोत जाँच +• डार्क मोड; सीमा अनुसार ग्राफ़ रंग; localhost/LAN URL सेव diff --git a/docs/whatsnew/1.2.5-it-IT b/docs/whatsnew/1.2.5-it-IT new file mode 100644 index 0000000..7cde1f7 --- /dev/null +++ b/docs/whatsnew/1.2.5-it-IT @@ -0,0 +1,3 @@ +• Gli avvisi sono ora per sorgente, ne indicano il nome e non scattano più per errore su dati obsoleti, non si bloccano più a vicenda e (raramente) non mostrano più la sorgente sbagliata +• Gli avvisi ora appaiono anche sullo schermo di Android Auto, e ogni sorgente attiva viene controllata in background +• Modalità scura, colori del grafico secondo le tue soglie e URL Nightscout locali/LAN diff --git a/docs/whatsnew/1.2.5-ja-JP b/docs/whatsnew/1.2.5-ja-JP new file mode 100644 index 0000000..3a3db33 --- /dev/null +++ b/docs/whatsnew/1.2.5-ja-JP @@ -0,0 +1,3 @@ +• アラートはソースごとになり、ソース名を表示し、古いデータで誤作動せず、互いに妨げず、(まれに)別のソース名を表示しなくなりました +• アラートはAndroid Autoの画面にも表示され、有効な各ソースがバックグラウンドで確認されます +• ダークモード、しきい値に応じたグラフの色、localhost/LANのNightscout URLを追加 diff --git a/docs/whatsnew/1.2.5-nl-NL b/docs/whatsnew/1.2.5-nl-NL new file mode 100644 index 0000000..d90eccc --- /dev/null +++ b/docs/whatsnew/1.2.5-nl-NL @@ -0,0 +1,3 @@ +• Meldingen zijn nu per bron, noemen de bron en gaan niet meer per ongeluk af bij oude data, blokkeren elkaar niet meer en tonen (zelden) niet meer de verkeerde bron +• Meldingen verschijnen nu ook op het Android Auto-scherm, en elke actieve bron wordt op de achtergrond gecontroleerd +• Donkere modus, grafiekkleuren volgens je drempels, en lokale/LAN Nightscout-URL's diff --git a/docs/whatsnew/1.2.5-pt-PT b/docs/whatsnew/1.2.5-pt-PT new file mode 100644 index 0000000..50a3192 --- /dev/null +++ b/docs/whatsnew/1.2.5-pt-PT @@ -0,0 +1,3 @@ +• Os alertas são agora por fonte, indicam a fonte e já não disparam por engano com dados antigos, não se bloqueiam entre si e (raramente) não mostram a fonte errada +• Os alertas aparecem agora também no ecrã do Android Auto, e cada fonte ativa é verificada em segundo plano +• Modo escuro, cores do gráfico segundo os seus limites, e URLs Nightscout locais/LAN diff --git a/docs/whatsnew/1.2.5-zh-CN b/docs/whatsnew/1.2.5-zh-CN new file mode 100644 index 0000000..9f95307 --- /dev/null +++ b/docs/whatsnew/1.2.5-zh-CN @@ -0,0 +1,3 @@ +• 提醒现在按数据源区分、显示来源名称,不再因旧数据误报、互相抑制,也(极少数情况下)不再显示错误来源 +• 提醒现在也会显示在 Android Auto 屏幕上,且每个已启用的数据源都会在后台被检查 +• 新增深色模式、按你的阈值着色的图表,以及本地/局域网 Nightscout 网址