Commit Graph

47 Commits

Author SHA1 Message Date
8fbbab30e2 ui: add DebugViewModel combining calendars + next 30d instances 2026-06-08 17:52:50 +02:00
ef0a4b0568 ui: add PermissionScreen with rationale and denied recovery 2026-06-08 17:50:33 +02:00
43f12812b6 ui: add PermissionViewModel with three-state machine 2026-06-08 17:49:39 +02:00
2400d5482c i18n: add permission + debug screen strings (en, de) 2026-06-08 17:49:06 +02:00
4d54501ed4 di: wire CalendarRepository, DataSource, DataStore, IoDispatcher 2026-06-08 17:48:34 +02:00
748df761bf data: add CalendarPrefs (hidden calendar ids in DataStore) 2026-06-08 17:47:55 +02:00
d13f2f07a5 data: add CalendarRepository + Impl with SharedFlow re-emit on data-source ticks 2026-06-08 17:47:13 +02:00
7abb2e6ab4 data: add CalendarDataSource seam returning domain lists (not Cursors)
Deviation from Plan 02: changing from Cursor-returning interface to
domain-returning interface so the repository unit tests can use a simple
fake without constructing ContentObserver/Handler/Looper on the JVM
(which would either crash or no-op via the mockable.jar stubs).
2026-06-08 17:44:19 +02:00
fb003d8806 data: add ColumnReader.toEventDetailCore() and toAttendee() mappers 2026-06-08 17:42:42 +02:00
40b531fa52 data: add ColumnReader.toEventInstance() with defensive validation (§8) 2026-06-08 17:41:29 +02:00
0e4c47febe data: add ColumnReader abstraction + Cursor.toCalendarSource mapper
Deviation from Plan 02: the JVM mockable-android.jar stubs every Cursor
method even with isReturnDefaultValues=true (returns null/0 regardless of
the underlying MatrixCursor backing). Introduce an internal ColumnReader
interface so mappers stay pure-Kotlin and JVM-testable via MapColumnReader,
while production reads through CursorColumnReader.
2026-06-08 17:40:37 +02:00
fb723fba68 data: add CalendarContract column projections + indices
Some checks failed
Build and Release to F-Droid / ci (push) Has been cancelled
Build and Release to F-Droid / build-and-deploy (push) Has been cancelled
v0.1.1
2026-06-08 17:37:23 +02:00
Jean-Luc Makiola
ffc7ed414f fix(fdroid): correct metadata format to fastlane convention + add icon.png
All checks were successful
CI / ci (push) Successful in 8m52s
Inspection of the local Hetzner-synced F-Droid repo after v0.1.0
revealed that fdroidserver only partially picked up Calendula's
metadata: summary was sourced from the YAML fallback (en-US only),
description appeared only for the "de" locale (not de-DE), and no
icon was shown anywhere. Root cause: we wrote Google Play conventions
(short_description.txt, full_description.txt, bare locale code "de")
where fdroidserver expects the fastlane format that the sibling
HouseHoldKeaper repo already uses successfully.

Changes:
- de/ -> de-DE/ (BCP-47 with region matches HHK and is more reliably
  parsed by fdroidserver)
- short_description.txt -> summary.txt
- full_description.txt -> description.txt
- Add icon.png (512x512) per locale, composed from the adaptive icon's
  foreground path + slate background (rendered via rsvg-convert).
  Required because XML-only adaptive icons in the APK aren't
  auto-rasterized by fdroidserver.

Verified locally against the previously-broken index by composing the
new icon and renaming the files in-tree.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 17:37:05 +02:00
af75965a31 domain: add pure-Kotlin models (CalendarSource, EventInstance, EventDetail, …) 2026-06-08 17:36:39 +02:00
1b456d2133 data: add TimeBridge helpers for epoch-millis ↔ kotlin.time.Instant 2026-06-08 17:35:49 +02:00
a826e82bdc build: add kotlinx-datetime, coroutines, turbine, hilt-nav-compose, lifecycle-compose 2026-06-08 17:32:45 +02:00
ed680b4482 docs: add Plan 02 - Data Layer & Permission Flow implementation plan
21 bite-sized tasks covering domain models, CalendarContract data layer
(Cursor mappers with §8 defensive validation, ContentObserver-backed
SharedFlow repository), DataStore-persisted hidden-calendar set, Hilt
wiring, READ_CALENDAR permission flow (rationale + denied recovery), and
a wegwerfbarer Debug screen that visually validates data is flowing.

