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
1 change: 0 additions & 1 deletion .claude/commands/commit.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
---
description: Commit recent changes using a conventional commit message, after syncing with the remote.
allowed-tools: Bash(git status:*), Bash(git pull:*), Bash(git diff:*), Bash(git add:*), Bash(git commit:*), Bash(git push:*), AskUserQuestion, Edit, Write
context: fork
---
Commit recent changes using a conventional commit message, after syncing with the remote.

Expand Down
1 change: 0 additions & 1 deletion .claude/commands/release.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
---
description: Bump the app version (major/minor/patch or explicit semver), update CHANGELOG.md, and commit the release.
allowed-tools: Bash(git status:*), Bash(git pull:*), Bash(git diff:*), Bash(git add:*), Bash(git commit:*), Bash(git push:*), Read, Edit, Bash, AskUserQuestion, TodoWrite, TodoRead
context: fork
---

You are preparing a new release for the AutoSugar Android app. Your job is to determine the new version number, update `app/build.gradle.kts`, restructure `CHANGELOG.md`, and commit the result.
Expand Down
41 changes: 41 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: CodeQL

on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
schedule:
- cron: '0 4 * * 1'

jobs:
analyze:
name: Analyze (Kotlin)
runs-on: ubuntu-latest
permissions:
security-events: write
actions: read
contents: read

steps:
- uses: actions/checkout@v4

- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17

- uses: gradle/actions/setup-gradle@v3

- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: java-kotlin

- name: Build
run: ./gradlew assembleDebug

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: /language:java-kotlin
13 changes: 11 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,23 @@ jobs:
if: steps.tag_check.outputs.exists == 'false'
run: echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > keystore.jks

- name: Build signed AAB
- name: Build signed APK and AAB
if: steps.tag_check.outputs.exists == 'false'
env:
KEYSTORE_PATH: keystore.jks
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
run: ./gradlew bundleRelease
run: ./gradlew assembleRelease bundleRelease

- name: Upload APK to GitHub Release
if: steps.tag_check.outputs.exists == 'false'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload "${{ steps.version.outputs.tag }}" \
app/build/outputs/apk/release/app-release.apk \
--clobber

- name: Prepare what's new for Google Play
if: steps.tag_check.outputs.exists == 'false'
Expand Down
99 changes: 18 additions & 81 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,93 +5,30 @@ Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Unreleased]

### Changed
- `/commit` command now includes frontmatter metadata for Claude Code command system integration

### Added
- Unit tests for GlucoseEntry display logic (displayValue, displayDelta, trendArrow)
- Unit tests for ProfileSerializer JSON round-trip including alertsEnabled and icon fallback
- Expanded NightscoutRepository unit tests covering all methods (getCurrentEntry delta
handling, getThresholds defaults, getHistory, saveProfile upsert, deleteProfile, saveAll)

### Fixed
- Remove hardcoded local JDK path from gradle.properties so CI builds succeed on GitHub Actions
- Add missing German translations for glucose alert strings (lint MissingTranslation)

### Added
- Per-profile glucose alert notifications for high and low BG using
Nightscout's bgHigh/bgLow thresholds, with 15-minute repeat cooldown
- Predictive alerts: 15-min linear extrapolation warns before a
threshold is crossed (requires delta data from Nightscout)
- POST_NOTIFICATIONS permission for Android 13+ alert support

### Changed
- Settings screen: profile cards show an alerts toggle; tap to edit,
long-press to drag-reorder profiles; unit removed from card display
- Profile edit screen: save moved to top-bar icon, test connection
placed directly below API token field, delete button moved to bottom
- `/commit` command now auto-stages recent changes and prompts about other unstaged files instead of stopping when nothing is staged

### Added
- Monochrome adaptive icon (`ic_launcher_monochrome.xml`) for Android 13+ themed icon support
- `<monochrome>` layer added to `ic_launcher.xml` adaptive icon definition

### Changed
- Adaptive icon background color updated from `#F4EFE4` to `#302919`

### Added
- Fetch glucose target thresholds (`bgTargetBottom`/`bgTargetTop`) from Nightscout `/api/v1/status.json` and use them for the graph's target band and dynamic Y-axis scaling
- `StatusDto`, `SettingsDto`, `ThresholdsDto` DTOs for Nightscout status response
- `getStatus()` endpoint in `NightscoutApi` and `getTargetRange()` in `NightscoutRepository`
- CodeQL security analysis workflow (runs on push/PR to main and develop, weekly schedule)

