P0 fixes: - Replace crypto.randomUUID() with uuid v4 (not available in RN) - Replace Platform.Version with expo-device osVersion - Fix auth navigation types, remove unused App route P1 fixes: - Push notification handler respects user preferences (useRef pattern) - Fix stale closure: use zustand subscribe + useRef for live preferences - Add retry logging for device registration failures - Replace emoji tab icons with @expo/vector-icons Ionicons - Document API integration TODOs in all local-only stores P2 fixes: - Add __DEV__ global declaration (global.d.ts) - Fix package.json main field to expo/AppEntry.js - Add retry logging for push device registration - Add z-index/elevation to LoadingOverlay - Add visual indicator to EmptyState icon P3 fixes: - Type navigation with NavigationProp<RootStackParamList> - Move getSeverityColor to theme.ts (single source of truth) - Add useMemo for SpamShield filter computations - Verified usesNonExemptEncryption: false is correct for expo-secure-store Co-Authored-By: Paperclip <noreply@paperclip.ing>
125 lines
3.8 KiB
TypeScript
125 lines
3.8 KiB
TypeScript
import React from 'react';
|
|
import { Text, ViewStyle, StyleSheet } from 'react-native';
|
|
import { Ionicons } from '@expo/vector-icons';
|
|
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
|
import { DashboardScreen } from '@/screens/dashboard';
|
|
import { DarkWatchScreen } from '@/screens/darkwatch';
|
|
import { SpamShieldScreen } from '@/screens/spamshield';
|
|
import { VoicePrintScreen } from '@/screens/voiceprint';
|
|
import { SettingsScreen } from '@/screens/settings';
|
|
import { COLORS, FONT_SIZES } from '@/constants/theme';
|
|
|
|
type MainTabParamList = {
|
|
Dashboard: undefined;
|
|
DarkWatch: undefined;
|
|
SpamShield: undefined;
|
|
VoicePrint: undefined;
|
|
Settings: undefined;
|
|
};
|
|
|
|
const Tab = createBottomTabNavigator<MainTabParamList>();
|
|
|
|
const iconMap: Record<string, keyof typeof Ionicons.glyphMap> = {
|
|
Dashboard: 'shield-outline',
|
|
DarkWatch: 'eye-outline',
|
|
SpamShield: 'ban-outline',
|
|
VoicePrint: 'mic-outline',
|
|
Settings: 'settings-outline',
|
|
};
|
|
|
|
const iconActiveMap: Record<string, keyof typeof Ionicons.glyphMap> = {
|
|
Dashboard: 'shield',
|
|
DarkWatch: 'eye',
|
|
SpamShield: 'ban',
|
|
VoicePrint: 'mic',
|
|
Settings: 'settings',
|
|
};
|
|
|
|
function TabIcon({ routeName, color, focused }: { routeName: string; color: string; focused: boolean }) {
|
|
const iconName = focused
|
|
? (iconActiveMap[routeName] as keyof typeof Ionicons.glyphMap)
|
|
: (iconMap[routeName] as keyof typeof Ionicons.glyphMap);
|
|
|
|
return <Ionicons name={iconName} size={24} color={color} />;
|
|
}
|
|
|
|
export function MainTabNavigator() {
|
|
return (
|
|
<Tab.Navigator
|
|
screenOptions={{
|
|
headerStyle: {
|
|
backgroundColor: COLORS.background,
|
|
},
|
|
headerTintColor: COLORS.text,
|
|
headerTitleStyle: {
|
|
fontSize: FONT_SIZES.lg,
|
|
fontWeight: '600',
|
|
},
|
|
tabBarStyle: {
|
|
backgroundColor: COLORS.backgroundLight,
|
|
borderTopColor: COLORS.border,
|
|
borderTopWidth: 1,
|
|
height: 60,
|
|
paddingBottom: 8,
|
|
paddingTop: 8,
|
|
} as ViewStyle,
|
|
tabBarActiveTintColor: COLORS.primary,
|
|
tabBarInactiveTintColor: COLORS.textMuted,
|
|
tabBarLabelStyle: {
|
|
fontSize: FONT_SIZES.xs,
|
|
},
|
|
tabBarIconStyle: {
|
|
marginTop: 4,
|
|
},
|
|
tabBarShowLabel: true,
|
|
}}
|
|
>
|
|
<Tab.Screen
|
|
name="Dashboard"
|
|
component={DashboardScreen}
|
|
options={{
|
|
headerTitle: 'Dashboard',
|
|
tabBarLabel: 'Home',
|
|
tabBarIcon: ({ color, focused }) => <TabIcon routeName="Dashboard" color={color} focused={focused} />,
|
|
}}
|
|
/>
|
|
<Tab.Screen
|
|
name="DarkWatch"
|
|
component={DarkWatchScreen}
|
|
options={{
|
|
headerTitle: 'DarkWatch',
|
|
tabBarLabel: 'DarkWatch',
|
|
tabBarIcon: ({ color, focused }) => <TabIcon routeName="DarkWatch" color={color} focused={focused} />,
|
|
}}
|
|
/>
|
|
<Tab.Screen
|
|
name="SpamShield"
|
|
component={SpamShieldScreen}
|
|
options={{
|
|
headerTitle: 'SpamShield',
|
|
tabBarLabel: 'SpamShield',
|
|
tabBarIcon: ({ color, focused }) => <TabIcon routeName="SpamShield" color={color} focused={focused} />,
|
|
}}
|
|
/>
|
|
<Tab.Screen
|
|
name="VoicePrint"
|
|
component={VoicePrintScreen}
|
|
options={{
|
|
headerTitle: 'VoicePrint',
|
|
tabBarLabel: 'VoicePrint',
|
|
tabBarIcon: ({ color, focused }) => <TabIcon routeName="VoicePrint" color={color} focused={focused} />,
|
|
}}
|
|
/>
|
|
<Tab.Screen
|
|
name="Settings"
|
|
component={SettingsScreen}
|
|
options={{
|
|
headerTitle: 'Settings',
|
|
tabBarLabel: 'Settings',
|
|
tabBarIcon: ({ color, focused }) => <TabIcon routeName="Settings" color={color} focused={focused} />,
|
|
}}
|
|
/>
|
|
</Tab.Navigator>
|
|
);
|
|
}
|