Out of scope: Month/Week/Day views (Plans 03-05), Event Detail Sheet
(Plan 06), Filter/Settings (Plan 07).
2026-06-08 17:30:41 +02:00
Jean-Luc Makiola
6ebe8d69e9 ci(cleanup): trim lint+test scope for faster pipelines
All checks were successful
CI / ci (push) Successful in 9m1s
Build and Release to F-Droid / ci (push) Successful in 5m21s
Build and Release to F-Droid / build-and-deploy (push) Successful in 7m28s
- ci.yaml: ./gradlew lint -> lintDebug, test -> testDebugUnitTest.
  Default lint task runs for BOTH debug and release variants which
  doubles the scan work; AGP's lint catalog is identical between
  variants for our scope so debug-only is sufficient. Same for test:
  testDebugUnitTest avoids running release-variant test compilation.

- release.yaml: drop lint step from ci-sanity job. Lint is enforced
  on every push to main via ci.yaml; by the time a tag exists at a
  main commit, lint has already passed. Release-sanity keeps test +
  assembleDebug to catch any tag-resolved drift (e.g. version code
  substitution issues).

Expected CI run time reduction: ~30% (lint accounts for the largest
single block of cold-cache work).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.1.0
2026-06-08 17:03:08 +02:00
Jean-Luc Makiola
0508ea9de5 fix(ci): split release.yaml sanity gradle calls to avoid OOM
Some checks failed
Build and Release to F-Droid / build-and-deploy (push) Has been cancelled
CI / ci (push) Has been cancelled
Build and Release to F-Droid / ci (push) Has been cancelled
The release workflow's ci-sanity job ran 'lint test assembleDebug' as
a single gradle invocation, which combined all three phases in one
JVM and exceeded the 2GB heap inside the gitea-actions docker
container ("Gradle build daemon disappeared unexpectedly"). Split
into three separate invocations matching ci.yaml - each gradle call
gets its own fresh 2GB JVM, well under the container's memory ceiling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 16:53:30 +02:00
Jean-Luc Makiola
ee14706d91 fix(build): revert ui-tooling-preview to implementation scope
Some checks failed
CI / ci (push) Successful in 10m50s
Build and Release to F-Droid / ci (push) Failing after 14m22s
Build and Release to F-Droid / build-and-deploy (push) Has been skipped
The previous batch fix tried to move ui-tooling-preview to
debugImplementation per a reviewer suggestion, but @Preview is used
in MainActivity.kt which lives in the main source set, so the
annotation class must be available at release-build compile time.

Moving @Preview composables to a debug-only source set would let the
dep stay debug-scoped - that is a Plan 02+ refactor, not foundation
work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 16:06:49 +02:00
Jean-Luc Makiola
376663b0d8 chore: batch fix final-review findings on Plan 01
- ROADMAP: mark v0.1 (Foundation & CI) as complete
- REQUIREMENTS: move Foundation & CI from Active to Validated (shipped)
- AndroidManifest: drop redundant android:label and android:theme on
  MainActivity - both inherited from <application>
- build.gradle.kts: move ui-tooling-preview to debugImplementation
  (@Preview annotations are dev-only; release APK stays smaller)

