import 'package:flutter/material.dart'; import 'package:geolocator/geolocator.dart'; import '../../../../api/connect/rmv/rmv_models.dart'; import '../../../../api/errors/error_mapper.dart'; import '../../../../routing/app_routes.dart'; import '../../../../state/app/modules/rmv/repository/rmv_repository.dart'; import '../../../../widget/app_progress_indicator.dart'; import '../../../../widget/centered_leading.dart'; class NearbyStationsView extends StatefulWidget { const NearbyStationsView({super.key}); @override State createState() => _NearbyStationsViewState(); } class _NearbyStationsViewState extends State { final RmvRepository _repo = RmvRepository(); List? _stops; bool _loading = true; String? _userError; Object? _apiError; int _radiusMeters = 1000; @override void initState() { super.initState(); _load(); } Future _load() async { setState(() { _loading = true; _userError = null; _apiError = null; }); final position = await _resolvePosition(); if (!mounted) return; if (position == null) { // _userError is set by _resolvePosition setState(() => _loading = false); return; } try { final stops = await _repo.nearbyStops( lat: position.latitude, lon: position.longitude, radiusMeters: _radiusMeters, max: 30, ); if (!mounted) return; stops.sort((a, b) => (a.distanceMeters ?? 0).compareTo(b.distanceMeters ?? 0)); setState(() { _stops = stops; _loading = false; }); } catch (e) { if (!mounted) return; setState(() { _apiError = e; _loading = false; }); } } Future _resolvePosition() async { if (!await Geolocator.isLocationServiceEnabled()) { _userError = 'Bitte aktiviere die Standortdienste in den System-Einstellungen.'; return null; } var permission = await Geolocator.checkPermission(); if (permission == LocationPermission.denied) { permission = await Geolocator.requestPermission(); } if (permission == LocationPermission.deniedForever) { _userError = 'Standortzugriff dauerhaft verweigert. Bitte in den App-Einstellungen aktivieren.'; return null; } if (permission == LocationPermission.denied) { _userError = 'Ohne Standortzugriff können keine Stationen in der Nähe gefunden werden.'; return null; } try { return await Geolocator.getCurrentPosition( locationSettings: const LocationSettings( accuracy: LocationAccuracy.medium, timeLimit: Duration(seconds: 10), ), ); } catch (e) { _userError = 'Standort konnte nicht ermittelt werden: $e'; return null; } } @override Widget build(BuildContext context) => Scaffold( appBar: AppBar( title: const Text('In meiner Nähe'), actions: [ PopupMenuButton( tooltip: 'Suchradius', icon: const Icon(Icons.tune), onSelected: (r) { setState(() => _radiusMeters = r); _load(); }, itemBuilder: (_) => [500, 1000, 2000, 5000] .map( (r) => CheckedPopupMenuItem( value: r, checked: r == _radiusMeters, child: Text(r >= 1000 ? '${r ~/ 1000} km' : '$r m'), ), ) .toList(), ), ], ), body: _body(), ); Widget _body() { if (_loading) return const Center(child: AppProgressIndicator.large()); if (_userError != null) { return Center( child: Padding( padding: const EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text(_userError!, textAlign: TextAlign.center), const SizedBox(height: 12), Wrap( spacing: 8, children: [ OutlinedButton.icon( icon: const Icon(Icons.refresh), onPressed: _load, label: const Text('Erneut versuchen'), ), OutlinedButton.icon( icon: const Icon(Icons.settings), onPressed: Geolocator.openAppSettings, label: const Text('Einstellungen'), ), ], ), ], ), ), ); } if (_apiError != null) { return Center( child: Padding( padding: const EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text( errorToUserMessage(_apiError), textAlign: TextAlign.center, style: TextStyle(color: Theme.of(context).colorScheme.error), ), const SizedBox(height: 12), FilledButton.icon( icon: const Icon(Icons.refresh), onPressed: _load, label: const Text('Erneut versuchen'), ), ], ), ), ); } final list = _stops ?? const []; if (list.isEmpty) { return const Center( child: Padding( padding: EdgeInsets.all(24), child: Text( 'Keine Stationen im gewählten Umkreis gefunden.', textAlign: TextAlign.center, ), ), ); } return RefreshIndicator( onRefresh: _load, child: ListView.separated( itemCount: list.length, separatorBuilder: (_, _) => const Divider(height: 1), itemBuilder: (_, i) => _tile(list[i]), ), ); } Widget _tile(StopLocation stop) => ListTile( leading: const CenteredLeading(Icon(Icons.directions_transit)), title: Text(stop.name), subtitle: stop.distanceMeters == null ? null : Text('${stop.distanceMeters} m entfernt'), onTap: () => AppRoutes.openRmvStationDetail(context, stop), ); }