Privacy/stigma layer on top of the authenticated Supabase session — re-auth on open so nobody but the user can open Rebreak. Not a login replacement. - expo-local-authentication; NSFaceIDUsageDescription in app.config - stores/appLock.ts: persisted `enabled` pref, in-memory `locked`, device- capability check (`available`), device-passcode fallback on biometric failure - AppLockGate wraps the root layout: locks immediately on `background` (not `inactive` → app-switcher peek doesn't lock), renders LockScreen while `enabled && locked && session` - LockScreen: dark brand screen, auto-prompts on mount + on return from background, "Abmelden" escape hatch (clears session → fresh login next launch) - Settings: new "Sicherheit" section, native UISwitch; enabling requires a successful biometric prompt first; row disabled + explained when device has no biometrics/passcode - de/en strings Per product call: the lock gates the whole app incl. SOS (SOS already requires an authenticated user, so there's no unauthenticated path to carve out). Cold-start: appLock init blocks the splash → `locked` is set before first paint, no flash of unlocked content. ios/ is gitignored so EAS prebuilds the new module. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
36 lines
1.3 KiB
TypeScript
36 lines
1.3 KiB
TypeScript
import { useEffect } from 'react';
|
|
import { AppState } from 'react-native';
|
|
import { useAppLockStore } from '../stores/appLock';
|
|
import { useAuthStore } from '../stores/auth';
|
|
import { LockScreen } from './LockScreen';
|
|
|
|
/**
|
|
* Hängt die App-Sperre vor den App-Inhalt:
|
|
* - sperrt sofort wenn die App in den Hintergrund geht (`background`-State —
|
|
* NICHT `inactive`, sonst würde der App-Switcher-Peek schon sperren)
|
|
* - rendert den LockScreen solange `enabled && locked && session` gilt
|
|
*
|
|
* `init()` der appLock-Store wird im RootLayout zusammen mit den anderen Stores
|
|
* aufgerufen; der Splash wartet auf `ready`, daher gibt es hier kein Flash-of-
|
|
* unlocked-content beim Kaltstart (init setzt `locked = enabled`).
|
|
*/
|
|
export function AppLockGate({ children }: { children: React.ReactNode }) {
|
|
const enabled = useAppLockStore((s) => s.enabled);
|
|
const locked = useAppLockStore((s) => s.locked);
|
|
const lock = useAppLockStore((s) => s.lock);
|
|
const session = useAuthStore((s) => s.session);
|
|
|
|
useEffect(() => {
|
|
if (!enabled) return;
|
|
const sub = AppState.addEventListener('change', (state) => {
|
|
if (state === 'background') lock();
|
|
});
|
|
return () => sub.remove();
|
|
}, [enabled, lock]);
|
|
|
|
if (enabled && locked && session) {
|
|
return <LockScreen />;
|
|
}
|
|
return <>{children}</>;
|
|
}
|