feat(android): notifications of due reminders with a Done action #290

Merged
kfickel merged 5 commits from 288_android-reminder-notifications into main 2026-10-17 15:20:07 +02:00
Collaborator

Closes #288.

The Android app now posts a notification when a reminder command (R29) comes due. Where the reminder has an into_file, the notification has a Done action. Done marks the reminder exactly as the banner on the Stream tab does: it writes the line, then commits and pushes according to the Changes switches, without opening the app.

Core and bindings

  • reminder::next_due (due.rs) gives the earliest moment after now at which a reminder that is not due now comes due:

    • a days rule: the start of its day, DST-safe, in the configured timezone;
    • a since rule: the next occurrence of its recurrence.

    It reuses finding / expected_moments and has no rules of its own. The facade has api::next_reminder_due, and the bindings have Project.nextReminderDue.

  • DueReminder in the bindings gains text (the description, else the key), which the notification uses as its title.

Android

  • Refactor: the RepositoryConnection, the StreamSession and the PinStore now belong to a new Streamd object, held by a new StreamdApplication. They no longer belong to the view model. The screens and the workers share one clone, and the app shows a done from a notification without reloading. Streamd.ready() lets a worker start the connection and wait for a read stream. The view model's behaviour is unchanged, and the screenshot tests are unchanged.
  • notify/: nothing outside this package names WorkManager or NotificationManager, apart from the manifest and StreamdApplication.
    • ReminderAlerts.kt is pure and unit-tested. A reminder rings when it comes due or its since changes, and again on a later day from 08:00 on. Otherwise it is updated silently, and only while the notification is still shown, so a swiped-away notification waits for the next ring. It is withdrawn once the reminder is no longer due.
    • ReminderWork:
      • a one-time job at the next due moment or the next 08:00, whichever is earlier;
      • a 3-hour periodic fallback;
      • an expedited DoneWorker, started by ReminderDoneReceiver.
    • A background check pulls first when the network is validated, and checks the local clone anyway if the pull fails. While the app runs, every new Ready stream triggers a check without a pull.
    • The POST_NOTIFICATIONS permission is asked for once, the first time the project has a reminder command.
  • WorkManager 2.12.0 has been added, and android/deps.json has been written again.

Tests

  • Core: next_due with days (with and without start, and with a future start), daily and timed since, across a DST change in Berlin, an already-due reminder, and the earliest of several. The facade is tested on a MemoryStore, and the ffi has a test too.
  • App: ReminderAlertsTest (9 tests), StreamdTest, and StreamSessionTest for text, since and nextDue.
  • nix flake check passes. nix build .#streamd-app passes, including the unit tests and the unchanged screenshot tests.
  • On the emulator, the app starts, ReminderWorker runs, and the fallback job is scheduled.
  • Not yet checked by hand: a reminder actually notifying, Done committing and pushing, a denied permission, and behaviour after a reboot. These need a connected repository on the emulator.

Notes

  • When the scheduled check itself plans the next check (REPLACE on its own unique work), WorkManager cancels the running worker. This happens only after the check has finished, so nothing is lost, but the run is logged as cancelled.
  • The README still mentioned the Review of a reminder, which was removed in 288fe85. That sentence has been dropped.
Closes #288. The Android app now posts a notification when a reminder command (R29) comes due. Where the reminder has an `into_file`, the notification has a **Done** action. Done marks the reminder exactly as the banner on the Stream tab does: it writes the line, then commits and pushes according to the Changes switches, without opening the app. ## Core and bindings - `reminder::next_due` (`due.rs`) gives the earliest moment after now at which a reminder that is not due now comes due: - a `days` rule: the start of its day, DST-safe, in the configured timezone; - a `since` rule: the next occurrence of its recurrence. It reuses `finding` / `expected_moments` and has no rules of its own. The facade has `api::next_reminder_due`, and the bindings have `Project.nextReminderDue`. - `DueReminder` in the bindings gains `text` (the description, else the key), which the notification uses as its title. ## Android - **Refactor:** the `RepositoryConnection`, the `StreamSession` and the `PinStore` now belong to a new `Streamd` object, held by a new `StreamdApplication`. They no longer belong to the view model. The screens and the workers share one clone, and the app shows a done from a notification without reloading. `Streamd.ready()` lets a worker start the connection and wait for a read stream. The view model's behaviour is unchanged, and the screenshot tests are unchanged. - **`notify/`:** nothing outside this package names WorkManager or `NotificationManager`, apart from the manifest and `StreamdApplication`. - `ReminderAlerts.kt` is pure and unit-tested. A reminder rings when it comes due or its `since` changes, and again on a later day from 08:00 on. Otherwise it is updated silently, and only while the notification is still shown, so a swiped-away notification waits for the next ring. It is withdrawn once the reminder is no longer due. - `ReminderWork`: - a one-time job at the next due moment or the next 08:00, whichever is earlier; - a 3-hour periodic fallback; - an expedited `DoneWorker`, started by `ReminderDoneReceiver`. - A background check pulls first when the network is validated, and checks the local clone anyway if the pull fails. While the app runs, every new `Ready` stream triggers a check without a pull. - The `POST_NOTIFICATIONS` permission is asked for once, the first time the project has a reminder command. - WorkManager 2.12.0 has been added, and `android/deps.json` has been written again. ## Tests - Core: `next_due` with `days` (with and without `start`, and with a future `start`), daily and timed `since`, across a DST change in Berlin, an already-due reminder, and the earliest of several. The facade is tested on a `MemoryStore`, and the ffi has a test too. - App: `ReminderAlertsTest` (9 tests), `StreamdTest`, and `StreamSessionTest` for `text`, `since` and `nextDue`. - `nix flake check` passes. `nix build .#streamd-app` passes, including the unit tests and the unchanged screenshot tests. - On the emulator, the app starts, `ReminderWorker` runs, and the fallback job is scheduled. - **Not yet checked by hand:** a reminder actually notifying, Done committing and pushing, a denied permission, and behaviour after a reboot. These need a connected repository on the emulator. ## Notes - When the scheduled check itself plans the next check (`REPLACE` on its own unique work), WorkManager cancels the running worker. This happens only after the check has finished, so nothing is lost, but the run is logged as cancelled. - The README still mentioned the **Review** of a reminder, which was removed in 288fe85. That sentence has been dropped.
kfickel deleted branch 288_android-reminder-notifications 2026-10-17 15:20:08 +02:00
Sign in to join this conversation.
No reviewers
No labels
android
cli
epic
planned
tui
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
kfickel/streamd!290
No description provided.