router.dart
1 import 'package:flutter/material.dart'; 2 import 'package:go_router/go_router.dart'; 3 import 'package:flutter_riverpod/flutter_riverpod.dart'; 4 5 import 'theme/app_responsive.dart'; 6 import 'utils/platform_utils.dart'; 7 import '../screens/desktop_shell.dart'; 8 import '../screens/onboarding/onboarding_screen.dart'; 9 import '../screens/onboarding/create_identity_screen.dart'; 10 import '../screens/contacts/contacts_screen.dart'; 11 import '../screens/contacts/add_contact_screen.dart'; 12 import '../screens/contacts/scan_qr_screen.dart'; 13 import '../screens/contacts/show_qr_screen.dart'; 14 import '../screens/contacts/confirm_contact_screen.dart'; 15 import '../screens/contacts/nearby_contacts_screen.dart'; 16 import '../screens/chat/chat_screen.dart'; 17 import '../screens/contacts/contact_info_screen.dart'; 18 import '../screens/contacts/verify_contact_screen.dart'; 19 import '../screens/groups/create_group_screen.dart'; 20 import '../screens/groups/group_chat_screen.dart'; 21 import '../screens/groups/group_settings_screen.dart'; 22 import '../screens/settings/settings_screen.dart'; 23 import '../providers/identity_provider.dart'; 24 import '../services/dead_drop_service.dart'; 25 import '../services/notification_service.dart'; 26 27 /// App routes 28 abstract final class AppRoutes { 29 static const onboarding = '/onboarding'; 30 static const createIdentity = '/onboarding/create'; 31 static const contacts = '/contacts'; 32 static const addContact = '/contacts/add'; 33 static const scanQr = '/contacts/scan'; 34 static const showQr = '/contacts/qr'; 35 static const confirmContact = '/contacts/confirm'; 36 static const nearbyContacts = '/contacts/nearby'; 37 static const chat = '/chat/:contactId'; 38 static const contactInfo = '/chat/:contactId/info'; 39 static const verifyContact = '/chat/:contactId/verify'; 40 static const createGroup = '/groups/create'; 41 static const groupChat = '/group/:groupId'; 42 static const groupSettings = '/group/:groupId/settings'; 43 static const settings = '/settings'; 44 45 /// Build chat route with contact ID 46 static String chatPath(String contactIdHex) => '/chat/$contactIdHex'; 47 48 /// Build contact info route 49 static String contactInfoPath(String contactIdHex) => 50 '/chat/$contactIdHex/info'; 51 52 /// Build verify contact route 53 static String verifyContactPath(String contactIdHex) => 54 '/chat/$contactIdHex/verify'; 55 56 /// Build group chat route with group ID 57 static String groupChatPath(String groupIdHex) => '/group/$groupIdHex'; 58 59 /// Build group settings route with group ID 60 static String groupSettingsPath(String groupIdHex) => 61 '/group/$groupIdHex/settings'; 62 } 63 64 /// Provider for pending contact (passed between scan and confirm screens) 65 final pendingContactProvider = StateProvider<PendingContact?>((ref) => null); 66 67 /// Router provider 68 final routerProvider = Provider<GoRouter>((ref) { 69 final hasIdentity = ref.watch(hasIdentityProvider); 70 71 return GoRouter( 72 navigatorKey: NotificationService.navigatorKey, 73 initialLocation: AppRoutes.onboarding, 74 redirect: (context, state) { 75 final isOnboarding = state.matchedLocation.startsWith( 76 AppRoutes.onboarding, 77 ); 78 79 return hasIdentity.when( 80 data: (hasId) { 81 if (hasId && isOnboarding) { 82 return AppRoutes.contacts; 83 } 84 if (!hasId && !isOnboarding) { 85 return AppRoutes.onboarding; 86 } 87 return null; 88 }, 89 loading: () => null, 90 error: (_, __) => AppRoutes.onboarding, 91 ); 92 }, 93 routes: [ 94 // Onboarding (outside shell — no sidebar) 95 GoRoute( 96 path: AppRoutes.onboarding, 97 builder: (context, state) => const OnboardingScreen(), 98 routes: [ 99 GoRoute( 100 path: 'create', 101 builder: (context, state) => const CreateIdentityScreen(), 102 ), 103 ], 104 ), 105 106 // Main app routes — wrapped in ShellRoute for desktop layout 107 ShellRoute( 108 builder: (context, state, child) { 109 if (AppResponsive.isWideScreen(context)) { 110 return DesktopShell( 111 child: child, 112 location: state.matchedLocation, 113 ); 114 } 115 return child; 116 }, 117 routes: [ 118 // Contacts (no sub-routes — flattened so desktop shell can 119 // swap the right pane without a nested navigator stack) 120 GoRoute( 121 path: AppRoutes.contacts, 122 builder: (context, state) => const ContactsScreen(), 123 ), 124 GoRoute( 125 path: AppRoutes.addContact, 126 builder: (context, state) => const AddContactScreen(), 127 ), 128 GoRoute( 129 path: AppRoutes.scanQr, 130 builder: (context, state) => const ScanQrScreen(), 131 ), 132 GoRoute( 133 path: AppRoutes.showQr, 134 builder: (context, state) => const ShowQrScreen(), 135 ), 136 GoRoute( 137 path: AppRoutes.confirmContact, 138 builder: (context, state) => const ConfirmContactScreen(), 139 ), 140 if (PlatformUtils.supportsBle) 141 GoRoute( 142 path: AppRoutes.nearbyContacts, 143 builder: (context, state) => const NearbyContactsScreen(), 144 ), 145 146 // Chat 147 GoRoute( 148 path: '/chat/:contactId', 149 builder: (context, state) { 150 final contactIdHex = state.pathParameters['contactId']!; 151 return ChatScreen(key: ValueKey(contactIdHex), contactIdHex: contactIdHex); 152 }, 153 routes: [ 154 GoRoute( 155 path: 'info', 156 builder: (context, state) { 157 final contactIdHex = state.pathParameters['contactId']!; 158 return ContactInfoScreen(contactIdHex: contactIdHex); 159 }, 160 ), 161 GoRoute( 162 path: 'verify', 163 builder: (context, state) { 164 final contactIdHex = state.pathParameters['contactId']!; 165 return VerifyContactScreen(contactIdHex: contactIdHex); 166 }, 167 ), 168 ], 169 ), 170 171 // Groups 172 GoRoute( 173 path: AppRoutes.createGroup, 174 builder: (context, state) => const CreateGroupScreen(), 175 ), 176 GoRoute( 177 path: '/group/:groupId', 178 builder: (context, state) { 179 final groupIdHex = state.pathParameters['groupId']!; 180 return GroupChatScreen(key: ValueKey(groupIdHex), groupIdHex: groupIdHex); 181 }, 182 routes: [ 183 GoRoute( 184 path: 'settings', 185 builder: (context, state) { 186 final groupIdHex = state.pathParameters['groupId']!; 187 return GroupSettingsScreen(groupIdHex: groupIdHex); 188 }, 189 ), 190 ], 191 ), 192 193 // Settings 194 GoRoute( 195 path: AppRoutes.settings, 196 builder: (context, state) => const SettingsScreen(), 197 ), 198 ], 199 ), 200 ], 201 ); 202 });