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,148 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../api/connect/rmv/rmv_models.dart';
import '../../../../routing/app_routes.dart';
import '../../../../state/app/modules/settings/bloc/settings_cubit.dart';
import '../../../../storage/settings.dart';
import '../../../../widget/centered_leading.dart';
import '../../../../widget/confirm_dialog.dart';
import '../favorites_controller.dart';
import '../widgets/station_picker_sheet.dart';
import 'nearby_stations_view.dart';
class StationOverviewTab extends StatelessWidget {
const StationOverviewTab({super.key});
@override
Widget build(BuildContext context) =>
BlocBuilder<SettingsCubit, Settings>(builder: _buildBody);
Widget _buildBody(BuildContext context, Settings settings) {
final rmv = settings.rmvSettings;
final favorites = rmv.favoriteStations;
final recents = rmv.recentStations;
final favCtrl = RmvFavoritesController(context.read<SettingsCubit>());
final children = <Widget>[
_searchBar(context),
_nearbyButton(context),
if (favorites.isEmpty && recents.isEmpty) _emptyState(context),
if (favorites.isNotEmpty) ...[
_sectionHeader(context, 'Favoriten', null),
...favorites.map((s) => _stationTile(context, s, favCtrl, isFavorite: true)),
],
if (recents.isNotEmpty) ...[
_sectionHeader(
context,
'Zuletzt verwendet',
IconButton(
icon: const Icon(Icons.delete_sweep_outlined),
tooltip: 'Alle löschen',
onPressed: () => _confirmClearRecents(context, favCtrl),
),
),
...recents.map((s) => _stationTile(context, s, favCtrl, isFavorite: false)),
],
];
return ListView(children: children);
}
Widget _searchBar(BuildContext context) => Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
child: FilledButton.tonalIcon(
icon: const Icon(Icons.search),
label: const Text('Station suchen…'),
onPressed: () async {
final picked = await showStationPickerSheet(context);
if (picked != null && context.mounted) {
AppRoutes.openRmvStationDetail(context, picked);
}
},
style: FilledButton.styleFrom(
minimumSize: const Size.fromHeight(48),
alignment: Alignment.centerLeft,
),
),
);
Widget _nearbyButton(BuildContext context) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: OutlinedButton.icon(
icon: const Icon(Icons.my_location),
label: const Text('In meiner Nähe'),
onPressed: () => Navigator.of(context).push<void>(
MaterialPageRoute(builder: (_) => const NearbyStationsView()),
),
style: OutlinedButton.styleFrom(
minimumSize: const Size.fromHeight(40),
),
),
);
Widget _sectionHeader(BuildContext context, String title, Widget? trailing) =>
Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 8, 4),
child: Row(
children: [
Expanded(
child: Text(
title,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
color: Theme.of(context).colorScheme.secondary,
),
),
),
?trailing,
],
),
);
Widget _emptyState(BuildContext context) => Padding(
padding: const EdgeInsets.fromLTRB(24, 40, 24, 16),
child: Center(
child: Text(
'Noch keine Stationen gespeichert. Suche eine Station, um sie zu öffnen oder als Favorit zu markieren.',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium,
),
),
);
Widget _stationTile(
BuildContext context,
StopLocation station,
RmvFavoritesController favCtrl, {
required bool isFavorite,
}) => ListTile(
leading: CenteredLeading(
Icon(isFavorite ? Icons.star : Icons.directions_transit),
),
title: Text(station.name),
subtitle: station.description == null ? null : Text(station.description!),
trailing: IconButton(
icon: Icon(
favCtrl.isFavorite(station) ? Icons.star : Icons.star_border,
),
tooltip: favCtrl.isFavorite(station)
? 'Favorit entfernen'
: 'Als Favorit speichern',
onPressed: () => favCtrl.toggleFavorite(station),
),
onTap: () => AppRoutes.openRmvStationDetail(context, station),
);
Future<void> _confirmClearRecents(
BuildContext context,
RmvFavoritesController favCtrl,
) async {
ConfirmDialog(
title: 'Verlauf leeren?',
content:
'Die zuletzt verwendeten Stationen werden aus der Übersicht entfernt. Favoriten bleiben bestehen.',
confirmButton: 'Leeren',
onConfirm: () => favCtrl.clearRecents(),
).asDialog(context);
}
}