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:
2026-05-20 19:08:05 +02:00
parent f185b3273a
commit 067012cc84
61 changed files with 7885 additions and 1 deletions
@@ -0,0 +1,79 @@
import 'package:flutter/material.dart';
import '../../../../extensions/date_time.dart';
/// Returns the "depart at / arrive by" time and the AB/AN-toggle. `null` for
/// [value] means "now" — the API treats an empty `when` parameter as the
/// current time.
class WhenPicker extends StatelessWidget {
final DateTime? value;
final bool byArrival;
final ValueChanged<DateTime?> onValueChanged;
final ValueChanged<bool> onByArrivalChanged;
const WhenPicker({
super.key,
required this.value,
required this.byArrival,
required this.onValueChanged,
required this.onByArrivalChanged,
});
@override
Widget build(BuildContext context) {
final label = value == null
? 'Jetzt'
: value!.formatDateRelativeShort() == 'Heute'
? value!.formatHm()
: '${value!.formatDateRelativeShort()} ${value!.formatHm()}';
return Row(
children: [
Expanded(
child: OutlinedButton.icon(
icon: const Icon(Icons.schedule),
label: Text(label),
onPressed: () => _pick(context),
),
),
const SizedBox(width: 8),
SegmentedButton<bool>(
segments: const [
ButtonSegment(value: false, label: Text('Ab')),
ButtonSegment(value: true, label: Text('An')),
],
selected: {byArrival},
onSelectionChanged: (s) => onByArrivalChanged(s.first),
),
if (value != null) ...[
const SizedBox(width: 4),
IconButton(
icon: const Icon(Icons.close),
tooltip: 'Zurück auf "Jetzt"',
onPressed: () => onValueChanged(null),
),
],
],
);
}
Future<void> _pick(BuildContext context) async {
final now = DateTime.now();
final initial = value ?? now;
final date = await showDatePicker(
context: context,
initialDate: initial,
firstDate: now.subtract(const Duration(days: 7)),
lastDate: now.add(const Duration(days: 365)),
);
if (date == null) return;
if (!context.mounted) return;
final time = await showTimePicker(
context: context,
initialTime: TimeOfDay(hour: initial.hour, minute: initial.minute),
);
if (time == null) return;
onValueChanged(
DateTime(date.year, date.month, date.day, time.hour, time.minute),
);
}
}