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,108 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../api/connect/rmv/rmv_models.dart';
|
||||
import '../../../../extensions/date_time.dart';
|
||||
import 'product_chip.dart';
|
||||
import 'realtime_time.dart';
|
||||
|
||||
/// Compact summary of a [Trip] used in the trip results list.
|
||||
class TripTile extends StatelessWidget {
|
||||
final Trip trip;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
const TripTile({super.key, required this.trip, this.onTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final firstLeg = trip.legs.isEmpty ? null : trip.legs.first;
|
||||
final lastLeg = trip.legs.isEmpty ? null : trip.legs.last;
|
||||
if (firstLeg == null || lastLeg == null) {
|
||||
return const ListTile(title: Text('Verbindung ohne Halt'));
|
||||
}
|
||||
final scheduledStart = firstLeg.origin.scheduledTime;
|
||||
final scheduledEnd = lastLeg.destination.scheduledTime;
|
||||
final cancelled =
|
||||
trip.legs.any((l) => l.cancelled || l.partCancelled);
|
||||
final transfers = trip.transferCount ?? _countTransfers(trip);
|
||||
final duration = trip.realDuration ?? trip.duration;
|
||||
final productChips = trip.legs
|
||||
.where((l) => l.type == LegType.journey && l.product != null)
|
||||
.map((l) => Padding(
|
||||
padding: const EdgeInsets.only(right: 4),
|
||||
child: ProductChip(product: l.product, fallbackLabel: l.name),
|
||||
))
|
||||
.toList();
|
||||
|
||||
return ListTile(
|
||||
onTap: onTap,
|
||||
isThreeLine: true,
|
||||
title: Row(
|
||||
children: [
|
||||
RealtimeTime(
|
||||
scheduled: scheduledStart,
|
||||
realtime: firstLeg.origin.realTime,
|
||||
delayMinutes: firstLeg.origin.delayMinutes?.toInt(),
|
||||
cancelled: cancelled,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 6),
|
||||
child: Text('–'),
|
||||
),
|
||||
RealtimeTime(
|
||||
scheduled: scheduledEnd,
|
||||
realtime: lastLeg.destination.realTime,
|
||||
delayMinutes: lastLeg.destination.delayMinutes?.toInt(),
|
||||
cancelled: cancelled,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 4),
|
||||
Wrap(runSpacing: 4, children: productChips),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: [
|
||||
if (duration != null) ...[
|
||||
const Icon(Icons.schedule, size: 14),
|
||||
const SizedBox(width: 2),
|
||||
Text(_formatDuration(duration)),
|
||||
const SizedBox(width: 12),
|
||||
],
|
||||
const Icon(Icons.swap_horiz, size: 14),
|
||||
const SizedBox(width: 2),
|
||||
Text(
|
||||
transfers == 0
|
||||
? 'Direkt'
|
||||
: '$transfers Umstieg${transfers > 1 ? 'e' : ''}',
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
);
|
||||
}
|
||||
|
||||
int _countTransfers(Trip trip) {
|
||||
final journeyLegs =
|
||||
trip.legs.where((l) => l.type == LegType.journey).length;
|
||||
return journeyLegs <= 1 ? 0 : journeyLegs - 1;
|
||||
}
|
||||
}
|
||||
|
||||
String _formatDuration(Duration d) {
|
||||
final hours = d.inHours;
|
||||
final minutes = d.inMinutes.remainder(60);
|
||||
if (hours == 0) return '$minutes min';
|
||||
return '$hours h ${minutes.toString().padLeft(2, '0')} min';
|
||||
}
|
||||
|
||||
/// Re-export for trip detail screen.
|
||||
String formatTripDuration(Duration d) => _formatDuration(d);
|
||||
|
||||
/// Helper used in date headers on the trip results list.
|
||||
String formatTripDateHeader(DateTime when) => when.formatDateRelativeShort();
|
||||
Reference in New Issue
Block a user