Petsitting Calendar
A 2-person (you + partner) petsitting planner/calendar Android app + self-hosted
backend, built with Kotlin + Jetpack Compose + Ktor. Visits are costed as
base + distance × rate; recurring bookings expand to individual visits. Data
syncs to https://pets.evileko.dev on a self-hosted VPS.
Status
Latest: v6.5a — action-after-snackbar pattern complete
(A1-A7). Destructive and state-changing actions (status, delete,
archive, reassign, mark-all-complete, bulk-delete) all gate on
snackbar confirmation before firing the backend call. No silent
mutations. Combined with v6.5 Part B (pickers + decimal keyboards),
| 17/17 v6.5 items shipped. 8/8 harness tests passing. |
|
|
 |
 |
 |
| Clients |
Reports |
Calendar |
Settings |
 |
 |
 |
 |
Screenshots (v2.0 polish pass)
| Overview v2 |
Client Detail v2 |
Client Form v2 |
Calendar v2 |
 |
 |
 |
 |
| Settings v2 |
Reports v2 |
Clients v2 |
 |
 |
 |
Screenshots (v6.1 — Schedule-a-Trip)
| Add menu | Trip form | Scheduled | Calendar (next month) |
|
|
|
|
|
Screenshots (v6.3 — Visit Checklist)
| Checklist empty |
Checklist 2/6 |
 |
 |
Screenshots (v6.4 — Per-Pet Instructions)
| Pet instructions on visit detail |
 |
| Clients: long-press multi-select |
Bulk action bar |
 |
 |
| Client Detail v3 (with pets) |
Reassign visit (bottom sheet) |
 |
 |
| After reassign (visit moved to Alex) |
Mark complete (snackbar Undo) |
 |
 |
| Overview FAB menu |
Upcoming visits carousel |
 |
 |
| Settings v3 (profile, theme, notifications) |
Reports v3 (per-sitter/per-client tables + comparison) |
 |
 |
Screenshots (v4.0 — Theme picker + persisted preferences)
| Light theme |
Dark theme |
 |
 |
(Theme picker is in Settings — System / Light / Dark / Dynamic; choice persists across
app restarts via EncryptedSharedPreferences.)
Screenshots (v5.0 — quick-tap status + relative-time chips)
| Status chip tap-to-cycle |
Upcoming: relative-time chips |
 |
 |
| Reports: quick-filter chips |
Visit: Share button |
 |
 |
| Today: long-press money chip |
|
 |
|
| Reports: Share weekly summary |
|
 |
|
| Overview: Mark all complete |
|
 |
|
| Overview: Quick stats strip |
|
 |
|
| Clients: Sort indicator chip |
|
 |
|
 |
|
| Client: Last visit summary |
|
 |
|
| Settings: About card |
|
 |
|
| Client: Swipe-left marks visit complete |
|
 |
|
| Settings: Clear local cache button |
|
 |
|
 |
|
 |
|
| First day of week: applied to This week |
|
 |
|
| Settings: Clear local cache button |
|
 |
|
 |
|
Tap a visit's status chip on Client Detail to cycle SCHEDULED → COMPLETED →
CANCELLED. Each Upcoming visit row shows a pill chip with the time-to-now
("in 7h 15m" / "12h late" in red for past visits). Reports screen has
quick-filter chips (Today / This week / This month / This year) that auto-fill
the From/To fields and refresh. Each visit row has a Share button that
opens Android's system share sheet with a one-line summary:

Architecture
petsitting-calendar/
├── shared/ # DTOs shared between backend and Android
├── backend/ # Ktor + Exposed + Postgres
│ ├── src/main/kotlin/pets/api/
│ │ ├── Application.kt
│ │ ├── db/ # Exposed tables + repos
│ │ ├── plugins/ # Ktor route plugins per resource
│ │ └── service/ # business logic
│ └── src/main/resources/db/migration/ # Flyway V1__init.sql, V2__pets_and_assignments.sql
├── android-app/ # Kotlin + Jetpack Compose + Ktor client
│ └── app/src/main/kotlin/dev/evileko/petsitting/
│ ├── data/ # TokenStore (EncryptedSharedPreferences) + PetsApi (Ktor)
│ └── ui/ # Compose screens (auth/calendar/clients/common/overview/reports/settings/theme)
├── harness/ # Python + ADB UI test driver + Waydroid scripts
│ ├── adb.py
│ ├── api.py
│ ├── scenarios.py
│ ├── ui_test.py
│ ├── shots/ # screenshots captured during E2E runs
│ └── scripts/ # bootstrap.sh, run-tests.sh, test-env-up.sh, test-env-down.sh
├── docker-compose.yml # backend (api + postgres) — built into `petsitting-pets-api` on VPS
└── plan.md, loops.md # autonomous agent loop state
Backend
Ktor app on https://pets.evileko.dev, with a Postgres 17 backend. Docker image petsitting-pets-api is built from backend/ and proxied through nginx (TLS via Let's Encrypt, DNS via Cloudflare).
Endpoints (v3.0)
POST /api/v1/auth/login — username + password → JWT
GET/PUT /api/v1/me — current sitter
GET /api/v1/sitters — list sitters (for visit assignment)
GET /api/v1/clients[?includeArchived=] — list clients
GET /api/v1/clients/{id} — single client (with stats)
POST /api/v1/clients / PUT /api/v1/clients/{id} / DELETE /api/v1/clients/{id} — CRUD
GET /api/v1/clients/{clientId}/pets[/{petId}] / POST /api/v1/clients/{clientId}/pets / PUT /api/v1/clients/{clientId}/pets/{petId} / DELETE — pet CRUD
GET /api/v1/bookings[?clientId=] / POST /api/v1/bookings — bookings
GET /api/v1/visits[?from=&to=&sitterId=&clientId=&status=] / PUT /api/v1/visits/{id} — visits (PUT accepts sitterId for reassignment)
GET /api/v1/visits/upcoming?days=N — upcoming visits for the Overview carousel
GET /api/v1/reports?from=&to= — report summary (per-sitter, per-client, totals)
POST /api/v1/device-token — register FCM token (no-op until FCM creds configured)
Releases
| Tag |
APK |
Notes |
v0.1 |
(see commit d14ca1a) |
Initial ship: backend + Android + harness |
v2.0 |
(see commit 2f8fe57) |
Polish pass: R1-R5 |
v3.0 |
app-debug.apk (21 MB) |
Long-press multi-select, visit assignment, pets, FAB menu, upcoming, settings |
v4.0 |
app-debug.apk (24 MB) |
Theme picker (System / Light / Dark / Dynamic), persisted settings, fixes (README screenshots) |
v5.0 |
app-debug.apk (24 MB) |
Tap-to-cycle status chip on visit detail; relative-time chips on Upcoming |
Download the latest APK from the v5.0 release on Forgejo.
Configuration
Edit .env.example (copy to .env on the VPS) and fill in:
PETS_DB_PASSWORD=<random>
PETS_JWT_SECRET=<random 64+ char>
PETSITTERS=[{"username":"eko","password":"hunter2","displayName":"Eko"}, ...]
FCM_SERVICE_ACCOUNT_PATH=/run/secrets/fcm-service-account.json
Android BuildConfig.API_BASE_URL = https://pets.evileko.dev/api/v1 (set in android-app/app/build.gradle.kts). google-services.json is gitignored; drop the real one from Firebase console in android-app/app/ before shipping.