diff --git a/README.md b/README.md index bc8e5ab..444c391 100644 --- a/README.md +++ b/README.md @@ -56,11 +56,19 @@ The **City map** layer on the Map tab is the City of Sandpoint's printed *Downto Waterfront Public Parking* map, georeferenced and drawn in the same colours as the legend: 2-hour free, 3-hour, 4-hour, no time limit, and the paid city lots. 49 areas in all. -**None of it touches ParkSmarter.** The areas live in the local database (bundled with the -app, refreshed from the zone-labels server, cached on-device), the countdown is the phone's -own clock, and the notification is the same foreground service every other session uses. So -tracking your time on a city spot works with no account, no signal, no payment, and in -Anonymous Mode. Two ways to start: +**The free areas never touch ParkSmarter.** They live in the local database (bundled with +the app, refreshed from the zone-labels server, cached on-device), the countdown is the +phone's own clock, and the notification is the same foreground service every other session +uses. So tracking your time on a free city spot works with no account, no signal, no +payment, and in Anonymous Mode. + +The **green city lots are the exception** — they're the map's only paid category, and paying +for them means ParkSmarter. They're hidden entirely when you're not signed in, since parking +you can't actually buy is worse than no parking at all. (Standing in one and tapping "Park +here" says so rather than reporting nothing nearby.) A single lot can be flipped back via +the server's `requiresAccount` field if it turns out to take payment another way. + +Two ways to start: - **Park here** — pins your car from GPS and works out which area you're in. No GPS fix (garage, indoors, radio off)? It asks you to tap the spot instead and pins that. The pin diff --git a/app/app.json b/app/app.json index b00e0ad..27cdffe 100644 --- a/app/app.json +++ b/app/app.json @@ -3,14 +3,14 @@ "name": "BigBrainParking", "slug": "bigbrainparking", "scheme": "bigbrainparking", - "version": "0.6.2", + "version": "0.6.3", "orientation": "portrait", "userInterfaceStyle": "automatic", "newArchEnabled": true, "icon": "./assets/icon.png", "android": { "package": "top.mowden.bigbrainparking", - "versionCode": 23, + "versionCode": 24, "edgeToEdgeEnabled": true, "adaptiveIcon": { "foregroundImage": "./assets/adaptive-icon.png", diff --git a/app/src/api/parkingAreas.ts b/app/src/api/parkingAreas.ts index 0200636..6aad019 100644 --- a/app/src/api/parkingAreas.ts +++ b/app/src/api/parkingAreas.ts @@ -40,6 +40,11 @@ export interface ParkingArea { color: string; shape: 'line' | 'polygon'; geometry: AreaGeometry; + /** + * Overrides the by-category default in [areaRequiresAccount]. Only set this to + * correct a specific lot — e.g. one that turns out to be kiosk- or permit-only. + */ + requiresAccount?: boolean; } export interface AreaData { @@ -76,6 +81,19 @@ export function areaIsFree(kind: AreaKind): boolean { return kind !== 'green_lot'; } +/** + * Whether you need a ParkSmarter account to park here. + * + * The city lots are the map's only paid category ("City lots — Paid hourly or + * permit"); paying for them means ParkSmarter, so they're no use to someone + * browsing without an account. Everything else is free with a posted time limit + * and needs nothing. A single lot can override this if it turns out to take + * payment some other way. + */ +export function areaRequiresAccount(area: ParkingArea): boolean { + return area.requiresAccount ?? area.kind === 'green_lot'; +} + /** * Durations offered when starting tracking, the posted limit first. * diff --git a/app/src/screens/CityAreaScreen.tsx b/app/src/screens/CityAreaScreen.tsx index 713c05e..007eb97 100644 --- a/app/src/screens/CityAreaScreen.tsx +++ b/app/src/screens/CityAreaScreen.tsx @@ -5,7 +5,8 @@ import { useFocusEffect, useNavigation, useRoute } from '@react-navigation/nativ import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; import type { RootStackParamList } from '@/navigation/RootNavigator'; import { useTheme } from '@/theme/ThemeContext'; -import { areaDurationOptions, areaIsFree } from '@/api/parkingAreas'; +import { areaDurationOptions, areaIsFree, areaRequiresAccount } from '@/api/parkingAreas'; +import { useAuth } from '@/auth/AuthContext'; import { endActiveParking, extendAreaParking, @@ -43,6 +44,7 @@ export function CityAreaScreen() { const { area, spot } = useRoute().params; const navigation = useNavigation