A one-off task saved with a start of "now" (the "Default start to today" setting, or the picker's default time) leaves out the "at start" default, since it would ring at once. Moving that start later afterwards never brought it back: the task already had a start, so the edit didn't count as gaining one.
`StoredReminders.withDefaults` now also brings a date's defaults when that date moves and all of its defaults had already rung at the old date (checked with `ReminderPlanner.triggerAt`, so all-day dates go by their calendar day). Only when:
- the task is one-off and has reminders of its own, none from that date — a task with no reminders keeps following the planner's defaults, and one with reminders turned off stays off;
- the date really moved — flipping all-day on the same day doesn't count.
Applies to start and due alike. The editor passes the loaded all-day flag so a kind switch is told apart from a move.
Deviation from the issue: creating a task with a future start already took the start default (checked on the emulator); what the reporter hit is editing a task saved with a start of "now". Known trade-off: a reminder from a date removed by hand comes back if that date is moved after its default would have rung — stored data can't tell the two apart.
Closes #121
Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de>
Reviewed-on: https://codeberg.org/jlmakiola/agendula/pulls/133
The window theme was always the light framework theme, so the launch window, and with it the Android 12+ splash, was white even in dark mode.
- `Theme.Agendula` is now a DayNight theme (AppCompat, as in Calendula). Only the values that differ are qualified: the window background colour and a bool for the status-bar icons.
- The window background is the background Compose then draws: the dynamic one on Android 12+ (`system_background_light/dark` on 14+, the nearest neutral-variant tone on 12–13), the fallback scheme's colour otherwise. A unit test keeps the XML fallback colours equal to `Color.kt`.
- The app's own theme setting is handed to the system (`SystemTheme`): on Android 12+ through `UiModeManager.setApplicationNightMode`, so a Light or Dark chosen in Agendula wins over the system's on the splash as well; on Android 13+ the splash theme is also switched to pure black for the Black theme and to the fixed colours when dynamic colour is off.
Deviation from the issue: it only asked for the system's dark mode, but following the system alone would have given a dark flash to anyone using Light in Agendula with a dark system, and kept the white one for Dark in Agendula with a light system.
Limits: on Android 10–11 the splash can only follow the system's dark mode. On 12, Black and "dynamic colour off" start on the dynamic background, since the splash theme can't be switched there. Changing the theme in Settings now recreates the screen once, as switching the system's dark mode already did.
Closes #119
Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de>
Reviewed-on: https://codeberg.org/jlmakiola/agendula/pulls/132
- "New task" from the Today list, the Today widget, or a task widget showing Today presets the due date to today (all-day), as saved filters already do. Before, with "Start today" on, the task got only a start date and never showed up in Today.
- Today now takes an open task that is due today, or starts today without being overdue, so start-only tasks (like recurring ones with just a start) appear there. In the Today list and the Today task widget, every open task sits under one "Today" heading. A widget chip keeps its day label when the task is due on another day.
- Tasks completed today stay in Today under a Completed section.
- One rule (`TaskFiltering.isToday`) now decides this everywhere: the Today list, its count and progress ring on the Lists screen, the Today widget's ring, and the Week widget's today tile. The two widgets still also count overdue tasks, as before.
Deviation from #123: the issue only asks for tasks dated today. Counting the start date at all is a change in what Today means, and was decided on purpose (start or due today, even with a later due date).
Closes #122
Closes #123
Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de>
Reviewed-on: https://codeberg.org/jlmakiola/agendula/pulls/131
In the app's own store, nothing re-ran the reminder scheduler when a task was completed in the list, the detail screen or a widget, or when a sync pulled in a completion from another device. The alarm stayed armed until the next app start or the daily maintenance pass. A reminder that was already showing never came down, because only its own "Done" button cleared it.
There was a second cause for repeating tasks. A ticked occurrence is saved as a row with its own id, but `occurrence()` searched the list by the series id. It never found the ticked occurrence, fell back to the next open one and posted the reminder anyway. That's probably the first half of #127.
- `ReminderRefresher` runs one debounced `ReminderScheduler.sync()` per burst of changes to tasks, lists or alarms. Account bookkeeping doesn't trigger it. The refresher and `TaskWidgetUpdater` now share a `StoreChangeDebouncer`.
- Each scheduler pass withdraws posted reminders whose occurrence is closed. It also runs when reminders are switched off. It only looks at reminders from the current storage mode.
- A missing task counts as deleted only in Agendula's own store. A provider that fails a read returns nothing rather than throwing.
- Reminders now record their occurrence in the notification extras. Reminders posted by the previous version are only withdrawn for tasks that don't repeat.
- `occurrence()` looks up occurrences by their start time, so it finds ticked rows. It falls back to scanning the list for providers whose exception rows the direct lookup misses.
- A fired reminder now stays marked as fired for the 6 h grace period, even after its task is ticked. Ticking a task and then tapping Undo no longer makes the reminder ring again.
- After posting, the receivers check the task once more. That catches a completion that lands between the check and the post.
- These callers no longer run the scheduler themselves, because the refresher now covers them: the edit screen, the Done action, a storage-mode switch, and the two import paths in Settings. `ProviderWriter` asks the refresher for a pass instead of syncing directly.
- `SyncWorker` still syncs directly. Once the worker returns, nothing keeps the process alive for the debounce.
Closes #127
Closes #129
Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de>
Reviewed-on: https://codeberg.org/jlmakiola/agendula/pulls/130
Renewed pins can be re-trusted via sign in again, discovery keeps walking
past an untrusted SRV target, trust is bound to the address it was given
for, reauth replaces old pins, and the trust save is covered by rollback.
Stable 1.2.0, after 1.2.0-beta.1 and beta.2.
- Version bumped to 1.2.0 (1020099); the CHANGELOG entries moved from Unreleased to 1.2.0.
- What's New for every shipped locale, now including cs-CZ.
- Full store listing (title, short and full description) for every locale; new for cs-CZ, ar and fr-FR. Play only gets these through the `listing` lane.
- `main` is merged in with the branch side kept. #81 and the Weblate updates already came in through the #88 squash, and the only lines unique to `main` were string keys that #88 removed.
The #75 fix landed after beta.2, so no reporter has tested it yet.
Closes #72, closes #78, closes #75, closes #92
Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de>
Reviewed-on: https://codeberg.org/jlmakiola/agendula/pulls/103
Reminders set in Agendula never reached the server, so a Mac or any other client saw none, and alarms set there never rang here. Reminders now sync as VALARMs in both directions.
**What syncs**
- Alarms Agendula can fire exactly as written become reminders: DISPLAY/AUDIO, relative to start or due, or absolute on a non-recurring task.
- Everything else stays in the residue and round-trips untouched: Apple default alarms, location alarms, repeating alarms, RFC 9074 snoozes, and triggers after their anchor.
- An unchanged alarm is re-emitted verbatim. An edited one is authored fresh under its existing uid. All-day leads go out as whole days.
- `ACKNOWLEDGED` is only added once a reminder is past the planner's missed-reminder grace period.
**Behaviour changes**
- Default reminders are stored on the task when it first gets a due date, so other clients see them. While untouched, they swap with the other default when the task flips between timed and all-day.
- Removing a task's alarms in another app turns them off here instead of falling back to the default. Tasks from clients that never write alarms keep the default.
- "All occurrences" reminder edits now reach occurrences that still had the series' old reminders.
- `.ics` import keeps VALARMs as reminders.
**Database v3**
- Alarms gain a `uid` and `raw` VALARM. Tasks gain `reminders_off`.
- The migration lifts claimable alarms out of the residue. It queues a one-time upload for tasks with local-only reminders on writable, non-confidential lists.
**Testing**
- Unit tests: full 662, offline 489. All instrumented tests pass on the emulator, including the migration. Lint is clean for both flavours.
- Not covered: the repository has no unit-test harness, so storing defaults at creation and the timed/all-day swap have no automated tests. Nothing has been checked against a real Mac yet.
Closes #52
Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de>
Reviewed-on: https://codeberg.org/jlmakiola/agendula/pulls/81
Agendula 1.1.0. Merging this triggers the release pipeline (F-Droid, Codeberg, Play) and mints the v1.1.0 tag.
- Tags, synced as CATEGORIES (#35)
- Saved filters, also usable in the Tasks widget (#47)
- Offline flavor without sync or network, published to Codeberg only (#39)
- New Today, Up next, Lists and Week widgets; widgets can show several lists
- Synced lists can be renamed and deleted
- All-day tasks no longer land a day off west of UTC in smart lists and filters
versionName 1.1.0 / versionCode 1010099. CHANGELOG has the 1.1.0 section, and What's New is written for en-US, en-GB, de-DE, pt-BR, plus new fr-FR and ar.
After the tag lands, fdroiddata!49998 needs a 1.1.0 build entry with gradle: full.
Closes #35, closes #39, closes #47
Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de>
Reviewed-on: https://codeberg.org/jlmakiola/agendula/pulls/51
Moves Gradle jobs to the dedicated `gradle` runner (capacity 1, 8g per job) and the cheap gate/lint jobs to `light`. Two runners used to share the `docker` label, so two Gradle builds could run at once and the VPS ran out of memory. Only `runs-on` lines change.
Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de>
Reviewed-on: https://codeberg.org/jlmakiola/agendula/pulls/48
Moves the floret-kit submodule from v0.4.0 (db4be68) to floret-kit main (8b33635). The kit's per-app branches were folded back into main in jlmakiola/floret-kit#11, so agendula now tracks the same line as calendula, clockula and vouch. The kit adds core-prefs, core-di, CollapsingScaffold FAB/no-back slots, the glance module, LocaleManager on API 33+ and a dependency bump; nothing existing is removed.
floret-kit main requires Gradle >= 9.6 in the consuming build, so this also bumps Gradle 9.5.1 → 9.8.0, AGP 9.2.1 → 9.4.1 and Kotlin 2.3.21 → 2.4.20. Hilt 2.60.1 / KSP 2.3.11 were already new enough. No source changes.
Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de>
Reviewed-on: https://codeberg.org/jlmakiola/agendula/pulls/44
### What this changes
- New `scripts/release_gate.sh`, used by the `detect` jobs of both `release.yaml` and `beta.yaml`, decides whether the committed version still needs publishing. Tags are read by exact name via `git ls-remote` instead of Codeberg's `git/refs/tags/<name>` API.
- A beta counts as done only once its Codeberg pre-release carries the APK, so a publish that failed part-way is redone by the next push of the branch.
- A beta is refused unless it is newer than the latest stable tag (previously only an exact `vX.Y.Z` tag was checked).
- `publish_codeberg_release.sh` fails a pre-release when `CODEBERG_RELEASE_TOKEN` is missing (stable still skips), fails on any asset upload that doesn't return 201, and no longer aborts when the asset listing returns an error.
- `version_info.sh` rejects leading zeros (`1.1.0-beta.01` would have shared `beta.1`'s versionCode under a different tag).
- `beta.yaml` header and `docs/RELEASING.md` updated to match.
### Why
Codeberg's `git/refs/tags/<name>` matches by prefix: asking for `v1.1.0` returns 200 because `v1.1.0-beta.1` exists. Merging 1.1.0 into `main` would have logged "tag already exists" and shipped nothing while the run stayed green, and `1.1.0-beta.2` would have been refused as "already shipped stable".
`release/v1.1.0` needs `main` merged in after this lands, since `beta.yaml` runs from the pushed branch's files.
### Also in here
`gradle/gradle-daemon-jvm.properties` now points at JetBrains' own download URLs for JBR 21.0.11 instead of foojay. foojay dropped JetBrains 21 from its index, so the pinned package ids return 400 and CI couldn't provision the daemon JVM (unrelated to the release change, but it blocks every PR until fixed).
### Checklist
- [x] No `values-*/strings.xml` touched
- [x] No `versionName` / `versionCode` bump
- [x] `CHANGELOG.md` not updated: release infrastructure, not a user-visible change
Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de>
Reviewed-on: https://codeberg.org/jlmakiola/agendula/pulls/40
### What this changes
Adds beta releases. Pushing a `release/*` branch whose committed `versionName` is `X.Y.Z-beta.N` makes the new `.gitea/workflows/beta.yaml` run the unit tests, build and sign the APK with the app key, and publish it as a **Codeberg pre-release** (APK + `.sha256`), plus a Gitea pre-release with the R8 mapping. F-Droid (self-hosted and official) and Play never get a beta; Obtainium only offers it with *Include prereleases* on.
- **`scripts/version_info.sh`** is the single source for `versionName` → `versionCode`, used by `release.yaml`, `beta.yaml`, the changelog sync and the store-listing check. From 1.1.0: `X*1000000 + Y*10000 + Z*100 + N` for betas (N = 1–98), `+ 99` for stable, so `1.1.0-beta.1` → `1010001`, `1.1.0` → `1010099`. All 1.0.x versions keep the legacy formula, so `release/v1.0.1` (code `10001`) stays valid.
- **Shared publish scripts:** `scripts/publish_codeberg_release.sh`, `scripts/publish_gitea_release.sh` and `scripts/release_notes.sh`, moved out of `release.yaml`. The stable path behaves as before.
- **Guards:**
- CI fails a PR whose committed `versionCode` doesn't match its `versionName`.
- CI fails a PR into `main` that carries a beta version.
- `release.yaml`'s `detect` refuses a beta on `main` as a backstop.
- `beta.yaml` refuses a beta of a version that has already shipped as stable.
- Betas get no store What's New file.
- **Docs:** "Cutting a beta" and the versionCode table in `docs/RELEASING.md`; a note on beta tags in `docs/fdroid-official/README.md`; how to opt in to betas in the README.
### Why
To ship a test build of an upcoming version (e.g. 1.1.0) to opted-in testers before the stable release, without it reaching F-Droid or Play users.
### How it was tested
- `scripts/version_info.sh` against stable, beta, legacy and invalid version names.
- Both publish scripts against a mock forge API: create, re-run (PATCH plus asset replacement), Codeberg's 500-then-retry path, and the skip when no token is set.
- A scratch copy with `1.1.0-beta.1` committed: the version check passes, the changelog sync and `check_store_listing.py --complete` pass without a What's New, the PR-into-main guard trips, and a wrong `versionCode` is rejected.
- `sync_changelog_to_fastlane.sh` and `check_store_listing.py` (with and without `--complete`) still pass on the current `1.0.0`.
- All three workflow files parse as YAML.
Not run on the real runners yet. The first beta push is the live test of `beta.yaml`, which assumes a mirrored branch push starts a workflow on Gitea, the same way pushes to `main` already do.
### Checklist
- [x] No `versionName` / `versionCode` bump
- [x] No `values-*/strings.xml` touched
- [x] `CHANGELOG.md` not updated: this is release infrastructure, not a user-visible change
Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de>
Reviewed-on: https://codeberg.org/jlmakiola/agendula/pulls/38