completed velocity integration

This commit is contained in:
2026-05-27 19:01:24 +02:00
parent fec46fb6e9
commit 6fdd3183fb
29 changed files with 2760 additions and 2293 deletions

View File

@@ -1,10 +1,14 @@
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';
@@ -16,15 +20,12 @@ 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 'screens/gateway/gateway_web_screen.dart';
import 'screens/home/home_screen.dart';
import 'screens/history/history_screen.dart';
import 'screens/profile_screen.dart';
import 'screens/splash_screen.dart';
import 'theme/app_theme.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';
@@ -43,8 +44,39 @@ String resolveEnvFile(String env) {
}
}
/// 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));
@@ -71,37 +103,54 @@ class MyApp extends StatefulWidget {
}
class _MyAppState extends State<MyApp> {
bool _showSplash = true;
void _onSplashComplete() {
setState(() {
_showSplash = false;
});
}
@override
Widget build(BuildContext context) {
if (_showSplash) {
return MaterialApp(
title: 'Velocity',
theme: AppTheme.lightTheme,
themeMode: ThemeMode.light,
home: SplashScreen(onSplashComplete: _onSplashComplete),
debugShowCheckedModeBanner: false,
);
}
return MaterialApp.router(
title: 'Velocity',
theme: AppTheme.lightTheme,
themeMode: ThemeMode.light, // Force light mode regardless of OS setting
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) {
// If the current location is an onboarding route, don't show the navbar
final location = GoRouterState.of(context).uri.toString();
if (location.startsWith('/onboarding/')) {
return child;
@@ -137,6 +186,12 @@ class _MyAppState extends State<MyApp> {
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(),
@@ -195,4 +250,4 @@ class _MyAppState extends State<MyApp> {
),
);
}
}
}