add back button functionality

This commit is contained in:
2025-07-26 23:28:45 +01:00
parent 34f0e8d96d
commit 115954f397
8 changed files with 139 additions and 81 deletions

View File

@@ -1,4 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/>
<application <application
android:label="mileograph_flutter" android:label="mileograph_flutter"
android:name="${applicationName}" android:name="${applicationName}"

View File

@@ -5,31 +5,6 @@ import 'package:mileograph_flutter/services/apiService.dart';
import 'package:mileograph_flutter/services/dataService.dart'; import 'package:mileograph_flutter/services/dataService.dart';
import './routeSummaryWidget.dart'; import './routeSummaryWidget.dart';
class RouteCalculatorLoader extends StatelessWidget {
const RouteCalculatorLoader({super.key});
@override
Widget build(BuildContext context) {
final data = context.read<DataService>();
return FutureBuilder<List<Station>>(
future: data.fetchStations(),
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return const Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
}
final stations = snapshot.data!;
return RouteCalculator(allStations: stations);
},
);
}
}
class StationAutocomplete extends StatefulWidget { class StationAutocomplete extends StatefulWidget {
const StationAutocomplete({ const StationAutocomplete({
super.key, super.key,
@@ -105,16 +80,14 @@ class _StationAutocompleteState extends State<StationAutocomplete> {
} }
class RouteCalculator extends StatefulWidget { class RouteCalculator extends StatefulWidget {
final List<Station> allStations; const RouteCalculator({super.key});
const RouteCalculator({super.key, required this.allStations});
@override @override
State<RouteCalculator> createState() => _RouteCalculatorState(); State<RouteCalculator> createState() => _RouteCalculatorState();
} }
class _RouteCalculatorState extends State<RouteCalculator> { class _RouteCalculatorState extends State<RouteCalculator> {
List<String> stations = ['']; List<Station> allStations = [];
RouteResult? _routeResult; RouteResult? _routeResult;
RouteResult? get result => _routeResult; RouteResult? get result => _routeResult;
@@ -122,6 +95,23 @@ class _RouteCalculatorState extends State<RouteCalculator> {
bool _showDetails = false; bool _showDetails = false;
bool _fetched = false;
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (!_fetched) {
_fetched = true;
WidgetsBinding.instance.addPostFrameCallback((_) async {
final data = context.read<DataService>();
final result = await data.fetchStations();
if (mounted) {
setState(() => allStations = result);
}
});
}
}
Future<void> _calculateRoute(List<String> stations) async { Future<void> _calculateRoute(List<String> stations) async {
setState(() { setState(() {
_errorMessage = null; _errorMessage = null;
@@ -146,25 +136,29 @@ class _RouteCalculatorState extends State<RouteCalculator> {
} }
void _addStation() { void _addStation() {
final data = context.read<DataService>();
setState(() { setState(() {
stations.add(''); data.stations.add('');
}); });
} }
void _removeStation(int index) { void _removeStation(int index) {
final data = context.read<DataService>();
setState(() { setState(() {
stations.removeAt(index); data.stations.removeAt(index);
}); });
} }
void _updateStation(int index, String value) { void _updateStation(int index, String value) {
final data = context.read<DataService>();
setState(() { setState(() {
stations[index] = value; data.stations[index] = value;
}); });
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final data = context.watch<DataService>();
if (_showDetails && _routeResult != null) { if (_showDetails && _routeResult != null) {
return RouteDetailsView( return RouteDetailsView(
route: _routeResult!.calculatedRoute, route: _routeResult!.calculatedRoute,
@@ -180,13 +174,13 @@ class _RouteCalculatorState extends State<RouteCalculator> {
onReorder: (oldIndex, newIndex) { onReorder: (oldIndex, newIndex) {
if (newIndex > oldIndex) newIndex -= 1; if (newIndex > oldIndex) newIndex -= 1;
setState(() { setState(() {
final moved = stations.removeAt(oldIndex); final moved = data.stations.removeAt(oldIndex);
stations.insert(newIndex, moved); data.stations.insert(newIndex, moved);
}); });
}, },
children: List.generate(stations.length, (index) { children: List.generate(data.stations.length, (index) {
return Container( return Container(
key: ValueKey('$index-${stations[index]}'), key: ValueKey('$index-${data.stations[index]}'),
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -207,8 +201,8 @@ class _RouteCalculatorState extends State<RouteCalculator> {
children: [ children: [
Expanded( Expanded(
child: StationAutocomplete( child: StationAutocomplete(
allStations: widget.allStations, allStations: allStations,
initialValue: stations[index], initialValue: data.stations[index],
onChanged: (val) => onChanged: (val) =>
_updateStation(index, val), _updateStation(index, val),
), ),
@@ -244,24 +238,34 @@ class _RouteCalculatorState extends State<RouteCalculator> {
else else
SizedBox.shrink(), SizedBox.shrink(),
const SizedBox(height: 10), const SizedBox(height: 10),
Row( LayoutBuilder(
mainAxisAlignment: MainAxisAlignment.center, builder: (context, constraints) {
children: [ double screenWidth = constraints.maxWidth;
ElevatedButton.icon(
icon: const Icon(Icons.add), return Padding(
label: const Text('Add Station'), padding: EdgeInsets.only(right: screenWidth < 450 ? 70 : 0),
onPressed: _addStation, child: Row(
), mainAxisAlignment: MainAxisAlignment.center,
const SizedBox(width: 16), children: [
ElevatedButton.icon( ElevatedButton.icon(
icon: const Icon(Icons.route), icon: const Icon(Icons.add),
label: const Text('Calculate Route'), label: const Text('Add Station'),
onPressed: () async { onPressed: _addStation,
await _calculateRoute(stations); ),
}, const SizedBox(width: 16),
), ElevatedButton.icon(
], icon: const Icon(Icons.route),
label: const Text('Calculate Route'),
onPressed: () async {
await _calculateRoute(data.stations);
},
),
],
),
);
},
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
], ],
); );

View File

@@ -5,6 +5,6 @@ import 'package:mileograph_flutter/services/dataService.dart';
class CalculatorPage extends StatelessWidget { class CalculatorPage extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return RouteCalculatorLoader(); return RouteCalculator();
} }
} }

View File

@@ -85,7 +85,11 @@ class DashboardHeader extends StatelessWidget {
Expanded( Expanded(
child: ListView( child: ListView(
scrollDirection: Axis.vertical, scrollDirection: Axis.vertical,
children: [TopTractionPanel(), LeaderboardPanel()], children: [
TopTractionPanel(),
LeaderboardPanel(),
SizedBox(height: 80),
],
), ),
), ),
], ],

View File

@@ -13,6 +13,8 @@ import 'components/login/login.dart';
import 'components/pages/dashboard.dart'; import 'components/pages/dashboard.dart';
import 'components/dashboard/topTractionPanel.dart'; import 'components/dashboard/topTractionPanel.dart';
import 'package:go_router/go_router.dart';
late ApiService api; late ApiService api;
void main() { void main() {
@@ -46,19 +48,6 @@ void main() {
); );
} }
class AppRoot extends StatelessWidget {
const AppRoot({super.key});
@override
Widget build(BuildContext context) {
return Consumer<AuthService>(
builder: (context, auth, child) {
return auth.isLoggedIn ? MyHomePage() : LoginScreen();
},
);
}
}
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
MyApp({super.key}); MyApp({super.key});
@@ -67,13 +56,48 @@ class MyApp extends StatelessWidget {
seedColor: Colors.red, seedColor: Colors.red,
brightness: Brightness.dark, brightness: Brightness.dark,
); );
// This widget is the root of your application. // This widget is the root of your application.
@override @override
Widget build(BuildContext context) { 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: (_, __) => const Dashboard()),
GoRoute(path: '/calculator', builder: (_, __) => CalculatorPage()),
GoRoute(path: '/legs', builder: (_, __) => LegsPage()),
GoRoute(path: '/traction', builder: (_, __) => TractionPage()),
],
),
GoRoute(path: '/login', builder: (_, __) => const LoginScreen()),
],
);
return DynamicColorBuilder( return DynamicColorBuilder(
builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) { builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) {
return MaterialApp( return MaterialApp.router(
title: 'Flutter Demo', title: 'Flutter Demo',
routerConfig: router,
theme: ThemeData( theme: ThemeData(
useMaterial3: true, useMaterial3: true,
// This is the theme of your application. // This is the theme of your application.
@@ -98,7 +122,6 @@ class MyApp extends StatelessWidget {
colorScheme: darkDynamic ?? defaultDark, colorScheme: darkDynamic ?? defaultDark,
), ),
themeMode: ThemeMode.system, themeMode: ThemeMode.system,
home: AppRoot(),
); );
}, },
); );
@@ -106,7 +129,8 @@ class MyApp extends StatelessWidget {
} }
class MyHomePage extends StatefulWidget { class MyHomePage extends StatefulWidget {
const MyHomePage({super.key}); final Widget child;
const MyHomePage({super.key, required this.child});
// This widget is the home page of your application. It is stateful, meaning // 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 // that it has a State object (defined below) that contains fields that affect
@@ -123,12 +147,12 @@ class MyHomePage extends StatefulWidget {
class _MyHomePageState extends State<MyHomePage> { class _MyHomePageState extends State<MyHomePage> {
int pageIndex = 0; int pageIndex = 0;
final List<Widget> contentPages = [ final List<String> contentPages = [
Dashboard(), "/",
CalculatorPage(), "/calculator",
LegsPage(), "/legs",
TractionPage(), "/traction",
Center(child: Text("Trips Page")), "/",
]; ];
bool loggedIn = false; bool loggedIn = false;
@@ -165,7 +189,7 @@ class _MyHomePageState extends State<MyHomePage> {
final auth = context.read<AuthService>(); final auth = context.read<AuthService>();
if (data.homepageStats != null) { if (data.homepageStats != null) {
currentPage = contentPages[pageIndex]; currentPage = widget.child;
} else { } else {
currentPage = Center(child: CircularProgressIndicator()); currentPage = Center(child: CircularProgressIndicator());
} }
@@ -210,6 +234,7 @@ class _MyHomePageState extends State<MyHomePage> {
onDestinationSelected: (int index) { onDestinationSelected: (int index) {
setState(() { setState(() {
pageIndex = index; pageIndex = index;
context.push(contentPages[index]);
}); });
}, },
destinations: [ destinations: [

View File

@@ -23,6 +23,8 @@ class DataService extends ChangeNotifier {
List<Station>? _cachedStations; List<Station>? _cachedStations;
DateTime? _stationsFetchedAt; DateTime? _stationsFetchedAt;
List<String> stations = [""];
bool _isHomepageLoading = false; bool _isHomepageLoading = false;
bool get isHomepageLoading => _isHomepageLoading; bool get isHomepageLoading => _isHomepageLoading;

View File

@@ -83,6 +83,19 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
go_router:
dependency: "direct main"
description:
name: go_router
sha256: c489908a54ce2131f1d1b7cc631af9c1a06fac5ca7c449e959192089f9489431
url: "https://pub.dev"
source: hosted
version: "16.0.0"
http: http:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -131,6 +144,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.0" version: "6.0.0"
logging:
dependency: transitive
description:
name: logging
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
url: "https://pub.dev"
source: hosted
version: "1.3.0"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
@@ -266,4 +287,4 @@ packages:
version: "1.1.1" version: "1.1.1"
sdks: sdks:
dart: ">=3.8.1 <4.0.0" dart: ">=3.8.1 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54" flutter: ">=3.27.0"

View File

@@ -37,6 +37,7 @@ dependencies:
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8 cupertino_icons: ^1.0.8
go_router: ^16.0.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: