import 'package:flutter/material.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'package:provider/provider.dart'; import 'package:go_router/go_router.dart'; import 'package:qpay/navbar.dart'; import 'package:qpay/screens/confirm/confirm_screen.dart'; import 'package:qpay/screens/gateway/gateway_redirect.dart'; import 'package:qpay/screens/gateway/gateway_screen.dart'; import 'package:qpay/screens/gateway/gateway_web_screen.dart'; import 'package:qpay/screens/home/home_screen.dart'; import 'package:qpay/screens/history/history_screen.dart'; import 'package:qpay/screens/integration/integration_screen.dart'; import 'package:qpay/screens/onboarding/complete_screen.dart'; import 'package:qpay/screens/onboarding/landing/landing_screen.dart'; import 'package:qpay/screens/onboarding/login/login_screen.dart'; import 'package:qpay/screens/onboarding/onboarding_controller.dart'; import 'package:qpay/screens/onboarding/password/password_screen.dart'; import 'package:qpay/screens/onboarding/phone/phone_screen.dart'; import 'package:qpay/screens/onboarding/register/register_screen.dart'; import 'package:qpay/screens/onboarding/verify/verify_screen.dart'; import 'package:qpay/screens/pay/pay_screen.dart'; import 'package:qpay/screens/poll/poll_screen.dart'; import 'package:qpay/screens/splash_screen.dart'; import 'package:qpay/screens/receipt/receipt_screen.dart'; import 'package:qpay/screens/recipient/recipients_screen.dart'; import 'package:qpay/screens/transaction_controller.dart'; import 'package:qpay/screens/profile_screen.dart'; import 'package:qpay/theme/app_theme.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:firebase_core/firebase_core.dart'; import 'firebase_options.dart'; const String testEnv = 'assets/.env.test'; const String liveEnv = 'assets/.env.live'; const String appEnv = String.fromEnvironment('APP_ENV', defaultValue: 'test'); String resolveEnvFile(String env) { switch (env.toLowerCase()) { case 'live': return liveEnv; case 'test': default: return testEnv; } } /// Tracks whether the splash animation is complete so that /// GoRouter's redirect can show the splash first, then release /// navigation to the originally intended URL. class SplashState extends ChangeNotifier { bool _complete = false; /// The route the user originally intended to visit before being /// redirected to /splash. Set by the redirect callback on first /// invocation. String? _intendedRoute; bool get complete => _complete; String? get intendedRoute => _intendedRoute; /// Called by the redirect to store the intended destination and /// return the splash route. String? guard(String attemptedRoute) { if (_complete) return null; // Avoid saving the splash route itself. if (attemptedRoute == '/splash') return null; _intendedRoute = attemptedRoute; return '/splash'; } void finish() { _complete = true; notifyListeners(); } } final SplashState splashState = SplashState(); void main() async { WidgetsFlutterBinding.ensureInitialized(); usePathUrlStrategy(); await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); await dotenv.load(fileName: resolveEnvFile(appEnv)); runApp( MultiProvider( providers: [ ChangeNotifierProvider( create: (_) => TransactionController(), ), ChangeNotifierProvider( create: (_) => OnboardingController(), ), ], child: const MyApp(), ), ); } class MyApp extends StatefulWidget { const MyApp({super.key}); @override State createState() => _MyAppState(); } class _MyAppState extends State { @override Widget build(BuildContext context) { return MaterialApp.router( title: 'Velocity', theme: AppTheme.lightTheme, themeMode: ThemeMode.light, debugShowCheckedModeBanner: false, routerConfig: GoRouter( initialLocation: '/', refreshListenable: splashState, errorBuilder: (context, state) => Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text('Page not found', style: TextStyle(fontSize: 20)), const SizedBox(height: 16), ElevatedButton( onPressed: () => context.go('/'), child: const Text('Go Home'), ), ], ), ), ), redirect: (context, state) { // Before splash is complete: redirect everything to /splash, // but save the original intended destination. final guardResult = splashState.guard(state.uri.path); if (guardResult != null) return guardResult; // Once splash is complete: if we're still on /splash, navigate // to the intended destination (or home as fallback). if (splashState.complete && state.uri.path == '/splash') { return splashState.intendedRoute ?? '/'; } return null; }, routes: [ // Splash route sits outside the ShellRoute so it renders // without the bottom nav bar or side rail. GoRoute( path: '/splash', builder: (context, state) => const SplashScreen(), ), ShellRoute( builder: (context, state, child) { final location = GoRouterState.of(context).uri.toString(); if (location.startsWith('/onboarding/')) { return child; } return ScaffoldWithNavBar(child: child); }, routes: [ GoRoute( path: '/', builder: (context, state) => const HomeScreen(), ), GoRoute( path: '/home', builder: (context, state) => const HomeScreen(), ), GoRoute( path: '/make-payment', builder: (context, state) => const PayScreen(), ), GoRoute( path: '/confirm', builder: (context, state) => const ConfirmScreen(), ), GoRoute( path: '/gateway', builder: (context, state) => const GatewayScreen(), ), GoRoute( path: '/gateway-web', builder: (context, state) => const GatewayWebScreen(), ), GoRoute( path: '/gateway-redirect', builder: (context, state) => const GatewayRedirectScreen(), ), GoRoute( path: '/poll/:id', builder: (context, state) => PollScreen( transactionId: state.pathParameters['id'], ), ), GoRoute( path: '/poll', builder: (context, state) => const PollScreen(), ), GoRoute( path: '/receipt', builder: (context, state) => const ReceiptScreen(), ), GoRoute( path: '/integration', builder: (context, state) => const IntegrationScreen(), ), GoRoute( path: '/recipients', builder: (context, state) => const RecipientsScreen(), ), GoRoute( path: '/history', builder: (context, state) => const HistoryScreen(), ), GoRoute( path: '/profile', builder: (context, state) => const ProfileScreen(), ), GoRoute( path: '/onboarding/landing', builder: (context, state) => const LandingScreen(), ), GoRoute( path: '/onboarding/register', builder: (context, state) => const RegisterScreen(), ), GoRoute( path: '/onboarding/phone', builder: (context, state) => const PhoneScreen(), ), GoRoute( path: '/onboarding/verify', builder: (context, state) => const VerifyScreen(), ), GoRoute( path: '/onboarding/password', builder: (context, state) => const PasswordScreen(), ), GoRoute( path: '/onboarding/login', builder: (context, state) => const LoginScreen(), ), GoRoute( path: '/onboarding/complete', builder: (context, state) => const CompleteScreen(), ), ], ), ], ), ); } }