diff --git a/app/(auth)/_layout.tsx b/app/(auth)/_layout.tsx
new file mode 100644
index 0000000..ba9343f
--- /dev/null
+++ b/app/(auth)/_layout.tsx
@@ -0,0 +1,23 @@
+import { Stack } from 'expo-router';
+import React from 'react';
+
+export default function AuthLayout() {
+ return (
+
+
+
+
+ );
+}
diff --git a/app/(tabs)/login.tsx b/app/(auth)/login.tsx
similarity index 100%
rename from app/(tabs)/login.tsx
rename to app/(auth)/login.tsx
diff --git a/app/(tabs)/reset-password.tsx b/app/(auth)/reset-password.tsx
similarity index 100%
rename from app/(tabs)/reset-password.tsx
rename to app/(auth)/reset-password.tsx
diff --git a/app/(main)/(tabs)/_layout.tsx b/app/(main)/(tabs)/_layout.tsx
new file mode 100644
index 0000000..5b035aa
--- /dev/null
+++ b/app/(main)/(tabs)/_layout.tsx
@@ -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('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 ;
+ }
+ return null;
+ };
+
+ return (
+ <>
+
+ ,
+ ...TransitionPresets.ShiftTransition,
+ }}
+ />
+ ,
+ ...TransitionPresets.ShiftTransition,
+ }}
+ />
+ ,
+ ...TransitionPresets.ShiftTransition,
+ }}
+ />
+
+
+ >
+ );
+}
diff --git a/app/(tabs)/index.tsx b/app/(main)/(tabs)/index.tsx
similarity index 100%
rename from app/(tabs)/index.tsx
rename to app/(main)/(tabs)/index.tsx
diff --git a/app/(tabs)/memo-list.tsx b/app/(main)/(tabs)/memo-list.tsx
similarity index 100%
rename from app/(tabs)/memo-list.tsx
rename to app/(main)/(tabs)/memo-list.tsx
diff --git a/app/(tabs)/owner.tsx b/app/(main)/(tabs)/owner.tsx
similarity index 100%
rename from app/(tabs)/owner.tsx
rename to app/(main)/(tabs)/owner.tsx
diff --git a/app/(main)/_layout.tsx b/app/(main)/_layout.tsx
new file mode 100644
index 0000000..c0674fb
--- /dev/null
+++ b/app/(main)/_layout.tsx
@@ -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('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 ;
+ }
+ return null;
+ };
+
+ return (
+ <>
+
+ ,
+ }}
+ />
+
+ >
+ );
+}
diff --git a/app/(tabs)/ask.tsx b/app/(main)/ask.tsx
similarity index 100%
rename from app/(tabs)/ask.tsx
rename to app/(main)/ask.tsx
diff --git a/app/(tabs)/debug.tsx b/app/(main)/debug.tsx
similarity index 100%
rename from app/(tabs)/debug.tsx
rename to app/(main)/debug.tsx
diff --git a/app/(tabs)/download.tsx b/app/(main)/download.tsx
similarity index 100%
rename from app/(tabs)/download.tsx
rename to app/(main)/download.tsx
diff --git a/app/(tabs)/loading.tsx b/app/(main)/loading.tsx
similarity index 100%
rename from app/(tabs)/loading.tsx
rename to app/(main)/loading.tsx
diff --git a/app/(tabs)/top.tsx b/app/(main)/top.tsx
similarity index 100%
rename from app/(tabs)/top.tsx
rename to app/(main)/top.tsx
diff --git a/app/(settings)/_layout.tsx b/app/(settings)/_layout.tsx
new file mode 100644
index 0000000..9971760
--- /dev/null
+++ b/app/(settings)/_layout.tsx
@@ -0,0 +1,39 @@
+import { Stack } from 'expo-router';
+import React from 'react';
+
+export default function SettingsLayout() {
+ return (
+
+
+
+
+
+
+
+ );
+}
diff --git a/app/(tabs)/privacy-policy.tsx b/app/(settings)/privacy-policy.tsx
similarity index 100%
rename from app/(tabs)/privacy-policy.tsx
rename to app/(settings)/privacy-policy.tsx
diff --git a/app/(tabs)/rights.tsx b/app/(settings)/rights.tsx
similarity index 100%
rename from app/(tabs)/rights.tsx
rename to app/(settings)/rights.tsx
diff --git a/app/(tabs)/setting.tsx b/app/(settings)/setting.tsx
similarity index 100%
rename from app/(tabs)/setting.tsx
rename to app/(settings)/setting.tsx
diff --git a/app/(tabs)/support.tsx b/app/(settings)/support.tsx
similarity index 100%
rename from app/(tabs)/support.tsx
rename to app/(settings)/support.tsx
diff --git a/app/(tabs)/user-message.tsx b/app/(settings)/user-message.tsx
similarity index 100%
rename from app/(tabs)/user-message.tsx
rename to app/(settings)/user-message.tsx
diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx
deleted file mode 100644
index 07c478a..0000000
--- a/app/(tabs)/_layout.tsx
+++ /dev/null
@@ -1,419 +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, useRouter } 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([]);
- const pollingInterval = useRef(null);
- const tokenInterval = useRef(null);
- const isMounted = useRef(true);
- const [token, setToken] = useState('');
- const [wsStatus, setWsStatus] = useState('disconnected');
- const [notificationPermissionRequested, setNotificationPermissionRequested] = useState(false);
- const sendNotification = async (item: PollingData) => {
- // 只在需要发送通知时才请求权限
- if (!notificationPermissionRequested) {
- const granted = await requestNotificationPermission();
- setNotificationPermissionRequested(granted);
- 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);
- setPollingData(prev => prev.filter((item) => item.id !== data.id));
-
- // 根据通知数据导航到指定页面
- if (data.screen === 'ask') {
- router.push({
- pathname: '/ask',
- params: {
- sessionId: data.id,
- extra: data.extra
- }
- });
- }
- });
-
- // 清理监听器
- 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);
- pollingInterval.current = null;
- }
- };
- }, []);
-
- // 获取推送消息
- const getMessageData = async () => {
- try {
- const response = await fetchApi("/notice/push/message", {
- method: "POST"
- });
- if (response && Array.isArray(response)) {
- setPollingData((prev) => ([...prev, ...response]));
- }
- } catch (error) {
- console.error('获取轮询数据时出错:', error);
- }
- };
-
- // 获取认证token
- const getAuthToken = async (): Promise => {
- let tokenValue = '';
- if (Platform.OS === 'web') {
- tokenValue = localStorage.getItem('token') || '';
- } else {
- tokenValue = (await SecureStore.getItemAsync('token')) || '';
- }
- // 只在获取到新token时更新状态
- if (tokenValue !== token) {
- setToken(tokenValue);
- }
- return tokenValue;
- };
-
- // 初始化时获取一次token
- useEffect(() => {
- getAuthToken();
- }, []);
-
- useEffect(() => {
- const checkAuthStatus = async () => {
- try {
- if (token) {
- // 启动轮询
- const cleanup = startPolling(5000);
- // 将清理函数保存到ref中,以便在组件卸载时调用
- return cleanup;
- }
- } catch (error) {
- console.error('获取推送消息出错:', error);
- }
- };
-
- const cleanupPolling = checkAuthStatus();
-
- return () => {
- // 清理函数
- if (pollingInterval.current) {
- clearInterval(pollingInterval.current);
- pollingInterval.current = null;
- }
- if (typeof cleanupPolling === 'function') {
- cleanupPolling();
- }
- isMounted.current = false;
- };
- }, [token, startPolling]);
-
- // 本地推送
- useEffect(() => {
- pollingData?.forEach((item) => {
- sendNotification(item)
- })
- }, [pollingData])
-
- // 轮询获取token
- useEffect(() => {
- // 如果已经有token,直接返回
- if (token) {
- if (tokenInterval.current) {
- clearInterval(tokenInterval.current);
- tokenInterval.current = null;
- }
- return;
- }
-
- // 设置轮询
- tokenInterval.current = setInterval(async () => {
- if (isMounted.current) {
- const currentToken = await getAuthToken();
- // 如果获取到token,清除定时器
- if (currentToken && tokenInterval.current) {
- clearInterval(tokenInterval.current);
- tokenInterval.current = null;
- }
- }
- }, 5000);
-
- // 返回清理函数
- return () => {
- if (tokenInterval.current) {
- clearInterval(tokenInterval.current);
- tokenInterval.current = null;
- }
- };
- }, [token]); // 添加token作为依赖
-
- useEffect(() => {
- if (token) {
- prefetchChats().catch(console.error);
- }
- }, [token]);
-
- return (
- <>
-
- {/* 落地页 */}
- null, // 隐藏底部标签栏
- headerShown: false, // 隐藏导航栏
- tabBarStyle: { display: 'none' } // 确保在标签栏中不显示
- }}
- />
- {/* 登录 */}
- null, // 隐藏底部标签栏
- headerShown: false, // 隐藏导航栏
- tabBarStyle: { display: 'none' } // 确保在标签栏中不显示
- }}
- />
- {/* 重置密码 */}
- null, // 隐藏底部标签栏
- headerShown: false, // 隐藏导航栏
- tabBarStyle: { display: 'none' } // 确保在标签栏中不显示
- }}
- />
- {/* loading页面 */}
- null, // 隐藏底部标签栏
- headerShown: false, // 隐藏导航栏
- tabBarStyle: { display: 'none' } // 确保在标签栏中不显示
- }}
- />
- {/* 用户信息收集 */}
- null, // 隐藏底部标签栏
- headerShown: false, // 隐藏导航栏
- tabBarStyle: { display: 'none' } // 确保在标签栏中不显示
- }}
- />
- {/* ask页面 */}
- null, // 隐藏底部标签栏
- headerShown: false, // 隐藏导航栏
- tabBarStyle: { display: 'none' }, // 确保在标签栏中不显示
- ...TransitionPresets.ShiftTransition,
- }}
- />
- {/* memo list */}
- null, // 隐藏底部标签栏
- headerShown: false, // 隐藏导航栏
- tabBarStyle: { display: 'none' }, // 确保在标签栏中不显示
- ...TransitionPresets.ShiftTransition,
- }}
- />
- {/* owner */}
- null, // 隐藏底部标签栏
- headerShown: false, // 隐藏导航栏
- tabBarStyle: { display: 'none' }, // 确保在标签栏中不显示
- ...TransitionPresets.ShiftTransition,
- }}
- />
- {/* 排行榜 */}
- null, // 隐藏底部标签栏
- headerShown: false, // 隐藏导航栏
- tabBarStyle: { display: 'none' } // 确保在标签栏中不显示
- }}
- />
- {/* 对话详情页 */}
- null, // 隐藏底部标签栏
- headerShown: false, // 隐藏导航栏
- tabBarStyle: { display: 'none' } // 确保在标签栏中不显示
- }}
- />
- {/* 隐私协议 */}
- null, // 隐藏底部标签栏
- headerShown: false, // 隐藏导航栏
- tabBarStyle: { display: 'none' } // 确保在标签栏中不显示
- }}
- />
-
- {/* Support Screen */}
- null, // 隐藏底部标签栏
- headerShown: false, // 隐藏导航栏
- tabBarStyle: { display: 'none' } // 确保在标签栏中不显示
- }}
- />
-
- {/* Debug Screen - only in development */}
- {process.env.NODE_ENV === 'development' && (
- (
-
- ),
- }}
- />
- )}
-
- {/* 下载页面 */}
- null, // 隐藏底部标签栏
- headerShown: false, // 隐藏导航栏
- tabBarStyle: { display: 'none' } // 确保在标签栏中不显示
- }}
- />
-
- {/* 购买权益页面 */}
- null, // 隐藏底部标签栏
- headerShown: false, // 隐藏导航栏
- tabBarStyle: { display: 'none' } // 确保在标签栏中不显示
- }}
- />
-
- {/* 设置页面 */}
- null, // 隐藏底部标签栏
- headerShown: false, // 隐藏导航栏
- tabBarStyle: { display: 'none' } // 确保在标签栏中不显示
- }}
- />
-
-
- >
- );
-}
diff --git a/app/_layout.tsx b/app/_layout.tsx
index 3f1d690..947deba 100644
--- a/app/_layout.tsx
+++ b/app/_layout.tsx
@@ -34,14 +34,9 @@ export default function RootLayout() {
-
-
+
+
+