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](obtainium://add/https://git.mowden.top/hank/BigBrainParking)** or follow **[INSTALL.md](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](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. ```bash 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](https://developer.android.com/reference/android/app/Notification.Builder#setChronometerCountDown(boolean)), 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`](docs/DISTRIBUTION.md) for the full server + CI + keystore setup.