From 423bf533f81177fe0e5bfd799d643551d067ece1 Mon Sep 17 00:00:00 2001 From: locphamtran Date: Thu, 10 Sep 2026 17:01:48 +0700 Subject: [PATCH] fix(gps): resolve place name when the watermark is switched on A photo picked while the GPS chip was off kept its coordinates but no locality (the geocode only ran when the chip was already on), and handleToggleGeotag skipped GPS loading entirely when coordinates already existed -- so switching the chip on showed STREET VIEW. Request location permission inside reverseGeocode (expo-location's Android geocoder throws LocationUnauthorizedException without it) and re-resolve the name from an effect whenever the mark has coordinates but no place name. Both paths now share localityName() so camera and photo stamps cannot disagree. --- App.tsx | 17 ++++++++++++++++- src/utils/locationUtils.ts | 22 +++++++++++++++++----- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/App.tsx b/App.tsx index 88189d1..505529d 100644 --- a/App.tsx +++ b/App.tsx @@ -19,7 +19,7 @@ import PhotoViewerModal from './src/components/PhotoViewerModal'; import { Recipe, ColorAdjustments, CustomWatermark, GpsWatermark, FrameId, GPSInfo, AspectRatio, MeterMode, PhotoEntry, ShutterSound, StartupMode } from './src/types'; import { getAllRecipes, saveCustomRecipe, deleteCustomRecipe } from './src/utils/storageUtils'; import { DEFAULT_ADJUSTMENTS, FILM_SIMS, filmSimToRecipe } from './src/utils/defaultRecipes'; -import { getCurrentGPS, requestLocationPermissions } from './src/utils/locationUtils'; +import { getCurrentGPS, requestLocationPermissions, reverseGeocode } from './src/utils/locationUtils'; import { resolvePhotoGPS } from './src/utils/photoGps'; import { processAndExportPhoto } from './src/utils/exportEngine'; import { processAndExportPhotoNative } from './src/utils/nativeExport'; @@ -245,6 +245,21 @@ export default function App() { } }, []); + // A photo picked while the GPS chip was OFF kept its coordinates but no place + // name (the geocode only ran when the chip was already on), and toggleGeotag + // skips GPS loading entirely when coordinates exist — so turning the chip on + // showed "STREET VIEW". Resolve the name whenever the mark needs one. + useEffect(() => { + if (!useGeotag || !gpsInfo || gpsInfo.locality) return; + let stale = false; + reverseGeocode(gpsInfo.latitude, gpsInfo.longitude).then((name) => { + if (!stale && name) setGpsInfo((g) => (g && !g.locality ? { ...g, locality: name } : g)); + }); + return () => { + stale = true; + }; + }, [useGeotag, gpsInfo]); + // Save/restore session on app lifecycle changes useEffect(() => { const subscription = AppState.addEventListener('change', async (nextAppState) => { diff --git a/src/utils/locationUtils.ts b/src/utils/locationUtils.ts index ae5d1e6..7092a26 100644 --- a/src/utils/locationUtils.ts +++ b/src/utils/locationUtils.ts @@ -29,8 +29,7 @@ export async function getCurrentGPS(): Promise { if (geocode && geocode.length > 0) { const address = geocode[0]; // Combine city, region, street or subregion to form a retro looking place string - locality = address.subregion || address.city || address.district || address.region || 'LOCAL REGION'; - locality = locality.toUpperCase(); + locality = localityName(address) || 'LOCAL REGION'; country = (address.country || '').toUpperCase(); } } catch (e) { @@ -50,12 +49,25 @@ export async function getCurrentGPS(): Promise { } } +// The one place-name picker. Order is the camera's original one +// (subregion = subAdminArea = district/county first, then city, district, +// region) kept verbatim so already-working camera stamps do not shift; only +// the photo path used to drop the district term. Shared so the camera stamp +// and a photo's stamp can never disagree about the same coordinates. +export function localityName(address: Location.LocationGeocodedAddress): string | null { + const name = address.subregion || address.city || address.district || address.region; + return name ? name.toUpperCase() : null; +} + export async function reverseGeocode(latitude: number, longitude: number): Promise { + // Foreground permission is not optional here: expo-location's Android + // geocoder throws LocationUnauthorizedException without it. A photo picked + // before the camera ever asked for location therefore resolved to no name at + // all and the stamp fell back to "STREET VIEW" — so ask before every call. + await requestLocationPermissions(); try { const geo = await Location.reverseGeocodeAsync({ latitude, longitude }); - if (geo && geo.length > 0) { - return (geo[0].subregion || geo[0].city || geo[0].region || '').toUpperCase() || null; - } + if (geo && geo.length > 0) return localityName(geo[0]); } catch (e) { console.warn('Reverse geocode failed:', e); }