Files
RecipesCam/src/utils/locationUtils.ts
T
admin 27a9dfb65f Ship HDF EFFECT to PRO only, and clean the build for Play
Rename LIGHT GLOW to HDF EFFECT and gate it behind PRO: LITE renders the
chip greyed out with a PRO tag and cannot open its slider; restoration of
older settings that carry hdf > 0 still blocks export in LITE.

Play/licence pass:
- drop unused permissions (ACTIVITY_RECOGNITION, READ_MEDIA_AUDIO/VIDEO,
  SYSTEM_ALERT_WINDOW) and requestLegacyExternalStorage
- rename the Leica sims to LC STREETLIFE CLASSIC/VIVID, drop the brand from
  comments; fix app name text, canvaskit copyright, LICENSE owner
- remove expo-image-picker and expo-sensors (no JS import), delete dead
  exports (computeMatrixTint, cinemaSeasonName, base64 helpers,
  formatCoordinate)
- versionCode 3 / versionName 1.2.1, drop the personal email from About
- add docs/privacy-policy.html for the Play listing

Size: R8 + shrinkResources + bundle compression + ABI trim, 161 MB -> 76 MB.
2026-09-15 11:36:52 +07:00

100 lines
3.7 KiB
TypeScript

import * as Location from 'expo-location';
import { GPSInfo } from '../types';
export async function requestLocationPermissions(): Promise<boolean> {
try {
const { status } = await Location.requestForegroundPermissionsAsync();
return status === 'granted';
} catch (error) {
console.error('Error requesting location permissions:', error);
return false;
}
}
// The OS permission screen takes the window focus for a moment and the camera
// session goes down with it, so a capture that asks for location comes back
// "Camera is closed" — one ask per shot turned a burst into failed frames.
// Check the status (no UI, cheap) and raise the dialog at most once per run.
let locationAsked = false;
async function ensureLocationPermission(): Promise<boolean> {
try {
if ((await Location.getForegroundPermissionsAsync()).granted) return true;
if (locationAsked) return false;
locationAsked = true;
return (await Location.requestForegroundPermissionsAsync()).status === 'granted';
} catch (error) {
console.error('Error requesting location permissions:', error);
return false;
}
}
export async function getCurrentGPS(): Promise<GPSInfo | null> {
try {
const hasPermission = await ensureLocationPermission();
if (!hasPermission) return null;
const location = await Location.getCurrentPositionAsync({
accuracy: Location.Accuracy.Balanced,
});
const { latitude, longitude } = location.coords;
let locality = 'UNKNOWN LOCATION';
let country = '';
try {
const geocode = await Location.reverseGeocodeAsync({ latitude, longitude });
if (geocode && geocode.length > 0) {
const address = geocode[0];
// Combine city, region, street or subregion to form a retro looking place string
locality = localityName(address) || 'LOCAL REGION';
country = (address.country || '').toUpperCase();
}
} catch (e) {
console.warn('Reverse geocoding failed, using coordinates only:', e);
}
return {
latitude,
longitude,
locality,
country,
timestamp: location.timestamp,
};
} catch (error) {
console.error('Error fetching GPS info:', error);
return null;
}
}
// The one place-name picker, shared so the camera stamp and a photo's stamp can
// never disagree. A Vietnamese geocoder answers with the commune/ward in
// subregion (post-2025 two-level administration: the commune is the district-
// level name) and the province/city in region, while city and district come
// back null, so the stamp reads "COMMUNE, CITY" instead of a bare city.
export function localityName(address: Location.LocationGeocodedAddress): string | null {
const district = address.subregion || address.district;
const city = address.city || address.region;
// Same name in both slots (one field echoed twice) must not print twice.
const parts = district && district !== city ? [district, city] : [district || city];
const name = parts.filter(Boolean).join(', ');
return name ? name.toUpperCase() : null;
}
export async function reverseGeocode(latitude: number, longitude: number): Promise<string | null> {
// 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 make sure it is held
// before every call (same once-per-run ask as getCurrentGPS).
await ensureLocationPermission();
try {
const geo = await Location.reverseGeocodeAsync({ latitude, longitude });
if (geo && geo.length > 0) return localityName(geo[0]);
} catch (e) {
console.warn('Reverse geocode failed:', e);
}
return null;
}