Commit graph

8 commits

Author SHA1 Message Date
eeed098b1a Cyclic and tapering dose schedules (schema v3)
Extends DoseTime with two orthogonal filters, as the brief foresaw
('cyclic/taper extend from the same model'):
- Cyclic (cycleActiveDays/cycleLengthDays from anchor): N-on/M-off, e.g.
  21/28 contraception. occursOn gains a cycle-window check; nextOccurrence
  is now a uniform day-by-day scan over occursOn (replacing the interval
  fast-path) so weekly/interval/cyclic/windowed all fall out of one
  correct path.
- Validity window (startEpochDay/endEpochDay, inclusive): a taper is
  several daily rows with descending amount and adjacent windows.
dailyConsumption now takes : cyclic scales by active/length, and
rows outside their window contribute 0 so a finished taper step stops
inflating days-of-supply.

UI: dose-time dialog gains a Syklisk section (på/av days) and is now
scrollable; a 'Lag nedtrapping' generator creates the windowed rows from
start dose / step-down / days-per-step / step-count. ScheduleText renders
'· syklus 21/28' and '· 11. juni–13. juni'.

Migration 2→3 is additive ADD COLUMNs with defaults matching the entity
(@ColumnInfo defaultValue), proven by an instrumented MigrationTestHelper
test. Backup DTO extended with defaults so v2 files still parse. 9 new
engine unit tests; versionCode 3 / 0.3.0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 22:09:51 +02:00
8c3cd0c965 Don't nag for future doses: time-guard escalation resume
'Ta nå' + 'Angre' on a not-yet-due dose leaves a PENDING log with a
future scheduledAt; armAll() (every app start/update) then resumed
'escalation' for it as if it were overdue — nagging hours early. Three
layers: armAll only resumes escalation for past-due logs, the
escalation receiver drops anything scheduled in the future (defense
in depth), and undo resets nagCount + cancels any escalation so stale
counts can't eat the real quota when the dose actually falls due.

Emulator-verified with both cases side by side: past-due PENDING nags
within the minute, future PENDING stays silent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 18:26:46 +02:00
80c8c4788a New launcher icon, version 0.2.0, lint cleanup
Icon: a capsule in a hurry — stadium pill at 45° with motion lines
trailing, «med det samme» as imagery. versionCode 2 / versionName 0.2.0
with a bump-on-release note. Lint: KTX conversions (prefs.edit{},
toUri, createBitmap), mipmap-anydpi-v26 merged into mipmap-anydpi
(minSdk 26 makes the qualifier dead weight), compose-bom 2026.05.01,
Gradle wrapper 9.5.1, and the three deliberate-decision warnings
(BatteryLife/OldTargetApi/NewerVersionAvailable) disabled with
documented reasons. Lint now runs clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 17:55:06 +02:00
9b99f2cf62 Typography: Manrope headings, Inter body — plus norsk dag/dager pluralization
Manrope (variable wght, instanced at 600/700/800) for display/headline/
title roles; Inter static Regular/Medium/SemiBold for body/label. M3
baseline metrics retained — only family and weight change, so component
layouts and accessibility text scaling are untouched. Both fonts are
OFL (THIRD_PARTY_LICENSES.md added) with full æøå/µ coverage, verified
by emulator screenshot. ScheduleText.daysCount fixes 'lager for 1
dager' spotted in that screenshot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 17:09:07 +02:00
608d84236b Rx-expiry ongoing alert (7-day lead), configurable morning check, Felleskatalogen links
The daily digest splits in two: low stock stays an auto-cancel
notification re-posted each morning while below the per-item limit;
prescription renewal becomes a separate ONGOING notification starting
7 days before rx expiry — survives 'Clear all', swipe-dismissable on
Android 14+, returns each morning until the rx date is updated, and
cancels itself once renewed (post-or-cancel in SupplyCheckReceiver).
Check time is now configurable (Innstillinger → Varsler, M3 TimePicker,
default 10:00); same PendingIntent so re-arming replaces. 'Slå opp i
Felleskatalogen' buttons on the item editor and the med editor's
summary card open the browser — decision #8 clarified: the ban covers
Felleskatalogen as a data source, not human-facing links.

Emulator-verified: both notifications post with correct flags
(ONGOING_EVENT vs AUTO_CANCEL), auto-clear after renewal/restock,
alarm follows the configured time (10:00 → 07:30), FK button opens
Chrome.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 16:21:25 +02:00
9b5817fcef Inventory split: InventoryItem entity, schema v2 with tested migration, Lager screen
Stock, package size and prescription state move to inventory_item;
medication is now a regimen (itemId FK RESTRICT — deleting a stock item
can never silently take adherence history with it). Supply warnings
aggregate consumption across all regimens sharing an item. New Lager
screen with one-tap '+1 pakning' restock; med editor picks an existing
item or creates one inline via the shared ItemFormState (one form, no
drift). Backup format v2 mirrors the split and still restores v1 files
by performing the same conversion the DB migration does.

MIGRATION_1_2 statements are copied from the exported 2.json and proven
by an instrumented MigrationTestHelper test on the emulator (validates
against the schema AND asserts data survival) before it ever touches
the phone's medical record. Heads-up: connectedAndroidTest must target
the emulator (ANDROID_SERIAL) — the phone's release signature rejects
debug test APKs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 15:13:28 +02:00
2e7eadeb39 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>
2026-06-10 14:22:29 +02:00
46a5d7e98e Reminder subsystem: exact alarms, escalation, boot re-arm — verified on emulator
Milestone 4. AlarmScheduler is the only AlarmManager writer; one stable
PendingIntent requestCode per dose-time means re-arming replaces and can
never stack (the no-drift invariant is OS-enforced). DoseAlarmReceiver
handles both occurrence and escalation alarms idempotently and always
re-arms the next occurrence before anything else so the chain can't
break. nagCount lives in dose_log so the ~6-nag cap survives reboot.
SCHEDULE_EXACT_ALARM added maxSdk 32 (USE_EXACT_ALARM is 33+ only).

Emulator-verified (API 35): exact alarm armed (window=0,
policy_permission), fired on time, HIGH notification with Tatt/Utsett,
escalation armed +10 min, next day re-armed, Tatt → TAKEN + inventory
10→9 + escalation cancelled, reboot → BootReceiver re-armed everything.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 13:48:39 +02:00