// app/_layout.tsx import { Stack, router } from 'expo-router'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { useEffect, useRef, useState } from 'react'; import { View, ActivityIndicator, AppState, AppStateStatus } from 'react-native'; import { useUserStore } from '@/store/useUserStore'; import { getDatabase } from '@/lib/database'; import { isPinEnabled } from '@/lib/pinService'; import '../global.css'; const LOCK_AFTER_SECONDS = 60; export default function RootLayout() { const [loading, setLoading] = useState(true); const setUser = useUserStore((s) => s.setUser); // Track when app was backgrounded const backgroundedAt = useRef(null); const appState = useRef(AppState.currentState); useEffect(() => { async function bootstrap() { try { const db = await getDatabase(); const user = await db.getFirstAsync<{ id: string; display_name: string; share_id: string; }>('SELECT id, display_name, share_id FROM users WHERE is_self = 1 LIMIT 1'); if (user) { setUser({ id: user.id, displayName: user.display_name, shareId: user.share_id, }); } } catch (e) { console.error('Bootstrap error:', e); } finally { setLoading(false); } } bootstrap(); }, []); // PIN lock on resume useEffect(() => { const subscription = AppState.addEventListener( 'change', async (nextState: AppStateStatus) => { const prev = appState.current; if ( (prev === 'active' || prev === 'inactive') && nextState === 'background' ) { // App going to background — record timestamp backgroundedAt.current = Date.now(); } if ( (prev === 'background' || prev === 'inactive') && nextState === 'active' ) { // App coming to foreground const bgAt = backgroundedAt.current; if (bgAt !== null) { const elapsedSec = (Date.now() - bgAt) / 1000; if (elapsedSec >= LOCK_AFTER_SECONDS) { const pinOn = await isPinEnabled(); if (pinOn) { router.replace('/lock'); } } } backgroundedAt.current = null; } appState.current = nextState; } ); return () => subscription.remove(); }, []); if (loading) { return ( ); } return ( ); }