import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../data/places_repository.dart'; import '../../providers/places_provider.dart'; import '../widgets/poi_chip.dart'; class PlaceDetailScreen extends ConsumerStatefulWidget { final String osmType; final int osmId; const PlaceDetailScreen({ super.key, required this.osmType, required this.osmId, }); @override ConsumerState createState() => _PlaceDetailScreenState(); } class _PlaceDetailScreenState extends ConsumerState { PlaceData? _place; bool _isLoading = true; String? _error; bool _isFavorited = false; int? _favoriteId; @override void initState() { super.initState(); _loadPlace(); } Future _loadPlace() async { try { final repo = ref.read(placesRepositoryProvider); final place = await repo.getPoiDetail(widget.osmType, widget.osmId); final fav = await repo.isFavorited(widget.osmType, widget.osmId); if (mounted) { setState(() { _place = place; _isLoading = false; _isFavorited = fav != null; _favoriteId = fav?.id; }); } } catch (e) { if (mounted) { setState(() { _isLoading = false; _error = 'Could not load place details.'; }); } } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(_place?.name ?? 'Place Details'), ), body: _buildBody(context), ); } Widget _buildBody(BuildContext context) { if (_isLoading) { return const Center(child: CircularProgressIndicator()); } if (_error != null) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.error_outline, size: 48), const SizedBox(height: 8), Text(_error!), const SizedBox(height: 16), FilledButton( onPressed: () { setState(() { _isLoading = true; _error = null; }); _loadPlace(); }, child: const Text('Retry'), ), ], ), ); } final place = _place!; return SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Name and category Text(place.name, style: Theme.of(context).textTheme.headlineSmall), const SizedBox(height: 8), PoiChip(category: place.category), const SizedBox(height: 16), // Address if (place.displayAddress.isNotEmpty) ...[ _InfoRow( icon: Icons.location_on, label: 'Address', value: place.displayAddress, ), const SizedBox(height: 12), ], // Opening hours if (place.openingHoursParsed != null) ...[ _InfoRow( icon: Icons.access_time, label: 'Hours', value: _formatOpeningHours(place), ), const SizedBox(height: 12), ] else if (place.openingHours != null) ...[ _InfoRow( icon: Icons.access_time, label: 'Hours', value: place.openingHours!, ), const SizedBox(height: 12), ], // Phone if (place.phone != null) ...[ _InfoRow( icon: Icons.phone, label: 'Phone', value: place.phone!, ), const SizedBox(height: 12), ], // Website if (place.website != null) ...[ _InfoRow( icon: Icons.language, label: 'Website', value: place.website!, ), const SizedBox(height: 12), ], // Wheelchair accessibility if (place.wheelchair != null) ...[ _InfoRow( icon: Icons.accessible, label: 'Wheelchair', value: _wheelchairLabel(place.wheelchair!), ), const SizedBox(height: 12), ], const SizedBox(height: 24), // Action buttons Row( children: [ Expanded( child: FilledButton.icon( onPressed: () { context.push('/route', extra: { 'destLat': place.latitude, 'destLon': place.longitude, 'destName': place.name, }); }, icon: const Icon(Icons.directions), label: const Text('Directions'), ), ), const SizedBox(width: 8), Expanded( child: _isFavorited ? FilledButton.tonalIcon( onPressed: () async { if (_favoriteId != null) { await ref .read(placesProvider.notifier) .removeFromFavorites(_favoriteId!); setState(() { _isFavorited = false; _favoriteId = null; }); } }, icon: const Icon(Icons.bookmark), label: const Text('Saved'), ) : OutlinedButton.icon( onPressed: () async { await ref .read(placesProvider.notifier) .addToFavorites(place); final fav = await ref .read(placesRepositoryProvider) .isFavorited(place.osmType, place.osmId); setState(() { _isFavorited = true; _favoriteId = fav?.id; }); }, icon: const Icon(Icons.bookmark_add_outlined), label: const Text('Save'), ), ), ], ), const SizedBox(height: 8), SizedBox( width: double.infinity, child: OutlinedButton.icon( onPressed: () { final coords = '${place.latitude.toStringAsFixed(6)},${place.longitude.toStringAsFixed(6)}'; Clipboard.setData(ClipboardData(text: coords)); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Coordinates copied: $coords')), ); }, icon: const Icon(Icons.share), label: const Text('Share coordinates'), ), ), ], ), ); } String _formatOpeningHours(PlaceData place) { final parsed = place.openingHoursParsed!; final parts = []; final isOpen = parsed['is_open'] as bool?; if (isOpen == true) { parts.add('Open now'); } else if (isOpen == false) { parts.add('Closed'); } final today = parsed['today'] as String?; if (today != null) { parts.add('Today: $today'); } final nextChange = parsed['next_change'] as String?; if (nextChange != null) { parts.add(nextChange); } return parts.join(' \u2022 '); } String _wheelchairLabel(String value) { switch (value) { case 'yes': return 'Wheelchair accessible'; case 'limited': return 'Limited wheelchair access'; case 'no': return 'Not wheelchair accessible'; default: return value; } } } class _InfoRow extends StatelessWidget { final IconData icon; final String label; final String value; const _InfoRow({ required this.icon, required this.label, required this.value, }); @override Widget build(BuildContext context) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(icon, size: 20, color: Theme.of(context).colorScheme.primary), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: Theme.of(context).textTheme.labelSmall?.copyWith( color: Theme.of(context).colorScheme.outline, ), ), Text(value, style: Theme.of(context).textTheme.bodyMedium), ], ), ), ], ); } }