All foundation verification (lint + test + assembleDebug) still green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 16:04:23 +02:00
Jean-Luc Makiola
89ff33fe3d docs: record v0.1.0 foundation in CHANGELOG, update STATE
Plan 01 (Foundation & CI) is complete. The app builds, tests pass,
lint is clean, both Gitea workflows are wired. CHANGELOG transitions
the foundation entries from [Unreleased] to [0.1.0] dated 2026-06-08.
STATE.md ticks off Plan 01 execution and points to Plan 02 (Data
Layer + Permission Flow) as the next milestone.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 15:55:20 +02:00
Jean-Luc Makiola
686aee9109 build: wire signing config from key.properties when present
The release workflow (release.yaml) drops a key.properties at project
root and an upload-keystore.jks in app/ from Gitea secrets. This
commit makes Gradle read them when present, configure a 'release'
signing config, and attach it to the release build type. When the
files are absent (local debug builds, fresh clones), the signing
config block is skipped and release builds emit unsigned APKs - that
is the intended local behavior; only CI tags signed releases.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 15:52:40 +02:00
Jean-Luc Makiola
bfa7757d88 ci: add gitea release workflow with F-Droid pipeline
Triggers on git tags. Runs CI sanity (lint+test+assembleDebug), then
in build-and-deploy job: writes version from tag into app/build.gradle.kts
(versionCode = MAJOR*10000 + MINOR*100 + PATCH, HouseHoldKeaper
convention), drops keystore + key.properties from secrets, runs
assembleRelease, pulls existing F-Droid repo from Hetzner, drops the
new APK + metadata, regenerates index with 'fdroid update -c', and
SCPs the whole tree back to Hetzner.

Required secrets: KEYSTORE_BASE64, KEY_PASSWORD, KEY_ALIAS,
HETZNER_HOST, HETZNER_USER, HETZNER_PASS. Configure these in Gitea
repo settings before pushing the first tag.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 15:49:01 +02:00
Jean-Luc Makiola
fb7f2ad1f3 ci: add gitea CI workflow (lint, test, assemble, trivy)
Runs on every push to any branch (tags excluded) and on pull requests.
Installs JDK 17 + Android SDK 36 + 37.0-preview (needed because the
Material 3 Expressive alpha transitively requires compileSdk 37).
Gradle dependency cache keyed on libs.versions.toml. Trivy scan runs
with continue-on-error like HouseHoldKeaper - we report findings but
don't block.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 15:46:57 +02:00
Jean-Luc Makiola
0593b9c60f test: add UI smoke test for MainActivity placeholder
createAndroidComposeRule<MainActivity>() launches the activity in the
test process and verifies both app_name and app_tagline texts render
on the placeholder screen. Uses JUnit 4 + AndroidJUnit4 runner -
instrumented tests on Android still don't support JUnit Jupiter.

Test compiles (assembleDebugAndroidTest passes); execution requires
an emulator/device, performed in CI.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 15:45:17 +02:00
Jean-Luc Makiola
dea5490bcf feat: scaffold CalendulaApp + MainActivity with themed placeholder
CalendulaApp registers the Hilt component graph. MainActivity uses
enableEdgeToEdge() and renders a centered placeholder showing app
name + tagline via CalendulaTheme. PlaceholderPreview lets the IDE
preview the layout. Resolves the pre-existing MissingClass lint
error from the manifest reference.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 15:43:17 +02:00
Jean-Luc Makiola
4fe8cd12cb feat: M3 Expressive theme with dynamic color + fallback scheme from slate seed
CalendulaSeed (0xFF5C6B7A) anchors the design palette. Theme picks
Dynamic Color on API 31+ when enabled (default true) and falls back
to a hand-tuned Light/Dark scheme otherwise. CalendulaTypography is
the M3 Expressive default for V1 - custom type scale lands in a
later UI-design iteration.

ColorSchemeTest pins the seed value (3 unit tests, all pass).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 15:39:28 +02:00
Jean-Luc Makiola
e4f445ac75 fix(build): bump compileSdk 36 -> 37 for Material 3 Expressive alpha
Material 3 Expressive 1.5.0-alpha21 transitively requires
material-ripple-android:1.12.0-alpha03, which mandates compileSdk >= 37
via its AAR metadata. The Android SDK platform 37.0 was installed
locally (already available as a preview).

targetSdk stays at 36 - we compile against API 37 only for the new
material-ripple constants, not to opt into runtime behavior changes.
targetSdk will follow when API 37 reaches stable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 15:34:36 +02:00
Jean-Luc Makiola
2cf2dafacd fix(deps): correct Compose BOM to 2026.05.01
The previously-declared 2026.06.00 is listed on
developer.android.com's BOM mapping page but is not yet published to
Google's Maven repository. Verified against maven-metadata.xml directly
that 2026.05.01 is the actual latest published BOM.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 15:31:52 +02:00
Jean-Luc Makiola
defc7795ad feat: adaptive launcher icon - '1' on slate squircle (kalendae)
Static stylized '1' on a slate (0xFF5C6B7A) background. The numeral
references kalendae, the Latin word for 'first day of the month' that is
the etymological root of both 'Calendar' and 'Calendula'. Adaptive icon
spec: foreground vector path, monochrome variant for themed icons (API
33+), no PNG fallback needed (minSdk 29 > 26).

