major refactor
This commit is contained in:
353
lib/main.dart
353
lib/main.dart
@@ -1,356 +1,7 @@
|
||||
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/calculator_details.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:provider/provider.dart';
|
||||
|
||||
import 'package:mileograph_flutter/components/pages/legs.dart';
|
||||
import 'package:mileograph_flutter/services/api_service.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 'components/login/login.dart';
|
||||
import 'components/pages/dashboard.dart';
|
||||
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
late ApiService api;
|
||||
import 'package:mileograph_flutter/app.dart';
|
||||
|
||||
void main() {
|
||||
runApp(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
Provider<ApiService>(
|
||||
create: (_) {
|
||||
api = ApiService(baseUrl: 'https://mileograph.co.uk/api/v1');
|
||||
return api;
|
||||
},
|
||||
),
|
||||
ChangeNotifierProxyProvider<ApiService, AuthService>(
|
||||
create: (context) => AuthService(api: context.read<ApiService>()),
|
||||
update: (_, api, previous) {
|
||||
return previous ?? AuthService(api: api);
|
||||
},
|
||||
),
|
||||
ProxyProvider<AuthService, void>(
|
||||
update: (_, auth, previous) {
|
||||
api.setTokenProvider(() => auth.token);
|
||||
api.setUnauthorizedHandler(() => auth.handleTokenExpired());
|
||||
},
|
||||
),
|
||||
ChangeNotifierProxyProvider<ApiService, DataService>(
|
||||
create: (context) => DataService(api: context.read<ApiService>()),
|
||||
update: (_, api, previous) => previous ?? DataService(api: api),
|
||||
),
|
||||
],
|
||||
child: MyApp(),
|
||||
),
|
||||
);
|
||||
runApp(const App());
|
||||
}
|
||||
|
||||
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>(), // `AuthService` extends `ChangeNotifier`
|
||||
redirect: (context, state) {
|
||||
final auth = Provider.of<AuthService>(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: (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()),
|
||||
],
|
||||
);
|
||||
|
||||
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<MyHomePage> createState() => _MyHomePageState();
|
||||
}
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
final List<String> 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<void> _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 loggedIn = false;
|
||||
bool _fetched = false;
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
|
||||
if (!_fetched) {
|
||||
_fetched = true;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
Future(() async {
|
||||
if (!mounted) return;
|
||||
final data = context.read<DataService>();
|
||||
final auth = context.read<AuthService>();
|
||||
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<DataService>();
|
||||
final auth = context.read<AuthService>();
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user