- Kotlin 99.3%
- Python 0.5%
- Dockerfile 0.2%
|
All checks were successful
ci / test (push) Successful in 16m5s
Lets the gate be re-run after a transient runner blip without an empty code push (run 320 died before tests with no artifacts on a healthy runner; the tree builds+tests clean locally). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U75Nk6AeGvsW6t1mLWLncL |
||
|---|---|---|
| .forgejo | ||
| app | ||
| docs | ||
| gradle | ||
| server | ||
| wear | ||
| .gitignore | ||
| build.gradle.kts | ||
| gradle.properties | ||
| gradlew | ||
| gradlew.bat | ||
| plan.md | ||
| README.md | ||
| REPORT.md | ||
| settings.gradle.kts | ||
| state.md | ||
Orbit
A feature-rich Android calendar app. Kotlin + Jetpack Compose, Material 3.
Signature feature: events can have multiple time slots in the same day (and across days) — like multi-day events, but for disjoint time ranges.
Screen tour
The app as it ships today, screen by screen.
Calendar views
| Month | Month + agenda | Week |
|---|---|---|
![]() |
![]() |
![]() |
| Day | 3-Day | Agenda |
|---|---|---|
![]() |
![]() |
![]() |
| Year | Timeline | Solar |
|---|---|---|
![]() |
![]() |
![]() |
Working with events
| Event detail | Editor | Quick add (natural language) |
|---|---|---|
![]() |
![]() |
![]() |
| Search | Stats | Critical alarm |
|---|---|---|
![]() |
![]() |
![]() |
Settings & management
| Settings | Customize features | Calendars |
|---|---|---|
![]() |
![]() |
![]() |
First run & Orbit Pro
| Welcome | Pro pitch | Paywall |
|---|---|---|
![]() |
![]() |
![]() |
| Code redeemed | Admin key generator | Review ask |
|---|---|---|
![]() |
![]() |
![]() |
On the home screen
| Widgets |
|---|
![]() |
Status
Feature-complete (M0–M32). Every planned milestone shipped — built, code-reviewed, and device-verified on Waydroid. Highlights beyond the core calendar: the M16 solar home view, the M17 visual overhaul (14 palettes), M19 markdown titles, the M24 widget overhaul, and the M20–M23 finish line: tags, holiday feeds, auto backup, editor smarts, trash, a views pack, notification actions, stats, and onboarding.
M32 — Customize everything + AI assistant. Two additions. First, Customize features grew from an editor-field toggle list into a full declutter panel: switch off whole calendar views (Solar, Timeline, Year, Month+agenda, Multi-day) and menu tools (Stats, Invitations, Print, Share day, Share availability, Templates, Add webcal) and they vanish everywhere — the view switcher, the keyboard shortcuts, the overflow menu — while a disabled current view quietly falls back to Month. The screen has search, a live "N of M on" count per group, and a one-tap "Turn everything back on". Second, an AI assistant (backed by a self-hosted account service): sign in, ask about your week or for free time, and accept the events it drafts with a single tap. Your calendar context is assembled on-device — only the summary you'd expect (next 7 days of titles and times) ever leaves the phone, and the session token lives in its own store, never in a settings backup.
| Customize (editor) | Customize (views + tools) | AI assistant |
|---|---|---|
![]() |
![]() |
![]() |
M31 — Premium parity, offline wave. Four features straight off the premium-app gap list, every one switchable in Customize features: calendar sets (save the calendars you have visible as "Work" or "Home" and flip between them in one tap — from Manage calendars or the overflow menu), bulk edit (long-press the agenda to multi-select, then delete, shift by days, duplicate to a date, or move between calendars — series stay protected), a curated calendar directory (one-tap subscriptions: holidays, F1, moon phases, rocket launches), print month (a proper wall-calendar PDF), and a "their time" line on event details when a second time zone is set.
| Calendar sets | Directory | Bulk select | Print month |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
M30 — Wear OS companion. A standalone watch app in the new :wear module — Wear OS mirrors your phone's calendar onto the watch, so Orbit reads it locally with no sync plumbing. On the wrist: a glanceable agenda (day headers, color-coded event chips, swipe-dismissable detail), an "Up next" tile with your next three events, and a next-event complication for any watch face. Ships from the same listing as the phone app.
| Watch agenda | Watch detail |
|---|---|
![]() |
![]() |
M28 — Promo keys + a real welcome. First-run onboarding grew into a four-page story — an animated welcome with feature highlights, then a full Orbit Pro pitch (what you get, both prices, "start free — decide later") before the permission and start-view steps. And for handing out free subscriptions: offline promo keys (ORBIT-XXXXX-XXXXX-XXXXX, HMAC-signed, one-time per device) redeemable from "Have a code?" on the paywall — with a hidden admin generator behind a master code that mints, lists, copies, and shares keys right on the device.
| Welcome | Pro pitch |
|---|---|
![]() |
![]() |
M27 — Play Store prep. The business layer: a 15-day free trial backed by Google Play Billing with two ways to keep Orbit — $4.99/year (with the trial attached as a Play offer) or $14.99 lifetime — behind a paywall that hard-gates on trial expiry, restores purchases, survives offline starts via a cached entitlement, and never flashes at paying users while the store connects. Plus a polite review funnel: after about a week of real use Orbit asks "Enjoying Orbit?" once — a yes routes into Google Play's in-app review, a no opens a feedback email instead, and either answer retires the prompt for good.
| Paywall | Review ask |
|---|---|
![]() |
![]() |
M26 — Premium-gap features. Seven features from a gap analysis against premium calendar apps: task time-blocking (quick-add a task with a time and it lands as a block on the day grid — tap for Done/Unschedule), an "Add video call" chip in the editor that fills a ready-to-share Jitsi Meet link, ICS email invites (RFC 5545 METHOD:REQUEST attachment mailed to attendees from the event detail), a proper ringing alarm screen for critical reminders (full-screen over the lock screen, alarm sound + vibration, Snooze 10 min / Dismiss), a 28-day timeline view (horizontal lane-packed strip, view #8), share day as text (formatted agenda into any messenger), and keyboard shortcuts (n / q / t / / / s / 1-8) for keyboard-attached tablets and desktop-mode.
M25 — UI polish pass. Twenty fixes and refinements from a full design review: widgets got vertically-centered cards, a description preview on Next event, stretched week-grid density bars with attached "+N" overflow, month-cell "+N" for busy days, card-structure dividers on the checklist, time prefixes + a legibility scrim on the Orbit strip, and per-row "⋯" quick actions (open / guarded delete) on the three list widgets. The config screen gained an aspect-correct live preview, live transparency %, sticky Save, an event-picker search filter, and custom hex colour entry. In-app: the editor's location field moved up beside the title, month chips wrap to two lines on sparse days, and the stacked detail view splits 60/40. A full light-mode QA sweep rode along — plus a root-cause fix for a long-lived bug where a wiped DataStore made the hidden trash calendar (and every "deleted" event) visible in the app and all widgets.
M24 — Widget overhaul. The whole widget suite was rebuilt on classic RemoteViews after the Glance stack turned out to never repaint placed widgets (updateAll() returned successfully without ever re-rendering — latent since M12). Widgets now genuinely refresh on every calendar change, and titles render markdown (**bold**, *italic*, ***both***) with tappable checkboxes that write the provider straight from the home screen — no app launch, multi-slot fan-out and Google sync included. Five new widgets join the suite (12 total): Next event (countdown, time range, location, a Join button for events with a meet link), Checklist (pin an event; its description's - [ ] lines become a tappable checklist), Week grid (7 columns of event-density blocks), Quick actions (5-button launcher strip: new event, new task, search, today, voice), and Orbit — a live orrery of the solar system rendered from the app's own ephemeris, planets at their real positions, tap to open the Solar view.

M23 — Notifications, stats & polish. Reminder notifications gain action buttons (snooze, "5 min before", RSVP Yes/No for invites) and per-calendar ringtones; a configurable morning digest summarizes the day. Search gets calendar/date/scope/attachment filters and recent searches; the agenda pulls to refresh (manual provider sync). A Stats screen charts hours per calendar and tag, busiest weekday, and a year heatmap. New Year-progress widget, a 3-page onboarding flow, a TalkBack/touch-target audit, and a baseline profile round it out.

M22 — Views pack. The agenda becomes a continuously scrolling schedule view (sticky month headers, 13-month window); the 3-day view generalizes to N days (2–14); month view gains a heat-map density mode; wide screens get a two-pane month+day layout; a collapsible mini-month strip sits above day/agenda; and the day/week gutter can show a second time zone.
M21 — Editor smarts. Conflict warnings ("Overlaps Standup 9:00"), title autocomplete with smart defaults, a "Find a time" free-slot suggester, quick-add NLP v2 ("jul 22nd", locations, guests, reminders, /calendar, !priority), voice quick-add, a 30-day trash for deleted events, and a duplicate finder.
M20 — Parity pack. Color-coded event tags with filter chips across views and search, a 24-country holiday feed picker, nightly auto backup to a folder you pick (JSON + optional ICS), and per-event color overrides.
M18 — Widget suite. Six configurable Glance widgets — month grid (day taps deep-link), agenda, tasks, day strip, countdown, date launcher (+ year progress from M23) — each with a per-instance style editor: any palette, transparency, text size, corners, OLED, calendar filter, and tap action.
M19 — Markdown in titles + descriptions. Event titles understand a lightweight markdown: **bold**, *italic*, and a leading [ ] / [x] that turns the event into a checkbox event. It renders styled everywhere — month chips, week/day tiles, agenda rows, search, invitations, quick-add's live preview, even the solar view's labels and the agenda widget (☐/☑ prefix) — and the checkbox is tappable in agenda and detail: one tap flips [ ]↔[x] in the provider (fanning out to multi-slot siblings) and strikes the title through. The editor gets B / I / ☑ buttons that wrap/unwrap the selection, with markers rendered live but dimmed while you type. Descriptions go further: - bullets, tappable [ ] checklist lines, and clickable bare links in the detail view. The markdown is stored raw in the provider TITLE/DESCRIPTION, so it syncs anywhere as plain text — other clients just see the literal markers, by design.

M17 — Visual overhaul. Every screen redesigned on a new theme engine. Fourteen color palettes — ten celestial (Orbit, Solar, Mars, Terra, Neptune, Nebula, Rosé, Comet, Aurora, Luna) plus ports of four classic editor schemes (Dracula, Nord, Gruvbox, Solarized) — derived tonally from seed colors: pick one from the swatch gallery in Settings and the whole app recolors live; dynamic color (Android 12+) and the AMOLED true-black mode still layer on top. The editor-scheme ports use their real published colors in dark mode (Dracula's purple/pink/cyan on #282A36, Nord's Frost on Polar Night, Gruvbox's yellow/orange/aqua on dark0, Solarized's blue/cyan/violet on base03), and a Themed mode (next to OLED) paints the whole background with the palette's signature color. Corner style is now a setting (Sharp / Soft / Round / Orbit) that drives the entire Material shape scale, and an Animations toggle gates the calendar screens' decorative transitions through a shared spring-motion vocabulary (OrbitMotion) — the Solar view's living scene keeps its own always-on motion by design. The redesign itself: card-based Settings with a collapsing hero title and live shape/palette previews; a decluttered home top bar (view switcher + actions consolidated into one iconed menu, tappable animated title, extended "New event" FAB, animated view transitions); tonal event chips with leading color bars in month/week/day/agenda; today rendered as a filled primary circle/pill everywhere; a hero-carded event detail with RSVP chips and iconed info rows; a sectioned event editor with chip-based recurrence (draft + Apply custom builder); pill search with result cards and proper empty states; a quick-add hero input whose parse preview animates in as chips; and carded calendars/invitations/import screens with animated RSVP dismissal.

M16 — Solar system home view. A live orrery as your calendar's homepage: all eight planets at their real heliocentric positions (computed continuously from J2000 mean elements), orbiting a breathing sun over a twinkling parallax starfield. Earth's orbit is the year — the grey arc is time already traveled since Jan 1, the white arc is what's left, and the boundary sits exactly where Earth is right now. Events from every visible calendar ride the ring as colored glowing markers that cluster when dense. Pinch to zoom (0.5–20×, anchored under your fingers), drag to tilt the whole plane up to 30° with depth-sorted fake 3D, pan and fling when zoomed. Zooming in reveals month ticks and labels, then event titles with dates, times and locations; tap a marker to open the event, tap a cluster to dive into it, double-tap (or the Today button) to fly home. Tap Earth to fly into the Earth–Moon system: the Moon rides its real orbit, and today's open tasks circle the path as little satellites. Shooting stars, drifting corona, twinkling parallax starfield — always deep-space dark, whatever the app theme. Selectable as the start view.

M15 — Feature blitz. Sixteen feature packages, built by parallel worktree agents and taken through seven Codex review rounds to a clean verdict:
- Views & navigation: 3-Day view, go-to-date jump, week numbers, hide weekends, configurable work-hours highlight (non-work hours shaded on the timeline), text scale, landscape polish.
- Weather: Open-Meteo forecast chips (emoji + hi/lo) on day headers and agenda — free-text location, no API key, 3 h cache, fully off by default.
- Events: availability/visibility fields (busy/free, public/private), attachments (link URIs via ExtendedProperties), meeting-link join button, tap-to-map for locations, RRULE BYSETPOS (nth/last weekday of month), duplicate to a picked date.
- Reminders: travel-time buffer alarms ("leave by" notification a configurable lead time before the event starts), multi-duration snooze (5/30/60 min), quiet hours deferral, per-calendar default reminders, critical full-screen alarms.
- Agenda & bulk ops: long-press multi-select with bulk delete / move-to-calendar, undo extended to moves and edits, invitations inbox (pending RSVPs in one place).
- Tasks: recurring tasks, subtasks, priority; a task widget joins the agenda widget, plus a month widget.
- Sharing & intents: availability-sharing text generator (free/busy gaps for a date range), app shortcuts, share-target (share text into quick-add), webcal link intent.
- Local calendars: full management screen — create / rename / recolor / delete.
Screenshots for M15 land after the next Waydroid session.
M14 — App lock + backup. A Security toggle locks the app behind the device credential; Backup exports/restores all app-local data (tasks, templates, webcal subscriptions, settings) as JSON via the Storage Access Framework — calendar events aren't included (they live in Google/device calendars).
M13 — Deep settings + theming. Settings screen: theme (System / Light / Dark / AMOLED black), dynamic color, start view, default event duration — all applied live and persisted.
M12 — Home-screen widget. A Glance agenda widget lists the next ~10 upcoming events (time + title), prompts for calendar access if needed, and opens the app on tap. It refreshes on every calendar change and is light/dark-aware.
M11 — ICS import/export + webcal. Open a .ics file to preview and import its events; Export .ics shares the next year's events as a calendar file. Add webcal… subscribes to a remote .ics (e.g. a public holiday feed) as an app-owned local calendar, refreshed every 6 h by a WorkManager job. The ICS parser is hand-rolled (no dependency).
M10 — Attendees + RSVP + contact birthdays. Add guests to an event (contact autocomplete or a typed email); detail shows the guest list with statuses and Yes / Maybe / No RSVP buttons (writes the calendar owner's attendee row). Contact birthdays appear as a read-only virtual "Birthdays" calendar (🎂, injected at the repo layer, toggle-able like any calendar); tapping one opens a read-only detail.
M9 — Quick-add (natural language) + Tasks. The FAB opens a quick-add box: type "dentist tue 3-4pm every week" and a pure-Kotlin parser (regex + java.time) turns it into an event — date (today/tomorrow/weekday/next-weekday/month-day), time or range, duration ("for 2h"), and recurrence ("every week", "every weekday") — with a live preview; Add saves, Edit details opens the prefilled editor. A Task mode saves a to-do instead; tasks interleave into the agenda by due date with tap-to-complete.
M8 — Drag-drop + resize + pinch-zoom. On the Day/Week timeline: long-press-drag an event to move it (snaps to 15 min, keeps duration), drag the top/bottom edge to resize (min 20 min), and pinch to zoom the hour height (persisted). Edits are gated to writable calendars; moving a recurring event opens the This/This-and-following/All scope dialog so the series can't be corrupted.
M7 — Search · Year · Duplicate · Templates · Share. Top-bar search (title/location over ±1 year, deduped to each event's nearest occurrence). Year view — 12 mini-months, tap a month to jump. From an event's detail: Duplicate (opens a prefilled new event), Share as text (system chooser), Share as .ics (hand-rolled VEVENT via FileProvider), and Save as template. Templates persist as JSON in DataStore (no Room); New from template opens a prefilled editor.
| Year view | Search |
|---|---|
![]() |
![]() |
M6 — Reminders + notifications. Per-event reminders (preset menu: at-time / 5–30 min / 1–2 h / 1–2 days, multiple per event), stored in the provider Reminders table so they persist and sync. ReminderScheduler arms exact alarms (setExactAndAllowWhileIdle, inexact fallback) for every trigger in the next 7 days — re-derived from the provider on each calendar change and re-armed at boot, so no alarm state is tracked. Firing posts a high-importance notification with Snooze (5 / 30 / 60 min) / Dismiss actions (snooze durations added in M15); stale alarms self-suppress if the event is gone.
M5 — Multi-slot events (signature feature). One event can hold multiple disjoint time slots in the same day (or across days) — like a multi-day event, but for separate ranges. Under the hood each slot is an ordinary provider event; siblings share a CUSTOM_APP_URI group id (orbit://slotgroup/<uuid>), so the grouping syncs to Google and is recoverable with no local database. Editor "+ Add time slot" builds the slots; detail shows "Slot N of M" with tappable siblings; renaming/recoloring fans out to every slot; delete offers This slot / Entire event.
| Multi-slot editor | Slot detail |
|---|---|
![]() |
![]() |
M4 — Recurrence. RRULE build/parse/humanize (FREQ/INTERVAL/BYDAY+ordinals/BYMONTHDAY/COUNT/UNTIL). Editor recurrence row: presets (daily / weekly on day / every weekday / monthly on day n / annually, computed from the start date) + a custom dialog (interval, weekday chips, ends never/on-date/after-n-times). Recurring writes use RRULE + DURATION (never DTEND); the provider expands occurrences. Editing/deleting a recurring event opens a This / This-and-following / All scope dialog: single = an exception occurrence, this-and-following = truncate the master's UNTIL and spawn a new series, all = edit the master. Detail humanizes the rule ("Every 2 weeks on Mon, Wed").
| Custom recurrence | Recurring detail |
|---|---|
![]() |
![]() |
M3 — Event CRUD. Tap any event → detail screen (duration + live countdown "in 20m" / "now · ends in 45m" / "3d ago", color dot, calendar). Create (FAB) / edit / delete via a full editor: title, writable-calendar picker, all-day switch, start/end date + time pickers, searchable time-zone dialog, 11-color Google palette + calendar-default, location, description. Delete shows an Undo snackbar. End auto-follows start (keeps duration); end ≤ start clamps to +1h on save.
| Editor | Detail |
|---|---|
![]() |
![]() |
M2 — Day + Week views. Shared TimeGrid timeline (hour gutter, grid lines, greedy overlap column-packing, all-day chip row, live now-line with dot, auto-scroll to 07:00): day = 1 column, week = 7 columns, both in horizontal pagers. Tapping a day in the month grid jumps to that day's view. View menu now Month/Week/Day/Agenda.
| Week | Day |
|---|---|
![]() |
![]() |
M1 — calendar read layer, Month + Agenda views. Reads all device calendars via CalendarContract.Instances (provider expands recurrence), month grid with event chips / today ring / +n overflow / multi-day spans, agenda with sticky day headers and Today/Tomorrow labels, per-calendar visibility toggles (DataStore-persisted) in a bottom sheet, view switcher + today button in the top bar, runtime permission gate. Debug builds seed two local calendars with sample events.
| Month | Agenda | Calendars |
|---|---|---|
![]() |
![]() |
![]() |
Architecture
- Events live in Android's CalendarProvider (system calendar store). Google Calendar sync comes from Google's own sync adapter on GMS devices — no OAuth, no custom sync code. Other calendar accounts (Outlook etc.) appear too.
- Multi-slot events = N ordinary provider events sharing a group UUID stamped on each event's
Events.CUSTOM_APP_URI(orbit://slotgroup/<uuid>). The stamp is the group key and recovery anchor, and it syncs to Google — so no local sidecar DB is needed for grouping. (A Room sidecar may still land later for tasks / templates / webcal / quick-add history if those features need it.) - Single Gradle module, package-by-feature under
dev.orbit. No DI framework.
Milestones
M0 toolchain/scaffold ✅ · M1 month+agenda views ✅ · M2 day+week views ✅ · M3 event CRUD ✅ · M4 recurrence ✅ · M5 multi-slot events ✅ · M6 reminders/notifications ✅ · M7 search/year/templates ✅ · M8 drag-drop+pinch ✅ · M9 quick-add NLP+tasks ✅ · M10 attendees+birthdays ✅ · M11 ICS+webcal ✅ · M12 widgets ✅ · M13 deep settings+theming ✅ · M14 app lock+backup+polish ✅ · M15 feature blitz (16 features) ✅ · M16 solar home view ✅ · M17 visual overhaul ✅ · M18 widget suite ✅ · M19 markdown titles+descriptions ✅ · M20 parity pack ✅ · M21 editor smarts ✅ · M22 views pack ✅ · M23 notifications+stats+polish ✅ · M24 widget overhaul ✅ · M25 UI polish ✅ · M26 premium-gap features ✅ · M27 Play Store prep ✅ · M28 promo keys + welcome ✅ · M29 visual redesign ✅ · M30 Wear OS companion ✅ · M31 premium parity (offline) ✅ · M32 customize expansion + AI assistant ✅
Building
Toolchain (Arch): paru -S --needed jdk17-openjdk gradle android-sdk-cmdline-tools-latest, then SDK components live in ~/android-sdk (sdkmanager --sdk_root=$HOME/android-sdk "platforms;android-37.0" "build-tools;35.0.0"). local.properties points at ~/android-sdk.
- Gradle 9.6.1 (wrapper) + AGP 9.2.1 (built-in Kotlin — no
org.jetbrains.kotlin.androidplugin) + Kotlin 2.4.0 compose plugin. - compileSdk 37 (required by current AndroidX), targetSdk 36, minSdk 26.
GRADLE_USER_HOME=$PWD/.gradle-home ./gradlew :app:assembleDebug
GRADLE_USER_HOME is project-local (.gradle-home/, gitignored) so builds work inside the Claude Code sandbox.
Testing on Waydroid
Agent-driven screenshot testing: build → installDebug → drive UI with adb shell input → screencap → inspect. Full runbook in .claude/skills/waydroid-test/SKILL.md (health checks, adb auth fix, interaction recipes, crash sweep).
Waydroid image is API 33 with GAPPS and live two-way Google sync — test events go in throwaway LOCAL calendars only, never the synced com.google ones (see the runbook's sync warning).
Docs
state.md— append-only log of every change.docs/screenshots/— current UI screenshots, embedded above.














































