Add version files and update imports for trip model; enhance error handling

This commit is contained in:
ImBenji
2026-03-27 21:17:56 +00:00
parent e41e14e252
commit 427bcadc77
89 changed files with 9455 additions and 395 deletions
+24
View File
@@ -0,0 +1,24 @@
class Duty {
const Duty({required this.dutyNumber, required this.busWorkNumber});
final String dutyNumber;
final String busWorkNumber;
Duty copyWith({String? dutyNumber, String? busWorkNumber}) {
return Duty(
dutyNumber: dutyNumber ?? this.dutyNumber,
busWorkNumber: busWorkNumber ?? this.busWorkNumber,
);
}
Map<String, dynamic> toJson() {
return {"dutyNumber": dutyNumber, "busWorkNumber": busWorkNumber};
}
factory Duty.fromJson(Map<String, dynamic> json) {
return Duty(
dutyNumber: (json["dutyNumber"] ?? "").toString(),
busWorkNumber: (json["busWorkNumber"] ?? "").toString(),
);
}
}
+58
View File
@@ -0,0 +1,58 @@
import "package:bus_running_record/models/operations/stop.dart";
class ScheduledStop extends Stop {
const ScheduledStop({
required super.name,
required this.sequence,
this.scheduledTime,
super.alias,
super.aliasSource,
});
final int sequence;
final String? scheduledTime;
ScheduledStop copyWith({
String? name,
String? alias,
String? aliasSource,
int? sequence,
String? scheduledTime,
}) {
return ScheduledStop(
name: name ?? this.name,
alias: alias ?? this.alias,
aliasSource: aliasSource ?? this.aliasSource,
sequence: sequence ?? this.sequence,
scheduledTime: scheduledTime ?? this.scheduledTime,
);
}
Map<String, dynamic> toJson() {
return {
"name": name,
"alias": alias,
"aliasSource": aliasSource,
"sequence": sequence,
"scheduledTime": scheduledTime,
};
}
factory ScheduledStop.fromJson(Map<String, dynamic> json) {
final raw = (json["scheduledTime"] ?? "").toString().trim();
return ScheduledStop(
name: (json["name"] ?? "").toString(),
alias: (json["alias"] ?? "").toString().trim().isEmpty
? null
: (json["alias"] ?? "").toString(),
aliasSource: (json["aliasSource"] ?? json["source"] ?? "")
.toString()
.trim()
.isEmpty
? null
: (json["aliasSource"] ?? json["source"] ?? "").toString(),
sequence: (json["sequence"] as num?)?.toInt() ?? 0,
scheduledTime: raw.isEmpty ? null : raw,
);
}
}
+15
View File
@@ -0,0 +1,15 @@
class Stop {
const Stop({required this.name, this.alias, this.aliasSource});
final String name;
final String? alias;
final String? aliasSource;
String get displayName {
final value = (alias ?? "").trim();
if (value.isEmpty) return name;
return value;
}
static String normalizeName(String name) => name.trim().toLowerCase();
}
+206
View File
@@ -0,0 +1,206 @@
import "package:bus_running_record/models/operations/duty.dart";
import "package:bus_running_record/models/operations/scheduled_stop.dart";
import "package:bus_running_record/models/operations/stop.dart";
class Trip {
final String scheduledTime; // "15:31" - default departure time
final String tripNumber; // "112"
final Duty duty;
final String tripType; // "", "N", "R", "F"
final bool isFinishing;
final List<ScheduledStop> scheduledStops;
final String direction; // "outbound" or "inbound"
String? actualDepartureTime; // "15:33" (nullable - user input)
String? actualFleetNumber; // "33523" (nullable - user input)
Trip({
required this.scheduledTime,
required this.tripNumber,
String? dutyNumber,
String? busWorkNumber,
Duty? duty,
this.tripType = "",
this.isFinishing = false,
Map<String, String> stationTimes = const {},
List<String> stationOrder = const [],
List<ScheduledStop>? scheduledStops,
this.direction = "outbound",
this.actualDepartureTime,
this.actualFleetNumber,
}) : duty =
duty ??
Duty(
dutyNumber: dutyNumber ?? "",
busWorkNumber: busWorkNumber ?? "",
),
scheduledStops =
scheduledStops ?? _buildScheduledStops(stationTimes, stationOrder);
bool get isComplete =>
actualDepartureTime != null && actualFleetNumber != null;
String get dutyNumber => duty.dutyNumber;
String get busWorkNumber => duty.busWorkNumber;
List<String> get stationOrder =>
scheduledStops.map((stop) => stop.name).toList(growable: false);
Map<String, String> get stationTimes {
final result = <String, String>{};
for (final stop in scheduledStops) {
final time = (stop.scheduledTime ?? "").trim();
if (time.isEmpty) continue;
result[stop.name] = time;
}
return result;
}
Trip copyWith({
String? scheduledTime,
String? tripNumber,
String? dutyNumber,
String? busWorkNumber,
Duty? duty,
String? tripType,
bool? isFinishing,
Map<String, String>? stationTimes,
List<String>? stationOrder,
List<ScheduledStop>? scheduledStops,
String? direction,
String? actualDepartureTime,
String? actualFleetNumber,
}) {
return Trip(
scheduledTime: scheduledTime ?? this.scheduledTime,
tripNumber: tripNumber ?? this.tripNumber,
duty:
duty ??
this.duty.copyWith(
dutyNumber: dutyNumber,
busWorkNumber: busWorkNumber,
),
tripType: tripType ?? this.tripType,
isFinishing: isFinishing ?? this.isFinishing,
scheduledStops:
scheduledStops ??
(stationTimes != null || stationOrder != null
? _buildScheduledStops(
stationTimes ?? this.stationTimes,
stationOrder ?? this.stationOrder,
)
: this.scheduledStops),
direction: direction ?? this.direction,
actualDepartureTime: actualDepartureTime ?? this.actualDepartureTime,
actualFleetNumber: actualFleetNumber ?? this.actualFleetNumber,
);
}
Trip withStopAliases(
Map<String, String> aliasesByRawName, {
String aliasSource = "ai",
}) {
if (aliasesByRawName.isEmpty) return this;
final aliasesByNormalizedName = <String, String>{};
for (final entry in aliasesByRawName.entries) {
aliasesByNormalizedName[Stop.normalizeName(entry.key)] = entry.value;
}
return copyWith(
scheduledStops: scheduledStops
.map(
(stop) => stop.copyWith(
alias: aliasesByNormalizedName[Stop.normalizeName(stop.name)],
aliasSource:
aliasesByNormalizedName[Stop.normalizeName(stop.name)] == null
? stop.aliasSource
: aliasSource,
),
)
.toList(growable: false),
);
}
Map<String, dynamic> toJson() {
return {
"scheduledTime": scheduledTime,
"tripNumber": tripNumber,
"duty": duty.toJson(),
"dutyNumber": dutyNumber,
"busWorkNumber": busWorkNumber,
"tripType": tripType,
"isFinishing": isFinishing,
"scheduledStops": scheduledStops.map((stop) => stop.toJson()).toList(),
"stationTimes": stationTimes,
"stationOrder": stationOrder,
"direction": direction,
"actualDepartureTime": actualDepartureTime,
"actualFleetNumber": actualFleetNumber,
};
}
factory Trip.fromJson(Map<String, dynamic> json) {
final dutyJson = json["duty"];
final duty = dutyJson is Map<String, dynamic>
? Duty.fromJson(dutyJson)
: Duty(
dutyNumber: (json["dutyNumber"] ?? "").toString(),
busWorkNumber:
(json["busWorkNumber"] ?? json["runningNumber"] ?? "")
.toString(),
);
final scheduledStopsJson = json["scheduledStops"];
final scheduledStops = scheduledStopsJson is List
? scheduledStopsJson
.whereType<Map>()
.map(
(stopJson) =>
ScheduledStop.fromJson(Map<String, dynamic>.from(stopJson)),
)
.toList()
: null;
return Trip(
scheduledTime: json["scheduledTime"] as String,
tripNumber: json["tripNumber"] as String,
duty: duty,
tripType: json["tripType"] as String? ?? "",
isFinishing: json["isFinishing"] as bool? ?? false,
scheduledStops: scheduledStops,
stationTimes: Map<String, String>.from(
json["stationTimes"] as Map? ?? {},
),
stationOrder: List<String>.from(json["stationOrder"] as List? ?? []),
direction: json["direction"] as String? ?? "outbound",
actualDepartureTime: json["actualDepartureTime"] as String?,
actualFleetNumber: json["actualFleetNumber"] as String?,
);
}
static List<ScheduledStop> _buildScheduledStops(
Map<String, String> stationTimes,
List<String> stationOrder,
) {
if (stationOrder.isEmpty) {
var index = 0;
return stationTimes.entries
.map(
(entry) => ScheduledStop(
name: entry.key,
sequence: ++index,
scheduledTime: entry.value.trim().isEmpty ? null : entry.value,
),
)
.toList(growable: false);
}
return List<ScheduledStop>.generate(stationOrder.length, (i) {
final stopName = stationOrder[i];
final time = (stationTimes[stopName] ?? "").trim();
return ScheduledStop(
name: stopName,
sequence: i + 1,
scheduledTime: time.isEmpty ? null : time,
);
}, growable: false);
}
}