Release v2.22.0 (#355)
Views: navigation, transitions and split-view behaviour. See CHANGELOG.md for the full list. Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de> Reviewed-on: https://codeberg.org/jlmakiola/calendula/pulls/355
This commit is contained in:
+35
-3
@@ -27,7 +27,7 @@ the package list (recurring writes, save conflicts, reminder delivery).
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph UI ["ui/ — Compose screens + ViewModels"]
|
||||
Screens["Month / Week / Day\nDetail / Edit / Settings\nOnboarding wizard"]
|
||||
Screens["Month / Timeline / Agenda\nDetail / Edit / Settings\nOnboarding wizard"]
|
||||
end
|
||||
subgraph Data ["data/"]
|
||||
Repo["CalendarRepository\n(interface + impl, Flow-based, io-dispatched)"]
|
||||
@@ -79,10 +79,42 @@ flowchart TD
|
||||
|
||||
There is no navigation library. `MainActivity` hosts `RootScreen`, which
|
||||
gates on the first-launch wizard (`ui/onboarding/`), then shows
|
||||
`CalendarHost`. `CalendarHost` holds the active view (month/week/day)
|
||||
plus overlay state for detail, edit, and settings — full-screen overlays
|
||||
`CalendarHost`. `CalendarHost` holds the active view (month, day/multi-day/week,
|
||||
agenda) plus overlay state for detail, edit, and settings — full-screen overlays
|
||||
driven by `AnimatedVisibility` with a *held-key* pattern: the last shown
|
||||
key stays alive through the slide-out so content never flashes empty.
|
||||
Overlays leave through floret-kit's `predictiveBackExit`, so a committed back
|
||||
gesture finishes from its scaled preview.
|
||||
|
||||
### Switching views (#184)
|
||||
|
||||
- **One focused date.** `CalendarHost` owns a `ViewFocus` (`LocalViewFocus`)
|
||||
that every view opens on and carries along. A view *reads* it once, on
|
||||
entry, synchronously (`EnterOnFocus` before it reads its own position, so
|
||||
its first frame is already on that date), and *writes* it only when the user
|
||||
moves — a pager settling (`focusOnPage` keeps the date's place in the page),
|
||||
a jump, Today, a tapped day, the agenda's top row. Writing on entry would
|
||||
drift the date on every round trip.
|
||||
- **Three screens, not five.** The host's `AnimatedContent` is keyed on
|
||||
`ViewScreen` (month, timeline, agenda), not on the view: day, multi-day and
|
||||
week are one `TimelineScreen` with a `TimelineKind` each and a view model
|
||||
each. Keying the three on one `contentKey` instead would still replay the
|
||||
enter transition on every switch among them.
|
||||
- **Timeline ↔ timeline resizes in place.** `ColumnGeometry` places every
|
||||
column (header cell, all-day bar, day column) in the layout pass; a switch
|
||||
swaps the pager for a frame over both pages' days, each column lerped
|
||||
between its slot on the one page and the other — a day the page doesn't
|
||||
show carries on at its pitch, off screen, which is what slides it in or
|
||||
out. Blocks sit in their column as shares of its width (`LaneColumn`), so
|
||||
nothing recomposes per frame. The frame starts and ends exactly as the two
|
||||
pages lay out, so the pagers either side hand over without a jump.
|
||||
- **Across screens, what both show morphs.** Inside a `SharedTransitionLayout`,
|
||||
an event shown in both views travels from its old bounds to its new ones,
|
||||
keyed per occurrence *and* per day it is drawn from (`ViewMorphKey`); only a
|
||||
pager's settled page carries the tags. The drawer, top bar and FAB are not
|
||||
per screen: CalendarHost draws them once, above the switch, from what the
|
||||
screen on show publishes (`PublishChrome` in `CalendarChrome.kt`), so a
|
||||
switch only changes what they say.
|
||||
A tapped reminder notification routes through `MainActivity` (`singleTop` +
|
||||
`onNewIntent`) as an external detail key that `CalendarHost` consumes
|
||||
exactly like an event tap.
|
||||
|
||||
+7
-7
@@ -1,7 +1,7 @@
|
||||
# Building from source
|
||||
|
||||
Calendula builds with the standard Android Gradle toolchain — no extra setup
|
||||
beyond the SDK, a JDK, and the submodule.
|
||||
Calendula builds with the standard Android Gradle toolchain. You need the SDK,
|
||||
a JDK and the submodule, nothing else.
|
||||
|
||||
## Clone
|
||||
|
||||
@@ -9,9 +9,9 @@ beyond the SDK, a JDK, and the submodule.
|
||||
git clone --recurse-submodules https://codeberg.org/jlmakiola/calendula.git
|
||||
```
|
||||
|
||||
Calendula depends on **[floret-kit](https://codeberg.org/jlmakiola/floret-kit)**,
|
||||
Calendula depends on [floret-kit](https://codeberg.org/jlmakiola/floret-kit),
|
||||
the shared Material 3 Expressive kit, as a git submodule wired in as a Gradle
|
||||
composite build (`includeBuild("floret-kit")` in `settings.gradle.kts`) — it is
|
||||
composite build (`includeBuild("floret-kit")` in `settings.gradle.kts`). It is
|
||||
compiled from source, not resolved from a repository. A clone without the
|
||||
submodule fails to configure. For an existing clone:
|
||||
|
||||
@@ -21,14 +21,14 @@ git submodule update --init --recursive
|
||||
|
||||
## Requirements
|
||||
|
||||
- **JDK 17** — not newer; the Android Gradle Plugin requires exactly 17. If your
|
||||
- **JDK 17**, not newer: the Android Gradle Plugin requires exactly 17. If your
|
||||
default JDK differs, set `JAVA_HOME` explicitly.
|
||||
- **Android SDK** — platform **37** (`compileSdk`) and **build-tools 36.0.0**.
|
||||
- **Android SDK**: platform 37 (`compileSdk`) and build-tools 36.0.0.
|
||||
`minSdk` is 29, `targetSdk` 36.
|
||||
|
||||
The SDK is located via `ANDROID_HOME` (or `ANDROID_SDK_ROOT`), or via a
|
||||
gitignored `local.properties` with `sdk.dir`. If you use `local.properties`, note
|
||||
that the composite build needs **its own** copy at `floret-kit/local.properties`;
|
||||
that the composite build needs its own copy at `floret-kit/local.properties`;
|
||||
setting `ANDROID_HOME` covers both builds at once and is the simpler route.
|
||||
|
||||
The Gradle wrapper is checked in, so you don't need a system Gradle.
|
||||
|
||||
+13
-13
@@ -26,7 +26,7 @@ Applies to the Android app **Calendula** (package `de.jeanlucmakiola.calendula`)
|
||||
|
||||
## In short
|
||||
|
||||
Calendula collects nothing, sends nothing, and has no user accounts. It has **no internet permission at all** — the app is technically incapable of transmitting your data anywhere. Everything it shows you is read from the calendars that already exist on your device.
|
||||
Calendula collects nothing, sends nothing, and has no user accounts. It has **no internet permission at all**, so it cannot transmit your data anywhere. Everything it shows you is read from the calendars that already exist on your device.
|
||||
|
||||
## 1. Controller
|
||||
|
||||
@@ -47,9 +47,9 @@ All of the following is processed **locally on your device only**. None of it is
|
||||
|
||||
### Calendar data
|
||||
|
||||
Calendula is a viewer and editor for the calendars Android already manages. It reads and writes events, reminders and calendar settings through Android's system calendar provider. The app keeps **no database of its own** — your events live in the system calendar, exactly where they lived before you installed Calendula, and they remain there if you uninstall it.
|
||||
Calendula is a viewer and editor for the calendars Android already manages. It reads and writes events, reminders and calendar settings through Android's system calendar provider. The app keeps **no database of its own**. Your events live in the system calendar, where they were before you installed Calendula, and they stay there if you uninstall it.
|
||||
|
||||
Note: if one of those system calendars is itself synchronised with an online account (for example a Google account, or a CalDAV server via DAVx5), that synchronisation is performed by Android and that other app — not by Calendula. The privacy policy of the respective provider applies to it.
|
||||
Note: if one of those system calendars is itself synchronised with an online account (for example a Google account, or a CalDAV server via DAVx5), that synchronisation is performed by Android and that other app, not by Calendula. The privacy policy of the respective provider applies to it.
|
||||
|
||||
### Contacts (optional)
|
||||
|
||||
@@ -67,7 +67,7 @@ When you import or export an ICS file, Calendula reads or writes exactly the fil
|
||||
|
||||
Your preferences (view options, theme, reminder defaults and similar) are stored locally on your device and are removed when you uninstall the app.
|
||||
|
||||
## 4. Crash reports — the only case where data can leave your device
|
||||
## 4. Crash reports: the only case where data can leave your device
|
||||
|
||||
If Calendula crashes, it offers to report the problem. Nothing is sent automatically. The report is copied to your clipboard and your browser is opened with the project's issue tracker, the text pre-filled. **You see the full content, you decide whether to submit it, and you can edit or discard it.**
|
||||
|
||||
@@ -86,19 +86,19 @@ If you choose to submit it, the report becomes a public issue on the project's i
|
||||
|
||||
## 5. External links
|
||||
|
||||
The settings screen contains links to the source code, the licence, the issue tracker and a voluntary donation page (Ko-fi). Following one of these links opens your browser and leaves the app; the privacy policy of the respective website then applies. Calendula transmits no data of yours in the process — it only opens the address.
|
||||
The settings screen contains links to the source code, the licence, the issue tracker and a voluntary donation page (Ko-fi). Following one of these links opens your browser and leaves the app; the privacy policy of the respective website then applies. Calendula transmits none of your data in the process; it only opens the address.
|
||||
|
||||
## 6. Permissions and why they exist
|
||||
|
||||
- `READ_CALENDAR`, `WRITE_CALENDAR` — display and edit your events; the core function.
|
||||
- `POST_NOTIFICATIONS` — show reminders.
|
||||
- `READ_CONTACTS` — optional, only for the “Contact special dates” feature.
|
||||
- `USE_EXACT_ALARM`, `SCHEDULE_EXACT_ALARM` — deliver reminders at the exact time, including after snoozing.
|
||||
- `RECEIVE_BOOT_COMPLETED` — re-register pending reminders after a restart.
|
||||
- `REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` — only to open the system dialog for the “Reliable delivery” setting.
|
||||
- `WAKE_LOCK`, `FOREGROUND_SERVICE`, `ACCESS_NETWORK_STATE` — required by the Android system component used for scheduled background work (WorkManager).
|
||||
- `READ_CALENDAR`, `WRITE_CALENDAR`: display and edit your events (the core function).
|
||||
- `POST_NOTIFICATIONS`: show reminders.
|
||||
- `READ_CONTACTS`: optional, only for the “Contact special dates” feature.
|
||||
- `USE_EXACT_ALARM`, `SCHEDULE_EXACT_ALARM`: deliver reminders at the exact time, including after snoozing.
|
||||
- `RECEIVE_BOOT_COMPLETED`: re-register pending reminders after a restart.
|
||||
- `REQUEST_IGNORE_BATTERY_OPTIMIZATIONS`: only to open the system dialog for the “Reliable delivery” setting.
|
||||
- `WAKE_LOCK`, `FOREGROUND_SERVICE`, `ACCESS_NETWORK_STATE`: required by the Android system component used for scheduled background work (WorkManager).
|
||||
|
||||
`ACCESS_NETWORK_STATE` allows reading *whether* a network connection exists — it does **not** permit using one. Without `INTERNET`, no connection is possible.
|
||||
`ACCESS_NETWORK_STATE` allows reading *whether* a network connection exists. It does **not** permit using one. Without `INTERNET`, no connection is possible.
|
||||
|
||||
## 7. Distribution channels
|
||||
|
||||
|
||||
+7
-10
@@ -9,17 +9,14 @@ Where to look for what:
|
||||
| [`ARCHITECTURE.md`](ARCHITECTURE.md) | Orientation tour: principles, layers, navigation, recurring-write / conflict / reminder pipelines, testing |
|
||||
| [`RELEASING.md`](RELEASING.md) | Release process: versioning, the merge-driven pipeline, the two-forge split, secrets, key custody |
|
||||
| [`../CHANGELOG.md`](../CHANGELOG.md) | Release history (Keep a Changelog, SemVer) |
|
||||
| [Issues](https://codeberg.org/jlmakiola/calendula/issues) + [milestones](https://codeberg.org/jlmakiola/calendula/milestones) | **The roadmap.** What's planned, in progress, and shipped — a milestone maps to its `release/vX.Y.Z` branch |
|
||||
| [Issues](https://codeberg.org/jlmakiola/calendula/issues) + [milestones](https://codeberg.org/jlmakiola/calendula/milestones) | The roadmap: what's planned, in progress and shipped. A milestone maps to its `release/vX.Y.Z` branch |
|
||||
| [`../.planning/PROJECT.md`](../.planning/PROJECT.md) | What the project is: core value, stack + version pins, constraints, naming, forge/release infrastructure |
|
||||
| [`design/`](design/) | Per-feature design notes kept for features whose provider behaviour is worth recording |
|
||||
| [`../fastlane/metadata/android/`](../fastlane/metadata/android/) | Store metadata (single source of truth): descriptions, title, icon, screenshots (DE + EN). Harvested directly by the official F-Droid repo; transformed into the self-hosted repo layout at release time by [`../scripts/fastlane_to_fdroid_localized.sh`](../scripts/fastlane_to_fdroid_localized.sh) |
|
||||
| [`../fastlane/metadata/android/`](../fastlane/metadata/android/) | Store metadata (single source of truth) for every app language, mapped in `store-locales.txt`: descriptions, title, graphics. Harvested directly by the official F-Droid repo, pushed to Google Play with every release, validated by [`../scripts/check_store_listing.py`](../scripts/check_store_listing.py); transformed into the self-hosted repo layout at release time by [`../scripts/fastlane_to_fdroid_localized.sh`](../scripts/fastlane_to_fdroid_localized.sh) |
|
||||
| [`../fdroid-metadata/`](../fdroid-metadata/) | App-level F-Droid control file (`*.yml`: Categories, License, links) for the self-hosted repo's `fdroid update` |
|
||||
| [`fdroid-official/`](fdroid-official/) | Recipe + notes for publishing to the **official** F-Droid repo (reproducible build + developer-signed binary) |
|
||||
| [`fdroid-official/`](fdroid-official/) | Recipe and notes for publishing to the official F-Droid repo (reproducible build + developer-signed binary) |
|
||||
|
||||
Conventions: planning lives in the **issue tracker**, not in this repository. The
|
||||
`.planning/` files that predated it (a roadmap, a development-state snapshot, and
|
||||
a per-milestone requirement checklist) are gone — issues and milestones say the
|
||||
same thing without going stale. `PROJECT.md` is what remains, and it describes
|
||||
the project rather than its plan.
|
||||
`ARCHITECTURE.md` is the authoritative orientation tour: it is updated with the
|
||||
code, and is the right place for a lesson learned about the calendar provider.
|
||||
Planning lives in the issue tracker and its milestones, not in this repository.
|
||||
`.planning/PROJECT.md` describes the project itself.
|
||||
`ARCHITECTURE.md` is updated with the code and is the place to record a lesson
|
||||
learned about the calendar provider.
|
||||
|
||||
+24
-14
@@ -162,24 +162,34 @@ before. fastlane appears only as the Play Developer API client (`supply`),
|
||||
because the store listing already lives in `fastlane/metadata/android/` — the
|
||||
same tree the official F-Droid repo harvests. One metadata source, two stores.
|
||||
|
||||
**What gets uploaded per release:** the AAB, plus the per-version "What's New"
|
||||
from `fastlane/metadata/android/en-US/changelogs/<versionCode>.txt` — the
|
||||
**What gets uploaded per release:** the AAB, the per-version "What's New"
|
||||
from `fastlane/metadata/android/<locale>/changelogs/<versionCode>.txt` (the
|
||||
hand-written summary from step 3, which is why it must stay **under 500
|
||||
characters**: Play rejects a longer one. Listing text is
|
||||
**not** touched — an accidental overwrite of a live listing triggers a Play
|
||||
policy review. Sync it deliberately with `bundle exec fastlane listing`.
|
||||
characters**), and the **whole store listing** — title, short and full
|
||||
description, icon, feature graphic and screenshots for every locale — all in one
|
||||
Play edit. Nothing about the Play listing is edited in the console any more:
|
||||
change the files, and the next release pushes them. Unchanged images are
|
||||
skipped (`sync_image_upload`). Every listing change goes through Play's review,
|
||||
together with the bundle. To push a listing fix between releases, run
|
||||
`bundle exec fastlane listing` (add `version_code:<code>` if the track holds
|
||||
more than one release).
|
||||
|
||||
**Screenshots and graphics are skipped**, because the committed assets satisfy
|
||||
F-Droid but not Play:
|
||||
The listing tree is guarded by `scripts/check_store_listing.py`, which CI runs
|
||||
on every pull request:
|
||||
|
||||
| Asset | Committed | Play requires |
|
||||
| --- | --- | --- |
|
||||
| `phoneScreenshots/*.png` | 1280×2856, 32-bit RGBA | long edge ≤ 2× short edge (so ≤ 2560), 24-bit PNG, no alpha |
|
||||
| `icon.png` | 512×512, 24-bit RGB | 512×512, 32-bit PNG |
|
||||
| `featureGraphic.png` | *missing* | required, exactly 1024×500 |
|
||||
| Rule | Play limit |
|
||||
| --- | --- |
|
||||
| `title.txt` / `short_description.txt` / `full_description.txt` | 30 / 80 / 4000 chars, all three present in every locale |
|
||||
| `full_description.txt` | one line per paragraph — Play renders every newline |
|
||||
| `images/icon.png` | 512×512, 32-bit PNG |
|
||||
| `images/featureGraphic.png` | 1024×500, JPEG or 24-bit PNG |
|
||||
| `images/*Screenshots/*` | JPEG or 24-bit PNG (no alpha), sides 320–3840, long edge ≤ 2× short, 2–8 per type |
|
||||
| locale directories | exactly the ones in `fastlane/store-locales.txt`, which maps every app `values-*` to a Play locale code |
|
||||
|
||||
Until those are fixed, Play's graphics are managed by hand in the console. Then
|
||||
pass `images:true` to the `listing` lane.
|
||||
A locale without its own images falls back to en-US in both stores, so graphics
|
||||
only need to exist there. Adding an app language means adding its line to
|
||||
`fastlane/store-locales.txt` and its listing directory — CI fails until both
|
||||
exist. Store text is not on Weblate; it is edited here.
|
||||
|
||||
**Track.** Uploads go straight to `production` at a full rollout
|
||||
(`PLAY_RELEASE_STATUS=completed`). The merge to `main` is already the human
|
||||
|
||||
Reference in New Issue
Block a user