import 'package:flutter/material.dart'; import 'package:dynamic_color/dynamic_color.dart'; import 'package:mileograph_flutter/components/pages/calculator.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:provider/provider.dart'; import 'package:mileograph_flutter/components/pages/legs.dart'; import 'package:mileograph_flutter/services/apiService.dart'; import 'package:mileograph_flutter/services/authservice.dart'; import 'package:mileograph_flutter/services/dataService.dart'; import 'components/login/login.dart'; import 'components/pages/dashboard.dart'; import 'package:go_router/go_router.dart'; late ApiService api; void main() { runApp( MultiProvider( providers: [ Provider( create: (_) { api = ApiService(baseUrl: 'https://mileograph.co.uk/api/v1'); return api; }, ), ChangeNotifierProxyProvider( create: (context) => AuthService(api: context.read()), update: (_, api, previous) { return previous ?? AuthService(api: api); }, ), ProxyProvider( update: (_, auth, __) { api.setTokenProvider(() => auth.token); api.setUnauthorizedHandler(() => auth.handleTokenExpired()); }, ), ChangeNotifierProxyProvider( create: (context) => DataService(api: context.read()), update: (_, api, previous) => previous ?? DataService(api: api), ), ], child: MyApp(), ), ); } class MyApp extends StatelessWidget { MyApp({super.key}); final ColorScheme defaultLight = ColorScheme.fromSeed(seedColor: Colors.red); final ColorScheme defaultDark = ColorScheme.fromSeed( seedColor: Colors.red, brightness: Brightness.dark, ); // This widget is the root of your application. @override Widget build(BuildContext context) { final GoRouter router = GoRouter( refreshListenable: context .read(), // `AuthService` extends `ChangeNotifier` redirect: (context, state) { final auth = Provider.of(context, listen: false); final loggedIn = auth.isLoggedIn; final loggingIn = state.uri.toString() == '/login'; // Redirect to login if not logged in and trying to access protected pages if (!loggedIn && !loggingIn) return '/login'; // Redirect to home if already logged in and trying to go to login if (loggedIn && loggingIn) return '/'; // No redirection return null; }, routes: [ ShellRoute( builder: (context, state, child) { return MyHomePage(child: child); }, routes: [ GoRoute(path: '/', builder: (_, __) => const Dashboard()), GoRoute(path: '/calculator', builder: (_, __) => CalculatorPage()), GoRoute( path: '/calculator/details', builder: (_, __) => CalculatorPage(), ), GoRoute(path: '/legs', builder: (_, __) => LegsPage()), GoRoute(path: '/traction', builder: (_, __) => TractionPage()), GoRoute( path: '/traction/new', builder: (_, __) => const NewTractionPage(), ), GoRoute(path: '/trips', builder: (_, __) => TripsPage()), GoRoute(path: '/add', builder: (_, __) => NewEntryPage()), ], ), GoRoute(path: '/login', builder: (_, __) => const LoginScreen()), ], ); return DynamicColorBuilder( builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) { return MaterialApp.router( title: 'Flutter Demo', routerConfig: router, theme: ThemeData( useMaterial3: true, // This is the theme of your application. // // TRY THIS: Try running your application with "flutter run". You'll see // the application has a purple toolbar. Then, without quitting the app, // try changing the seedColor in the colorScheme below to Colors.green // and then invoke "hot reload" (save your changes or press the "hot // reload" button in a Flutter-supported IDE, or press "r" if you used // the command line to start the app). //fullPage // Notice that the counter didn't reset back to zero; the application // state is not lost during the reload. To reset the state, use hot // restart instead. // // This works for code too, not just values: Most code changes can be // tested with just a hot reload. 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}); // This widget is the home page of your application. It is stateful, meaning // that it has a State object (defined below) that contains fields that affect // how it looks. // This class is the configuration for the state. It holds the values (in this // case the title) provided by the parent (in this case the App widget) and // used by the build method of the State. Fields in a Widget subclass are // always marked "final". @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; } void _onItemTapped(int index, int currentIndex) { if (index < 0 || index >= contentPages.length || index == currentIndex) { return; } context.push(contentPages[index]); _getIndexFromLocation(contentPages[index]); } bool loggedIn = false; bool _fetched = false; @override void didChangeDependencies() { super.didChangeDependencies(); if (!_fetched) { _fetched = true; WidgetsBinding.instance.addPostFrameCallback((_) { Future(() async { final data = context.read(); final auth = context.read(); api.setTokenProvider(() => auth.token); 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) { Widget currentPage; final location = GoRouterState.of(context).uri.toString(); final pageIndex = _getIndexFromLocation(location); final data = context.watch(); final auth = context.read(); if (data.homepageStats != null || !data.isHomepageLoading) { currentPage = widget.child; } else { currentPage = Center(child: CircularProgressIndicator()); } // This method is rerun every time setState is called, for instance as done // by the _incrementCounter method above. // // The Flutter framework has been optimized to make rerunning build methods // fast, so that you can just rebuild anything that needs updating rather // than having to individually change instances of widgets. return Scaffold( appBar: AppBar( // TRY THIS: Try changing the color here to a specific color (to // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar // change color while the other colors stay the same. backgroundColor: Theme.of(context).colorScheme.inversePrimary, // Here we take the value from the MyHomePage object that was created by // the App.build method, and use it to set our appbar title. title: Text.rich( TextSpan( children: [ TextSpan(text: "Mile"), TextSpan( text: "O", style: TextStyle(color: Colors.red), ), TextSpan(text: "graph"), ], style: TextStyle( decoration: TextDecoration.none, color: Colors.white, fontFamily: "Tomatoes", ), ), ), actions: [ IconButton(onPressed: null, icon: Icon(Icons.account_circle)), IconButton(onPressed: auth.logout, icon: Icon(Icons.logout)), ], ), bottomNavigationBar: NavigationBar( selectedIndex: pageIndex, onDestinationSelected: (int index) { _onItemTapped(index, pageIndex); }, destinations: [ 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, ); } }