import 'dart:convert'; import 'package:flutter/widgets.dart'; import 'package:go_router/go_router.dart'; import 'package:sxchnge_wallet/sxchnge_wallet.dart'; import '../auth/page.dart'; import '../destinations/list.dart'; import '../kyc/page.dart'; import '../security/challenge.dart'; import '../verification/page.dart'; import '../workspace/accounts.dart'; import '../workspace/activity.dart'; import '../workspace/limits.dart'; import '../workspace/operation.dart'; import '../workspace/settings.dart'; import '../workspace/shell.dart'; import '../workspace/transfer.dart'; import 'routes.dart'; GoRouter createAppRouter({bool Function()? authenticated}) { final topUpKey = GlobalKey(); final exchangeKey = GlobalKey(); final payoutKey = GlobalKey(); final identityKey = GlobalKey(); return GoRouter( redirect: (context, state) { if (authenticated == null || authenticated()) return null; final path = state.uri.path; if (path == '/' || path == '/verify-email' || path == '/verification-sent' || path == '/two-factor') { return null; } return '/'; }, // Signup email is an in-memory navigation extra, never browser-history data. extraCodec: const _TransientExtras(), routes: [ GoRoute( path: '/two-factor', redirect: (context, state) => state.extra is FactorRouteRequest ? null : '/', builder: (context, state) => FactorRoute(request: state.extra as FactorRouteRequest), onExit: (context, state) => state.extra is! FactorRouteRequest || !(state.extra as FactorRouteRequest).provider.busy, ), ShellRoute( builder: (context, state, child) => WorkspaceShell(path: state.uri.path, child: child), routes: [ GoRoute( path: '/accounts', builder: (context, state) => const AccountsPage(), routes: [ GoRoute( path: ':accountId', builder: (context, state) => AccountsPage(accountId: state.pathParameters['accountId']), ), ], ), GoRoute( path: '/activity', builder: (context, state) => const ActivityPage(), routes: [ GoRoute( path: ':operationId', builder: (context, state) => OperationPage(id: state.pathParameters['operationId']!), ), ], ), GoRoute( path: '/limits', builder: (context, state) => const LimitsPage(), ), GoRoute( path: '/settings', builder: (context, state) => const SettingsPage(), routes: [ GoRoute( path: 'payout-methods', builder: (context, state) => const DestinationsPage(), ), ], ), GoRoute( path: '/top-up', builder: (context, state) => TransferPage( key: topUpKey, kind: TransferKind.topUp, source: state.extra is String ? state.extra as String : null, ), onExit: (context, state) async => await topUpKey.currentState?.allowExit() ?? true, ), GoRoute( path: '/exchange', builder: (context, state) => TransferPage( key: exchangeKey, kind: TransferKind.exchange, source: state.extra is String ? state.extra as String : null, ), onExit: (context, state) async => await exchangeKey.currentState?.allowExit() ?? true, ), GoRoute( path: '/payout', builder: (context, state) => TransferPage( key: payoutKey, kind: TransferKind.payout, source: state.extra is String ? state.extra as String : null, ), onExit: (context, state) async => await payoutKey.currentState?.allowExit() ?? true, ), GoRoute( path: '/identity', name: AppRoutes.identity, builder: (context, state) => IdentityPage( key: identityKey, onboarding: state.uri.queryParameters['onboarding'] == '1', ), onExit: (context, state) async => await identityKey.currentState?.allowExit() ?? true, ), ], ), GoRoute( path: '/', name: AppRoutes.login, builder: (context, state) => AuthPage( email: state.extra is String ? state.extra as String : null, ), ), GoRoute( path: '/verification-sent', name: AppRoutes.verificationSent, builder: (context, state) => VerificationPage( email: state.extra is String ? state.extra as String : null, ), ), GoRoute( path: '/verify-email', name: AppRoutes.verifyEmail, builder: (context, state) => VerificationPage(token: state.uri.queryParameters['token'] ?? ''), ), ], // Preserve the existing fallback, and keep the visible URL canonical. onException: (context, state, router) => router.goNamed(AppRoutes.login), ); } class _TransientExtras extends Codec { const _TransientExtras(); @override Converter get encoder => const _DiscardExtra(); @override Converter get decoder => const _DiscardExtra(); } class _DiscardExtra extends Converter { const _DiscardExtra(); @override Object? convert(Object? input) => null; }