Visual refinement is expected during the UI design iteration; this is
the foundational shape and meaning.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 15:28:27 +02:00
Jean-Luc Makiola
b20da4d4c6 feat: add android manifest, strings (DE+EN), colors, base theme
READ_CALENDAR permission declared. Strings split into English master
(values/) and German (values-de/) with the Loading/Failure/Success
generic state strings used across screens. Backup rules let DataStore
back up by default with no file-based content. Theme stub delegates
real theming to Compose; the Activity-level XML theme only sets
transparent system bars and dark-mode hint.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 15:23:52 +02:00
Jean-Luc Makiola
8312bc6e0c chore(gradle): fix code-review findings on app module setup
- Restore 4-space indent on kotlin-compose plugin alias (regression from
  the previous cleanup commit)
- Fix invalid ProGuard rule: HiltAndroidApp is an annotation type, not
  a class - use '-keep @dagger.hilt.android.HiltAndroidApp class *' to
  actually retain the @HiltAndroidApp-annotated Application
- Remove vectorDrawables.useSupportLibrary - dead config since minSdk 29
  (native VectorDrawable support since API 21)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 15:22:07 +02:00
Jean-Luc Makiola
5e2cee1147 chore(gradle): drop orphaned kotlin-android plugin references
AGP 9.0+ removes the kotlin-android plugin requirement (handled
automatically by AGP itself once kotlin-compose is applied). Task 5
removed it from app/build.gradle.kts; this commit removes the
no-longer-consumed alias from the root build and version catalog.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 15:18:47 +02:00
Jean-Luc Makiola
a9c141f146 chore: configure :app module gradle build
Compose with Material 3 Expressive (1.5.0-alpha21 explicit override of
BOM stable), Hilt 2.59.2 + KSP 2.3.9, DataStore. Single-module project.
JVM 17 + Kotlin 2.3.21. JUnit Jupiter 6.1.0 unified (jupiter + platform).
testOptions opts into JUnit Platform so JUnit5/6 tests get discovered.

Note: kotlin-android plugin removed — AGP 9.0+ provides built-in Kotlin
support and the plugin now hard-errors when applied alongside AGP 9.x.
kotlin-compose plugin retained (separate Compose compiler plugin).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 15:16:36 +02:00
Jean-Luc Makiola
89e89eb47e chore(gradle): add wrapper sha256 + parallel/caching flags
Code review on Task 4 flagged:
- distributionSha256Sum missing (F-Droid reproducibility concern)
- gradle.properties lacked org.gradle.parallel/caching (default off)
- inaccurate K2 attribution in a comment

SHA256 fetched live from downloads.gradle.org. Configuration cache
left commented for now until all plugins confirm AGP 9.1 compat.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 15:13:52 +02:00
Jean-Luc Makiola
df5a358b74 chore: add gradle wrapper + version catalog + root build
Bootstrapped Gradle 8.14 wrapper from HouseHoldKeaper, then upgraded
to 9.5.1 (required by AGP 9.1.1). All dependency versions verified as
latest stable on 2026-06-08, with Material 3 deliberately pinned to
1.5.0-alpha21 (Expressive APIs are not in any stable release yet).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 15:08:58 +02:00
Jean-Luc Makiola
abcef550b7 chore(fdroid): add Summary field to metadata yml
Code review flagged that F-Droid clients use Summary as the canonical
single-line app subtitle on the detail page; without it the slot would
fall back to the package id.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 15:06:06 +02:00
Jean-Luc Makiola
a81b165bcc chore: add F-Droid metadata for de.jeanlucmakiola.calendula
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 15:03:23 +02:00
Jean-Luc Makiola
59d118aba1 docs(planning): pin Kotlin/KSP/AGP patch versions, document KSP constraint
Code review on Task 2 flagged that 'Kotlin 2.3' could mislead a future
reader into bumping to 2.4.0, which has no KSP release yet. Spell out
the full versions and the KSP pairing constraint in both PROJECT.md
(Stack section) and REQUIREMENTS.md (Constraints section).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 15:01:50 +02:00
Jean-Luc Makiola
ec56e61ccd docs: add .planning/ project-tracking documents
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 14:58:38 +02:00
Jean-Luc Makiola
a8ba6e32e2 chore: add repo meta files (LICENSE, README, CHANGELOG, gitignore)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 14:54:40 +02:00
Jean-Luc Makiola
d951089e88 docs(plan): bump all dependency versions to verified latest stable
All versions verified against canonical registries on 2026-06-08:

