import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; import '../../../constant/box_name.dart'; import '../../../main.dart'; import '../../widgets/error_snakbar.dart'; import 'navigation_controller.dart'; // ─── Color Palette Matching the HTML Specs ────────────────────────────────── Color get _kSurface => Get.isDarkMode ? const Color(0xFF131b2e) : const Color(0xFFfaf8ff); Color get _kSurfaceContainerLowest => Get.isDarkMode ? const Color(0xFF1e293b) : const Color(0xFFffffff); Color get _kSurfaceContainerHigh => Get.isDarkMode ? const Color(0xFF334155) : const Color(0xFFe2e7ff); Color get _kPrimary => const Color(0xFF000000); Color get _kPrimaryContainer => const Color(0xFF131b2e); Color get _kOnPrimaryContainer => const Color(0xFF7c839b); Color get _kOnSurface => Get.isDarkMode ? const Color(0xFFffffff) : const Color(0xFF131b2e); Color get _kOnSurfaceVariant => const Color(0xFF45464d); Color get _kErrorContainer => const Color(0xFFffdad6); Color get _kError => const Color(0xFFba1a1a); Color get _kOutlineVariant => const Color(0xFFc6c6cd); class NavigationView extends StatelessWidget { const NavigationView({super.key}); @override Widget build(BuildContext context) { final NavigationController c = Get.put(NavigationController()); return AnnotatedRegion( value: Get.isDarkMode ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark, child: Scaffold( backgroundColor: _kSurface, body: GetBuilder( builder: (_) => Stack( children: [ // ── 1. Map Layer ────────────────────────────────────────────── MapLibreMap( onMapCreated: c.onMapCreated, onStyleLoadedCallback: c.onStyleLoaded, onMapLongClick: c.onMapLongPressed, onMapClick: (point, tappedPoint) => c.onMapTapped(point, tappedPoint), styleString: Get.isDarkMode ? "assets/style_dark.json" : "assets/style.json", initialCameraPosition: CameraPosition( target: c.myLocation ?? const LatLng(33.5138, 36.2765), zoom: 16.0), myLocationEnabled: false, compassEnabled: false, trackCameraPosition: true, ), // ── 2. Top UI (Explore Mode) ────────────────────────────────── if (!c.isNavigating) _ExploreTopUI(controller: c), // ── 3. Top UI (Active Navigation Banner) ────────────────────── if (c.isNavigating && c.currentInstruction.isNotEmpty) _ActiveTopBanner(controller: c), // ── 4. Map Controls (Floating Right) ────────────────────────── _MapControls(controller: c), // ── 5. Bottom Panel (Explore Mode / Route Setup) ────────────── if (!c.isNavigating) _ExploreBottomPanel(controller: c), // ── 6. Bottom HUD (Active Navigation) ───────────────────────── if (c.isNavigating) _ActiveBottomHUD(controller: c), // ── 7. Speedometer Badge ────────────────────────────────────── if (c.isNavigating) _SpeedBadge(speed: c.currentSpeed), // ── 8. Search Results Dropdown ──────────────────────────────── if (c.placesDestination.isNotEmpty && !c.isNavigating) _SearchResults(controller: c), // ── 9. Loading Overlay ──────────────────────────────────────── if (c.isLoading) const _LoadingOverlay(), ], ), ), ), ); } } // ============================================================================= // EXPLORE MODE COMPONENTS // ============================================================================= class _ExploreTopUI extends StatelessWidget { final NavigationController controller; const _ExploreTopUI({required this.controller}); @override Widget build(BuildContext context) { return Positioned( top: 0, left: 0, right: 0, child: SafeArea( bottom: false, child: Column( children: [ // Search Pill Padding( padding: const EdgeInsets.fromLTRB(16, 16, 16, 16), child: Container( decoration: BoxDecoration( color: _kSurfaceContainerLowest.withOpacity(0.95), borderRadius: BorderRadius.circular(50), border: Border.all(color: Colors.white.withOpacity(0.2)), boxShadow: const [ BoxShadow( color: Color(0x0F000000), blurRadius: 32, offset: Offset(0, 8)) ], ), padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 6), child: Row( children: [ IconButton( icon: Icon(Icons.menu_rounded, color: _kOnSurface), onPressed: () {}, // Drawer or Menu logic here ), Expanded( child: TextField( controller: controller.placeDestinationController, onChanged: controller.onSearchChanged, textInputAction: TextInputAction.search, style: TextStyle( fontSize: 16, color: _kOnSurface, fontWeight: FontWeight.w600), decoration: InputDecoration( hintText: 'Where to?'.tr, hintStyle: TextStyle( color: _kOnSurfaceVariant, fontSize: 16, fontWeight: FontWeight.w500), border: InputBorder.none, isDense: true, contentPadding: const EdgeInsets.symmetric(vertical: 10), ), ), ), if (controller.placeDestinationController.text.isNotEmpty) IconButton( icon: const Icon(Icons.close_rounded), color: _kOnSurfaceVariant, onPressed: () { controller.placeDestinationController.clear(); controller.placesDestination = []; controller.update(); }) else if (controller.destinationSymbol != null) IconButton( icon: const Icon(Icons.close_rounded), color: _kError, onPressed: () => controller.clearRoute()), // Avatar const Padding( padding: EdgeInsets.only(right: 4, left: 4), child: CircleAvatar( radius: 18, backgroundColor: Colors.grey, child: Icon(Icons.person_rounded, color: Colors.white, size: 20), // backgroundImage: AssetImage('assets/images/placeholder_avatar.png'), ), ), ], ), ), ), // Quick Access Chips SizedBox( height: 44, child: ListView( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 16), physics: const BouncingScrollPhysics(), children: [ // "Home" (Al-Ra'isiya) removed as per user request if it was redundant, // but we keep the functional Home/Work/Saved/Airport _QuickChip( icon: Icons.home_rounded, label: 'Home'.tr, onTap: () => controller.goToFavorite('home'), ), _QuickChip( icon: Icons.work_rounded, label: 'Work'.tr, onTap: () => controller.goToFavorite('work'), ), _QuickChip( icon: Icons.bookmark_rounded, label: 'Saved'.tr, onTap: () {}, // Future logic ), _QuickChip( icon: Icons.flight_rounded, label: 'Airport'.tr, onTap: () => controller.goToFavorite('airport'), ), ], ), ), ], ), ), ); } } class _QuickChip extends StatelessWidget { final IconData icon; final String label; final VoidCallback onTap; const _QuickChip( {required this.icon, required this.label, required this.onTap}); @override Widget build(BuildContext context) { return GestureDetector( onTap: () { HapticFeedback.lightImpact(); onTap(); }, child: Container( margin: const EdgeInsets.only(right: 8), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), decoration: BoxDecoration( color: _kSurfaceContainerLowest.withOpacity(0.95), borderRadius: BorderRadius.circular(50), boxShadow: const [ BoxShadow( color: Color(0x0A000000), blurRadius: 4, offset: Offset(0, 2)) ], ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, size: 18, color: _kOnSurface), const SizedBox(width: 8), Text(label, style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: _kOnSurface)), ], ), ), ); } } class _ExploreBottomPanel extends StatelessWidget { final NavigationController controller; const _ExploreBottomPanel({required this.controller}); String _formatDuration(double seconds) { final mins = (seconds / 60).toInt(); if (mins >= 60) { final h = mins ~/ 60; final m = mins % 60; return box.read(BoxName.lang) == 'ar' ? '$h ساعة ${m > 0 ? '$m د' : ''}' : '${h}h ${m > 0 ? '${m}m' : ''}'; } return box.read(BoxName.lang) == 'ar' ? '$mins دقيقة' : '$mins min'; } String _formatDistance(double meters) { if (meters >= 1000) { return box.read(BoxName.lang) == 'ar' ? '${(meters / 1000).toStringAsFixed(1)} كم' : '${(meters / 1000).toStringAsFixed(1)} km'; } return box.read(BoxName.lang) == 'ar' ? '${meters.toInt()} م' : '${meters.toInt()} m'; } @override Widget build(BuildContext context) { final bool hasRoutes = controller.routes.isNotEmpty; final bool isArabic = box.read(BoxName.lang) == 'ar'; final bottomPad = MediaQuery.of(context).padding.bottom; return Positioned( bottom: 0, left: 0, right: 0, child: ClipRRect( borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20), child: Container( padding: EdgeInsets.only(bottom: bottomPad + 12), decoration: BoxDecoration( color: _kSurfaceContainerLowest.withOpacity(0.92), boxShadow: const [ BoxShadow( color: Color(0x14000000), blurRadius: 48, offset: Offset(0, -12)) ], ), child: Column( mainAxisSize: MainAxisSize.min, children: [ // Drag Handle Container( width: 40, height: 4, margin: const EdgeInsets.only(top: 12, bottom: 16), decoration: BoxDecoration( color: _kOutlineVariant.withOpacity(0.4), borderRadius: BorderRadius.circular(10))), // ── Route Selection Cards ── if (hasRoutes) ...[ Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Column( children: List.generate(controller.routes.length, (index) { final r = controller.routes[index]; final isSelected = controller.selectedRouteIndex == index; return _RouteOptionCard( index: index, distance: _formatDistance(r.distanceM), duration: _formatDuration(r.durationS), isSelected: isSelected, isArabic: isArabic, onTap: () => controller.selectRoute(index), ); }), ), ), const SizedBox(height: 16), ], // ── Main Action Button ── Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Material( color: Colors.transparent, child: InkWell( onTap: hasRoutes ? () { HapticFeedback.mediumImpact(); controller.isNavigating = true; controller.relockCameraToUser(); controller.update(); } : null, borderRadius: BorderRadius.circular(16), child: Ink( padding: const EdgeInsets.symmetric(vertical: 18), decoration: BoxDecoration( gradient: LinearGradient( colors: hasRoutes ? [const Color(0xFF0D47A1), const Color(0xFF1565C0)] : [Colors.grey.shade400, Colors.grey.shade500], ), borderRadius: BorderRadius.circular(16), boxShadow: hasRoutes ? [ BoxShadow( color: const Color(0xFF0D47A1).withOpacity(0.35), blurRadius: 16, offset: const Offset(0, 6)) ] : [], ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.navigation_rounded, color: Colors.white, size: 22), const SizedBox(width: 10), Text( hasRoutes ? (isArabic ? 'ابدأ الملاحة' : 'Start Navigation') : (isArabic ? 'خطط المسار' : 'Plan Route'), style: const TextStyle( color: Colors.white, fontSize: 17, fontWeight: FontWeight.w800, letterSpacing: 0.3)), ], ), ), ), ), ), // ── Recent Places ── if (!hasRoutes && controller.recentLocations.isNotEmpty) ...[ const SizedBox(height: 20), Padding( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 4), child: Align( alignment: Alignment.centerLeft, child: Text(isArabic ? 'الأماكن الأخيرة' : 'RECENT PLACES', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w800, color: _kOnSurfaceVariant.withOpacity(0.6), letterSpacing: 1.2)), ), ), ...controller.recentLocations.map((loc) => _CompactRecentPlace( title: loc['name'] ?? 'Unknown', subtitle: loc['address'] ?? '', onTap: () { if (controller.myLocation != null && loc['latitude'] != null && loc['longitude'] != null) { controller.getRoute( controller.myLocation!, LatLng( double.parse(loc['latitude'].toString()), double.parse(loc['longitude'].toString()))); } }, )), const SizedBox(height: 8), ] else if (!hasRoutes) ...[ const SizedBox(height: 16), ], ], ), ), ), ), ); } } class _RouteOptionCard extends StatelessWidget { final int index; final String distance; final String duration; final bool isSelected; final bool isArabic; final VoidCallback onTap; const _RouteOptionCard({ required this.index, required this.distance, required this.duration, required this.isSelected, required this.isArabic, required this.onTap, }); @override Widget build(BuildContext context) { return GestureDetector( onTap: () { HapticFeedback.selectionClick(); onTap(); }, child: AnimatedContainer( duration: const Duration(milliseconds: 200), margin: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), decoration: BoxDecoration( color: isSelected ? const Color(0xFF0D47A1) : _kSurfaceContainerHigh.withOpacity(0.4), borderRadius: BorderRadius.circular(16), border: Border.all( color: isSelected ? const Color(0xFF42A5F5).withOpacity(0.4) : _kOutlineVariant.withOpacity(0.15), width: isSelected ? 1.5 : 1, ), ), child: Row( children: [ // Route icon Container( width: 40, height: 40, decoration: BoxDecoration( color: isSelected ? Colors.white.withOpacity(0.15) : _kSurfaceContainerHigh.withOpacity(0.5), borderRadius: BorderRadius.circular(12), ), child: Icon( index == 0 ? Icons.route_rounded : Icons.alt_route_rounded, color: isSelected ? Colors.white : _kOnSurfaceVariant, size: 20, ), ), const SizedBox(width: 14), // Route Label Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( index == 0 ? (isArabic ? 'أفضل مسار' : 'Best Route') : (isArabic ? 'مسار بديل $index' : 'Alternative $index'), style: TextStyle( color: isSelected ? Colors.white : _kOnSurface, fontSize: 14, fontWeight: FontWeight.w700, ), ), const SizedBox(height: 2), Text( distance, style: TextStyle( color: isSelected ? Colors.white.withOpacity(0.7) : _kOnSurfaceVariant, fontSize: 12, ), ), ], ), ), // Duration (prominent) Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: isSelected ? Colors.white.withOpacity(0.15) : const Color(0xFFE3F2FD), borderRadius: BorderRadius.circular(10), ), child: Text( duration, style: TextStyle( color: isSelected ? Colors.white : const Color(0xFF0D47A1), fontSize: 14, fontWeight: FontWeight.w800, ), ), ), // Selection indicator const SizedBox(width: 8), Icon( isSelected ? Icons.check_circle_rounded : Icons.radio_button_unchecked_rounded, color: isSelected ? Colors.white : _kOutlineVariant, size: 22, ), ], ), ), ); } } class _CompactRecentPlace extends StatelessWidget { final String title; final String subtitle; final VoidCallback onTap; const _CompactRecentPlace({ required this.title, required this.subtitle, required this.onTap, }); @override Widget build(BuildContext context) { return ListTile( onTap: onTap, dense: true, contentPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 0), leading: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: _kSurfaceContainerHigh.withOpacity(0.5), shape: BoxShape.circle, ), child: Icon(Icons.history_rounded, size: 18, color: _kOnSurface), ), title: Text(title, style: TextStyle( fontWeight: FontWeight.w700, color: _kOnSurface, fontSize: 14)), subtitle: Text(subtitle, style: TextStyle(color: _kOnSurfaceVariant, fontSize: 12), maxLines: 1, overflow: TextOverflow.ellipsis), ); } } // ============================================================================= // ACTIVE NAVIGATION MODE COMPONENTS // ============================================================================= class _ActiveTopBanner extends StatelessWidget { final NavigationController controller; const _ActiveTopBanner({required this.controller}); @override Widget build(BuildContext context) { return Positioned( top: 0, left: 0, right: 0, child: SafeArea( bottom: false, child: Padding( padding: const EdgeInsets.fromLTRB(12, 12, 12, 0), child: Container( decoration: BoxDecoration( color: const Color(0xFF1B5E20), // Google Maps Green borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.3), blurRadius: 15, offset: const Offset(0, 5)) ], ), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20), child: Row( children: [ // Direction indicator Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white.withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), child: Icon(controller.currentManeuverIcon, color: Colors.white, size: 48), ), const SizedBox(width: 16), // Full instruction text Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( controller.currentInstruction, style: const TextStyle( color: Colors.white, fontSize: 22, fontWeight: FontWeight.w800, letterSpacing: -0.5), maxLines: 3, overflow: TextOverflow.visible, ), const SizedBox(height: 4), Text( controller.distanceToNextStep, style: const TextStyle( color: Colors.white70, fontSize: 16, fontWeight: FontWeight.w900), ), ], ), ), ], ), ), ), ), ); } } class _ActiveBottomHUD extends StatelessWidget { final NavigationController controller; const _ActiveBottomHUD({required this.controller}); @override Widget build(BuildContext context) { final bottomPad = MediaQuery.of(context).padding.bottom; final isArabic = (Get.locale?.languageCode ?? 'ar') == 'ar'; return Positioned( bottom: 0, left: 0, right: 0, child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( decoration: BoxDecoration( color: _kSurfaceContainerLowest, borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), boxShadow: const [ BoxShadow( color: Color(0x0F000000), blurRadius: 32, offset: Offset(0, -8)) ], ), padding: EdgeInsets.fromLTRB( 24, 16, 24, bottomPad + 12), // Reduced top padding child: Column( mainAxisSize: MainAxisSize.min, children: [ // Stats Row Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ _StatItem( label: isArabic ? 'الوصول' : 'ARRIVAL', value: controller.arrivalTime, color: _kOnSurface), _StatItem( label: isArabic ? 'الوقت' : 'TIME', value: isArabic ? '${controller.estimatedTimeRemaining} دقيقة' : '${controller.estimatedTimeRemaining} min', color: _kOnSurface), _StatItem( label: isArabic ? 'المسافة' : 'DISTANCE', value: controller.distanceWithUnit, // Use fix color: _kOnSurface), ], ), const SizedBox(height: 16), const Divider(height: 1), const SizedBox(height: 16), // Action Buttons Row Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _ActionButton( icon: Icons.report_problem_rounded, label: isArabic ? 'تقرير' : 'Report', color: const Color(0xFFE8EAF6), onPressed: () {}), _ActionButton( icon: controller.isMuted ? Icons.volume_off_rounded : Icons.volume_up_rounded, label: isArabic ? 'الصوت' : 'Sound', color: const Color(0xFFE8EAF6), onPressed: () => controller.toggleMute()), _ActionButton( icon: Icons.add_rounded, label: isArabic ? 'إضافة' : 'Add', color: const Color(0xFFE8EAF6), onPressed: () => _showSuggestPlaceDialog(context, controller)), _ActionButton( icon: Icons.close_rounded, label: isArabic ? 'إنهاء' : 'End', color: const Color(0xFFFFEBEE), iconColor: _kError, onPressed: () => controller.clearRoute()), ], ), ], ), ), ], ), ); } } void _showSuggestPlaceDialog( BuildContext context, NavigationController controller) { final entryController = TextEditingController(); Get.dialog( AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), title: Row( children: [ const Icon(Icons.stars_rounded, color: Colors.amber, size: 28), const SizedBox(width: 10), Text(box.read(BoxName.lang) == 'ar' ? "ساهم في تحسين الخريطة" : "Contribute to Map"), ], ), content: Column( mainAxisSize: MainAxisSize.min, children: [ Text( box.read(BoxName.lang) == 'ar' ? "هل هناك مكان غير موجود؟ أضفه الآن واحصل على ٥٠ نقطة مكافأة!" : "Is there a missing place? Add it now and earn 50 reward points!", style: const TextStyle(fontSize: 14), ), const SizedBox(height: 20), TextField( controller: entryController, decoration: InputDecoration( hintText: box.read(BoxName.lang) == 'ar' ? "اسم المكان" : "Place Name", filled: true, fillColor: Colors.grey.shade100, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), ), ), ], ), actions: [ TextButton( onPressed: () => Get.back(), child: Text(box.read(BoxName.lang) == 'ar' ? "تراجع" : "Cancel", style: const TextStyle(color: Colors.grey)), ), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF1B5E20), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), onPressed: () { if (entryController.text.isNotEmpty) { controller.submitPlaceSuggestion(entryController.text); Get.back(); } }, child: Text( box.read(BoxName.lang) == 'ar' ? "إرسال واحصل على النقاط" : "Submit & Earn Points", style: const TextStyle(color: Colors.white)), ), ], ), ); } class _StatItem extends StatelessWidget { final String label; final String value; final Color color; const _StatItem( {required this.label, required this.value, required this.color}); @override Widget build(BuildContext context) { return Column( children: [ Text(label, style: TextStyle( fontSize: 10, color: _kOnSurfaceVariant, fontWeight: FontWeight.bold)), Text(value, style: TextStyle( fontSize: 16, color: color, fontWeight: FontWeight.w900)), ], ); } } class _ActionButton extends StatelessWidget { final IconData icon; final String label; final Color color; final Color? iconColor; final VoidCallback onPressed; const _ActionButton( {required this.icon, required this.label, required this.color, this.iconColor, required this.onPressed}); @override Widget build(BuildContext context) { return Column( children: [ IconButton( onPressed: onPressed, icon: Icon(icon, color: iconColor ?? _kOnSurface), style: IconButton.styleFrom( backgroundColor: color, fixedSize: const Size(50, 50)), ), const SizedBox(height: 4), Text(label, style: const TextStyle(fontSize: 11)), ], ); } } class _SpeedBadge extends StatelessWidget { final double speed; const _SpeedBadge({required this.speed}); @override Widget build(BuildContext context) { final int kmh = speed.toInt(); // Exact positioning from HTML (bottom-64 left-6, which is approx 256px from bottom) return Positioned( bottom: 256, left: 24, child: Container( width: 80, height: 80, decoration: BoxDecoration( color: _kSurfaceContainerLowest, shape: BoxShape.circle, border: Border.all(color: _kPrimary, width: 4), boxShadow: const [ BoxShadow( color: Color(0x0F000000), blurRadius: 32, offset: Offset(0, 8)) ], ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('$kmh', style: TextStyle( color: _kOnSurface, fontSize: 28, fontWeight: FontWeight.w900, height: 1.0)), Text('km/h', style: TextStyle( color: _kOnSurfaceVariant, fontSize: 10, fontWeight: FontWeight.w800, letterSpacing: -0.5, )), ], ), ), ); } } // ============================================================================= // SHARED UTILITIES // ============================================================================= class _MapControls extends StatelessWidget { final NavigationController controller; const _MapControls({required this.controller}); @override Widget build(BuildContext context) { return Positioned( right: 16, top: MediaQuery.of(context).size.height * 0.45, child: Column( children: [ _MapFab( icon: Icons.my_location_rounded, iconColor: controller.isCameraLocked ? _kPrimary : Colors.grey[400]!, onTap: () { HapticFeedback.lightImpact(); controller.relockCameraToUser(); }, ), ], ), ); } } class _MapFab extends StatelessWidget { final IconData icon; final Color iconColor; final VoidCallback onTap; const _MapFab( {required this.icon, required this.iconColor, required this.onTap}); @override Widget build(BuildContext context) { return GestureDetector( onTap: onTap, child: ClipRRect( borderRadius: BorderRadius.circular(16), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), child: Container( width: 56, height: 56, decoration: BoxDecoration( color: _kSurfaceContainerLowest.withOpacity(0.95), borderRadius: BorderRadius.circular(16), boxShadow: const [ BoxShadow( color: Color(0x1A000000), blurRadius: 16, offset: Offset(0, 8)) ]), child: Icon(icon, color: iconColor, size: 26), ), ), ), ); } } class _SearchResults extends StatelessWidget { final NavigationController controller; const _SearchResults({required this.controller}); @override Widget build(BuildContext context) { return Positioned( top: MediaQuery.of(context).padding.top + 90, left: 16, right: 16, child: ClipRRect( borderRadius: BorderRadius.circular(24), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20), child: Container( decoration: BoxDecoration( color: _kSurfaceContainerLowest.withOpacity(0.95), borderRadius: BorderRadius.circular(24), border: Border.all(color: Colors.white.withOpacity(0.2)), boxShadow: const [ BoxShadow( color: Color(0x1A000000), blurRadius: 32, offset: Offset(0, 16)) ]), padding: const EdgeInsets.symmetric(vertical: 8), child: ConstrainedBox( constraints: const BoxConstraints(maxHeight: 300), child: ListView.separated( shrinkWrap: true, padding: EdgeInsets.zero, itemCount: controller.placesDestination.length, separatorBuilder: (_, __) => Divider( height: 1, color: _kOutlineVariant.withOpacity(0.2), indent: 72), itemBuilder: (_, i) { final place = controller.placesDestination[i]; final dist = place['distanceKm'] as double?; return InkWell( onTap: () => controller.selectDestination(place), child: Padding( padding: const EdgeInsets.symmetric( horizontal: 20, vertical: 16), child: Row( children: [ Container( width: 40, height: 40, decoration: BoxDecoration( color: _kSurfaceContainerHigh, borderRadius: BorderRadius.circular(12)), child: Icon(Icons.place_rounded, color: _kOnSurfaceVariant, size: 20)), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(place['name'] ?? '', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 16, color: _kOnSurface), maxLines: 1, overflow: TextOverflow.ellipsis), if ((place['address'] ?? '').isNotEmpty) Text(place['address'], style: TextStyle( fontSize: 14, color: _kOnSurfaceVariant), maxLines: 1, overflow: TextOverflow.ellipsis), ], ), ), if (dist != null) ...[ const SizedBox(width: 12), Text('${dist.toStringAsFixed(1)} km', style: TextStyle( color: _kOnSurfaceVariant, fontSize: 14, fontWeight: FontWeight.bold)), ], ], ), ), ); }, ), ), ), ), ), ); } } class _LoadingOverlay extends StatelessWidget { const _LoadingOverlay(); @override Widget build(BuildContext context) { return Positioned.fill( child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 6, sigmaY: 6), child: Container( color: _kPrimaryContainer.withOpacity(0.4), child: Center( child: Container( padding: const EdgeInsets.all(32), decoration: BoxDecoration( color: _kSurfaceContainerLowest, borderRadius: BorderRadius.circular(24), boxShadow: const [ BoxShadow( color: Color(0x33000000), blurRadius: 48, offset: Offset(0, 16)) ]), child: Column( mainAxisSize: MainAxisSize.min, children: [ const CircularProgressIndicator( valueColor: AlwaysStoppedAnimation(Colors.black), strokeWidth: 4), const SizedBox(height: 24), Text('Routing...'.tr, style: TextStyle( color: _kOnSurface, fontSize: 16, fontWeight: FontWeight.w800)), ], ), ), ), ), ), ); } }