Compare commits
6 Commits
main
...
fix/screen
| Author | SHA1 | Date | |
|---|---|---|---|
| 01bc0588b8 | |||
| 003235d732 | |||
| ecada3e279 | |||
| 3904f8da66 | |||
| ec83f9ce34 | |||
| 3bc8dda46f |
3
.gitignore
vendored
3
.gitignore
vendored
@ -42,4 +42,5 @@ app-example
|
|||||||
android/
|
android/
|
||||||
ios/
|
ios/
|
||||||
|
|
||||||
build*
|
build*
|
||||||
|
.env
|
||||||
3
.qwen/settings.json
Normal file
3
.qwen/settings.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"theme": "Qwen Light"
|
||||||
|
}
|
||||||
23
app/(auth)/_layout.tsx
Normal file
23
app/(auth)/_layout.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { Stack } from 'expo-router';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default function AuthLayout() {
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<Stack.Screen
|
||||||
|
name="login"
|
||||||
|
options={{
|
||||||
|
headerShown: false,
|
||||||
|
animation: 'fade'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name="reset-password"
|
||||||
|
options={{
|
||||||
|
headerShown: false,
|
||||||
|
animation: 'fade'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
76
app/(main)/(tabs)/_layout.tsx
Normal file
76
app/(main)/(tabs)/_layout.tsx
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import { Tabs } from 'expo-router';
|
||||||
|
import React from 'react';
|
||||||
|
import { TabBarIcon } from '@/components/navigation/TabBarIcon';
|
||||||
|
import { Colors } from '@/constants/Colors';
|
||||||
|
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||||
|
import { HapticTab } from '@/components/HapticTab';
|
||||||
|
import { TransitionPresets } from '@react-navigation/bottom-tabs';
|
||||||
|
import { Platform } from 'react-native';
|
||||||
|
import AskNavbar from '@/components/layout/ask';
|
||||||
|
import { webSocketManager, WebSocketStatus } from '@/lib/websocket-util';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
// 只在iOS平台上导入TabBarBackground组件
|
||||||
|
const TabBarBackground = Platform.OS === 'ios' ? require('@/components/ui/TabBarBackground').default : null;
|
||||||
|
|
||||||
|
export default function TabLayout() {
|
||||||
|
const colorScheme = useColorScheme();
|
||||||
|
const [wsStatus, setWsStatus] = useState<WebSocketStatus>('disconnected');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleStatusChange = (status: WebSocketStatus) => {
|
||||||
|
setWsStatus(status);
|
||||||
|
};
|
||||||
|
webSocketManager.subscribeStatus(handleStatusChange);
|
||||||
|
return () => {
|
||||||
|
webSocketManager.unsubscribeStatus(handleStatusChange);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 只在iOS平台上使用TabBarBackground
|
||||||
|
const renderTabBarBackground = () => {
|
||||||
|
if (Platform.OS === 'ios' && TabBarBackground) {
|
||||||
|
return <TabBarBackground />;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Tabs
|
||||||
|
screenOptions={{
|
||||||
|
tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint,
|
||||||
|
headerShown: false,
|
||||||
|
tabBarBackground: renderTabBarBackground, // 使用自定义背景
|
||||||
|
tabBarButton: HapticTab, // 添加触觉反馈
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tabs.Screen
|
||||||
|
name="index"
|
||||||
|
options={{
|
||||||
|
title: 'Home',
|
||||||
|
tabBarIcon: ({ color }) => <TabBarIcon name="home" color={color} />,
|
||||||
|
...TransitionPresets.ShiftTransition,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Tabs.Screen
|
||||||
|
name="memo-list"
|
||||||
|
options={{
|
||||||
|
title: 'Memos',
|
||||||
|
tabBarIcon: ({ color }) => <TabBarIcon name="document-text" color={color} />,
|
||||||
|
...TransitionPresets.ShiftTransition,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Tabs.Screen
|
||||||
|
name="owner"
|
||||||
|
options={{
|
||||||
|
title: 'Profile',
|
||||||
|
tabBarIcon: ({ color }) => <TabBarIcon name="person" color={color} />,
|
||||||
|
...TransitionPresets.ShiftTransition,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tabs>
|
||||||
|
<AskNavbar wsStatus={wsStatus} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -223,14 +223,36 @@ export default function HomeScreen() {
|
|||||||
|
|
||||||
// 组件挂载时启动动画
|
// 组件挂载时启动动画
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsLoading(true);
|
let isMounted = true;
|
||||||
checkAuthStatus(router, () => {
|
|
||||||
router.replace('/ask')
|
const initializeComponent = async () => {
|
||||||
}, false).then(() => {
|
try {
|
||||||
setIsLoading(false);
|
setIsLoading(true);
|
||||||
}).catch(() => {
|
|
||||||
setIsLoading(false);
|
// 添加延迟确保 AuthProvider 已经初始化
|
||||||
});
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
|
||||||
|
if (!isMounted) return;
|
||||||
|
|
||||||
|
await checkAuthStatus(router, () => {
|
||||||
|
if (isMounted) {
|
||||||
|
router.replace('/ask');
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
if (!isMounted) return;
|
||||||
|
|
||||||
|
setIsLoading(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('初始化组件时出错:', error);
|
||||||
|
if (isMounted) {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
initializeComponent();
|
||||||
|
|
||||||
// IP图标的淡入动画
|
// IP图标的淡入动画
|
||||||
Animated.timing(fadeAnim, {
|
Animated.timing(fadeAnim, {
|
||||||
toValue: 1,
|
toValue: 1,
|
||||||
@ -249,14 +271,29 @@ export default function HomeScreen() {
|
|||||||
startWaveAnimation();
|
startWaveAnimation();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 组件卸载时清理动画
|
// 组件卸载时清理动画和状态
|
||||||
return () => {
|
return () => {
|
||||||
|
isMounted = false;
|
||||||
|
|
||||||
|
// 清理所有动画
|
||||||
if (buttonLoopAnim.current) {
|
if (buttonLoopAnim.current) {
|
||||||
buttonLoopAnim.current.stop();
|
buttonLoopAnim.current.stop();
|
||||||
}
|
}
|
||||||
if (animationRef.current) {
|
if (animationRef.current) {
|
||||||
animationRef.current.stop();
|
animationRef.current.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 重置所有动画值
|
||||||
|
fadeAnim.setValue(0);
|
||||||
|
shakeAnim.setValue(0);
|
||||||
|
descriptionAnim.setValue(0);
|
||||||
|
buttonAnim.setValue(0);
|
||||||
|
buttonShakeAnim.setValue(0);
|
||||||
|
fadeInAnim.setValue(0);
|
||||||
|
waveAnim.setValue(0);
|
||||||
|
|
||||||
|
// 重置文本动画
|
||||||
|
Object.values(textAnimations).forEach(anim => anim.setValue(0));
|
||||||
};
|
};
|
||||||
|
|
||||||
}, []);
|
}, []);
|
||||||
@ -532,4 +569,4 @@ const styles = StyleSheet.create({
|
|||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
57
app/(main)/_layout.tsx
Normal file
57
app/(main)/_layout.tsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import { Tabs } from 'expo-router';
|
||||||
|
import React from 'react';
|
||||||
|
import { TabBarIcon } from '@/components/navigation/TabBarIcon';
|
||||||
|
import { Colors } from '@/constants/Colors';
|
||||||
|
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||||
|
import { HapticTab } from '@/components/HapticTab';
|
||||||
|
import { webSocketManager, WebSocketStatus } from '@/lib/websocket-util';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Platform } from 'react-native';
|
||||||
|
|
||||||
|
// 只在iOS平台上导入TabBarBackground组件
|
||||||
|
const TabBarBackground = Platform.OS === 'ios' ? require('@/components/ui/TabBarBackground').default : null;
|
||||||
|
|
||||||
|
export default function MainLayout() {
|
||||||
|
const colorScheme = useColorScheme();
|
||||||
|
const [wsStatus, setWsStatus] = useState<WebSocketStatus>('disconnected');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleStatusChange = (status: WebSocketStatus) => {
|
||||||
|
setWsStatus(status);
|
||||||
|
};
|
||||||
|
webSocketManager.subscribeStatus(handleStatusChange);
|
||||||
|
return () => {
|
||||||
|
webSocketManager.unsubscribeStatus(handleStatusChange);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 只在iOS平台上使用TabBarBackground
|
||||||
|
const renderTabBarBackground = () => {
|
||||||
|
if (Platform.OS === 'ios' && TabBarBackground) {
|
||||||
|
return <TabBarBackground />;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Tabs
|
||||||
|
screenOptions={{
|
||||||
|
tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint,
|
||||||
|
headerShown: false,
|
||||||
|
tabBarBackground: renderTabBarBackground, // 使用自定义背景
|
||||||
|
tabBarButton: HapticTab, // 添加触觉反馈
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tabs.Screen
|
||||||
|
name="(tabs)" // 这将渲染 (tabs) 目录下的 _layout.tsx
|
||||||
|
options={{
|
||||||
|
headerShown: false,
|
||||||
|
title: 'Home',
|
||||||
|
tabBarIcon: ({ color }) => <TabBarIcon name="home" color={color} />,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tabs>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,12 +1,10 @@
|
|||||||
import ReturnArrow from "@/assets/icons/svg/returnArrow.svg";
|
|
||||||
import Chat from "@/components/ask/chat";
|
import Chat from "@/components/ask/chat";
|
||||||
import AskHello from "@/components/ask/hello";
|
import AskHello from "@/components/ask/hello";
|
||||||
import SendMessage from "@/components/ask/send";
|
import SendMessage from "@/components/ask/send";
|
||||||
import { ThemedText } from "@/components/ThemedText";
|
|
||||||
import { fetchApi } from "@/lib/server-api-util";
|
import { fetchApi } from "@/lib/server-api-util";
|
||||||
import { getWebSocketErrorMessage, webSocketManager, WsMessage } from "@/lib/websocket-util";
|
import { getWebSocketErrorMessage, webSocketManager, WsMessage } from "@/lib/websocket-util";
|
||||||
import { Assistant, Message } from "@/types/ask";
|
import { Assistant, Message } from "@/types/ask";
|
||||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
import { useFocusEffect, useLocalSearchParams, useRouter } from "expo-router";
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
@ -16,8 +14,6 @@ import {
|
|||||||
KeyboardAvoidingView,
|
KeyboardAvoidingView,
|
||||||
Platform,
|
Platform,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
TextInput,
|
|
||||||
TouchableOpacity,
|
|
||||||
View
|
View
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { Gesture, GestureDetector } from "react-native-gesture-handler";
|
import { Gesture, GestureDetector } from "react-native-gesture-handler";
|
||||||
@ -25,6 +21,7 @@ import { runOnJS } from 'react-native-reanimated';
|
|||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
|
|
||||||
export default function AskScreen() {
|
export default function AskScreen() {
|
||||||
|
const router = useRouter();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
|
|
||||||
const chatListRef = useRef<FlatList>(null);
|
const chatListRef = useRef<FlatList>(null);
|
||||||
@ -36,9 +33,10 @@ export default function AskScreen() {
|
|||||||
const fadeAnimChat = useRef(new Animated.Value(0)).current;
|
const fadeAnimChat = useRef(new Animated.Value(0)).current;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { sessionId, newSession } = useLocalSearchParams<{
|
const { sessionId, newSession, extra } = useLocalSearchParams<{
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
newSession: string;
|
newSession: string;
|
||||||
|
extra: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
// 创建一个可复用的滚动函数
|
// 创建一个可复用的滚动函数
|
||||||
@ -60,7 +58,8 @@ export default function AskScreen() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.minPointers(1)
|
.minPointers(1)
|
||||||
.activeOffsetX([-10, 10]); // 在 X 方向触发的范围
|
.activeOffsetX([-20, 20]) // 扩大触发范围,避免与ScrollView冲突
|
||||||
|
.failOffsetY([-10, 10]); // 限制Y轴的偏移,避免垂直滚动时触发
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isHello && userMessages.length > 0) {
|
if (!isHello && userMessages.length > 0) {
|
||||||
@ -242,11 +241,20 @@ export default function AskScreen() {
|
|||||||
}
|
}
|
||||||
}, [isHello]);
|
}, [isHello]);
|
||||||
|
|
||||||
|
// 组件卸载时清理动画
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
// 停止所有可能正在运行的动画
|
||||||
|
fadeAnim.stopAnimation();
|
||||||
|
fadeAnimChat.stopAnimation();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
useCallback(() => {
|
useCallback(() => {
|
||||||
if (!sessionId) {
|
if (!sessionId) {
|
||||||
setIsHello(true);
|
setIsHello(true);
|
||||||
setUserMessages([])
|
setUserMessages([]);
|
||||||
}
|
}
|
||||||
}, [sessionId])
|
}, [sessionId])
|
||||||
);
|
);
|
||||||
@ -254,28 +262,6 @@ export default function AskScreen() {
|
|||||||
return (
|
return (
|
||||||
<GestureDetector gesture={gesture}>
|
<GestureDetector gesture={gesture}>
|
||||||
<View style={[styles.container, { paddingTop: insets.top, paddingBottom: insets.bottom }]}>
|
<View style={[styles.container, { paddingTop: insets.top, paddingBottom: insets.bottom }]}>
|
||||||
{/* 导航栏 */}
|
|
||||||
<View style={[styles.navbar, isHello && styles.hiddenNavbar]}>
|
|
||||||
<TouchableOpacity
|
|
||||||
style={styles.backButton}
|
|
||||||
onPress={() => {
|
|
||||||
try {
|
|
||||||
if (TextInput.State?.currentlyFocusedInput) {
|
|
||||||
const input = TextInput.State.currentlyFocusedInput();
|
|
||||||
if (input) input.blur();
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log('失去焦点失败:', error);
|
|
||||||
}
|
|
||||||
Keyboard.dismiss();
|
|
||||||
router.push('/memo-list');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ReturnArrow />
|
|
||||||
</TouchableOpacity>
|
|
||||||
<ThemedText style={styles.title} onPress={() => { router.push('/owner') }}>MemoWake</ThemedText>
|
|
||||||
<View style={styles.placeholder} />
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={styles.contentContainer}>
|
<View style={styles.contentContainer}>
|
||||||
{/* 欢迎页面 */}
|
{/* 欢迎页面 */}
|
||||||
@ -320,7 +306,7 @@ export default function AskScreen() {
|
|||||||
{/* 输入框区域 */}
|
{/* 输入框区域 */}
|
||||||
<KeyboardAvoidingView
|
<KeyboardAvoidingView
|
||||||
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
||||||
keyboardVerticalOffset={0} >
|
keyboardVerticalOffset={Platform.OS === "ios" ? 90 : 0}>
|
||||||
<View style={styles.inputContainer} key={conversationId}>
|
<View style={styles.inputContainer} key={conversationId}>
|
||||||
<SendMessage
|
<SendMessage
|
||||||
setIsHello={setIsHello}
|
setIsHello={setIsHello}
|
||||||
39
app/(settings)/_layout.tsx
Normal file
39
app/(settings)/_layout.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { Stack } from 'expo-router';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default function SettingsLayout() {
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<Stack.Screen
|
||||||
|
name="setting"
|
||||||
|
options={{
|
||||||
|
headerShown: false
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name="privacy-policy"
|
||||||
|
options={{
|
||||||
|
headerShown: false
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name="support"
|
||||||
|
options={{
|
||||||
|
headerShown: false
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name="rights"
|
||||||
|
options={{
|
||||||
|
headerShown: false
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name="user-message"
|
||||||
|
options={{
|
||||||
|
headerShown: false
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,383 +0,0 @@
|
|||||||
import { HapticTab } from '@/components/HapticTab';
|
|
||||||
import AskNavbar from '@/components/layout/ask';
|
|
||||||
import { TabBarIcon } from '@/components/navigation/TabBarIcon';
|
|
||||||
import { requestNotificationPermission } from '@/components/owner/utils';
|
|
||||||
import TabBarBackground from '@/components/ui/TabBarBackground';
|
|
||||||
import { Colors } from '@/constants/Colors';
|
|
||||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
|
||||||
import { prefetchChats } from '@/lib/prefetch';
|
|
||||||
import { fetchApi } from '@/lib/server-api-util';
|
|
||||||
import { webSocketManager, WebSocketStatus } from '@/lib/websocket-util';
|
|
||||||
import { TransitionPresets } from '@react-navigation/bottom-tabs';
|
|
||||||
import * as Notifications from 'expo-notifications';
|
|
||||||
import { Tabs } from 'expo-router';
|
|
||||||
import * as SecureStore from 'expo-secure-store';
|
|
||||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { Platform } from 'react-native';
|
|
||||||
|
|
||||||
interface PollingData {
|
|
||||||
title: string;
|
|
||||||
id: string;
|
|
||||||
content: string;
|
|
||||||
extra: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function TabLayout() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const colorScheme = useColorScheme();
|
|
||||||
const [pollingData, setPollingData] = useState<PollingData[]>([]);
|
|
||||||
const pollingInterval = useRef<NodeJS.Timeout | number>(null);
|
|
||||||
const tokenInterval = useRef<NodeJS.Timeout | number>(null);
|
|
||||||
const isMounted = useRef(true);
|
|
||||||
const [token, setToken] = useState('');
|
|
||||||
const [wsStatus, setWsStatus] = useState<WebSocketStatus>('disconnected');
|
|
||||||
const sendNotification = async (item: PollingData) => {
|
|
||||||
// 请求通知权限
|
|
||||||
const granted = await requestNotificationPermission();
|
|
||||||
if (!granted) {
|
|
||||||
console.log('用户拒绝了通知权限');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 调度本地通知
|
|
||||||
await Notifications.scheduleNotificationAsync({
|
|
||||||
content: {
|
|
||||||
title: item.title,
|
|
||||||
body: item.content,
|
|
||||||
data: { screen: 'ask', extra: item.extra, id: item.id },
|
|
||||||
priority: 'high', // 关键:设置 high 或 max
|
|
||||||
},
|
|
||||||
trigger: {
|
|
||||||
seconds: 2, // 延迟2秒显示
|
|
||||||
type: Notifications.SchedulableTriggerInputTypes.TIME_INTERVAL // 添加 type 字段
|
|
||||||
}, // 延迟2秒显示
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 监听通知点击事件
|
|
||||||
useEffect(() => {
|
|
||||||
const notificationListener = Notifications.addNotificationResponseReceivedListener(response => {
|
|
||||||
const data = response.notification.request.content.data;
|
|
||||||
console.log('通知被点击,数据:', data);
|
|
||||||
pollingData?.filter((item) => item.id !== data.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 清理监听器
|
|
||||||
return () => {
|
|
||||||
Notifications.removeNotificationSubscription(notificationListener);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleStatusChange = (status: WebSocketStatus) => {
|
|
||||||
setWsStatus(status);
|
|
||||||
};
|
|
||||||
webSocketManager.subscribeStatus(handleStatusChange);
|
|
||||||
return () => {
|
|
||||||
webSocketManager.unsubscribeStatus(handleStatusChange);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// 轮询获取推送消息
|
|
||||||
const startPolling = useCallback(async (interval: number = 5000) => {
|
|
||||||
|
|
||||||
// 设置轮询
|
|
||||||
pollingInterval.current = setInterval(async () => {
|
|
||||||
if (isMounted.current) {
|
|
||||||
await getMessageData();
|
|
||||||
}
|
|
||||||
}, interval);
|
|
||||||
|
|
||||||
// 返回清理函数
|
|
||||||
return () => {
|
|
||||||
if (pollingInterval.current) {
|
|
||||||
clearInterval(pollingInterval.current);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// 获取推送消息
|
|
||||||
const getMessageData = async () => {
|
|
||||||
try {
|
|
||||||
const response = await fetchApi<PollingData[]>("/notice/push/message", {
|
|
||||||
method: "POST"
|
|
||||||
});
|
|
||||||
setPollingData((prev) => ([...prev, ...response]));
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取轮询数据时出错:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 获取认证token
|
|
||||||
const getAuthToken = async (): Promise<string> => {
|
|
||||||
let tokenValue = '';
|
|
||||||
if (Platform.OS === 'web') {
|
|
||||||
tokenValue = localStorage.getItem('token') || '';
|
|
||||||
} else {
|
|
||||||
tokenValue = (await SecureStore.getItemAsync('token')) || '';
|
|
||||||
}
|
|
||||||
setToken(tokenValue); // 只在获取到新token时更新状态
|
|
||||||
return tokenValue;
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const checkAuthStatus = async () => {
|
|
||||||
try {
|
|
||||||
if (token) {
|
|
||||||
// 启动轮询
|
|
||||||
startPolling(5000);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取推送消息出错:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
checkAuthStatus();
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
// 清理函数
|
|
||||||
if (pollingInterval.current) {
|
|
||||||
clearInterval(pollingInterval.current);
|
|
||||||
}
|
|
||||||
isMounted.current = false;
|
|
||||||
};
|
|
||||||
}, [token]);
|
|
||||||
|
|
||||||
// 本地推送
|
|
||||||
useEffect(() => {
|
|
||||||
pollingData?.map((item) => {
|
|
||||||
sendNotification(item)
|
|
||||||
})
|
|
||||||
}, [pollingData])
|
|
||||||
|
|
||||||
// 轮询获取token
|
|
||||||
useEffect(() => {
|
|
||||||
// 如果已经有token,直接返回
|
|
||||||
if (token) {
|
|
||||||
if (tokenInterval.current) {
|
|
||||||
clearInterval(tokenInterval.current);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!tokenInterval.current) return;
|
|
||||||
// 设置轮询
|
|
||||||
tokenInterval.current = setInterval(async () => {
|
|
||||||
if (isMounted.current) {
|
|
||||||
const currentToken = await getAuthToken();
|
|
||||||
// 如果获取到token,清除定时器
|
|
||||||
if (currentToken && tokenInterval.current) {
|
|
||||||
clearInterval(tokenInterval.current);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 5000);
|
|
||||||
|
|
||||||
// 返回清理函数
|
|
||||||
return () => {
|
|
||||||
if (tokenInterval.current) {
|
|
||||||
clearInterval(tokenInterval.current);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [token]); // 添加token作为依赖
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (token) {
|
|
||||||
prefetchChats().catch(console.error);
|
|
||||||
}
|
|
||||||
}, [token]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Tabs
|
|
||||||
screenOptions={{
|
|
||||||
tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint,
|
|
||||||
headerShown: false,
|
|
||||||
tabBarButton: HapticTab,
|
|
||||||
tabBarBackground: TabBarBackground,
|
|
||||||
tabBarStyle: Platform.select({
|
|
||||||
ios: {
|
|
||||||
// Use a transparent background on iOS to show the blur effect
|
|
||||||
position: 'absolute',
|
|
||||||
},
|
|
||||||
default: {},
|
|
||||||
}),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* 落地页 */}
|
|
||||||
<Tabs.Screen
|
|
||||||
name="index"
|
|
||||||
options={{
|
|
||||||
title: 'Memo',
|
|
||||||
tabBarButton: () => null, // 隐藏底部标签栏
|
|
||||||
headerShown: false, // 隐藏导航栏
|
|
||||||
tabBarStyle: { display: 'none' } // 确保在标签栏中不显示
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/* 登录 */}
|
|
||||||
<Tabs.Screen
|
|
||||||
name="login"
|
|
||||||
options={{
|
|
||||||
title: 'Login',
|
|
||||||
href: '/login',
|
|
||||||
// tabBarButton: () => null, // 隐藏底部标签栏
|
|
||||||
headerShown: false, // 隐藏导航栏
|
|
||||||
tabBarStyle: { display: 'none' } // 确保在标签栏中不显示
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/* 重置密码 */}
|
|
||||||
<Tabs.Screen
|
|
||||||
name="reset-password"
|
|
||||||
options={{
|
|
||||||
title: 'reset-password',
|
|
||||||
href: '/reset-password',
|
|
||||||
// tabBarButton: () => null, // 隐藏底部标签栏
|
|
||||||
headerShown: false, // 隐藏导航栏
|
|
||||||
tabBarStyle: { display: 'none' } // 确保在标签栏中不显示
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/* loading页面 */}
|
|
||||||
<Tabs.Screen
|
|
||||||
name="loading"
|
|
||||||
options={{
|
|
||||||
title: 'loading',
|
|
||||||
href: '/loading',
|
|
||||||
// tabBarButton: () => null, // 隐藏底部标签栏
|
|
||||||
headerShown: false, // 隐藏导航栏
|
|
||||||
tabBarStyle: { display: 'none' } // 确保在标签栏中不显示
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/* 用户信息收集 */}
|
|
||||||
<Tabs.Screen
|
|
||||||
name="user-message"
|
|
||||||
options={{
|
|
||||||
title: 'user-message',
|
|
||||||
href: '/user-message',
|
|
||||||
// tabBarButton: () => null, // 隐藏底部标签栏
|
|
||||||
headerShown: false, // 隐藏导航栏
|
|
||||||
tabBarStyle: { display: 'none' } // 确保在标签栏中不显示
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/* ask页面 */}
|
|
||||||
<Tabs.Screen
|
|
||||||
name="ask"
|
|
||||||
options={{
|
|
||||||
title: 'ask',
|
|
||||||
tabBarButton: () => null, // 隐藏底部标签栏
|
|
||||||
headerShown: false, // 隐藏导航栏
|
|
||||||
tabBarStyle: { display: 'none' }, // 确保在标签栏中不显示
|
|
||||||
...TransitionPresets.ShiftTransition,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/* memo list */}
|
|
||||||
<Tabs.Screen
|
|
||||||
name="memo-list"
|
|
||||||
options={{
|
|
||||||
title: 'memo-list',
|
|
||||||
tabBarButton: () => null, // 隐藏底部标签栏
|
|
||||||
headerShown: false, // 隐藏导航栏
|
|
||||||
tabBarStyle: { display: 'none' }, // 确保在标签栏中不显示
|
|
||||||
...TransitionPresets.ShiftTransition,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/* owner */}
|
|
||||||
<Tabs.Screen
|
|
||||||
name="owner"
|
|
||||||
options={{
|
|
||||||
title: 'owner',
|
|
||||||
tabBarButton: () => null, // 隐藏底部标签栏
|
|
||||||
headerShown: false, // 隐藏导航栏
|
|
||||||
tabBarStyle: { display: 'none' }, // 确保在标签栏中不显示
|
|
||||||
...TransitionPresets.ShiftTransition,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/* 排行榜 */}
|
|
||||||
<Tabs.Screen
|
|
||||||
name="top"
|
|
||||||
options={{
|
|
||||||
title: 'top',
|
|
||||||
tabBarButton: () => null, // 隐藏底部标签栏
|
|
||||||
headerShown: false, // 隐藏导航栏
|
|
||||||
tabBarStyle: { display: 'none' } // 确保在标签栏中不显示
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/* 对话详情页 */}
|
|
||||||
<Tabs.Screen
|
|
||||||
name="chat-details"
|
|
||||||
options={{
|
|
||||||
title: 'chat-details',
|
|
||||||
tabBarButton: () => null, // 隐藏底部标签栏
|
|
||||||
headerShown: false, // 隐藏导航栏
|
|
||||||
tabBarStyle: { display: 'none' } // 确保在标签栏中不显示
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/* 隐私协议 */}
|
|
||||||
<Tabs.Screen
|
|
||||||
name="privacy-policy"
|
|
||||||
options={{
|
|
||||||
title: 'privacy-policy',
|
|
||||||
tabBarButton: () => null, // 隐藏底部标签栏
|
|
||||||
headerShown: false, // 隐藏导航栏
|
|
||||||
tabBarStyle: { display: 'none' } // 确保在标签栏中不显示
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Support Screen */}
|
|
||||||
<Tabs.Screen
|
|
||||||
name="support"
|
|
||||||
options={{
|
|
||||||
title: t('tabTitle', { ns: 'support' }),
|
|
||||||
tabBarButton: () => null, // 隐藏底部标签栏
|
|
||||||
headerShown: false, // 隐藏导航栏
|
|
||||||
tabBarStyle: { display: 'none' } // 确保在标签栏中不显示
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Debug Screen - only in development */}
|
|
||||||
{process.env.NODE_ENV === 'development' && (
|
|
||||||
<Tabs.Screen
|
|
||||||
name="debug"
|
|
||||||
options={{
|
|
||||||
title: 'Debug',
|
|
||||||
tabBarIcon: ({ color, focused }) => (
|
|
||||||
<TabBarIcon name={focused ? 'bug' : 'bug-outline'} color={color} />
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 下载页面 */}
|
|
||||||
<Tabs.Screen
|
|
||||||
name="download"
|
|
||||||
options={{
|
|
||||||
title: 'download',
|
|
||||||
tabBarButton: () => null, // 隐藏底部标签栏
|
|
||||||
headerShown: false, // 隐藏导航栏
|
|
||||||
tabBarStyle: { display: 'none' } // 确保在标签栏中不显示
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* 购买权益页面 */}
|
|
||||||
<Tabs.Screen
|
|
||||||
name="rights"
|
|
||||||
options={{
|
|
||||||
title: 'rights',
|
|
||||||
tabBarButton: () => null, // 隐藏底部标签栏
|
|
||||||
headerShown: false, // 隐藏导航栏
|
|
||||||
tabBarStyle: { display: 'none' } // 确保在标签栏中不显示
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* 设置页面 */}
|
|
||||||
<Tabs.Screen
|
|
||||||
name="setting"
|
|
||||||
options={{
|
|
||||||
title: 'setting',
|
|
||||||
tabBarButton: () => null, // 隐藏底部标签栏
|
|
||||||
headerShown: false, // 隐藏导航栏
|
|
||||||
tabBarStyle: { display: 'none' } // 确保在标签栏中不显示
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Tabs >
|
|
||||||
<AskNavbar wsStatus={wsStatus} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,11 +1,13 @@
|
|||||||
import { PermissionProvider } from '@/context/PermissionContext';
|
import { PermissionProvider } from '@/context/PermissionContext';
|
||||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||||
import { registerBackgroundUploadTask } from '@/lib/background-uploader/automatic';
|
import { registerBackgroundUploadTask } from '@/lib/background-uploader/automatic';
|
||||||
|
import { webSocketManager } from '@/lib/websocket-util';
|
||||||
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
|
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
|
||||||
import * as MediaLibrary from 'expo-media-library';
|
import * as MediaLibrary from 'expo-media-library';
|
||||||
import { Stack } from 'expo-router';
|
import { Stack } from 'expo-router';
|
||||||
import { StatusBar } from 'expo-status-bar';
|
import { StatusBar } from 'expo-status-bar';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
import { AppState } from 'react-native';
|
||||||
import 'react-native-reanimated';
|
import 'react-native-reanimated';
|
||||||
import '../global.css';
|
import '../global.css';
|
||||||
import { Provider } from "../provider";
|
import { Provider } from "../provider";
|
||||||
@ -29,19 +31,32 @@ export default function RootLayout() {
|
|||||||
setupBackgroundUpload();
|
setupBackgroundUpload();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleAppStateChange = (nextAppState: string) => {
|
||||||
|
if (nextAppState === 'background') {
|
||||||
|
// 应用进入后台时断开WebSocket连接
|
||||||
|
webSocketManager.disconnect();
|
||||||
|
} else if (nextAppState === 'active') {
|
||||||
|
// 应用回到前台时重新连接WebSocket
|
||||||
|
webSocketManager.connect();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const subscription = AppState.addEventListener('change', handleAppStateChange);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
subscription?.remove();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
|
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
|
||||||
<PermissionProvider>
|
<PermissionProvider>
|
||||||
<Provider>
|
<Provider>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
<Stack.Screen name="(auth)" options={{ headerShown: false }} />
|
||||||
<Stack.Screen
|
<Stack.Screen name="(main)" options={{ headerShown: false }} />
|
||||||
name="login"
|
<Stack.Screen name="(settings)" options={{ headerShown: false }} />
|
||||||
options={{
|
|
||||||
headerShown: false,
|
|
||||||
animation: 'fade'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Stack.Screen name="+not-found" />
|
<Stack.Screen name="+not-found" />
|
||||||
</Stack>
|
</Stack>
|
||||||
</Provider>
|
</Provider>
|
||||||
|
|||||||
@ -22,7 +22,6 @@ const Login = ({ updateUrlParam, setError, setShowPassword, showPassword }: Logi
|
|||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [rememberMe, setRememberMe] = useState(false);
|
|
||||||
|
|
||||||
const handleLogin = async () => {
|
const handleLogin = async () => {
|
||||||
if (!email) {
|
if (!email) {
|
||||||
@ -44,12 +43,18 @@ const Login = ({ updateUrlParam, setError, setShowPassword, showPassword }: Logi
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body),
|
||||||
}, true, false);
|
}, true, false);
|
||||||
login({ ...res, email: res?.account }, res.access_token || '');
|
|
||||||
const userInfo = await fetchApi<User>("/iam/user-info");
|
// 确保用户数据和令牌存在
|
||||||
if (userInfo?.nickname) {
|
if (res && res.access_token) {
|
||||||
router.replace('/ask');
|
login({ ...res, email: res?.account || email }, res.access_token);
|
||||||
|
const userInfo = await fetchApi<User>("/iam/user-info");
|
||||||
|
if (userInfo?.nickname) {
|
||||||
|
router.replace('/ask');
|
||||||
|
} else {
|
||||||
|
router.replace('/user-message');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
router.replace('/user-message');
|
throw new Error(t('auth.login.loginError', { ns: 'login' }));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = error instanceof Error ? error.message : t('auth.login.loginError', { ns: 'login' });
|
const errorMessage = error instanceof Error ? error.message : t('auth.login.loginError', { ns: 'login' });
|
||||||
|
|||||||
@ -31,9 +31,8 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
|||||||
if (Platform.OS === 'web') {
|
if (Platform.OS === 'web') {
|
||||||
token = localStorage.getItem('token') || "";
|
token = localStorage.getItem('token') || "";
|
||||||
} else {
|
} else {
|
||||||
await SecureStore.getItemAsync('token').then((token) => {
|
const storedToken = await SecureStore.getItemAsync('token');
|
||||||
token = token || "";
|
token = storedToken || "";
|
||||||
})
|
|
||||||
}
|
}
|
||||||
if (token) {
|
if (token) {
|
||||||
// 验证当前 token 是否有效
|
// 验证当前 token 是否有效
|
||||||
@ -61,7 +60,8 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
|||||||
} catch (refreshError) {
|
} catch (refreshError) {
|
||||||
// 刷新 token 失败,才进行登出操作
|
// 刷新 token 失败,才进行登出操作
|
||||||
// console.error("Token refresh failed, logging out", refreshError);
|
// console.error("Token refresh failed, logging out", refreshError);
|
||||||
logout();
|
// 只有在确实需要登出时才调用logout()
|
||||||
|
// 如果是首次启动应用,没有token是正常的,不需要登出
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import Constants from 'expo-constants';
|
|||||||
import * as SecureStore from 'expo-secure-store';
|
import * as SecureStore from 'expo-secure-store';
|
||||||
import { Platform } from 'react-native';
|
import { Platform } from 'react-native';
|
||||||
import Toast from 'react-native-toast-message';
|
import Toast from 'react-native-toast-message';
|
||||||
import { useAuth } from '../contexts/auth-context';
|
|
||||||
import { store } from '../store';
|
import { store } from '../store';
|
||||||
import { User } from '../types/user';
|
import { User } from '../types/user';
|
||||||
|
|
||||||
@ -29,9 +28,8 @@ export const API_ENDPOINT = Constants.expoConfig?.extra?.API_ENDPOINT || "http:/
|
|||||||
|
|
||||||
|
|
||||||
// 更新 access_token 的逻辑 - 用于React组件中
|
// 更新 access_token 的逻辑 - 用于React组件中
|
||||||
export const useAuthToken = async<T>(message: string | null) => {
|
export const useAuthToken = async<T>(message: string | null, login: (user: User, jwt: string) => void) => {
|
||||||
try {
|
try {
|
||||||
const { login } = useAuth();
|
|
||||||
const response = await fetch(`${API_ENDPOINT}/v1/iam/access-token-refresh`);
|
const response = await fetch(`${API_ENDPOINT}/v1/iam/access-token-refresh`);
|
||||||
const apiResponse: ApiResponse<T> = await response.json();
|
const apiResponse: ApiResponse<T> = await response.json();
|
||||||
|
|
||||||
@ -57,18 +55,37 @@ export const useAuthToken = async<T>(message: string | null) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 使用Redux存储token的刷新token函数
|
// 使用Redux存储token的刷新token函数
|
||||||
export const refreshAuthToken = async<T>(message: string | null): Promise<User> => {
|
export const refreshAuthToken = async<T>(message: string | null): Promise<User | null> => {
|
||||||
try {
|
try {
|
||||||
let cookie = "";
|
let cookie = "";
|
||||||
let userId = "";
|
let userId = "";
|
||||||
if (Platform.OS === 'web') {
|
if (Platform.OS === 'web') {
|
||||||
cookie = localStorage.getItem('user') ? JSON.parse(localStorage.getItem('user') || "")?.refresh_token || "" : "";
|
const userStr = localStorage.getItem('user');
|
||||||
userId = localStorage.getItem('user') ? JSON.parse(localStorage.getItem('user') || "")?.user_id || "" : "";
|
if (userStr) {
|
||||||
|
try {
|
||||||
|
const userObj = JSON.parse(userStr);
|
||||||
|
cookie = userObj?.refresh_token || "";
|
||||||
|
userId = userObj?.user_id || "";
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to parse user data from localStorage", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
await SecureStore.getItemAsync('user').then((user: string | null) => {
|
const userStr = await SecureStore.getItemAsync('user');
|
||||||
cookie = JSON.parse(user || "")?.refresh_token || "";
|
if (userStr) {
|
||||||
userId = JSON.parse(user || "")?.user_id || "";
|
try {
|
||||||
})
|
const userObj = JSON.parse(userStr);
|
||||||
|
cookie = userObj?.refresh_token || "";
|
||||||
|
userId = userObj?.user_id || "";
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to parse user data from SecureStore", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有必要的数据,抛出错误
|
||||||
|
if (!cookie || !userId) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 退出刷新会重新填充数据
|
// 退出刷新会重新填充数据
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user