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
+83
View File
@@ -0,0 +1,83 @@
import 'package:flutter/material.dart';
import '../../../routing/app_routes.dart';
import '../../../state/app/infrastructure/loadable_state/loadable_state.dart';
import '../../../state/app/infrastructure/utility_widgets/bloc_module.dart';
import '../../../state/app/modules/rmv/bloc/rmv_bloc.dart';
import '../../../state/app/modules/rmv/bloc/rmv_state.dart';
import 'stations/station_overview_tab.dart';
import 'trip_search/trip_search_tab.dart';
class RmvView extends StatelessWidget {
const RmvView({super.key});
@override
Widget build(BuildContext context) =>
BlocModule<RmvBloc, LoadableState<RmvState>>(
create: (context) => RmvBloc(),
autoRebuild: true,
child: (context, bloc, state) {
final disruptions = bloc.getDisruptions();
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: const Text('RMV-Fahrplan'),
bottom: const TabBar(
tabs: [
Tab(icon: Icon(Icons.alt_route), text: 'Verbindung'),
Tab(icon: Icon(Icons.directions_bus), text: 'Stationen'),
],
),
actions: [
Builder(
builder: (ctx) => IconButton(
icon: _disruptionsIcon(disruptions.length),
tooltip: 'Störungsmeldungen',
onPressed: () => AppRoutes.openRmvDisruptions(ctx),
),
),
],
),
body: const TabBarView(
children: [
TripSearchTab(),
StationOverviewTab(),
],
),
),
);
},
);
Widget _disruptionsIcon(int count) {
if (count <= 0) return const Icon(Icons.warning_amber_outlined);
return Stack(
clipBehavior: Clip.none,
children: [
const Icon(Icons.warning_amber_outlined),
Positioned(
right: -6,
top: -6,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(10),
),
constraints: const BoxConstraints(minWidth: 16, minHeight: 16),
child: Text(
count > 99 ? '99+' : '$count',
style: const TextStyle(
color: Colors.white,
fontSize: 10,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
),
),
],
);
}
}