Persist device/camera settings across restarts independent of startup mode

Restore shutter sound/metering/RAW from a dedicated key under both 'default'
and 'last session' startup (the session file only stores the look and is
written on background events, so it can be stale after a force-kill). Guard
the persist effect until hydration finishes so the mount-time defaults cannot
overwrite the stored values; drop the stale session override of these three.
This commit is contained in:
2026-09-10 07:36:22 +07:00
parent 144b211d35
commit 3eeef6feb9
+40 -3
View File
@@ -30,6 +30,9 @@ import './global.css';
const SESSION_KEY = 'saved_session';
const STARTUP_MODE_KEY = 'startup_mode';
// Camera/device settings that survive restarts regardless of startup mode
// (the session file only restores the full look under 'last session').
const CAMERA_SETTINGS_KEY = 'camera_settings';
// P5: native Kotlin export path. EXPO_PUBLIC_NATIVE_EXPORT=1 swaps the Skia
// render engine for the RecipesCamExport native pipeline (same look contract,
@@ -204,10 +207,31 @@ export default function App() {
return () => subscription.remove();
}, [mode, libraryImageUri, adjustments, selectedFrame, useGeotag, selectedRecipe, gpsInfo, customWm, aspectRatio, meterMode, shutterSound]);
// Guards the camera-settings persist effect below: until initSession has
// read the stored values, changes are just the initial defaults — writing
// them would overwrite the saved settings (async restore loses the race
// against the mount effect).
const settingsHydratedRef = useRef(false);
const initSession = async () => {
const list = await getAllRecipes();
setRecipes(list);
// Device/camera settings restore first: they apply under BOTH startup
// modes ('default' resets only the look, not these hardware choices).
try {
const raw = await AsyncStorage.getItem(CAMERA_SETTINGS_KEY);
if (raw) {
const s = JSON.parse(raw);
if (s.shutterSound === 'on' || s.shutterSound === 'off') setShutterSound(s.shutterSound);
if (s.meterMode === 'evaluative' || s.meterMode === 'highlight') setMeterMode(s.meterMode);
if (typeof s.rawEnabled === 'boolean') setRawEnabled(s.rawEnabled);
}
} catch (e) {
console.warn('Failed to read camera settings:', e);
}
settingsHydratedRef.current = true;
// Startup mode: 'default' opens camera + PROVIA film sim (neutral
// adjustments, DEFAULT_ADJUSTMENTS) at 4:3. 'last' restores the previous
// session from AsyncStorage; with no saved session it falls back to the
@@ -266,9 +290,11 @@ export default function App() {
setAdjustments({ ...DEFAULT_ADJUSTMENTS, ...session.adjustments });
if (session.selectedFrame) setSelectedFrame(session.selectedFrame);
if (session.useGeotag !== undefined) setUseGeotag(session.useGeotag);
if (session.aspectRatio === '4:3' || session.aspectRatio === '3:2') setAspectRatio(session.aspectRatio);
if (session.meterMode === 'evaluative' || session.meterMode === 'highlight') setMeterMode(session.meterMode);
if (session.shutterSound === 'on' || session.shutterSound === 'off') setShutterSound(session.shutterSound);
if (session.aspectRatio === 'full' || session.aspectRatio === '4:3' || session.aspectRatio === '3:2') setAspectRatio(session.aspectRatio);
// NOTE: meterMode/shutterSound/rawEnabled are NOT restored from the
// session snapshot here — the session file is only written on background,
// so it can be stale after a force-kill. CAMERA_SETTINGS_KEY above
// persists them on every change and is authoritative for these three.
if (session.gpsInfo) setGpsInfo(session.gpsInfo);
if (session.customWm && typeof session.customWm === 'object') {
// Restore defensively: a session saved by an older build may miss the
@@ -384,6 +410,17 @@ export default function App() {
AsyncStorage.setItem(STARTUP_MODE_KEY, mode).catch(() => {});
};
// Persist device/camera settings on every change — not just on background —
// so they survive a kill/relaunch even under 'default' startup, which only
// resets the look (mode/ratio/recipe/geotag), not these.
useEffect(() => {
if (!settingsHydratedRef.current) return;
AsyncStorage.setItem(
CAMERA_SETTINGS_KEY,
JSON.stringify({ shutterSound, meterMode, rawEnabled })
).catch(() => {});
}, [shutterSound, meterMode, rawEnabled]);
// Latest-rawEnabled ref so the stable session-error callback below can read
// the live toggle without re-subscribing the Camera session on every render.
const rawEnabledRef = useRef(rawEnabled);