### Changed
- Graph Y-axis now scales dynamically to fit actual readings plus the target range (with padding), instead of a fixed 40–400 mg/dL range
- Time labels moved to the bottom of the graph; grid lines skip values too close to chart edges

### Added
- Glucose history graph in Car UI: 3-hour bitmap rendered via `Pane.setImage()` with target range band, dotted grid lines, drop-pin value labels at 20-min marks, and half-hour time labels
- Reading age ("X min ago") and received timestamp shown as secondary row in Car UI
- Expanded profile icon set: male, female, boy, girl, baby, elderly man, elderly woman variants
- German translations for all new strings
- Removed `context: fork` from commit and release command frontmatter

### Fixed
- Profile icon now shown in Settings overview list
- Glucose unit displayed as `mg/dL` / `mmol/L` (was `MG/DL` / `MMOL/L`)
- Release signing keystore path now resolved relative to project root via `rootProject.file()`

### Changed
- Icon picker in profile edit screen uses `FlowRow` for dynamic wrapping instead of fixed 4-per-row

- TabTemplate for multi-profile switching (CarApi >= 6, 2–4 profiles); falls back to numbered icon ActionStrip
- Trend arrow rendered as bitmap image in Car UI row; delta shown below glucose value
- Configurable auto-refresh interval (30 s / 1 min / 2 min / 5 min) via Settings
- Configurable tab icon per profile (Person, Home, Heart, Star, Car, Medical) with icon picker in profile edit screen
- App launcher icon and mipmap assets
- DHU simulator setup guide (`TESTING.md`)
- `AppPreferencesDataStore` for persisting app-level preferences
- History endpoint (`getEntries`) in Nightscout API client

### Fixed
- App not appearing in Android Auto (missing `com.google.android.gms.car.application` meta-data)
- Crash on `ActionStrip` exceeding max 1 titled action
- Glucose unit labels shown in uppercase (now `mg/dL` / `mmol/L`)
- Java 17 toolchain required for Kotlin 2.0.21 compatibility
## [1.0.0] - 2026-03-31

### Added
- Gradle wrapper (8.9) so CI runners can build without a local Gradle install
- GitHub Actions CI workflow with separate Build, Unit Tests, and Lint jobs
- Branch strategy documented in AGENTS.md: `main` is protected, all work on `develop`
- Android Auto app for monitoring Nightscout CGM blood glucose data while driving
- Multi-profile support: monitor multiple Nightscout sources simultaneously with configurable tab icons and per-profile settings
- Car UI: current glucose value, trend arrow, delta, reading age, and 3-hour history graph with target range band
- Glucose alerts: high/low notifications with 15-minute cooldown and predictive alerts based on 15-min linear extrapolation
- Configurable auto-refresh interval (30 s, 1 min, 2 min, 5 min)
- Profile icon picker with male, female, boy, girl, baby, elderly man, and elderly woman variants
- Monochrome adaptive icon for Android 13+ themed icon support
- App UI translated into English, German, Spanish, French, Dutch, Italian, Portuguese, Arabic, Japanese, Chinese (Simplified), and Hindi
- Unit tests for GlucoseEntry display logic, ProfileSerializer JSON round-trip, and NightscoutRepository
- GitHub Actions CI workflow (Build, Unit Tests, Lint) and release workflow with signed APK and AAB artifacts
- Dedicated installation page and configuration guide on the project website

