From 3bef606d419e1eb9388cf5176a331608b3c91118 Mon Sep 17 00:00:00 2001 From: petegregoryy Date: Fri, 25 Jul 2025 17:36:26 +0100 Subject: [PATCH] list traction and add leaderboard panel --- .../dashboard/leaderboardPanel.dart | 70 +++++++++++++ .../dashboard/topTractionPanel.dart | 99 +++++++++++-------- lib/components/pages/dashboard.dart | 3 +- lib/components/pages/traction.dart | 23 +++++ lib/main.dart | 13 ++- lib/objects/objects.dart | 3 +- lib/services/dataService.dart | 46 ++++++--- 7 files changed, 191 insertions(+), 66 deletions(-) create mode 100644 lib/components/dashboard/leaderboardPanel.dart create mode 100644 lib/components/pages/traction.dart diff --git a/lib/components/dashboard/leaderboardPanel.dart b/lib/components/dashboard/leaderboardPanel.dart new file mode 100644 index 0000000..6268f29 --- /dev/null +++ b/lib/components/dashboard/leaderboardPanel.dart @@ -0,0 +1,70 @@ +import 'package:flutter/material.dart'; +import 'package:mileograph_flutter/services/dataService.dart'; + +import 'package:provider/provider.dart'; + +class LeaderboardPanel extends StatelessWidget { + Widget build(BuildContext context) { + final data = context.watch(); + return Padding( + padding: const EdgeInsets.all(10.0), + child: Card( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Leaderboard", + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + decoration: TextDecoration.underline, + ), + ), + Column( + children: List.generate( + data.homepageStats?.leaderboard.length ?? 0, + (index) { + final leaderboardEntry = + data.homepageStats!.leaderboard[index]; + return Container( + width: double.infinity, + child: Container( + margin: EdgeInsets.symmetric(horizontal: 0, vertical: 8), + child: Padding( + padding: EdgeInsets.all(8), + + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text.rich( + TextSpan( + children: [ + TextSpan( + text: '${index + 1}. ', + style: TextStyle( + fontWeight: FontWeight.bold, + ), + ), + TextSpan( + text: '${leaderboardEntry.userFullName}', + ), + ], + ), + ), + Text( + '${leaderboardEntry.mileage.toStringAsFixed(1)} mi', + ), + ], + ), + ), + ), + ); + }, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/components/dashboard/topTractionPanel.dart b/lib/components/dashboard/topTractionPanel.dart index ee4de84..f400e22 100644 --- a/lib/components/dashboard/topTractionPanel.dart +++ b/lib/components/dashboard/topTractionPanel.dart @@ -8,52 +8,69 @@ class TopTractionPanel extends StatelessWidget { final data = context.watch(); return Padding( padding: const EdgeInsets.all(10.0), - child: Card( child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text("Top Traction", style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, decoration: TextDecoration.underline)), - Column( - children: List.generate( - data.homepageStats?.topLocos.length ?? 0, - (index) { - final loco = data.homepageStats!.topLocos[index]; - return Container( - width: double.infinity, - child: Card( - margin: EdgeInsets.symmetric(horizontal: 0, vertical: 8), - child: Padding( - padding: EdgeInsets.all(16), - - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text.rich( - TextSpan( + child: Card( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Top Traction", + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + decoration: TextDecoration.underline, + ), + ), + Column( + children: List.generate(data.homepageStats?.topLocos.length ?? 0, ( + index, + ) { + final loco = data.homepageStats!.topLocos[index]; + return Container( + width: double.infinity, + child: Container( + margin: EdgeInsets.symmetric(horizontal: 0, vertical: 8), + child: Padding( + padding: EdgeInsets.all(8), + + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - TextSpan(text: '${index + 1}. ', style: TextStyle(fontWeight: FontWeight.bold)), - TextSpan(text: '${loco.locoClass} ${loco.locoNumber}'), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text.rich( + TextSpan( + children: [ + TextSpan( + text: '${index + 1}. ', + style: TextStyle( + fontWeight: FontWeight.bold, + ), + ), + TextSpan( + text: + '${loco.locoClass} ${loco.locoNumber}', + ), + ], + ), + ), + Text( + '${loco.locoName}', + style: TextStyle(fontStyle: FontStyle.italic), + ), + ], + ), + Text('${loco.locoMileage?.toStringAsFixed(1)} mi'), ], ), ), - Text('${loco.locoName}', style: TextStyle(fontStyle: FontStyle.italic)), - - ], - ), - Text('${loco.locoMileage?.toStringAsFixed(1)} mi'), - ], + ), + ); + }), ), - ), + ], ), - ); - }, - ), -) - - ], - )), + ), ); } -} \ No newline at end of file +} diff --git a/lib/components/pages/dashboard.dart b/lib/components/pages/dashboard.dart index 416fcd1..0d40597 100644 --- a/lib/components/pages/dashboard.dart +++ b/lib/components/pages/dashboard.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:mileograph_flutter/components/dashboard/leaderboardPanel.dart'; import 'package:mileograph_flutter/services/authservice.dart'; import 'package:mileograph_flutter/services/dataService.dart'; import 'package:mileograph_flutter/components/dashboard/topTractionPanel.dart'; @@ -84,7 +85,7 @@ class DashboardHeader extends StatelessWidget { Expanded( child: ListView( scrollDirection: Axis.vertical, - children: [TopTractionPanel()], + children: [TopTractionPanel(), LeaderboardPanel()], ), ), ], diff --git a/lib/components/pages/traction.dart b/lib/components/pages/traction.dart new file mode 100644 index 0000000..4def901 --- /dev/null +++ b/lib/components/pages/traction.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:mileograph_flutter/objects/objects.dart'; +import 'package:provider/provider.dart'; +import 'package:mileograph_flutter/services/dataService.dart'; + +class TractionPage extends StatelessWidget { + Widget build(BuildContext context) { + final data = context.watch(); + return ListView.builder( + itemCount: data.traction.length, + itemBuilder: (context, index) { + final loco = data.traction[index]; + return Card( + margin: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Padding( + padding: EdgeInsets.all(16), + child: Text('${loco.locoClass} ${loco.locoNumber}'), + ), + ); + }, + ); + } +} diff --git a/lib/main.dart b/lib/main.dart index f06b37b..52ac872 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:dynamic_color/dynamic_color.dart'; +import 'package:mileograph_flutter/components/pages/traction.dart'; import 'package:provider/provider.dart'; import 'package:mileograph_flutter/components/pages/legs.dart'; @@ -125,7 +126,7 @@ class _MyHomePageState extends State { Dashboard(), Center(child: Text("Calculator Page")), LegsPage(), - Center(child: Text("Traction Page")), + TractionPage(), Center(child: Text("Trips Page")), ]; @@ -148,6 +149,9 @@ class _MyHomePageState extends State { if (data.legs.isEmpty) { data.fetchLegs(); } + if (data.traction.isEmpty) { + data.fetchHadTraction(); + } }); } } @@ -162,12 +166,7 @@ class _MyHomePageState extends State { if (data.homepageStats != null) { currentPage = contentPages[pageIndex]; } else { - currentPage = Center( - child: FilledButton( - onPressed: data.fetchHomepageStats, - child: CircularProgressIndicator(), - ), - ); + currentPage = Center(child: CircularProgressIndicator()); } // This method is rerun every time setState is called, for instance as done // by the _incrementCounter method above. diff --git a/lib/objects/objects.dart b/lib/objects/objects.dart index 7c53d58..e70bfe3 100644 --- a/lib/objects/objects.dart +++ b/lib/objects/objects.dart @@ -105,7 +105,7 @@ class LocoSummary { locoType: json['loco_type'], locoClass: json['loco_class'], locoNumber: json['loco_number'], - locoName: json['loco_name'], + locoName: json['loco_name'] ?? "", locoOperator: json['loco_operator'], locoNotes: json['loco_notes'], locoEvn: json['loco_evn'], @@ -224,4 +224,3 @@ class Leg { .toList(), ); } - diff --git a/lib/services/dataService.dart b/lib/services/dataService.dart index 92ca63c..d968fb8 100644 --- a/lib/services/dataService.dart +++ b/lib/services/dataService.dart @@ -13,6 +13,9 @@ class DataService extends ChangeNotifier { List _legs = []; List get legs => _legs; + List _traction = []; + List get traction => _traction; + bool _isHomepageLoading = false; bool get isHomepageLoading => _isHomepageLoading; @@ -31,23 +34,36 @@ class DataService extends ChangeNotifier { notifyListeners(); } } - - Future fetchLegs({ - int offset = 0, - int limit = 100, - String sortBy = 'date', - int sortDirection = 0, -}) async { - final query = '?sort_direction=$sortDirection&sort_by=$sortBy&offset=$offset&limit=$limit'; - final json = await api.get('/user/legs$query'); - if (json is List) { - _legs = json.map((e) => Leg.fromJson(e)).toList(); - notifyListeners(); - } else { - throw Exception('Unexpected legs response: $json'); + Future fetchLegs({ + int offset = 0, + int limit = 100, + String sortBy = 'date', + int sortDirection = 0, + }) async { + final query = + '?sort_direction=$sortDirection&sort_by=$sortBy&offset=$offset&limit=$limit'; + final json = await api.get('/user/legs$query'); + + if (json is List) { + _legs = json.map((e) => Leg.fromJson(e)).toList(); + notifyListeners(); + } else { + throw Exception('Unexpected legs response: $json'); + } + } + + Future fetchHadTraction({int offset = 0, int limit = 100}) async { + final query = '?offset=$offset&limit=$limit'; + final json = await api.get('/loco/mileage$query'); + + if (json is List) { + _traction = json.map((e) => LocoSummary.fromJson(e)).toList(); + notifyListeners(); + } else { + throw Exception('Unexpected traction response: $json'); + } } -} void clear() { _homepageStats = null;