Medisin medisin med det samme. En mindre masete løsning enn andre.
  • Kotlin 97.8%
  • Python 2.2%
Find a file
Ole-Morten Duesund e16fd72d06 In-app preview of the doctor summary before sharing
New SummaryPreviewScreen renders the generated PDF page by page with
the platform PdfRenderer (2x scale, no new dependencies) and puts share
in the top bar — see before you send. The settings button now opens the
preview instead of jumping straight to the share sheet; the share path
reuses the already-rendered file via DoctorSummaryPdf.shareIntentFor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 17:27:59 +02:00
app In-app preview of the doctor summary before sharing 2026-06-11 17:27:59 +02:00
docs FEST: in-repo flattener bundles slim.json; release builds auto-refresh when stale 2026-06-10 15:19:49 +02:00
gradle Inventory split: InventoryItem entity, schema v2 with tested migration, Lager screen 2026-06-10 15:13:28 +02:00
tools FEST: in-repo flattener bundles slim.json; release builds auto-refresh when stale 2026-06-10 15:19:49 +02:00
.gitignore Release signing from gitignored keystore.properties 2026-06-10 14:27:40 +02:00
build.gradle.kts Project skeleton: Gradle 9.4.1 wrapper, AGP 9.2.1, Compose, manifest with reminder permission set 2026-06-10 13:31:39 +02:00
CLAUDE.md Rx-expiry ongoing alert (7-day lead), configurable morning check, Felleskatalogen links 2026-06-11 16:21:25 +02:00
gradle.properties Project skeleton: Gradle 9.4.1 wrapper, AGP 9.2.1, Compose, manifest with reminder permission set 2026-06-10 13:31:39 +02:00
gradlew Project skeleton: Gradle 9.4.1 wrapper, AGP 9.2.1, Compose, manifest with reminder permission set 2026-06-10 13:31:39 +02:00
gradlew.bat Project skeleton: Gradle 9.4.1 wrapper, AGP 9.2.1, Compose, manifest with reminder permission set 2026-06-10 13:31:39 +02:00
med-det-samme-build-prompt.md Project skeleton: Gradle 9.4.1 wrapper, AGP 9.2.1, Compose, manifest with reminder permission set 2026-06-10 13:31:39 +02:00
README.md README: credit the app name's origin — Arve Opsahl's 'Medisin, medisin med det samme!' (1962) 2026-06-11 17:17:24 +02:00
settings.gradle.kts Project skeleton: Gradle 9.4.1 wrapper, AGP 9.2.1, Compose, manifest with reminder permission set 2026-06-10 13:31:39 +02:00
THIRD_PARTY_LICENSES.md Typography: Manrope headings, Inter body — plus norsk dag/dager pluralization 2026-06-11 17:09:07 +02:00

Med det samme

A local-first Android medication reminder. «Med det samme» — "right away" — is the design brief in three words: it nags you to take the dose now.

The name tips its hat to Arve Opsahl's 1962 classic «Medisin, medisin med det samme!» — a man demanding his medicine right away, which is exactly what this app does on his behalf.

Built as a personal replacement for MyTherapy: no ads, no account, no analytics, no cloud service. Single user, single device. Reminders are the entire point, so reliability beats polish wherever the two trade off.

Features

  • Escalating reminders — a due dose posts a notification and re-nags every 10 minutes (capped at ~6) until you tap Tatt or Utsett. Snooze defers 15 minutes and keeps nagging. The nag counter lives in the database, so a reboot mid-escalation resumes where it left off.
  • Flexible schedules — time-of-day + weekday mask (different times on different days) or every-N-days from an anchor date. All schedule math is a pure, unit-tested engine.
  • Inventory — physical stock is its own entity with one-tap package restock. Taking a dose decrements stock; refill warnings are derived from stock ÷ scheduled consumption, never manually scheduled. Prescription renewal (expiry, reiterutleveringer) is tracked separately from stock — you can have pills and a dead e-resept, or vice versa.
  • Offline drug autocomplete — a slim extract of FEST (the open national dataset from DMP) is bundled in the APK and searched offline. Release builds auto-refresh the bundle when it's >30 days old.
  • Encrypted backups you can leave with — versioned JSON, encrypted as a standard age v1 file (scrypt/passphrase mode), uploaded nightly to a self-hosted S3-compatible bucket (Garage) with a hand-rolled SigV4 signer. Any backup decrypts on any machine with plain age -d — no app lock-in.
  • Doctor summary — a one-page PDF (current meds, schedules, supply, rx status, 30-day adherence) generated with the platform PdfDocument and shared via the system sheet. Human-readable, distinct from the machine-readable backup.

