/** * AppLockGate: covers TREK until Face ID, Touch ID or the passcode unlocks it, * when the app lock is on (native/appLock.ts). It locks at launch and again * after the app spent more than half a minute in the background, and asks at * once rather than waiting for a tap; the button is there for a second try. * Rendered once in App; outside the app, or with the lock off, it is nothing. */ import { App } from '@capacitor/app'; import { Lock } from 'lucide-react'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { useTranslation } from '../../i18n'; import { RELOCK_AFTER_MS, isAppLockEnabled, unlockApp } from '../../native/appLock'; import { useNativeCapabilities } from '../../native/capabilities'; export default function AppLockGate(): React.ReactElement | null { const { t } = useTranslation(); // Re-read once the binary has said what it can do. const { caps } = useNativeCapabilities(); const [locked, setLocked] = useState(false); const asking = useRef(false); const backgroundSince = useRef(null); const ask = useCallback(async () => { if (asking.current) return; asking.current = true; const ok = await unlockApp(t('native.lock.reason')); asking.current = false; if (ok) setLocked(false); }, [t]); // At launch, once the capabilities are known: locked in the same render the // capabilities arrive in, so nothing of the app shows before the prompt. const [capsSeen, setCapsSeen] = useState(undefined); if (caps !== capsSeen) { setCapsSeen(caps); if (isAppLockEnabled()) setLocked(true); } useEffect(() => { // Asked after the effect, not inside it: the prompt's answer sets state. if (isAppLockEnabled()) void Promise.resolve().then(ask); }, [caps, ask]); // Back from the background after a while. useEffect(() => { if (!isAppLockEnabled()) return; const listener = App.addListener('appStateChange', ({ isActive }) => { if (!isActive) { backgroundSince.current = Date.now(); return; } const since = backgroundSince.current; backgroundSince.current = null; if (since != null && Date.now() - since > RELOCK_AFTER_MS) { setLocked(true); void ask(); } }); return () => { void listener.then((handle) => handle.remove()); }; }, [caps, ask]); if (!locked) return null; return (

{t('native.lock.title')}

); }