Reverse-engineered ParkSmarter API client (TypeScript, live-verified) plus a de-Googled Expo/React Native app for GrapheneOS: MapLibre meter map with GPS, VisionCamera QR kiosk scanning with save/share, local session-expiry reminders, UnifiedPush wiring, and Gitea CI to publish signed APKs for Obtainium. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
63 lines
2 KiB
TypeScript
63 lines
2 KiB
TypeScript
import { useCallback, useEffect, useState } from 'react';
|
|
import * as Location from 'expo-location';
|
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
|
|
export interface Coords {
|
|
latitude: number;
|
|
longitude: number;
|
|
}
|
|
|
|
const LAST_LOC_KEY = 'ps_last_location';
|
|
|
|
/** Persist the most recent fix so the "near my last location" button works cold. */
|
|
async function saveLastLocation(c: Coords) {
|
|
await AsyncStorage.setItem(LAST_LOC_KEY, JSON.stringify(c));
|
|
}
|
|
export async function getLastKnownSavedLocation(): Promise<Coords | null> {
|
|
const raw = await AsyncStorage.getItem(LAST_LOC_KEY);
|
|
return raw ? (JSON.parse(raw) as Coords) : null;
|
|
}
|
|
|
|
/**
|
|
* Foreground location. On GrapheneOS this uses the OS location provider directly
|
|
* (no Google Play Services). We prefer a fast last-known fix, then refine.
|
|
*/
|
|
export function useLocation() {
|
|
const [coords, setCoords] = useState<Coords | null>(null);
|
|
const [granted, setGranted] = useState<boolean | null>(null);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const refresh = useCallback(async () => {
|
|
try {
|
|
const { status } = await Location.requestForegroundPermissionsAsync();
|
|
const ok = status === 'granted';
|
|
setGranted(ok);
|
|
if (!ok) {
|
|
setError('Location permission denied.');
|
|
return null;
|
|
}
|
|
const last = await Location.getLastKnownPositionAsync();
|
|
if (last) {
|
|
const c = { latitude: last.coords.latitude, longitude: last.coords.longitude };
|
|
setCoords(c);
|
|
void saveLastLocation(c);
|
|
}
|
|
const cur = await Location.getCurrentPositionAsync({
|
|
accuracy: Location.Accuracy.Balanced,
|
|
});
|
|
const c = { latitude: cur.coords.latitude, longitude: cur.coords.longitude };
|
|
setCoords(c);
|
|
void saveLastLocation(c);
|
|
return c;
|
|
} catch (e: any) {
|
|
setError(e?.message ?? 'Failed to get location.');
|
|
return null;
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
void refresh();
|
|
}, [refresh]);
|
|
|
|
return { coords, granted, error, refresh };
|
|
}
|