import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:map_launcher/map_launcher.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:anyway/domain/entities/landmark.dart'; import 'package:anyway/domain/entities/trip.dart'; import 'package:anyway/presentation/utils/trip_location_utils.dart'; import 'package:anyway/presentation/widgets/trip_details/trip_hero_header.dart'; import 'package:anyway/presentation/widgets/trip_details/trip_hero_meta_card.dart'; import 'package:anyway/presentation/widgets/trip_details/trip_landmark_card.dart'; import 'package:anyway/presentation/widgets/trip_details/trip_step_between_landmarks.dart'; class TripDetailsPanel extends StatefulWidget { const TripDetailsPanel({super.key, required this.trip, required this.controller, this.onLandmarkUpdated}); final Trip trip; final ScrollController controller; final VoidCallback? onLandmarkUpdated; @override State createState() => _TripDetailsPanelState(); } class _TripDetailsPanelState extends State { late Future _localeFuture; @override void initState() { super.initState(); _localeFuture = TripLocationUtils.resolveLocaleInfo(widget.trip); } @override void didUpdateWidget(covariant TripDetailsPanel oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.trip.uuid != widget.trip.uuid) { _localeFuture = TripLocationUtils.resolveLocaleInfo(widget.trip); } } @override Widget build(BuildContext context) { final landmarks = widget.trip.landmarks; if (landmarks.isEmpty) { return const Center(child: Text('No landmarks in this trip yet.')); } return CustomScrollView( controller: widget.controller, slivers: [ SliverToBoxAdapter( child: Column( children: [ const SizedBox(height: 10), Container( width: 42, height: 5, decoration: BoxDecoration(color: Colors.grey.shade300, borderRadius: BorderRadius.circular(12)), ), const SizedBox(height: 10), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: FutureBuilder( future: _localeFuture, builder: (context, snapshot) { final info = snapshot.data; final isLoading = snapshot.connectionState == ConnectionState.waiting; final subtitle = isLoading ? 'Pinpointing your destination...' : 'Here is a recommended route for your trip in ${info?.cityName ?? 'your area'}.'; return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ TripHeroHeader(localeInfo: info), const SizedBox(height: 10), TripHeroMetaCard( subtitle: subtitle, totalStops: widget.trip.landmarks.length, totalMinutes: widget.trip.totalTimeMinutes, countryName: info?.countryName, isLoading: isLoading, ), ], ); }, ), ), const SizedBox(height: 14), ], ), ), SliverPadding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 32), sliver: SliverList( delegate: SliverChildBuilderDelegate((context, index) { final landmark = landmarks[index]; return Column( children: [ TripLandmarkCard( landmark: landmark, position: index + 1, onToggleVisited: () => _toggleVisited(landmark), onOpenWebsite: landmark.websiteUrl == null ? null : () => _openWebsite(landmark.websiteUrl!), ), if (index < landmarks.length - 1) TripStepBetweenLandmarks(current: landmark, next: landmarks[index + 1], onRequestDirections: () => _showNavigationOptions(context, landmark, landmarks[index + 1])), ], ); }, childCount: landmarks.length), ), ), ], ); } void _toggleVisited(Landmark landmark) { setState(() => landmark.isVisited = !landmark.isVisited); widget.onLandmarkUpdated?.call(); } Future _openWebsite(String url) async { final uri = Uri.tryParse(url); if (uri == null) return; await launchUrl(uri, mode: LaunchMode.externalApplication); } Future _showNavigationOptions(BuildContext context, Landmark current, Landmark next) async { if (!_hasValidLocation(current) || !_hasValidLocation(next)) { if (!context.mounted) return; ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Coordinates unavailable for directions.'))); return; } List availableMaps = []; try { availableMaps = await MapLauncher.installedMaps; } catch (error) { debugPrint('Unable to load maps: $error'); } if (!context.mounted) return; if (availableMaps.isEmpty) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('No navigation apps detected.'))); return; } final origin = Coords(current.location[0], current.location[1]); final destination = Coords(next.location[0], next.location[1]); await showModalBottomSheet( context: context, builder: (sheetContext) { return SafeArea( child: Column( mainAxisSize: MainAxisSize.min, children: [ Padding( padding: const EdgeInsets.fromLTRB(24, 24, 24, 8), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Navigate to ${next.name}', style: Theme.of(sheetContext).textTheme.titleMedium), const SizedBox(height: 4), Text('Choose your preferred maps app.', style: Theme.of(sheetContext).textTheme.bodySmall), ], ), ), for (final map in availableMaps) ListTile( leading: ClipRRect(borderRadius: BorderRadius.circular(8), child: SvgPicture.asset(map.icon, height: 30, width: 30)), title: Text(map.mapName), onTap: () async { await map.showDirections(origin: origin, originTitle: current.name, destination: destination, destinationTitle: next.name, directionsMode: DirectionsMode.walking); if (sheetContext.mounted) Navigator.of(sheetContext).pop(); }, ), ], ), ); }, ); } bool _hasValidLocation(Landmark landmark) => landmark.location.length >= 2; }