implemented RMV public transit module including trip search, station departures, and nearby stop lookup, added "Marianum Connect" API integration with bearer token authentication and auto-refresh logic, integrated geolocator for location-based station search, added persistent storage for favorite stations and recent trip queries, and implemented comprehensive UI for journey details, trip results, and disruption alerts
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
/// Formats a [DateTime] as `2026-05-19T14:30:00` for Java's
|
||||
/// `LocalDateTime` parser (no timezone, no millis).
|
||||
String formatLocalDateTime(DateTime dt) {
|
||||
String two(int v) => v.toString().padLeft(2, '0');
|
||||
return '${dt.year}-${two(dt.month)}-${two(dt.day)}T'
|
||||
'${two(dt.hour)}:${two(dt.minute)}:${two(dt.second)}';
|
||||
}
|
||||
|
||||
/// Formats a [DateTime] as `2026-05-19` for Java's `LocalDate` parser.
|
||||
String formatLocalDate(DateTime dt) {
|
||||
String two(int v) => v.toString().padLeft(2, '0');
|
||||
return '${dt.year}-${two(dt.month)}-${two(dt.day)}';
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import '../../connect_api.dart';
|
||||
import '../rmv_models.dart';
|
||||
import '_query_format.dart';
|
||||
|
||||
class GetArrivals extends ConnectApi<List<Arrival>> {
|
||||
final String stopId;
|
||||
final DateTime? when;
|
||||
final int durationMinutes;
|
||||
final int maxJourneys;
|
||||
|
||||
GetArrivals({
|
||||
required this.stopId,
|
||||
this.when,
|
||||
this.durationMinutes = 60,
|
||||
this.maxJourneys = -1,
|
||||
}) : super('rmv/arrivals');
|
||||
|
||||
@override
|
||||
Map<String, String>? get queryParameters => {
|
||||
'stopId': stopId,
|
||||
if (when != null) 'when': formatLocalDateTime(when!),
|
||||
'duration': durationMinutes.toString(),
|
||||
'max': maxJourneys.toString(),
|
||||
};
|
||||
|
||||
@override
|
||||
List<Arrival> assemble(String raw) => (jsonDecode(raw) as List)
|
||||
.map((e) => Arrival.fromJson(e as Map<String, dynamic>))
|
||||
.toList(growable: false);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import '../../connect_api.dart';
|
||||
import '../rmv_models.dart';
|
||||
import '_query_format.dart';
|
||||
|
||||
class GetDepartures extends ConnectApi<List<Departure>> {
|
||||
final String stopId;
|
||||
final DateTime? when;
|
||||
final int durationMinutes;
|
||||
final int maxJourneys;
|
||||
|
||||
GetDepartures({
|
||||
required this.stopId,
|
||||
this.when,
|
||||
this.durationMinutes = 60,
|
||||
this.maxJourneys = -1,
|
||||
}) : super('rmv/departures');
|
||||
|
||||
@override
|
||||
Map<String, String>? get queryParameters => {
|
||||
'stopId': stopId,
|
||||
if (when != null) 'when': formatLocalDateTime(when!),
|
||||
'duration': durationMinutes.toString(),
|
||||
'max': maxJourneys.toString(),
|
||||
};
|
||||
|
||||
@override
|
||||
List<Departure> assemble(String raw) => (jsonDecode(raw) as List)
|
||||
.map((e) => Departure.fromJson(e as Map<String, dynamic>))
|
||||
.toList(growable: false);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import '../../connect_api.dart';
|
||||
import '../rmv_models.dart';
|
||||
import '_query_format.dart';
|
||||
|
||||
class GetDisruptions extends ConnectApi<List<HimMessage>> {
|
||||
final DateTime? when;
|
||||
|
||||
GetDisruptions({this.when}) : super('rmv/disruptions');
|
||||
|
||||
@override
|
||||
Map<String, String>? get queryParameters =>
|
||||
when == null ? null : {'when': formatLocalDateTime(when!)};
|
||||
|
||||
@override
|
||||
List<HimMessage> assemble(String raw) => (jsonDecode(raw) as List)
|
||||
.map((e) => HimMessage.fromJson(e as Map<String, dynamic>))
|
||||
.toList(growable: false);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import '../../connect_api.dart';
|
||||
import '../rmv_models.dart';
|
||||
import '_query_format.dart';
|
||||
|
||||
class GetJourneyDetail extends ConnectApi<JourneyDetail> {
|
||||
final String journeyRef;
|
||||
final DateTime? date;
|
||||
|
||||
GetJourneyDetail({required this.journeyRef, this.date})
|
||||
: super('rmv/journey');
|
||||
|
||||
@override
|
||||
Map<String, String>? get queryParameters => {
|
||||
'ref': journeyRef,
|
||||
if (date != null) 'date': formatLocalDate(date!),
|
||||
};
|
||||
|
||||
@override
|
||||
JourneyDetail assemble(String raw) =>
|
||||
JourneyDetail.fromJson(jsonDecode(raw) as Map<String, dynamic>);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import '../../connect_api.dart';
|
||||
import '../rmv_models.dart';
|
||||
|
||||
class MoreTrips extends ConnectApi<TripSearchResult> {
|
||||
final String ctx;
|
||||
|
||||
MoreTrips({required this.ctx}) : super('rmv/trips/more');
|
||||
|
||||
@override
|
||||
Map<String, String>? get queryParameters => {'ctx': ctx};
|
||||
|
||||
@override
|
||||
TripSearchResult assemble(String raw) =>
|
||||
TripSearchResult.fromJson(jsonDecode(raw) as Map<String, dynamic>);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import '../../connect_api.dart';
|
||||
import '../rmv_models.dart';
|
||||
|
||||
class NearbyStops extends ConnectApi<List<StopLocation>> {
|
||||
final double lat;
|
||||
final double lon;
|
||||
final int radiusMeters;
|
||||
final int max;
|
||||
|
||||
NearbyStops({
|
||||
required this.lat,
|
||||
required this.lon,
|
||||
this.radiusMeters = 1000,
|
||||
this.max = 20,
|
||||
}) : super('rmv/stops/nearby');
|
||||
|
||||
@override
|
||||
Map<String, String>? get queryParameters => {
|
||||
'lat': lat.toString(),
|
||||
'lon': lon.toString(),
|
||||
'radius': radiusMeters.toString(),
|
||||
'max': max.toString(),
|
||||
};
|
||||
|
||||
@override
|
||||
List<StopLocation> assemble(String raw) => (jsonDecode(raw) as List)
|
||||
.map((e) => StopLocation.fromJson(e as Map<String, dynamic>))
|
||||
.toList(growable: false);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import '../../connect_api.dart';
|
||||
import '../rmv_models.dart';
|
||||
|
||||
class SearchStops extends ConnectApi<List<StopLocation>> {
|
||||
final String query;
|
||||
final int max;
|
||||
|
||||
SearchStops({required this.query, this.max = 10}) : super('rmv/stops');
|
||||
|
||||
@override
|
||||
Map<String, String>? get queryParameters => {
|
||||
'q': query,
|
||||
'max': max.toString(),
|
||||
};
|
||||
|
||||
@override
|
||||
List<StopLocation> assemble(String raw) => (jsonDecode(raw) as List)
|
||||
.map((e) => StopLocation.fromJson(e as Map<String, dynamic>))
|
||||
.toList(growable: false);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import '../../connect_api.dart';
|
||||
import '../rmv_models.dart';
|
||||
import '_query_format.dart';
|
||||
|
||||
class SearchTrips extends ConnectApi<TripSearchResult> {
|
||||
final String fromStopId;
|
||||
final String toStopId;
|
||||
final DateTime? when;
|
||||
final bool searchByArrival;
|
||||
|
||||
SearchTrips({
|
||||
required this.fromStopId,
|
||||
required this.toStopId,
|
||||
this.when,
|
||||
this.searchByArrival = false,
|
||||
}) : super('rmv/trips');
|
||||
|
||||
@override
|
||||
Map<String, String>? get queryParameters => {
|
||||
'from': fromStopId,
|
||||
'to': toStopId,
|
||||
if (when != null) 'when': formatLocalDateTime(when!),
|
||||
'searchByArrival': searchByArrival.toString(),
|
||||
};
|
||||
|
||||
@override
|
||||
TripSearchResult assemble(String raw) =>
|
||||
TripSearchResult.fromJson(jsonDecode(raw) as Map<String, dynamic>);
|
||||
}
|
||||
Reference in New Issue
Block a user