Commit Graph

35 Commits

Author SHA1 Message Date
9c4ebbc65a feat(permission): redesign first-run grant-access screen (M3 Expressive)
The onboarding screen is the first thing a new user sees; it was a bare
centred title + body + button. Rebuild it as a proper Material 3 Expressive
welcome:

- Branded hero reconstructing the launcher mark (slate squircle + foreground
  vector); the denied state adds a lock badge over the corner
- App-name eyebrow, a benefit-led headline, and three trust rows (stays on
  device / every calendar together / no tracking) with tonal icon chips
- Full-width filled CTA with a trailing arrow, pinned in a Scaffold bottom bar
  clear of the navigation bar; scrollable body for short screens
- "Read-only · no internet permission" footnote — accurate: the app declares
  only READ_CALENDAR
- Denied/recovery state reuses the same shell with Open-settings (primary) and
  Try-again (text) actions
- 8dp spacing scale, edge-to-edge insets handled via Scaffold

Built with the newly installed material-3 skill's token/component guidance.
Resolves the pre-1.0 polish backlog item.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 09:17:54 +02:00
dca0245a42 refactor(detail): show availability only when Free, pin it by the title
The Busy availability value is the default for nearly every event, so a
"Busy" chip on every detail screen was noise. Show the pill only for Free
(the noteworthy case) and move it to the top-right of the title row instead
of the under-title chip strip. That strip now carries only status/access and
hides entirely when there's nothing noteworthy. Drops the now-unused
event_availability_busy string from both locales.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 09:08:21 +02:00
024512959f feat(detail): full event read — surface every readable field (v0.6.0)
Round out the read-only model so the detail view shows everything
CalendarContract actually stores, ahead of write support.

Data layer:
- New domain types: Reminder, EventStatus, Availability, AccessLevel,
  AttendeeRelationship, AttendeeType; EventDetail gains reminders, status,
  availability, accessLevel, eventTimezone, selfStatus and Attendee gains
  relationship + type (all defaulted so existing callers compile)
- EventDetailProjection reads STATUS / AVAILABILITY / ACCESS_LEVEL /
  EVENT_TIMEZONE / SELF_ATTENDEE_STATUS; AttendeeProjection reads
  RELATIONSHIP + TYPE; new ReminderProjection queries CalendarContract.Reminders
- Mappers translate each provider integer code, guarding STATUS's null-vs-0
  ambiguity (0 == TENTATIVE) so an absent status reads as Confirmed
- Mapper unit tests cover every new column's codes

Detail UI:
- Status / availability / access chips under the title; cancelled also strikes
  the title through
- Reminders card with humanised lead times (plurals, DE + EN)
- Foreign-timezone card, shown only for timed events in a non-device zone
- Attendee role badges + the user's own "Your response: …" line
- http(s) URLs in the description are now tappable

URL field cut: CalendarContract exposes no Events.URL column (only the
CUSTOM_APP_URI app deep-link), so URLs are surfaced by linkifying the
description instead. Recorded in ROADMAP/CHANGELOG.

Version bumped to 0.6.0 / 6.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 08:56:40 +02:00
adcbed6e02 feat(filter,settings): calendar filter in drawer + settings (v0.5.0)
All checks were successful
CI / ci (push) Successful in 12m42s
CI / ci (pull_request) Successful in 12m23s
M3 — calendar filter: the navigation drawer now hosts the calendar list
inline (grouped by account, colour swatch + checkbox per calendar). Hidden
calendars are persisted app-side and filtered centrally in the repository,
so month/week/day re-filter live the moment a checkbox flips. Drawer trimmed
to Today, the calendar filter, and Settings, with leading icons and a clear
title/section type scale; the stubbed jump-to-date entry (M2) was removed.

M4 — settings: full-screen destination with appearance (theme System/Light/
Dark, Material You dynamic colour auto-disabled < API 31, week start Auto/Mon/
Sun), language (per-app locales via AppCompat, persisted to API 29), and an
about section (version, licence, source link). Theme is driven by one
activity-scoped settings source so changes apply app-wide at once. Week start
now drives the month grid and week view; Auto follows the locale.

Also:
- default view switched from month to week
- Settings screen handles system back (was closing the app)
- fix pre-existing NonObservableLocale/LocalContextConfigurationRead lint
  errors in EventDetailScreen so CI lint is green again
- versionName/versionCode bumped to 0.5.0 / 5

Tests: repository hidden-filter (incl. live re-emit), SettingsPrefs round-trip
+ week-start resolution, filter grouping. lint + unit tests + assembleDebug green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 22:55:33 +02:00
efa0abbaed feat(detail): full-screen event detail (S4) with humanized recurrence
Some checks failed
CI / ci (push) Failing after 6m55s
Build and Release to F-Droid / ci (push) Successful in 7m48s
Build and Release to F-Droid / build-and-deploy (push) Successful in 9m40s
Tapping an event in the week/day timeline opens a full-screen detail
destination (MD3 list→detail, not a bottom sheet) overlaying the calendar
with a slide transition. One card per field (when, calendar, location,
description, attendees, recurrence) with leading icons; location taps open
a maps intent. Loading/Failure/Success throughout.

Recurrence is humanized from the RRULE — e.g. "Every week on Tue and Thu
until 31 Dec 2026" — covering FREQ/INTERVAL/BYDAY/UNTIL/COUNT with
abbreviated, italicised day names and localized list formatting, falling
back to a generic label for rules it can't render.

