feat(gps): a Settings GPS switch that reads the location; the watermark chip only prints it
The watermark chip used to be the only GPS control, so turning the watermark
off also stopped the photo from carrying coordinates. Split the two: a GPS
master switch in Settings decides whether the location is read at all, and the
chip decides whether it is drawn on the photo.
0x889d is also written as UTF-8 now — a place name with accents ("TAM KỲ, ĐÀ
NẴNG") was going out latin-1 and reading back as mojibake.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { View, Text, Modal, TouchableOpacity } from 'react-native';
|
||||
import * as Haptics from 'expo-haptics';
|
||||
import { X, Volume2, Info, ChevronRight, Settings as SettingsIcon } from 'lucide-react-native';
|
||||
import { X, Volume2, Info, ChevronRight, MapPin, Settings as SettingsIcon } from 'lucide-react-native';
|
||||
import { AspectRatio, MeterMode, ShutterSound, StartupMode } from '../types';
|
||||
import CreditsModal from './CreditsModal';
|
||||
|
||||
@@ -31,6 +31,8 @@ interface SettingsModalProps {
|
||||
onMeterModeChange: (mode: MeterMode) => void;
|
||||
shutterSound: ShutterSound;
|
||||
onShutterSound: (sound: ShutterSound) => void;
|
||||
gpsEnabled: boolean;
|
||||
onGpsEnabled: (enabled: boolean) => void;
|
||||
startupMode: StartupMode;
|
||||
onStartupMode: (mode: StartupMode) => void;
|
||||
}
|
||||
@@ -88,6 +90,8 @@ export default function SettingsModal({
|
||||
onMeterModeChange,
|
||||
shutterSound,
|
||||
onShutterSound,
|
||||
gpsEnabled,
|
||||
onGpsEnabled,
|
||||
startupMode,
|
||||
onStartupMode,
|
||||
}: SettingsModalProps) {
|
||||
@@ -121,6 +125,23 @@ export default function SettingsModal({
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* Device GPS: the master switch. The watermark chip only prints it. */}
|
||||
<View>
|
||||
<SectionLabel>GPS</SectionLabel>
|
||||
<ChipRow
|
||||
chips={[
|
||||
{ key: 'gps-on', label: 'ON', active: gpsEnabled, onPress: () => onGpsEnabled(true) },
|
||||
{ key: 'gps-off', label: 'OFF', active: !gpsEnabled, onPress: () => onGpsEnabled(false) },
|
||||
]}
|
||||
/>
|
||||
<View className="flex-row items-center mt-1.5 ml-1">
|
||||
<MapPin size={11} color="#52525b" strokeWidth={1.75} />
|
||||
<Text className="text-zinc-600 font-mono text-[10px] ml-1.5">
|
||||
ON: use your location for the photo's coordinates and the GPS watermark. OFF: nothing is read. The watermark chip only decides whether it is printed.
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Startup mode */}
|
||||
<View>
|
||||
<SectionLabel>STARTUP MODE</SectionLabel>
|
||||
|
||||
+14
-3
@@ -84,10 +84,21 @@ function u32w(b: Uint8Array, p: number, v: number): void {
|
||||
|
||||
// --- entry builders --------------------------------------------------------
|
||||
|
||||
// ASCII-typed byte string, NUL-terminated as EXIF requires. The bytes are UTF-8:
|
||||
// a place name ("TAM KỲ, ĐÀ NẴNG") or a market name can carry accents, and the
|
||||
// charCodeAt & 0xff shortcut would mangle them into latin-1. Pure-ASCII text
|
||||
// encodes byte-for-byte the same as before.
|
||||
function asciiEntry(tag: number, text: string): Entry {
|
||||
const data = new Uint8Array(text.length + 1); // NUL-terminated, as EXIF requires
|
||||
for (let i = 0; i < text.length; i++) data[i] = text.charCodeAt(i) & 0xff;
|
||||
return { tag, type: ASCII, count: data.length, data };
|
||||
const bytes: number[] = [];
|
||||
for (const ch of text) {
|
||||
const cp = ch.codePointAt(0) as number;
|
||||
if (cp < 0x80) bytes.push(cp);
|
||||
else if (cp < 0x800) bytes.push(0xc0 | (cp >> 6), 0x80 | (cp & 0x3f));
|
||||
else if (cp < 0x10000) bytes.push(0xe0 | (cp >> 12), 0x80 | ((cp >> 6) & 0x3f), 0x80 | (cp & 0x3f));
|
||||
else bytes.push(0xf0 | (cp >> 18), 0x80 | ((cp >> 12) & 0x3f), 0x80 | ((cp >> 6) & 0x3f), 0x80 | (cp & 0x3f));
|
||||
}
|
||||
bytes.push(0);
|
||||
return { tag, type: ASCII, count: bytes.length, data: new Uint8Array(bytes) };
|
||||
}
|
||||
|
||||
function shortEntry(tag: number, v: number): Entry {
|
||||
|
||||
@@ -717,11 +717,11 @@ export async function processAndExportPhoto(
|
||||
bytes,
|
||||
{
|
||||
dateTime: options?.capturedAt ? new Date(options.capturedAt) : null,
|
||||
gps: useGeotag && gpsInfo ? { latitude: gpsInfo.latitude, longitude: gpsInfo.longitude } : null,
|
||||
gps: gpsInfo ? { latitude: gpsInfo.latitude, longitude: gpsInfo.longitude } : null,
|
||||
width: resultImage.width(),
|
||||
height: resultImage.height(),
|
||||
dpi: options?.dpi ?? null,
|
||||
locationName: useGeotag && gpsInfo ? gpsInfo.locality : null,
|
||||
locationName: gpsInfo ? gpsInfo.locality : null,
|
||||
device: getDeviceIdentity(),
|
||||
},
|
||||
await readPhotoBytes(sourceUri)
|
||||
|
||||
Reference in New Issue
Block a user