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); }