- Kotlin 2.1.0 -> 2.3.21 (paired with KSP)
- AGP 8.7.2 -> 9.1.1
- KSP 2.1.0-1.0.29 -> 2.3.9
- Hilt 2.53 -> 2.59.2
- Compose BOM 2025.05.00 -> 2026.06.00
- Material 3 -> PINNED to 1.5.0-alpha21 (Expressive APIs live only here)
- AndroidX Core KTX 1.15.0 -> 1.19.0
- Lifecycle 2.8.7 -> 2.10.0
- Activity Compose 1.9.3 -> 1.13.0
- DataStore 1.1.1 -> 1.2.1
- JUnit Jupiter 5.11.4 -> 6.1.0 (with unified platform 6.1.0)
- Truth 1.4.4 -> 1.4.5
- AndroidX Test JUnit 1.2.1 -> 1.3.0
- Espresso 3.6.1 -> 3.7.0
- Gradle 8.14 -> 9.5.1 (required by AGP 9.1.1; bootstrap via two-step
  wrapper upgrade from HouseHoldKeaper's 8.14 wrapper)

Also moved kotlinOptions DSL to the new top-level kotlin {} block
(required with Kotlin 2.3+ / AGP 9.x).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 14:52:44 +02:00
Jean-Luc Makiola
f13523c865 docs(plan): adapt foundation plan to actual dev toolchain
Dev machine has no host gradle binary; bootstrap from HouseHoldKeaper's
wrapper (Gradle 8.14, compatible with AGP 8.7.2). Default JDK is 26,
but AGP 8.7.2 needs JDK 17-21; require JAVA_HOME=jdk-17 on local
invocations. CI is unaffected (setup-java pins 17).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 14:46:05 +02:00
Jean-Luc Makiola
42a1183b6f docs: add Plan 01 (Foundation & CI) implementation plan
First of an 8-plan sequence to build V1. Plan 01 covers the buildable
Android project scaffold: Gradle setup, Hilt, DataStore, Material 3
Expressive theme, adaptive launcher icon (statische "1" on slate
squircle, referencing kalendae), DE+EN i18n infrastructure, ColorScheme
unit tests, smoke UI test, Gitea CI workflow, F-Droid release workflow,
F-Droid metadata stubs, and .planning/ project-tracking documents.

14 tasks, each ending in a commit. Output is a working APK with green
CI before any feature code is written.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 14:42:37 +02:00
Jean-Luc Makiola
270ef9a605 docs: resolve open design decisions in V1 spec
- App-Name: Calendula (etymologisch von 'kalendae' = erster Tag des
  Monats, Wortwurzel von 'Kalender'; gleichzeitig die Ringelblume)
- Package: de.jeanlucmakiola.calendula
- Seed-Color: 0xFF5C6B7A (desaturiertes Schiefer-Blaugrau)
- Icon-Konzept: statische '1' auf M3-Expressive-Squircle, Slate-
  Background; die '1' referenziert kalendae

UI-Layout-Details bleiben bewusst offen fuer die UI-Design-Iteration
nach Spec-Approval.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 14:33:22 +02:00
Jean-Luc Makiola
473f0d2004 docs: add V1 design spec for calendar app
Initial design document for the Material 3 Expressive calendar app.
Covers scope (V1 read-only MVP, variant "B"), tech stack (Kotlin +
Compose + Material3 Expressive, minSdk 29), architecture, data flow
over CalendarContract, screens/menus, the mandatory Loading/Failure/
Success state pattern per screen, error handling, i18n, accessibility,
testing approach, and CI/CD adaptation from HouseHoldKeaper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 14:26:17 +02:00