Also:
- fix: recurring events failed to open (series row stores DURATION, not
  DTEND, so the mapper dropped them as EventNotFound). The detail keeps
  them and shows the tapped occurrence's own times from Instances.
- feat: month day cell → opens the day view anchored to that date.
- build: add material-icons-extended (R8 strips unused icons in release).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 21:52:35 +02:00
951fb640a6 feat(day): single-column day view, wire into view switcher
Day view as a one-column slice of the week view: shared TimedBlock/
AllDaySpan layout, per-day swipe navigation, hoisted noon-centred scroll,
animated all-day strip, and a compact top bar showing the full date.

- DayUiState / DayViewModel / DayScreen under ui/day
- reuse layoutDay/layoutAllDay/coversDay from the week package
- add Day to IMPLEMENTED_VIEWS; CalendarHost routes it explicitly
- day_today_action strings (en/de)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 20:40:02 +02:00
94fa206e2e refactor(week): polish timeline — rounded viewport, scroll persistence, week badge
All checks were successful
CI / ci (push) Successful in 11m40s
- Rounded, permanently-soft day-column scroll viewport via two viewports
  sharing one scroll state (gutter + columns stay aligned); plain
  rectangular column cards inside
- Vertical scroll position now persists across week swipes; noon-centring
  only runs on first entry into the week view (from month/day)
- All-day strip height is hoisted + animated, shared by both swipe pages,
  so it slides along and resizes smoothly instead of jumping
- Multi-line event time label so the end time isn't clipped in narrow
  columns; hour labels centred in the gutter
- Calendar-week (ISO) badge in the header gutter, aligned with the date
  numbers; dropped the redundant "All-day" gutter label
- Small breathing room between the top section and the timeline

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 20:05:40 +02:00
6a90bade8a feat(ui): month card grid + week timeline, wire view switcher
Replace the throwaway debug screen with the first real calendar UI and a
functional Month <-> Week switcher, on Material 3 Expressive.

Month view (S1):
- Material 3 Expressive card-per-day grid; only the current month's weeks
  render (neighbouring days left blank)
- per-day event dots with "+N" overflow, today via primaryContainer
- spring-based press feedback from the active motion scheme
- swipe + drawer navigation, Loading/Failure/Success states

Week view (S2):
- vertical time schedule with overlap-resolved lanes (per-day clipping,
  midnight spanning, instant events)
- all-day / multi-day events as connected horizontal spans
- single scroll container (gutter + day columns stay aligned), columns
  bundled in a rounded container, noon-centred on load
- top section colour-shifts with the app bar on scroll; swipe navigation,
  three states

Shared / infra:
- CalendarHost holds the active view; RootScreen renders it post-permission
- ui/common building blocks: CalendarDrawer, CalendarFailure,
  ViewSwitcherPill, pastelize, observable locale, M3 Expressive slide
  transition (motionScheme fastSpatialSpec)
- unit tests for the week layout (lanes, clipping, all-day spans)
- build: compileSdk 37, material3 pinned to 1.5.0-alpha21 for Expressive

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 20:05:40 +02:00
b792ddc2f0 style: fix launcher icon scaling and centering, update AGP
All checks were successful
CI / ci (push) Successful in 9m58s
2026-06-08 20:30:40 +02:00
440fa57161 fix(debug): use prefixed composite keys in LazyColumn
All checks were successful
CI / ci (push) Successful in 9m35s
Cal.id and Event.instanceId share a numeric range, and the LazyColumn
keys both sections — colliding values (e.g. cal-id=4 + event-instance-id=4)
crashed with "Key '4' was already used". Additionally, Instances._ID is
inherited from the parent Event, so recurring events produce multiple
rows with the same instanceId; the start instant disambiguates them.
2026-06-08 20:19:58 +02:00
Jean-Luc Makiola
00b5aeaac7 feat(icon): line-art cal + calendula bloom + numeral "1"
All checks were successful
CI / ci (push) Successful in 9m42s
Replaces the simple numeral-only foreground from v0.1.0. The new mark
keeps the kalendae reference explicit (a bold "1" inside the
calendar body) and adds a small calendula bloom as a badge in the
bottom-right corner so the app's "calendula" brand reads at first
glance.

- design/icon/calendula_mark.svg: source SVG (232x232 viewport,
  monochrome, lawnicons-style strokes 12/8)
- app/src/main/res/drawable/ic_launcher_foreground.xml: regenerated
  as a VectorDrawable preserving the source path data. Off-white
  (#FAF6F0) strokes on the existing slate background. Reused as the
  <monochrome> slot so Android 13+ themed-icon launchers can recolor
  it from wallpaper.
- fdroid-metadata/.../{en-US,de-DE}/icon.png: 512x512 PNG composed
  from the same source SVG with the slate background baked in, so
  F-Droid clients show a fully rendered tile in the app catalog.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 18:24:04 +02:00
3ced240e23 test: instrumented repository smoke against real CalendarContract 2026-06-08 17:57:12 +02:00
035ac9b003 test: replace placeholder smoke with permission-rationale assert 2026-06-08 17:56:03 +02:00
c03389abe0 ui: replace placeholder with RootScreen routing permission ↔ debug 2026-06-08 17:55:34 +02:00
98f8433156 ui: add DebugScreen showing calendars + next 50 instances 2026-06-08 17:54:23 +02:00
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
2026-06-08 17:37:23 +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
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
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
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