Reliability design

Android actively fights background work; a medication app has to fight back:

  • Exact alarms (USE_EXACT_ALARM, plus SCHEDULE_EXACT_ALARM on API 3132) with setExactAndAllowWhileIdle, always gated on canScheduleExactAlarms() with an inexact fallback.
  • Re-arm on boot and app update (BOOT_COMPLETED, MY_PACKAGE_REPLACED) — AlarmManager state is wiped on both; every alarm is rebuilt from the DB.
  • No alarm drift by construction — the alarm layer holds at most one pending alarm per dose-time and always recomputes from the database via the pure engine. Reboot, update and "Taken" all just re-ask. PendingIntent identity (stable requestCode + FLAG_UPDATE_CURRENT) makes stacking impossible at the OS level.
  • One-time battery-optimisation exemption prompt — the biggest cause of dropped reminders on aggressive OEMs.
  • One implementation of Taken/Snooze/Skip (DoseActions), shared by the notification actions and the UI, so the two can never disagree about inventory or escalation state.

Data model

InventoryItem  — physical stock: name, strength, unit, form, ATC,
                 package size, stock, low-stock lead, rx expiry/refills
Medication     — a dosing regimen referencing an item (RESTRICT FK:
                 deleting stock can never erase adherence history)
DoseTime       — minute-of-day + weekday mask (bit 0 = Sunday) or
                 every-N-days from an anchor epoch day
DoseLog        — one scheduled occurrence and its outcome
                 (PENDING/TAKEN/SKIPPED/SNOOZED), amount snapshotted
ScheduleEngine — pure: next occurrence, daily consumption,
                 days-of-supply, needs-refill, needs-renewal, adherence

Room with exported schemas committed under app/schemas/; migrations are proven by instrumented MigrationTestHelper tests before they touch real data. Google Auto Backup is disabled (allowBackup=false + full dataExtractionRules opt-out) — health data leaves the device only through the app's own encrypted backup.

Backup format

{"version": 2, "inventoryItems": [...], "medications": [...], "doseTimes": [...], "doseLogs": [...]} — pretty-printed JSON, then encrypted. Decrypt anywhere:

age -d backup-20260610T020000Z.json.age | jq .

Version 1 files (pre inventory split) still import. The JCE baseline format (MDS1 header, PBKDF2-HMAC-SHA256 → AES-256-GCM) remains readable; new backups are always age. The age implementation (kage) is verified in unit tests against the C2SP CCTV scrypt vectors.

FEST dataset

tools/fest_flatten.py (stdlib Python) downloads DMP's M30 Rekvirent extract, streams the ~115 MB XML, and flattens ~9 000 active human-use preparations into app/src/main/assets/fest/slim.json. assembleRelease runs it automatically: fresh file → fast skip; download failure → keep the old file (an offline release build never breaks). See docs/fest-dataset.md for the JSON contract. Felleskatalogen is deliberately not used: licensed editorial content, no open API.

Building

Requirements: JDK 17 and an Android SDK with platform 37.

./gradlew assembleDebug          # debug build
./gradlew test                   # unit tests (engine, crypto, SigV4, FEST search)
./gradlew connectedDebugAndroidTest   # migration tests (needs an emulator/device)
./gradlew assembleRelease        # signed if keystore.properties exists, else unsigned

Release signing reads a gitignored keystore.properties at the repo root (storeFile/storePassword/keyAlias/keyPassword); without it the release build is unsigned rather than failing.

  • minSdk 26, targetSdk 35, compileSdk 37
  • Kotlin + Jetpack Compose (Material3), Room (+KSP), WorkManager, OkHttp, kotlinx-serialization, kage — Gradle Kotlin DSL with a version catalog, no DI framework, no AWS SDK.

Out of scope

Caregiver alerts, multi-profile, Health Connect, streaks/gamification, symptom diary. This is one person's medication reminder, not a platform.