expo-popcore-app/app/_layout.tsx

190 lines
6.8 KiB
TypeScript

import '../global.css'
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native'
import { Stack, usePathname, useRouter } from 'expo-router'
import { StatusBar } from 'expo-status-bar'
import { useEffect } from 'react'
import 'react-native-reanimated'
import { configureReanimatedLogger, ReanimatedLogLevel } from 'react-native-reanimated'
// 禁用 Reanimated strict 模式警告(第三方库导致的警告)
configureReanimatedLogger({
level: ReanimatedLogLevel.warn,
strict: false,
})
import { GestureHandlerRootView } from 'react-native-gesture-handler'
import { BottomSheetModalProvider } from '@gorhom/bottom-sheet'
import { View, ActivityIndicator, AppState, LogBox } from 'react-native'
import * as Updates from 'expo-updates'
// 抑制已知的第三方库警告
LogBox.ignoreLogs(['SafeAreaView has been deprecated'])
import { useColorScheme } from '@/hooks/use-color-scheme'
import { KeyboardProvider } from 'react-native-keyboard-controller'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { ModalPortal } from '@/components/ui'
import '@/lib/i18n'
import { useSession } from '@/lib/auth'
export const unstable_settings = {
anchor: '(tabs)',
}
// 受保护的路由(需要登录才能访问)
const PROTECTED_ROUTES = [
'/(tabs)',
'/channels',
'/generateVideo',
'/generationRecord',
'/worksList',
'/membership',
'/changePassword',
]
// 公开路由(无需登录)
const PUBLIC_ROUTES = [
'/auth',
'/modal',
'/searchTemplate',
'/searchResults',
'/searchWorks',
'/searchWorksResults',
'/templateDetail',
'/terms',
'/privacy',
]
// 认证守卫组件
function AuthGuard({ children }: { children: React.ReactNode }) {
const pathname = usePathname()
const router = useRouter()
const { data: session, isPending } = useSession()
useEffect(() => {
// 加载中不处理
if (isPending) return
// 检查是否在受保护路由
const isProtectedRoute = PROTECTED_ROUTES.some(route => pathname?.startsWith(route))
// 未登录访问受保护路由 -> 跳转到登录页
if (isProtectedRoute && !session?.user) {
router.replace('/auth')
return
}
// 已登录访问登录页 -> 跳转到首页
if (pathname === '/auth' && session?.user) {
router.replace('/(tabs)')
return
}
}, [pathname, session, isPending, router])
// 加载中显示 loading
if (isPending) {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#090A0B' }}>
<ActivityIndicator size="large" color="#9966FF" />
</View>
)
}
return <>{children}</>
}
// 路由层
export default function RootLayout() {
return (
<Providers>
<AuthGuard>
<Stack>
<Stack.Screen name="auth" options={{ headerShown: false }} />
<Stack.Screen name="modal" options={{ presentation: 'modal', title: 'Modal' }} />
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="channels" options={{ headerShown: false }} />
<Stack.Screen name="searchTemplate" options={{ headerShown: false }} />
<Stack.Screen name="searchResults" options={{ headerShown: false }} />
<Stack.Screen name="generationRecord" options={{ headerShown: false }} />
<Stack.Screen name="generateVideo" options={{ headerShown: false }} />
<Stack.Screen name="templateDetail" options={{ headerShown: false }} />
<Stack.Screen name="membership" options={{ headerShown: false }} />
<Stack.Screen name="terms" options={{ headerShown: false }} />
<Stack.Screen name="privacy" options={{ headerShown: false }} />
<Stack.Screen name="worksList" options={{ headerShown: false }} />
<Stack.Screen name="searchWorks" options={{ headerShown: false }} />
<Stack.Screen name="searchWorksResults" options={{ headerShown: false }} />
<Stack.Screen name="changePassword" options={{ headerShown: false }} />
</Stack>
</AuthGuard>
<StatusBar style="auto" />
</Providers>
)
}
function Providers({ children }: { children: React.ReactNode }) {
const colorScheme = useColorScheme()
// 热更新检查
useEffect(() => {
// 检查更新的函数
async function checkForUpdates() {
// 开发环境不检查更新
if (__DEV__) return
try {
const update = await Updates.checkForUpdateAsync()
if (update.isAvailable) {
await Updates.fetchUpdateAsync()
// 提示用户重启应用
;(global as any).toast?.show({
type: 'success',
message: '发现新版本,即将重启应用',
})
// 延迟重启,让用户看到提示
setTimeout(() => {
Updates.reloadAsync()
}, 2000)
}
} catch (error) {
console.error('检查更新失败:', error)
}
}
// App 启动时检查
checkForUpdates()
// 监听 App 状态变化,从后台返回前台时检查
const subscription = AppState.addEventListener('change', (nextAppState) => {
if (nextAppState === 'active') {
checkForUpdates()
}
})
return () => {
subscription.remove()
}
}, [])
return (
<SafeAreaProvider>
<GestureHandlerRootView style={{ flex: 1 }}>
<BottomSheetModalProvider>
<KeyboardProvider>
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
{children}
{/* modals */}
{/* 挂载全局方法 */}
<ModalPortal ref={(ref) => ((global as any).actionSheet = ref)} />
<ModalPortal ref={(ref) => ((global as any).modal = ref)} />
<ModalPortal ref={(ref) => ((global as any).loading = ref)} />
<ModalPortal ref={(ref) => ((global as any).toast = ref)} />
</ThemeProvider>
</KeyboardProvider>
</BottomSheetModalProvider>
</GestureHandlerRootView>
</SafeAreaProvider>
)
}