UI buildout: today screen, med editor with FEST autocomplete, schedule editor, settings

Milestone 9. Hand-rolled back-stack navigation (4 screens don't justify
navigation-compose). DoseActions is now the single implementation of
Taken/Snooze/Skip/Undo shared by notification receiver and UI — two
code paths for 'taken' would eventually disagree about inventory.
Screens read DAO Flows directly; every schedule mutation re-arms
alarms via armAll() (idempotent, OS-deduplicated). One-time battery
prompt + POST_NOTIFICATIONS request on the today screen. Settings:
S3/Garage config, typed-passphrase export/import (replace-all import
re-arms alarms), FEST refresh, PDF share. TalkBack: merged semantics
with full descriptions on dose cards, content descriptions on icon
buttons and day chips. Doctor PDF locale pinned to Bokmål — verified
on emulator after catching English month names on an en-US device.

Emulator-verified end-to-end: battery prompt → system dialog, med
created via editor (validation catches bad input), dose time added →
exact alarm armed immediately, Ta nå → TAKEN + inventory 20→19 +
reactive UI update, Angre → PENDING + inventory restored, settings
renders, PDF generated and share sheet opened.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Ole-Morten Duesund 2026-06-10 14:22:29 +02:00
commit 2e7eadeb39
13 changed files with 1390 additions and 100 deletions

View file

@ -31,7 +31,19 @@ class DoctorSummaryPdf(private val context: Context) {
const val ADHERENCE_WINDOW_DAYS = 30L
}
private val dateFmt = DateTimeFormatter.ofPattern("d. MMMM yyyy")
// Locale pinned to Bokmål: the system formatter would print English month
// names on a non-Norwegian device, and this document goes to a Norwegian GP.
private val dateFmt = DateTimeFormatter.ofPattern("d. MMMM yyyy", java.util.Locale.forLanguageTag("nb"))
private val formLabels = mapOf(
no.naiv.meddetsamme.data.MedForm.TABLET to "Tablett",
no.naiv.meddetsamme.data.MedForm.CAPSULE to "Kapsel",
no.naiv.meddetsamme.data.MedForm.LIQUID to "Flytende",
no.naiv.meddetsamme.data.MedForm.INJECTION to "Injeksjon",
no.naiv.meddetsamme.data.MedForm.DROPS to "Dråper",
no.naiv.meddetsamme.data.MedForm.SPRAY to "Spray",
no.naiv.meddetsamme.data.MedForm.OTHER to "Annet",
)
/** Generates the PDF and returns a ready-to-launch share chooser intent. */
suspend fun buildShareIntent(): Intent {
@ -91,7 +103,7 @@ class DoctorSummaryPdf(private val context: Context) {
line("${med.name} ${med.strength}".trim(), heading, 3f)
val formLine = buildString {
append(med.form.name.lowercase().replaceFirstChar { it.uppercase() })
append(formLabels[med.form] ?: med.form.name)
if (med.withFood) append(" — tas med mat")
med.atcCode?.let { append(" · ATC $it") }
}