import { useEffect, useRef, useState } from 'react' import { AppState, AppStateStatus, View } from 'react-native' import { Stack, useRouter, useSegments } from 'expo-router' import { StatusBar } from 'expo-status-bar' import * as SplashScreen from 'expo-splash-screen' import { AuthContext, useAuthState } from '../hooks/useAuth' import { isSessionExpired, recordActivity, clearSession } from '../hooks/useSessionExpiry' import PinLock, { isPinEnabled } from '../components/PinLock' import { ThemeProvider } from '../contexts/ThemeContext' import { FontProvider } from '../contexts/FontContext' import { OfflineProvider } from '../contexts/OfflineContext' SplashScreen.preventAutoHideAsync() export default function RootLayout() { const auth = useAuthState() const router = useRouter() const segments = useSegments() // #30 PIN 잠금 상태 const [locked, setLocked] = useState(false) const appState = useRef(AppState.currentState) useEffect(() => { if (auth.loading) return SplashScreen.hideAsync() const inAuth = segments[0] === '(auth)' if (!auth.token && !inAuth) { router.replace('/(auth)/login') } else if (auth.token && inAuth) { router.replace('/(tabs)') } }, [auth.loading, auth.token]) /* #31 세션 자동 만료 + #30 PIN 잠금 — AppState background→foreground 처리 */ useEffect(() => { const onChange = async (next: AppStateStatus) => { const prev = appState.current appState.current = next if (next === 'active' && prev.match(/inactive|background/)) { // 포그라운드 복귀 if (auth.token) { // #31 15분 초과 시 세션 종료 if (await isSessionExpired()) { await clearSession() await auth.logout() setLocked(false) router.replace('/(auth)/login') return } // #30 PIN이 활성화되어 있으면 잠금 화면 표시 if (await isPinEnabled()) { setLocked(true) } await recordActivity() } } else if (next.match(/inactive|background/)) { // 백그라운드 진입 시 활동 시각 갱신 if (auth.token) await recordActivity() } } const sub = AppState.addEventListener('change', onChange) return () => sub.remove() }, [auth.token]) const handleUnlock = async () => { await recordActivity() setLocked(false) } const handlePinFail = async () => { // 5회 실패 → 세션 종료 await clearSession() await auth.logout() setLocked(false) router.replace('/(auth)/login') } return ( {/* #30 PIN 잠금 오버레이 — 인증된 상태에서만 */} {locked && auth.token && ( )} ) }