BigBrainParking/README.md
Erik ad55559f55
All checks were successful
build-apk / build (push) Successful in 28m55s
v0.5.0: persistent parking notification — foreground service, End/Extend
The ongoing countdown now appears whenever a car is parked, paid or free,
and sticks around the way ntfy's does on GrapheneOS.

Why it wasn't showing at all on a paid park: StartSessionScreen carried its
own stricter copy of parseApiTime that demanded MM-DD-YYYY + AM/PM. The
dual-format fix from deb78bf only landed in sessionStatus.ts, so when the
API returned the other format the parser returned null and the notification
was simply never posted. There is now one parser (api/parseTime.ts), and the
failure logs instead of going silent.

Persistence: a plain notify() was never enough — Android 14+ lets the user
swipe an ongoing notification away, and nothing brought it back after a
reboot. BbpSessionService is a real foreground service (type specialUse) that
owns the notification, plus a BOOT_COMPLETED receiver to restore it; specialUse
is one of the types Android 14/15 still allow to start from BOOT_COMPLETED.
The service's life is exactly the session's life: it stops itself, removing
the notification, on End, at expiry, or when there's no session to show.

Buttons: paid sessions previously got no actions at all (only free check-ins
did). Both now get End and Extend/Pay. End stops tracking — honest about the
fact that ParkSmarter has no stop-session endpoint, so bought time keeps
running at the meter. Extend opens the purchase screen for that exact zone.

The active session (with its Zone) is persisted locally, so the countdown and
Extend survive reboot, offline, and Anonymous Mode instead of depending on a
round-trip. sessionStatus.ts + features/checkin collapse into one owner,
features/session/activeParking — one record, one notification.

Verified: :bbp-notify:compileDebugKotlin passes, and the merged app manifest
carries the service (specialUse + FGS subtype property), both receivers, and
the FOREGROUND_SERVICE/SPECIAL_USE/RECEIVE_BOOT_COMPLETED permissions.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 20:10:54 +00:00

5.9 KiB
Raw Blame History

BigBrainParking — big-brain driver

BigBrainParking

An unofficial, de-Googled client for the ParkSmarter (IPS Group) parking system, built to run on GrapheneOS with UnifiedPush notifications and distributed via Obtainium — no Google Play Services, no Firebase, no app store.

Install it (users)

BigBrainParking installs and updates through Obtainium — no Google Play, no account. Works on GrapheneOS. On your phone (with Obtainium installed), tap:

Add BigBrainParking to Obtainium

or follow INSTALL.md for the manual steps.

Monorepo layout

bigbrainparking/
├── parksmarter-client/   # Zero-dep TypeScript API client (reverse-engineered, live-verified)
├── app/                  # Expo / React Native app (BigBrainParking)
└── .gitea/workflows/     # CI: build signed APK -> publish release for Obtainium
  • parksmarter-client — the API layer. All ~40 endpoints, the custom-header auth model, and response models (most verified against production). Runs anywhere; the app imports it directly. See its own README for the API details.
  • app — the phone app. React Native (Expo prebuild), MapLibre maps, VisionCamera QR scanning, expo-secure-store token storage, local session-expiry reminders, and UnifiedPush wiring.

Features

Feature Status Notes
Phone + password login wired tokens in OS keystore
Map of nearby meters (clickable, zoom, live GPS) wired MapLibre + OpenFreeMap tiles (no key)
Last-lot + My-location search wired opens on your last session's lot; GPS sent only via explicit "My location"
QR kiosk scan → meter lookup wired on-device VisionCamera
Save / share kiosks wired local (no server favorites API exists)
Active / past sessions wired list views + tap for full receipt
Start a paid session works confirmed live end-to-end (real $0.10 DL-zone charge); declines surfaced
Ongoing parking countdown wired foreground service; ticks down, End / Extend buttons, survives reboot
Session-expiry reminders wired local on-device notifications — no server, no push
UnifiedPush (ntfy) optional not needed for reminders; stub for future server-initiated msgs

Privacy

BigBrainParking sends your location to ParkSmarter only when you explicitly tap "My location" and search; it opens on your last parking lot instead of your GPS, and ships no analytics/tracking (no Segment/Amplitude/Firebase/Sentry, no ad-ID, no Google services). For a plain-language comparison with the official app — which auto-sends your GPS on map open and bundles that tracking stack — see docs/OFFICIAL_APP_PRIVACY.md. What reaches the API and what never does is also spelled out in the app's About page.

Build & run (dev)

Requires Node 20, JDK 17, Android SDK, and a GrapheneOS device (or any Android device) with USB debugging.

npm install                                   # installs both workspaces
npm run build --workspace parksmarter-client  # compile the client
cd app
npx expo prebuild --platform android          # generate native project
npx expo run:android                          # build + install a dev client

The app talks to prod (apiv2.parksmarter.com) by default — change extra.psEnvironment in app/app.json to point elsewhere.

Notifications on GrapheneOS

Everything here is entirely on-device — no server, no push, no FCM, no Play Services — so it works fully offline. UnifiedPush (ntfy) is wired only as an optional, no-op stub for any future server-initiated messages; nothing time-based needs it.

The ongoing parking countdown. Whenever a session is active — a paid one you bought or a free check-in — a persistent notification shows the time left and ticks down, with End and Extend buttons. It is held up by a real foreground service (modules/bbp-notify, type specialUse), which is what makes it stick on GrapheneOS the way ntfy's does: it survives the app being killed, can't be swiped away, and a BOOT_COMPLETED receiver brings it back after a reboot. The service's life is exactly the session's life — it stops itself, removing the notification, on End, when the meter runs out, or whenever there's no session to show.

The countdown itself costs no battery: the end time is handed to Android as a chronometer, and the system redraws the ticking text with the app closed and no timer of its own.

  • End stops tracking and clears the notification. On a free check-in that genuinely ends it. On a paid session it only stops the display — ParkSmarter has no stop-session endpoint, so time you already bought keeps running at the meter either way.
  • Extend (labeled Pay on a free check-in) opens the purchase screen for that exact zone. The active session is stored locally with its zone, so this works offline, in Anonymous Mode, and after a reboot. Extending doesn't end anything until the purchase actually goes through.

Expiry reminders fire a configurable lead time (default 15 min) before the end, via AlarmManager. Configure them in Account → Notifications, where a "Send a test reminder" button lets you confirm they fire on your phone; the same screen has a toggle for the ongoing countdown.

Distribution via Obtainium (self-hosted)

Tag a release and CI builds a signed APK and publishes it to this repo's releases; your phone's Obtainium tracks the repo and offers updates. See docs/DISTRIBUTION.md for the full server + CI + keystore setup.