Add display of legs and top traction, adjust colour based on device theme
This commit is contained in:
@@ -8,7 +8,7 @@ plugins {
|
||||
android {
|
||||
namespace = "com.example.mileograph_sbb_flutter"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
ndkVersion = flutter.ndkVersion
|
||||
ndkVersion = "27.0.12077973"
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
|
||||
59
lib/components/dashboard/topTractionPanel.dart
Normal file
59
lib/components/dashboard/topTractionPanel.dart
Normal file
@@ -0,0 +1,59 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mileograph_flutter/services/dataService.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class TopTractionPanel extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final data = context.watch<DataService>();
|
||||
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(
|
||||
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'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
],
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mileograph_flutter/services/authservice.dart';
|
||||
import 'package:mileograph_flutter/services/dataService.dart';
|
||||
import 'package:mileograph_flutter/components/dashboard/topTractionPanel.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
@@ -22,36 +23,70 @@ class DashboardHeader extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
return Column(
|
||||
children: [
|
||||
Column(
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
auth.fullName ?? "Unknown",
|
||||
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
||||
),
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(text: "Total Mileage: "),
|
||||
TextSpan(
|
||||
text: data.homepageStats?.totalMileage.toString() ?? "0",
|
||||
Row(
|
||||
children: [
|
||||
Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(text: "Total Mileage: "),
|
||||
TextSpan(
|
||||
text:
|
||||
data.homepageStats?.totalMileage
|
||||
.toString() ??
|
||||
"0",
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(text: DateTime.now().year.toString()),
|
||||
TextSpan(text: " Mileage: "),
|
||||
TextSpan(
|
||||
text: data
|
||||
.getMileageForCurrentYear()
|
||||
.toString(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(text: DateTime.now().year.toString()),
|
||||
TextSpan(text: " Mileage: "),
|
||||
TextSpan(text: data.getMileageForCurrentYear().toString()),
|
||||
],
|
||||
),
|
||||
),
|
||||
Card(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: Column(
|
||||
children: [
|
||||
Text("Total Winners: 123"),
|
||||
Text("Average mileage: 45.6"),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
Expanded(
|
||||
child: ListView(
|
||||
scrollDirection: Axis.vertical,
|
||||
children: [TopTractionPanel()],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
31
lib/components/pages/legs.dart
Normal file
31
lib/components/pages/legs.dart
Normal file
@@ -0,0 +1,31 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:mileograph_flutter/services/dataService.dart';
|
||||
|
||||
|
||||
class LegsPage extends StatelessWidget {
|
||||
Widget build(BuildContext context){
|
||||
final data = context.watch<DataService>();
|
||||
return ListView.builder(
|
||||
itemCount: data.legs.length,
|
||||
itemBuilder: (context, index) {
|
||||
final leg = data.legs[index];
|
||||
return Card(
|
||||
margin: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('${leg.start} → ${leg.end}', style: TextStyle(fontSize: 16)),
|
||||
Text('Mileage: ${leg.mileage.toStringAsFixed(2)} km'),
|
||||
Text('Headcode: ${leg.headcode}'),
|
||||
Text('Begin: ${leg.beginTime}'),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,15 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:dynamic_color/dynamic_color.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 'package:provider/provider.dart';
|
||||
import 'components/login/login.dart';
|
||||
import 'components/pages/dashboard.dart';
|
||||
import 'components/dashboard/topTractionPanel.dart';
|
||||
|
||||
late ApiService api;
|
||||
|
||||
@@ -54,41 +58,47 @@ class AppRoot extends StatelessWidget {
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
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) {
|
||||
return MaterialApp(
|
||||
title: 'Flutter Demo',
|
||||
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: ColorScheme.fromSeed(seedColor: Colors.blueGrey),
|
||||
),
|
||||
darkTheme: ThemeData(
|
||||
useMaterial3: true,
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: Colors.blueGrey,
|
||||
brightness: Brightness.dark,
|
||||
),
|
||||
),
|
||||
themeMode: ThemeMode.system,
|
||||
home: AppRoot(),
|
||||
return DynamicColorBuilder(
|
||||
builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) {
|
||||
return MaterialApp(
|
||||
title: 'Flutter Demo',
|
||||
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,
|
||||
home: AppRoot(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -114,7 +124,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
final List<Widget> contentPages = [
|
||||
Dashboard(),
|
||||
Center(child: Text("Calculator Page")),
|
||||
Center(child: Text("Entries Page")),
|
||||
LegsPage(),
|
||||
Center(child: Text("Traction Page")),
|
||||
Center(child: Text("Trips Page")),
|
||||
];
|
||||
@@ -135,6 +145,9 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
if (data.homepageStats == null) {
|
||||
data.fetchHomepageStats();
|
||||
}
|
||||
if (data.legs.isEmpty) {
|
||||
data.fetchLegs();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -152,7 +165,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
currentPage = Center(
|
||||
child: FilledButton(
|
||||
onPressed: data.fetchHomepageStats,
|
||||
child: Text("Fetch"),
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -84,8 +84,9 @@ class YearlyMileage {
|
||||
class LocoSummary {
|
||||
final String locoType, locoClass, locoNumber, locoName, locoOperator;
|
||||
final String? locoNotes, locoEvn;
|
||||
final int locoId, locoJourneys;
|
||||
final double locoMileage;
|
||||
final int locoId;
|
||||
final int? locoJourneys;
|
||||
final double? locoMileage;
|
||||
|
||||
LocoSummary({
|
||||
required this.locoType,
|
||||
@@ -96,8 +97,8 @@ class LocoSummary {
|
||||
this.locoNotes,
|
||||
this.locoEvn,
|
||||
required this.locoId,
|
||||
required this.locoMileage,
|
||||
required this.locoJourneys,
|
||||
this.locoMileage,
|
||||
this.locoJourneys,
|
||||
});
|
||||
|
||||
factory LocoSummary.fromJson(Map<String, dynamic> json) => LocoSummary(
|
||||
@@ -151,3 +152,76 @@ class TripSummary {
|
||||
tripMileage: (json['trip_mileage'] as num).toDouble(),
|
||||
);
|
||||
}
|
||||
|
||||
class Loco {
|
||||
final int id;
|
||||
final String type, number, name, locoClass, operator;
|
||||
final String? notes, evn;
|
||||
|
||||
Loco({
|
||||
required this.id,
|
||||
required this.type,
|
||||
required this.number,
|
||||
required this.name,
|
||||
required this.locoClass,
|
||||
required this.operator,
|
||||
this.notes,
|
||||
this.evn,
|
||||
});
|
||||
|
||||
factory Loco.fromJson(Map<String, dynamic> json) => Loco(
|
||||
id: json['loco_id'],
|
||||
type: json['loco_type'],
|
||||
number: json['loco_number'],
|
||||
name: json['loco_name'] ?? "",
|
||||
locoClass: json['loco_class'],
|
||||
operator: json['loco_operator'],
|
||||
notes: json['loco_notes'],
|
||||
evn: json['loco_evn'],
|
||||
);
|
||||
}
|
||||
|
||||
class Leg {
|
||||
final int id, tripId, timezone, driving;
|
||||
final String start, end, route, network, notes, headcode, user;
|
||||
final DateTime beginTime;
|
||||
final double mileage;
|
||||
final List<Loco> locos;
|
||||
|
||||
Leg({
|
||||
required this.id,
|
||||
required this.tripId,
|
||||
required this.start,
|
||||
required this.end,
|
||||
required this.beginTime,
|
||||
required this.timezone,
|
||||
required this.network,
|
||||
required this.route,
|
||||
required this.mileage,
|
||||
required this.notes,
|
||||
required this.headcode,
|
||||
required this.driving,
|
||||
required this.user,
|
||||
required this.locos,
|
||||
});
|
||||
|
||||
factory Leg.fromJson(Map<String, dynamic> json) => Leg(
|
||||
id: json['leg_id'],
|
||||
tripId: json['leg_trip'] ?? 0,
|
||||
start: json['leg_start'],
|
||||
end: json['leg_end'],
|
||||
beginTime: DateTime.parse(json['leg_begin_time']),
|
||||
timezone: (json['leg_timezone'] as num).toInt(),
|
||||
network: json['leg_network'] ?? "",
|
||||
route: json['leg_route'],
|
||||
mileage: (json['leg_mileage'] as num).toDouble(),
|
||||
notes: json['leg_notes'] ?? "",
|
||||
headcode: json['leg_headcode'] ?? "",
|
||||
driving: json['leg_driving'],
|
||||
user: json['leg_user'],
|
||||
locos: (json['locos'] as List)
|
||||
.map((e) => Loco.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,9 @@ class DataService extends ChangeNotifier {
|
||||
HomepageStats? _homepageStats;
|
||||
HomepageStats? get homepageStats => _homepageStats;
|
||||
|
||||
List<Leg> _legs = [];
|
||||
List<Leg> get legs => _legs;
|
||||
|
||||
bool _isHomepageLoading = false;
|
||||
bool get isHomepageLoading => _isHomepageLoading;
|
||||
|
||||
@@ -29,6 +32,23 @@ class DataService extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> 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');
|
||||
}
|
||||
}
|
||||
|
||||
void clear() {
|
||||
_homepageStats = null;
|
||||
notifyListeners();
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <dynamic_color/dynamic_color_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) dynamic_color_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "DynamicColorPlugin");
|
||||
dynamic_color_plugin_register_with_registrar(dynamic_color_registrar);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
dynamic_color
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import dynamic_color
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin"))
|
||||
}
|
||||
|
||||
@@ -49,6 +49,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.8"
|
||||
dynamic_color:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dynamic_color
|
||||
sha256: eae98052fa6e2826bdac3dd2e921c6ce2903be15c6b7f8b6d8a5d49b5086298d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.7.0"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -32,6 +32,7 @@ dependencies:
|
||||
sdk: flutter
|
||||
http: ^1.4.0
|
||||
provider: ^6.1.5
|
||||
dynamic_color: ^1.6.6
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
|
||||
@@ -13,7 +13,7 @@ import 'package:mileograph_flutter/main.dart';
|
||||
void main() {
|
||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||
// Build our app and trigger a frame.
|
||||
await tester.pumpWidget(const MyApp());
|
||||
await tester.pumpWidget(MyApp());
|
||||
|
||||
// Verify that our counter starts at 0.
|
||||
expect(find.text('0'), findsOneWidget);
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <dynamic_color/dynamic_color_plugin_c_api.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
DynamicColorPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("DynamicColorPluginCApi"));
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
dynamic_color
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
||||
Reference in New Issue
Block a user