323 lines
9.5 KiB
TypeScript
323 lines
9.5 KiB
TypeScript
import { HapticTab } from '@/components/HapticTab';
|
||
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 { fetchApi } from '@/lib/server-api-util';
|
||
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 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);
|
||
};
|
||
}, []);
|
||
|
||
// 轮询获取推送消息
|
||
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作为依赖
|
||
|
||
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' } // 确保在标签栏中不显示
|
||
}}
|
||
/>
|
||
{/* memo list */}
|
||
<Tabs.Screen
|
||
name="memo-list"
|
||
options={{
|
||
title: 'memo-list',
|
||
tabBarButton: () => null, // 隐藏底部标签栏
|
||
headerShown: false, // 隐藏导航栏
|
||
tabBarStyle: { display: 'none' } // 确保在标签栏中不显示
|
||
}}
|
||
/>
|
||
{/* owner */}
|
||
<Tabs.Screen
|
||
name="owner"
|
||
options={{
|
||
title: 'owner',
|
||
tabBarButton: () => null, // 隐藏底部标签栏
|
||
headerShown: false, // 隐藏导航栏
|
||
tabBarStyle: { display: 'none' } // 确保在标签栏中不显示
|
||
}}
|
||
/>
|
||
{/* 排行榜 */}
|
||
<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 >
|
||
);
|
||
}
|