### Changed
- Replaced deprecated `kotlinOptions { jvmTarget }` with `kotlin { jvmToolchain(17) }` in app build script
- MIT License (Copyright 2026 Nico Wiedemann)
- Full Android project scaffold: Gradle version catalog, root and app-level build files, ProGuard rules
- `AndroidManifest.xml` with `CarAppService` (IOT category), `MainActivity`, internet and foreground-service permissions
- Data layer: `NightscoutProfile` and `GlucoseEntry` domain models, `EntryDto` Moshi DTO, `NightscoutApi` Retrofit interface, `NightscoutApiFactory` with per-URL instance caching and OkHttp logging interceptor
- Persistence: `ProfileDataStore` (DataStore<Preferences>) and `ProfileSerializer` (Moshi JSON) for storing N Nightscout profiles
- `NightscoutRepository` singleton (Hilt) exposing profile CRUD, active-profile state, and coroutine-based `getCurrentEntry`
- Car UI screens: `GlucoseScreen` (PaneTemplate, 60 s polling, ActionStrip for ≤4 sources), `SourceSelectScreen` (ListTemplate for >4 sources), `NoProfilesScreen` (watches for profiles added on phone)
- Phone settings UI: `SettingsScreen` (profile list), `ProfileEditScreen` (form with Test Connection), `SettingsViewModel`, `ProfileEditViewModel`
- `MainActivity` with Compose navigation host
- String resources in English and German (full i18n coverage)
- ADR 001: architecture, IOT category, and multi-source strategy
- Unit tests for `NightscoutRepository` (MockK + coroutines-test)
### Fixed
- App not appearing in Android Auto launcher (missing `com.google.android.gms.car.application` meta-data)
- Glucose unit labels now correctly shown as `mg/dL` / `mmol/L`
6 changes: 3 additions & 3 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ android {
applicationId = "de.autosugar"
minSdk = 26
targetSdk = 35
versionCode = 1
versionName = "0.1.0"
versionCode = 2
versionName = "1.0.0"
}

