support new fields in adding
All checks were successful
Release / meta (push) Successful in 9s
Release / linux-build (push) Successful in 6m54s
Release / android-build (push) Successful in 23m30s
Release / release-master (push) Successful in 30s
Release / release-dev (push) Successful in 32s

This commit is contained in:
2025-12-31 18:23:37 +00:00
parent e1ad1ea685
commit 1c15546b66
9 changed files with 1302 additions and 252 deletions

View File

@@ -1,3 +1,5 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
@@ -20,6 +22,35 @@ String _asString(dynamic value, [String fallback = '']) {
return (str == null) ? fallback : str;
}
List<String> _asStringList(dynamic value) {
if (value is List) {
return value.map((e) => e.toString()).toList();
}
final trimmed = value?.toString().trim() ?? '';
if (trimmed.isEmpty) return const [];
try {
final decoded = jsonDecode(trimmed);
if (decoded is List) {
return decoded.map((e) => e.toString()).toList();
}
} catch (_) {}
if (trimmed.contains('->')) {
return trimmed
.split('->')
.map((e) => e.trim())
.where((e) => e.isNotEmpty)
.toList();
}
if (trimmed.contains(',')) {
return trimmed
.split(',')
.map((e) => e.trim())
.where((e) => e.isNotEmpty)
.toList();
}
return [trimmed];
}
bool _asBool(dynamic value, [bool fallback = false]) {
if (value is bool) return value;
if (value is num) return value != 0;
@@ -487,25 +518,45 @@ class TripSummary {
final int tripId;
final String tripName;
final double tripMileage;
final int legCount;
final List<TripLocoStat> locoStats;
int get locoHadCount => locoStats.length;
int get winnersCount => locoStats.where((e) => e.won).length;
TripSummary({
required this.tripId,
required this.tripName,
required this.tripMileage,
});
this.legCount = 0,
List<TripLocoStat>? locoStats,
}) : locoStats = locoStats ?? const [];
factory TripSummary.fromJson(Map<String, dynamic> json) => TripSummary(
tripId: _asInt(json['trip_id']),
tripName: _asString(json['trip_name']),
tripMileage: _asDouble(json['trip_mileage']),
legCount: _asInt(
json['leg_count'],
(json['trip_legs'] as List?)?.length ?? 0,
),
locoStats: TripLocoStat.listFromJson(
json['stats'] ?? json['trip_locos'] ?? json['locos'],
),
);
}
class Leg {
final int id, tripId, timezone, driving;
final String start, end, route, network, notes, headcode, user;
final String start, end, network, notes, headcode, user;
final String origin, destination;
final List<String> route;
final DateTime beginTime;
final DateTime? endTime;
final DateTime? originTime;
final DateTime? destinationTime;
final double mileage;
final int? beginDelayMinutes, endDelayMinutes;
final List<Loco> locos;
Leg({
@@ -523,27 +574,55 @@ class Leg {
required this.driving,
required this.user,
required this.locos,
this.endTime,
this.originTime,
this.destinationTime,
this.beginDelayMinutes,
this.endDelayMinutes,
this.origin = '',
this.destination = '',
});
factory Leg.fromJson(Map<String, dynamic> json) => Leg(
id: _asInt(json['leg_id']),
tripId: _asInt(json['leg_trip']),
start: _asString(json['leg_start']),
end: _asString(json['leg_end']),
beginTime: _asDateTime(json['leg_begin_time']),
timezone: _asInt(json['leg_timezone']),
network: _asString(json['leg_network']),
route: _asString(json['leg_route']),
mileage: _asDouble(json['leg_mileage']),
notes: _asString(json['leg_notes']),
headcode: _asString(json['leg_headcode']),
driving: _asInt(json['leg_driving']),
user: _asString(json['leg_user']),
locos: (json['locos'] is List ? (json['locos'] as List) : const [])
.whereType<Map>()
.map((e) => Loco.fromJson(Map<String, dynamic>.from(e)))
.toList(),
);
factory Leg.fromJson(Map<String, dynamic> json) {
final endTimeRaw = json['leg_end_time'];
final parsedEndTime = (endTimeRaw == null || '$endTimeRaw'.isEmpty)
? null
: _asDateTime(endTimeRaw);
return Leg(
id: _asInt(json['leg_id']),
tripId: _asInt(json['leg_trip']),
start: _asString(json['leg_start']),
end: _asString(json['leg_end']),
beginTime: _asDateTime(json['leg_begin_time']),
endTime: parsedEndTime,
originTime: json['leg_origin_time'] == null
? null
: _asDateTime(json['leg_origin_time']),
destinationTime: json['leg_destination_time'] == null
? null
: _asDateTime(json['leg_destination_time']),
timezone: _asInt(json['leg_timezone']),
network: _asString(json['leg_network']),
route: _asStringList(json['leg_route']),
mileage: _asDouble(json['leg_mileage']),
notes: _asString(json['leg_notes']),
headcode: _asString(json['leg_headcode']),
driving: _asInt(json['leg_driving']),
user: _asString(json['leg_user']),
locos: (json['locos'] is List ? (json['locos'] as List) : const [])
.whereType<Map>()
.map((e) => Loco.fromJson(Map<String, dynamic>.from(e)))
.toList(),
beginDelayMinutes: json['leg_begin_delay'] == null
? null
: _asInt(json['leg_begin_delay']),
endDelayMinutes: json['leg_end_delay'] == null
? null
: _asInt(json['leg_end_delay']),
origin: _asString(json['leg_origin']),
destination: _asString(json['leg_destination']),
);
}
}
class RouteError {
@@ -625,17 +704,23 @@ class TripLeg {
});
factory TripLeg.fromJson(Map<String, dynamic> json) => TripLeg(
id: json['leg_id'],
start: json['leg_start'] ?? '',
end: json['leg_end'] ?? '',
id: _asInt(json['leg_id']),
start: _asString(json['leg_start']),
end: _asString(json['leg_end']),
beginTime:
json['leg_begin_time'] != null && json['leg_begin_time'] is String
? DateTime.tryParse(json['leg_begin_time'])
: (json['leg_begin_time'] is DateTime ? json['leg_begin_time'] : null),
network: json['leg_network'],
route: json['leg_route'],
network: _asString(json['leg_network'], ''),
route: () {
final route = json['leg_route'];
if (route is List) {
return route.whereType<String>().join('');
}
return _asString(route, '');
}(),
mileage: (json['leg_mileage'] as num?)?.toDouble(),
notes: json['leg_notes'],
notes: _asString(json['leg_notes'], ''),
locos:
(json['locos'] as List?)
?.map((e) => Loco.fromJson(e as Map<String, dynamic>))
@@ -649,21 +734,32 @@ class TripDetail {
final String name;
final double mileage;
final int legCount;
final List<TripLocoStat> locoStats;
final List<TripLeg> legs;
int get locoHadCount => locoStats.length;
int get winnersCount => locoStats.where((e) => e.won).length;
TripDetail({
required this.id,
required this.name,
required this.mileage,
required this.legCount,
required this.legs,
});
List<TripLocoStat>? locoStats,
}) : locoStats = locoStats ?? const [];
factory TripDetail.fromJson(Map<String, dynamic> json) => TripDetail(
id: json['trip_id'] ?? json['id'] ?? 0,
name: json['trip_name'] ?? '',
mileage: (json['trip_mileage'] as num?)?.toDouble() ?? 0,
legCount: json['leg_count'] ?? ((json['trip_legs'] as List?)?.length ?? 0),
legCount: _asInt(
json['leg_count'],
(json['trip_legs'] as List?)?.length ?? 0,
),
locoStats: TripLocoStat.listFromJson(
json['stats'] ?? json['trip_locos'] ?? json['locos'],
),
legs:
(json['trip_legs'] as List?)
?.map((e) => TripLeg.fromJson(e as Map<String, dynamic>))
@@ -712,6 +808,26 @@ class TripLocoStat {
);
}
static List<TripLocoStat> listFromJson(dynamic json) {
List<dynamic>? list;
if (json is List) {
list = json.expand((e) => e is List ? e : [e]).toList();
} else if (json is Map) {
for (final key in ['locos', 'stats', 'data', 'trip_locos']) {
final candidate = json[key];
if (candidate is List) {
list = candidate.expand((e) => e is List ? e : [e]).toList();
break;
}
}
}
if (list == null) return const [];
return list
.whereType<Map>()
.map((e) => TripLocoStat.fromJson(Map<String, dynamic>.from(e)))
.toList();
}
static bool _parseWonFlag(dynamic value) {
if (value == null) return false;
if (value is bool) return value;