import 'package:dynamic_color/dynamic_color.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:mileograph_flutter/components/login/login.dart'; import 'package:mileograph_flutter/components/pages/calculator.dart'; import 'package:mileograph_flutter/components/pages/calculator_details.dart'; import 'package:mileograph_flutter/components/pages/dashboard.dart'; import 'package:mileograph_flutter/components/pages/legs.dart'; import 'package:mileograph_flutter/components/pages/loco_legs.dart'; import 'package:mileograph_flutter/components/pages/loco_timeline.dart'; import 'package:mileograph_flutter/components/pages/new_entry.dart'; import 'package:mileograph_flutter/components/pages/new_traction.dart'; import 'package:mileograph_flutter/components/pages/traction.dart'; import 'package:mileograph_flutter/components/pages/trips.dart'; import 'package:mileograph_flutter/services/authservice.dart'; import 'package:mileograph_flutter/services/data_service.dart'; import 'package:mileograph_flutter/services/navigation_guard.dart'; import 'package:provider/provider.dart'; class MyApp extends StatefulWidget { const MyApp({super.key}); @override State createState() => _MyAppState(); } class _MyAppState extends State { late final GoRouter _router; bool _routerInitialized = false; final ColorScheme defaultLight = ColorScheme.fromSeed(seedColor: Colors.red); final ColorScheme defaultDark = ColorScheme.fromSeed( seedColor: Colors.red, brightness: Brightness.dark, ); @override void didChangeDependencies() { super.didChangeDependencies(); if (_routerInitialized) return; _routerInitialized = true; final auth = context.read(); _router = GoRouter( refreshListenable: auth, redirect: (context, state) { final loggedIn = auth.isLoggedIn; final loggingIn = state.uri.toString() == '/login'; if (!loggedIn && !loggingIn) return '/login'; if (loggedIn && loggingIn) return '/'; return null; }, routes: [ ShellRoute( builder: (context, state, child) => MyHomePage(child: child), routes: [ GoRoute(path: '/', builder: (context, state) => const Dashboard()), GoRoute( path: '/calculator', builder: (context, state) => CalculatorPage(), ), GoRoute( path: '/calculator/details', builder: (context, state) => CalculatorDetailsPage(result: state.extra), ), GoRoute(path: '/legs', builder: (context, state) => LegsPage()), GoRoute( path: '/traction', builder: (context, state) => TractionPage(), ), GoRoute( path: '/traction/:id/timeline', builder: (_, state) { final idParam = state.pathParameters['id']; final locoId = int.tryParse(idParam ?? '') ?? 0; final extra = state.extra; String label = state.uri.queryParameters['label'] ?? ''; if (extra is Map && extra['label'] is String) { label = extra['label'] as String; } else if (extra is String && extra.isNotEmpty) { label = extra; } if (label.trim().isEmpty) label = 'Loco $locoId'; return LocoTimelinePage(locoId: locoId, locoLabel: label); }, ), GoRoute( path: '/traction/:id/legs', builder: (_, state) { final idParam = state.pathParameters['id']; final locoId = int.tryParse(idParam ?? '') ?? 0; final extra = state.extra; String label = state.uri.queryParameters['label'] ?? ''; if (extra is Map && extra['label'] is String) { label = extra['label'] as String; } else if (extra is String && extra.isNotEmpty) { label = extra; } if (label.trim().isEmpty) label = 'Loco $locoId'; return LocoLegsPage(locoId: locoId, locoLabel: label); }, ), GoRoute( path: '/traction/new', builder: (context, state) => const NewTractionPage(), ), GoRoute(path: '/trips', builder: (context, state) => TripsPage()), GoRoute(path: '/add', builder: (context, state) => NewEntryPage()), GoRoute( path: '/legs/edit/:id', builder: (_, state) { final idParam = state.pathParameters['id']; final legId = idParam == null ? null : int.tryParse(idParam); return NewEntryPage(editLegId: legId); }, ), ], ), GoRoute(path: '/login', builder: (context, state) => const LoginScreen()), ], ); } @override Widget build(BuildContext context) { return DynamicColorBuilder( builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) { return MaterialApp.router( title: 'Mileograph', routerConfig: _router, theme: ThemeData( useMaterial3: true, colorScheme: lightDynamic ?? defaultLight, ), darkTheme: ThemeData( useMaterial3: true, colorScheme: darkDynamic ?? defaultDark, ), themeMode: ThemeMode.system, ); }, ); } } class MyHomePage extends StatefulWidget { final Widget child; const MyHomePage({super.key, required this.child}); @override State createState() => _MyHomePageState(); } class _MyHomePageState extends State { final List contentPages = [ "/", "/calculator", "/legs", "/traction", "/trips", "/add", ]; int _getIndexFromLocation(String location) { int newIndex = contentPages.indexWhere((path) { if (location == path) return true; if (path == '/') return location == '/'; return location.startsWith('$path/'); }); if (newIndex < 0) { return 0; } return newIndex; } Future _onItemTapped(int index, int currentIndex) async { if (index < 0 || index >= contentPages.length || index == currentIndex) { return; } await NavigationGuard.attemptNavigation(() async { if (!mounted) return; context.go(contentPages[index]); }); } bool _fetched = false; @override void didChangeDependencies() { super.didChangeDependencies(); if (_fetched) return; _fetched = true; WidgetsBinding.instance.addPostFrameCallback((_) { Future(() async { if (!mounted) return; final data = context.read(); final auth = context.read(); await auth.tryRestoreSession(); if (!auth.isLoggedIn) return; data.fetchEventFields(); if (data.homepageStats == null) { data.fetchHomepageStats(); } if (data.legs.isEmpty) { data.fetchLegs(); } if (data.traction.isEmpty) { data.fetchHadTraction(); } if (data.onThisDay.isEmpty) { data.fetchOnThisDay(); } if (data.tripDetails.isEmpty) { data.fetchTripDetails(); } }); }); } @override Widget build(BuildContext context) { final location = GoRouterState.of(context).uri.toString(); final pageIndex = _getIndexFromLocation(location); final homepageReady = context.select( (data) => data.homepageStats != null || !data.isHomepageLoading, ); final auth = context.read(); final currentPage = homepageReady ? widget.child : const Center(child: CircularProgressIndicator()); return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: Text.rich( TextSpan( children: const [ TextSpan(text: "Mile"), TextSpan(text: "O", style: TextStyle(color: Colors.red)), TextSpan(text: "graph"), ], style: const TextStyle( decoration: TextDecoration.none, color: Colors.white, fontFamily: "Tomatoes", ), ), ), actions: [ const IconButton(onPressed: null, icon: Icon(Icons.account_circle)), IconButton(onPressed: auth.logout, icon: const Icon(Icons.logout)), ], ), bottomNavigationBar: NavigationBar( selectedIndex: pageIndex, onDestinationSelected: (int index) => _onItemTapped(index, pageIndex), destinations: const [ NavigationDestination(icon: Icon(Icons.home), label: "Home"), NavigationDestination(icon: Icon(Icons.route), label: "Calculator"), NavigationDestination(icon: Icon(Icons.list), label: "Entries"), NavigationDestination(icon: Icon(Icons.train), label: "Traction"), NavigationDestination(icon: Icon(Icons.book), label: "Trips"), NavigationDestination(icon: Icon(Icons.add), label: "Add"), ], ), body: currentPage, ); } }