v0.3.0: local free check-in with actionable countdown notification (Phase C)
All checks were successful
build-apk / build (push) Successful in 40m22s

For a zone labeled free_2h/3h/4h, "Check in (free · Xh)" starts a local, API-free
countdown to the free limit — no ParkSmarter call. The bbp-notify module gains a
showCheckin() that posts the ticking chronometer with "End" / "Pay" buttons, a
BbpActionReceiver (declared in the module's new AndroidManifest.xml) that records
the tap in SharedPreferences and, for Pay, relaunches the app; consumePendingAction()
is drained on foreground by useCheckinSync() — End clears the check-in, Pay hands
off to StartSession for the stored zone. A pre-expiry alert reuses the existing
reminder lead-minutes. Starting a paid session supersedes an active check-in.

Also: MeterDetail shows the free/pay-immediate label and (admins) labeling chips.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-07-24 18:23:58 +00:00
parent eeaf09ea0e
commit 463facbe5a
10 changed files with 368 additions and 59 deletions

View file

@ -7,10 +7,22 @@ interface BbpNotifyNative {
* to endTimeMillis. Returns a short diagnostic string (e.g. "posted enabled=true").
*/
showCountdown(title: string, body: string, endTimeMillis: number): Promise<string>;
/** Same ticking countdown, plus "End" / "Pay" action buttons. */
showCheckin(
title: string,
body: string,
endTimeMillis: number,
endLabel: string,
payLabel: string,
): Promise<string>;
/** Remove the countdown notification. */
clear(): Promise<void>;
/** Read + clear the action a notification button set: 'end' | 'pay' | ''. */
consumePendingAction(): Promise<string>;
}
export type PendingAction = 'end' | 'pay' | null;
// Android-only, and only present in a build that includes the native module
// (returns null in Expo Go / other platforms — callers degrade gracefully).
const native =
@ -30,6 +42,23 @@ export async function showCountdown(
return native.showCountdown(title, body, endTimeMillis);
}
export async function showCheckin(
title: string,
body: string,
endTimeMillis: number,
endLabel: string,
payLabel: string,
): Promise<string> {
if (!native) return 'no-native-module';
return native.showCheckin(title, body, endTimeMillis, endLabel, payLabel);
}
export async function clearCountdown(): Promise<void> {
await native?.clear();
}
/** Read + clear a notification-button action taken while the app was away. */
export async function consumePendingAction(): Promise<PendingAction> {
const a = await native?.consumePendingAction();
return a === 'end' || a === 'pay' ? a : null;
}