signingConfigs {
create("release") {
storeFile = System.getenv("KEYSTORE_PATH")?.let { file(it) }
storeFile = System.getenv("KEYSTORE_PATH")?.let { rootProject.file(it) }
storePassword = System.getenv("KEYSTORE_PASSWORD")
keyAlias = System.getenv("KEY_ALIAS")
keyPassword = System.getenv("KEY_PASSWORD")
Expand Down
60 changes: 60 additions & 0 deletions app/src/main/res/values-ar/strings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">AutoSugar</string>

<!-- Car UI -->
<string name="label_glucose_value">سكر الدم</string>
<string name="label_trend">الاتجاه</string>
<string name="label_delta">التغيير</string>
<string name="label_unit_mgdl">mg/dL</string>
<string name="label_unit_mmoll">mmol/L</string>
<string name="action_switch_source">تبديل المصدر</string>
<string name="label_loading">جارٍ التحميل…</string>
<string name="error_fetch_failed">فشل جلب البيانات</string>
<string name="action_retry">إعادة المحاولة</string>
<string name="label_no_profiles">لم يتم تكوين أي مصادر.\nافتح AutoSugar على هاتفك لإضافة مصدر Nightscout.</string>
<string name="label_graph">3h</string>
<string name="label_just_now">الآن</string>
<string name="label_n_min_ago">منذ %d دقيقة</string>
<string name="label_reading">القراءة: %s</string>
<string name="label_received">تم الاستقبال: %s</string>

<!-- Settings UI -->
<string name="label_settings">الإعدادات</string>
<string name="btn_add_profile">إضافة مصدر</string>
<string name="btn_save">حفظ</string>
<string name="btn_delete">حذف</string>
<string name="btn_edit">تعديل</string>
<string name="btn_test_connection">اختبار الاتصال</string>
<string name="hint_display_name">اسم العرض</string>
<string name="hint_base_url">رابط Nightscout (مثال: https://mynb.herokuapp.com)</string>
<string name="hint_api_token">رمز API</string>
<string name="label_unit">وحدة الجلوكوز</string>
<string name="label_add_source">إضافة مصدر</string>
<string name="label_edit_source">تعديل المصدر</string>
<string name="msg_test_success">متصل! مستوى السكر الحالي: %1$s %2$s</string>
<string name="msg_test_failed">فشل الاتصال: %1$s</string>
<string name="msg_saved">تم حفظ المصدر.</string>
<string name="msg_deleted">تم حذف المصدر.</string>
<string name="label_no_sources">لا توجد مصادر بعد.\nاضغط + لإضافة رابط Nightscout الخاص بك.</string>
<string name="label_tab_icon">أيقونة التبويب</string>

<!-- Glucose alerts -->
<string name="label_glucose_alerts">تنبيهات الجلوكوز</string>
<string name="notif_channel_name">تنبيهات الجلوكوز</string>
<string name="notif_channel_desc">تنبيهات ارتفاع وانخفاض سكر الدم مع فترة راحة تكرار 15 دقيقة</string>
<string name="notif_title_high">ارتفاع السكر</string>
<string name="notif_title_low">انخفاض السكر</string>
<string name="notif_title_predicted_high">السكر في ارتفاع</string>
<string name="notif_title_predicted_low">السكر في انخفاض</string>
<string name="notif_text_predicted">متوقع خلال 15 دقيقة: %s</string>

<!-- Refresh interval -->
<string name="label_refresh_interval">فترة التحديث</string>
<string name="label_refresh_30s">30 ثانية</string>
<string name="label_refresh_1min">دقيقة</string>
<string name="label_refresh_2min">دقيقتان</string>
<string name="label_refresh_5min">5 دقائق</string>
<string name="label_app_version">الإصدار %s</string>
<string name="error_invalid_url">يجب أن يبدأ بـ http:// أو https://</string>
</resources>
60 changes: 60 additions & 0 deletions app/src/main/res/values-es/strings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">AutoSugar</string>

<!-- Car UI -->
<string name="label_glucose_value">Glucosa en sangre</string>
<string name="label_trend">Tendencia</string>
<string name="label_delta">Cambio</string>
<string name="label_unit_mgdl">mg/dL</string>
<string name="label_unit_mmoll">mmol/L</string>
<string name="action_switch_source">Cambiar fuente</string>
<string name="label_loading">Cargando…</string>
<string name="error_fetch_failed">Error al obtener datos</string>
<string name="action_retry">Reintentar</string>
<string name="label_no_profiles">Ninguna fuente configurada.\nAbre AutoSugar en tu teléfono para añadir una fuente de Nightscout.</string>
<string name="label_graph">3h</string>
<string name="label_just_now">ahora mismo</string>
<string name="label_n_min_ago">hace %d min</string>
<string name="label_reading">Lectura: %s</string>
<string name="label_received">Recibido: %s</string>

<!-- Settings UI -->
<string name="label_settings">Ajustes</string>
<string name="btn_add_profile">Añadir fuente</string>
<string name="btn_save">Guardar</string>
<string name="btn_delete">Eliminar</string>
<string name="btn_edit">Editar</string>
<string name="btn_test_connection">Probar conexión</string>
<string name="hint_display_name">Nombre para mostrar</string>
<string name="hint_base_url">URL de Nightscout (p. ej. https://mynb.herokuapp.com)</string>
<string name="hint_api_token">Token de API</string>
<string name="label_unit">Unidad de glucosa</string>
<string name="label_add_source">Añadir fuente</string>
<string name="label_edit_source">Editar fuente</string>
<string name="msg_test_success">¡Conectado! Glucosa actual: %1$s %2$s</string>
<string name="msg_test_failed">Conexión fallida: %1$s</string>
<string name="msg_saved">Fuente guardada.</string>
<string name="msg_deleted">Fuente eliminada.</string>
<string name="label_no_sources">Sin fuentes aún.\nPulsa + para añadir tu URL de Nightscout.</string>
<string name="label_tab_icon">Icono de pestaña</string>

<!-- Glucose alerts -->
<string name="label_glucose_alerts">Alertas de glucosa</string>
<string name="notif_channel_name">Alertas de glucosa</string>
<string name="notif_channel_desc">Alertas de glucosa alta y baja con intervalo de repetición de 15 minutos</string>
<string name="notif_title_high">Glucosa alta</string>
<string name="notif_title_low">Glucosa baja</string>
<string name="notif_title_predicted_high">Glucosa en tendencia alta</string>
<string name="notif_title_predicted_low">Glucosa en tendencia baja</string>
<string name="notif_text_predicted">Predicción en 15 min: %s</string>

<!-- Refresh interval -->
<string name="label_refresh_interval">Intervalo de actualización</string>
<string name="label_refresh_30s">30 seg</string>
<string name="label_refresh_1min">1 min</string>
<string name="label_refresh_2min">2 min</string>
<string name="label_refresh_5min">5 min</string>
<string name="label_app_version">Versión %s</string>
<string name="error_invalid_url">Debe comenzar con http:// o https://</string>
</resources>
Loading
Loading