180 lines
6.3 KiB
TypeScript
180 lines
6.3 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 { GestureHandlerRootView } from 'react-native-gesture-handler'
|
|
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 }}>
|
|
<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>
|
|
</GestureHandlerRootView>
|
|
</SafeAreaProvider>
|
|
)
|
|
}
|