Blame · Line-by-line history
AppNavigator.tsx
Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.
| 9b18281 | 1 | import React from 'react'; |
| 2 | import { Text } from 'react-native'; | |
| 3 | import { createNativeStackNavigator } from '@react-navigation/native-stack'; | |
| 4 | import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; | |
| 5 | import { colors } from '../theme/colors'; | |
| 6 | ||
| 7 | // Screens | |
| 8 | import { DashboardScreen } from '../screens/DashboardScreen'; | |
| 9 | import { RepoListScreen } from '../screens/RepoListScreen'; | |
| 10 | import { RepoScreen } from '../screens/RepoScreen'; | |
| 11 | import { FileViewerScreen } from '../screens/FileViewerScreen'; | |
| 12 | import { CommitsScreen } from '../screens/CommitsScreen'; | |
| 13 | import { IssuesScreen } from '../screens/IssuesScreen'; | |
| 14 | import { IssueDetailScreen } from '../screens/IssueDetailScreen'; | |
| 15 | import { PullsScreen } from '../screens/PullsScreen'; | |
| 16 | import { PullDetailScreen } from '../screens/PullDetailScreen'; | |
| 17 | import { GateStatusScreen } from '../screens/GateStatusScreen'; | |
| 18 | import { NotificationsScreen } from '../screens/NotificationsScreen'; | |
| 19 | import { AskAIScreen } from '../screens/AskAIScreen'; | |
| 20 | import { SettingsScreen } from '../screens/SettingsScreen'; | |
| 21 | ||
| 22 | // ─── Param list types ──────────────────────────────────────────────────────── | |
| 23 | ||
| 24 | export type RepoStackParamList = { | |
| 25 | RepoList: undefined; | |
| 26 | Repo: { owner: string; repo: string }; | |
| 27 | FileViewer: { owner: string; repo: string; ref: string; path: string }; | |
| 28 | Commits: { owner: string; repo: string; branch?: string }; | |
| 29 | Issues: { owner: string; repo: string }; | |
| 30 | IssueDetail: { owner: string; repo: string; number: number }; | |
| 31 | Pulls: { owner: string; repo: string }; | |
| 32 | PullDetail: { owner: string; repo: string; number: number }; | |
| 33 | GateStatus: { owner: string; repo: string }; | |
| 34 | }; | |
| 35 | ||
| 36 | export type MainTabParamList = { | |
| 37 | Dashboard: undefined; | |
| 38 | Repos: undefined; | |
| 39 | Notifications: undefined; | |
| 40 | AskAI: undefined; | |
| 41 | Settings: undefined; | |
| 42 | }; | |
| 43 | ||
| 44 | // ─── Repo Stack ─────────────────────────────────────────────────────────────── | |
| 45 | ||
| 46 | const RepoStack = createNativeStackNavigator<RepoStackParamList>(); | |
| 47 | ||
| 48 | function RepoStackNavigator(): React.ReactElement { | |
| 49 | return ( | |
| 50 | <RepoStack.Navigator | |
| 51 | screenOptions={{ | |
| 52 | headerStyle: { backgroundColor: colors.bgSecondary }, | |
| 53 | headerTintColor: colors.text, | |
| 54 | headerTitleStyle: { color: colors.text, fontWeight: '600' }, | |
| 55 | contentStyle: { backgroundColor: colors.bg }, | |
| 56 | }} | |
| 57 | > | |
| 58 | <RepoStack.Screen | |
| 59 | name="RepoList" | |
| 60 | component={RepoListScreen} | |
| 61 | options={{ title: 'Repositories' }} | |
| 62 | /> | |
| 63 | <RepoStack.Screen | |
| 64 | name="Repo" | |
| 65 | component={RepoScreen} | |
| 66 | options={({ route }) => ({ | |
| 67 | title: `${route.params.owner}/${route.params.repo}`, | |
| 68 | })} | |
| 69 | /> | |
| 70 | <RepoStack.Screen | |
| 71 | name="FileViewer" | |
| 72 | component={FileViewerScreen} | |
| 73 | options={({ route }) => ({ | |
| 74 | title: route.params.path.split('/').pop() ?? 'File', | |
| 75 | })} | |
| 76 | /> | |
| 77 | <RepoStack.Screen | |
| 78 | name="Commits" | |
| 79 | component={CommitsScreen} | |
| 80 | options={{ title: 'Commits' }} | |
| 81 | /> | |
| 82 | <RepoStack.Screen | |
| 83 | name="Issues" | |
| 84 | component={IssuesScreen} | |
| 85 | options={{ title: 'Issues' }} | |
| 86 | /> | |
| 87 | <RepoStack.Screen | |
| 88 | name="IssueDetail" | |
| 89 | component={IssueDetailScreen} | |
| 90 | options={({ route }) => ({ title: `Issue #${route.params.number}` })} | |
| 91 | /> | |
| 92 | <RepoStack.Screen | |
| 93 | name="Pulls" | |
| 94 | component={PullsScreen} | |
| 95 | options={{ title: 'Pull Requests' }} | |
| 96 | /> | |
| 97 | <RepoStack.Screen | |
| 98 | name="PullDetail" | |
| 99 | component={PullDetailScreen} | |
| 100 | options={({ route }) => ({ title: `PR #${route.params.number}` })} | |
| 101 | /> | |
| 102 | <RepoStack.Screen | |
| 103 | name="GateStatus" | |
| 104 | component={GateStatusScreen} | |
| 105 | options={{ title: 'Gate Runs' }} | |
| 106 | /> | |
| 107 | </RepoStack.Navigator> | |
| 108 | ); | |
| 109 | } | |
| 110 | ||
| 111 | // ─── Tab icons ─────────────────────────────────────────────────────────────── | |
| 112 | ||
| 113 | function TabIcon({ | |
| 114 | label, | |
| 115 | focused, | |
| 116 | }: { | |
| 117 | label: string; | |
| 118 | focused: boolean; | |
| 119 | }): React.ReactElement { | |
| 120 | const icons: Record<string, string> = { | |
| 121 | Dashboard: '⌂', | |
| 122 | Repos: '⑂', | |
| 123 | Notifications: '🔔', | |
| 124 | AskAI: '✦', | |
| 125 | Settings: '⚙', | |
| 126 | }; | |
| 127 | const icon = icons[label] ?? label[0]; | |
| 128 | return ( | |
| 129 | <Text style={{ fontSize: 18, color: focused ? colors.accentBlue : colors.textMuted }}> | |
| 130 | {icon} | |
| 131 | </Text> | |
| 132 | ); | |
| 133 | } | |
| 134 | ||
| 135 | // ─── Bottom Tabs ───────────────────────────────────────────────────────────── | |
| 136 | ||
| 137 | const Tab = createBottomTabNavigator<MainTabParamList>(); | |
| 138 | ||
| 139 | export function AppNavigator(): React.ReactElement { | |
| 140 | return ( | |
| 141 | <Tab.Navigator | |
| 142 | screenOptions={({ route }) => ({ | |
| 143 | headerShown: false, | |
| 144 | tabBarStyle: { | |
| 145 | backgroundColor: colors.bgSecondary, | |
| 146 | borderTopColor: colors.border, | |
| 147 | borderTopWidth: 1, | |
| 148 | }, | |
| 149 | tabBarActiveTintColor: colors.accentBlue, | |
| 150 | tabBarInactiveTintColor: colors.textMuted, | |
| 151 | tabBarLabelStyle: { fontSize: 11, fontWeight: '500' }, | |
| 152 | tabBarIcon: ({ focused }) => ( | |
| 153 | <TabIcon label={route.name} focused={focused} /> | |
| 154 | ), | |
| 155 | })} | |
| 156 | > | |
| 157 | <Tab.Screen | |
| 158 | name="Dashboard" | |
| 159 | component={DashboardScreen} | |
| 160 | options={{ tabBarLabel: 'Home' }} | |
| 161 | /> | |
| 162 | <Tab.Screen | |
| 163 | name="Repos" | |
| 164 | component={RepoStackNavigator} | |
| 165 | options={{ tabBarLabel: 'Repos' }} | |
| 166 | /> | |
| 167 | <Tab.Screen | |
| 168 | name="Notifications" | |
| 169 | component={NotificationsScreen} | |
| 170 | options={{ tabBarLabel: 'Inbox' }} | |
| 171 | /> | |
| 172 | <Tab.Screen | |
| 173 | name="AskAI" | |
| 174 | component={AskAIScreen} | |
| 175 | options={{ | |
| 176 | tabBarLabel: 'Ask AI', | |
| 177 | tabBarActiveTintColor: colors.accentPurple, | |
| 178 | tabBarIcon: ({ focused }) => ( | |
| 179 | <Text style={{ fontSize: 18, color: focused ? colors.accentPurple : colors.textMuted }}> | |
| 180 | ✦ | |
| 181 | </Text> | |
| 182 | ), | |
| 183 | }} | |
| 184 | /> | |
| 185 | <Tab.Screen | |
| 186 | name="Settings" | |
| 187 | component={SettingsScreen} | |
| 188 | options={{ tabBarLabel: 'Settings' }} | |
| 189 | /> | |
| 190 | </Tab.Navigator> | |
| 191 | ); | |
| 192 | } |