Files
Kordant/packages/mobile/src/navigation/MainTabNavigator.tsx
Michael Freno 90a223bc79 fix: address code review findings for mobile app (FRE-4572)
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>
2026-05-17 10:51:14 -04:00

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>
);
}