27a9dfb65f
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.
100 lines
3.7 KiB
TypeScript
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;
|
|
}
|
|
|