import 'dart:math'; import 'dart:ui'; import 'package:Intaleq/constant/colors.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:intaleq_maps/intaleq_maps.dart'; import 'package:Intaleq/env/env.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 ────────────────────────────────────────────── IntaleqMap( apiKey: Env.mapSaasKey, onMapCreated: c.onMapCreated, onLongPress: (pos) => c.onMapLongPressed( Point(0, 0), pos), // Adapted for IntaleqMap API onTap: (pos) => c.onMapTapped(Point(0, 0), pos), markers: c.markers, polylines: c.polylines, circles: c.circles, polygons: c.polygons, mapType: Get.isDarkMode ? IntaleqMapType.normal : IntaleqMapType.light, initialCameraPosition: CameraPosition( target: c.myLocation ?? const LatLng(33.5138, 36.2765), zoom: 16.0), myLocationEnabled: true, ), // ── 2. Top UI (Explore Mode) ────────────────────────────────── if (!c.isNavigating) _ExploreTopUI(controller: c), // ── 3. Top UI (Active Navigation Banner) ────────────────────── if (c.isNavigating && c.currentInstruction.isNotEmpty) _ActiveTopInstruction(controller: c), // ── 4. Explore Action Row (Capsules) ────────────────────────── if (!c.isNavigating) _ExploreActionRow(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 (Bottom) ────────────────────────────── 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(), // ── 10. Location Picker Overlay (Place Creation) ────────────── _LocationPickerOverlay(controller: c), // ── 11. Recenter / Follow Me Button ─────────────────────────── if (!c.isCameraLocked) Positioned( right: 16, bottom: c.isNavigating ? 140 : 250, child: FloatingActionButton.small( onPressed: () => c.relockCameraToUser(), backgroundColor: Colors.white, child: const Icon(Icons.my_location_rounded, color: Color(0xFF0D47A1)), ), ), ], ), ), ), ); } } // ============================================================================= // 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.symmetric(horizontal: 16, vertical: 16), child: ClipRRect( borderRadius: BorderRadius.circular(28), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 16, sigmaY: 16), child: Container( decoration: BoxDecoration( color: _kSurfaceContainerLowest.withOpacity(0.85), borderRadius: BorderRadius.circular(28), border: Border.all( color: Colors.white.withOpacity(0.3), width: 1.5), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.12), blurRadius: 32, offset: const Offset(0, 12), ) ], ), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), child: Row( children: [ IconButton( icon: Icon(Icons.menu_rounded, color: _kOnSurface, size: 26), onPressed: () {}, // Drawer or Menu logic here ), const SizedBox(width: 8), Expanded( child: TextField( controller: controller.placeDestinationController, onChanged: controller.onSearchChanged, textInputAction: TextInputAction.search, style: TextStyle( fontSize: 18, color: _kOnSurface, fontWeight: FontWeight.w700), decoration: InputDecoration( hintText: box.read(BoxName.lang) == 'ar' ? 'إلى أين؟' : 'Where to?', hintStyle: TextStyle( color: _kOnSurfaceVariant.withOpacity(0.7), fontSize: 18, fontWeight: FontWeight.w600), border: InputBorder.none, isDense: true, contentPadding: const EdgeInsets.symmetric(vertical: 12), ), ), ), if (controller .placeDestinationController.text.isNotEmpty || controller.routes.isNotEmpty) Container( decoration: const BoxDecoration( color: Color(0xFFFFEBEE), shape: BoxShape.circle, ), child: IconButton( icon: const Icon(Icons.close_rounded), color: Colors.red, onPressed: () => controller.clearEverything(), ), ) else // Avatar const Padding( padding: EdgeInsets.only(right: 4), child: CircleAvatar( radius: 20, backgroundColor: Color(0xFF0D47A1), child: Icon(Icons.person_rounded, color: Colors.white, size: 22), ), ), ], ), ), ), ), ), ], ), ), ); } } 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; if (!hasRoutes && controller.recentLocations.isEmpty) { return const SizedBox.shrink(); } 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 + 8), 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: [ // Minimal Drag Handle Container( width: 32, height: 3, margin: const EdgeInsets.only(top: 8, bottom: 8), decoration: BoxDecoration( color: _kOutlineVariant.withOpacity(0.3), 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: 12), // Start Button Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Material( color: Colors.transparent, child: InkWell( onTap: () { HapticFeedback.mediumImpact(); controller.isNavigating = true; controller.relockCameraToUser(); controller.update(); }, borderRadius: BorderRadius.circular(16), child: Ink( padding: const EdgeInsets.symmetric(vertical: 16), decoration: BoxDecoration( gradient: const LinearGradient( colors: [Color(0xFF0D47A1), Color(0xFF1565C0)], ), borderRadius: BorderRadius.circular(16), boxShadow: [ 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: 20), const SizedBox(width: 8), Text( isArabic ? 'ابدأ الملاحة' : 'Start Navigation', style: const TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.w800)), ], ), ), ), ), ), ] else if (!hasRoutes) ...[ const SizedBox.shrink(), ], ], ), ), ), ), ); } } 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 _ActiveTopInstruction extends StatelessWidget { final NavigationController controller; const _ActiveTopInstruction({required this.controller}); @override Widget build(BuildContext context) { return Positioned( top: MediaQuery.of(context).padding.top + 12, left: 16, right: 16, child: ClipRRect( borderRadius: BorderRadius.circular(24), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 16, sigmaY: 16), child: Container( decoration: BoxDecoration( gradient: const LinearGradient( colors: [Color(0xFF1B5E20), Color(0xFF2E7D32)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(24), border: Border.all(color: Colors.white.withOpacity(0.2), width: 1), boxShadow: const [ BoxShadow( color: Color(0x33000000), blurRadius: 24, offset: Offset(0, 12)) ], ), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), child: Row( children: [ // Direction indicator Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), shape: BoxShape.circle, ), child: Icon(controller.currentManeuverIcon, color: Colors.white, size: 36), ), 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: 20, fontWeight: FontWeight.w800, letterSpacing: -0.5), maxLines: 2, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 2), Text( controller.distanceToNextStep, style: TextStyle( color: Colors.white.withOpacity(0.9), fontSize: 16, fontWeight: FontWeight.w700), ), ], ), ), ], ), ), ), ), ); } } 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 = box.read(BoxName.lang) == 'ar'; return Positioned( bottom: bottomPad + 16, left: 16, right: 16, child: ClipRRect( borderRadius: BorderRadius.circular(32), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 16, sigmaY: 16), child: Container( decoration: BoxDecoration( color: _kSurfaceContainerLowest.withOpacity(0.85), borderRadius: BorderRadius.circular(32), border: Border.all(color: Colors.white.withOpacity(0.4), width: 1.5), boxShadow: const [ BoxShadow( color: Color(0x14000000), blurRadius: 24, offset: Offset(0, 12)) ], ), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), child: Column( mainAxisSize: MainAxisSize.min, children: [ // Stats Row Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Text( controller.arrivalTime, style: const TextStyle( fontSize: 22, fontWeight: FontWeight.w900, color: Color(0xFF0D47A1), ), ), Container( width: 1.5, height: 24, color: _kOutlineVariant.withOpacity(0.3)), Text( isArabic ? '${controller.estimatedTimeRemaining} دقيقة' : '${controller.estimatedTimeRemaining} min', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w700, color: _kOnSurface, ), ), Container( width: 1.5, height: 24, color: _kOutlineVariant.withOpacity(0.3)), Text( controller.distanceWithUnit, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w700, color: _kOnSurfaceVariant, ), ), ], ), const SizedBox(height: 16), // Action Buttons Row Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ _IconBtn( icon: Icons.report_problem_rounded, bgColor: const Color(0xFFFFF3E0), iconColor: Colors.orange.shade800, onTap: () {}, ), _IconBtn( icon: controller.isMuted ? Icons.volume_off_rounded : Icons.volume_up_rounded, bgColor: const Color(0xFFE3F2FD), iconColor: const Color(0xFF1976D2), onTap: () => controller.toggleMute(), ), _IconBtn( icon: Icons.add_rounded, bgColor: const Color(0xFFE8F5E9), iconColor: const Color(0xFF388E3C), onTap: () => controller.togglePlaceSelectionMode(), ), Expanded( child: Padding( padding: const EdgeInsets.only(left: 16), child: GestureDetector( onTap: () => controller.clearRoute(), child: Container( height: 48, decoration: BoxDecoration( gradient: const LinearGradient( colors: [Color(0xFFE53935), Color(0xFFC62828)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(24), boxShadow: [ BoxShadow( color: Colors.red.withOpacity(0.3), blurRadius: 12, offset: const Offset(0, 6), ) ], ), child: Center( child: Text( isArabic ? 'إنهاء الملاحة' : 'End Route', style: const TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold, letterSpacing: 0.5, ), ), ), ), ), ), ), ], ), ], ), ), ), ), ); } } class _IconBtn extends StatelessWidget { final IconData icon; final Color bgColor; final Color iconColor; final VoidCallback onTap; const _IconBtn( {required this.icon, required this.bgColor, required this.iconColor, required this.onTap}); @override Widget build(BuildContext context) { return GestureDetector( onTap: () { HapticFeedback.lightImpact(); onTap(); }, child: Container( width: 48, height: 48, margin: const EdgeInsets.only(right: 12), decoration: BoxDecoration( color: bgColor, shape: BoxShape.circle, ), child: Icon(icon, color: iconColor, size: 24), ), ); } } void _showAddPlaceFormDialog( BuildContext context, NavigationController controller) { final nameController = TextEditingController(); final categoryNotifier = ValueNotifier?>(null); final isAr = box.read(BoxName.lang) == 'ar'; Get.dialog( AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)), backgroundColor: _kSurfaceContainerLowest, title: Row( children: [ const Icon(Icons.add_business_rounded, color: Color(0xFF0D47A1), size: 28), const SizedBox(width: 12), Text(isAr ? 'إضافة مكان جديد' : 'Add New Place', style: TextStyle(color: _kOnSurface, fontWeight: FontWeight.bold)), ], ), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( isAr ? "ساهم في تحسين الخريطة بإضافة الأماكن الناقصة." : "Help improve the map by adding missing places.", style: TextStyle(color: _kOnSurfaceVariant, fontSize: 13), ), const SizedBox(height: 20), TextField( controller: nameController, style: TextStyle(color: _kOnSurface), decoration: InputDecoration( labelText: isAr ? 'اسم المكان' : 'Place Name', labelStyle: TextStyle(color: _kOnSurfaceVariant), prefixIcon: Icon(Icons.label_rounded, color: _kOnSurfaceVariant), filled: true, fillColor: _kSurfaceContainerHigh.withOpacity(0.3), border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), ), ), const SizedBox(height: 16), // Category Picker Trigger ValueListenableBuilder?>( valueListenable: categoryNotifier, builder: (context, selected, _) { return InkWell( onTap: () => _showCategoryPicker(context, (cat) { categoryNotifier.value = cat; }), borderRadius: BorderRadius.circular(12), child: Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 16), decoration: BoxDecoration( color: _kSurfaceContainerHigh.withOpacity(0.3), borderRadius: BorderRadius.circular(12), border: Border.all( color: _kOutlineVariant.withOpacity(0.5)), ), child: Row( children: [ Icon(Icons.category_rounded, color: _kOnSurfaceVariant), const SizedBox(width: 12), Expanded( child: Text( selected != null ? (isAr ? selected['ar']! : selected['en']!) : (isAr ? 'اختر الفئة' : 'Select Category'), style: TextStyle( color: selected != null ? _kOnSurface : _kOnSurfaceVariant, fontSize: 16, ), ), ), Icon(Icons.keyboard_arrow_down_rounded, color: _kOnSurfaceVariant), ], ), ), ); }), const SizedBox(height: 24), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF0D47A1), padding: const EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12)), ), onPressed: () { if (nameController.text.isNotEmpty && categoryNotifier.value != null) { Get.back(); controller.submitNewPlace( nameController.text, categoryNotifier.value!['id']!); } else { mySnackbarWarning( isAr ? 'يرجى إكمال البيانات' : 'Please fill all fields'); } }, child: Text(isAr ? 'إرسال' : 'Submit', style: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold)), ), TextButton( onPressed: () => Get.back(), child: Text(isAr ? 'إلغاء' : 'Cancel', style: TextStyle(color: _kOnSurfaceVariant)), ), ], ), ), ), ); } void _showCategoryPicker( BuildContext context, Function(Map) onSelected) { final isAr = box.read(BoxName.lang) == 'ar'; Get.bottomSheet( Container( decoration: BoxDecoration( color: _kSurfaceContainerLowest, borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), ), padding: const EdgeInsets.only(top: 12, bottom: 24), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 40, height: 4, decoration: BoxDecoration( color: _kOutlineVariant.withOpacity(0.3), borderRadius: BorderRadius.circular(2), ), ), const SizedBox(height: 16), Text(isAr ? 'اختر الفئة' : 'Select Category', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: _kOnSurface)), const SizedBox(height: 16), Flexible( child: ListView.builder( shrinkWrap: true, itemCount: NavigationController.placeCategories.length, itemBuilder: (context, index) { final cat = NavigationController.placeCategories[index]; return ListTile( leading: Icon(_getIconData(cat['icon']!), color: const Color(0xFF0D47A1)), title: Text(isAr ? cat['ar']! : cat['en']!, style: TextStyle( color: _kOnSurface, fontWeight: FontWeight.w600)), onTap: () { HapticFeedback.lightImpact(); onSelected(cat); Get.back(); }, ); }, ), ), ], ), ), isScrollControlled: true, ); } IconData _getIconData(String name) { switch (name) { case 'restaurant': return Icons.restaurant_rounded; case 'coffee': return Icons.coffee_rounded; case 'shopping_basket': return Icons.shopping_basket_rounded; case 'local_pharmacy': return Icons.local_pharmacy_rounded; case 'local_gas_station': return Icons.local_gas_station_rounded; case 'atm': return Icons.atm_rounded; case 'account_balance': return Icons.account_balance_rounded; case 'mosque': return Icons.mosque_rounded; case 'local_hospital': return Icons.local_hospital_rounded; case 'school': return Icons.school_rounded; case 'park': return Icons.park_rounded; case 'hotel': return Icons.hotel_rounded; case 'shopping_mall': return Icons.store_rounded; case 'fitness_center': return Icons.fitness_center_rounded; case 'content_cut': return Icons.content_cut_rounded; case 'bakery_dining': return Icons.bakery_dining_rounded; case 'local_laundry_service': return Icons.local_laundry_service_rounded; case 'build': return Icons.build_rounded; case 'gavel': return Icons.gavel_rounded; default: return Icons.place_rounded; } } class _LocationPickerOverlay extends StatelessWidget { final NavigationController controller; const _LocationPickerOverlay({required this.controller}); @override Widget build(BuildContext context) { if (!controller.isSelectingPlaceLocation) return const SizedBox.shrink(); final isAr = box.read(BoxName.lang) == 'ar'; return Stack( children: [ // Dim the background slightly - Non-blocking IgnorePointer( child: Container(color: Colors.black.withOpacity(0.1)), ), // Center Crosshair/Pointer - Non-blocking IgnorePointer( child: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.white.withOpacity(0.9), shape: BoxShape.circle, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.2), blurRadius: 10, offset: const Offset(0, 4)) ], ), child: const Icon(Icons.add_location_alt_rounded, color: Color(0xFF0D47A1), size: 40), ), const SizedBox(height: 40), ], ), ), ), // Confirm Button Positioned( bottom: 110, left: 32, right: 32, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF1B5E20), padding: const EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16)), elevation: 8, shadowColor: const Color(0xFF1B5E20).withOpacity(0.5), ), onPressed: () => _showAddPlaceFormDialog(context, controller), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.check_circle_rounded, color: Colors.white), const SizedBox(width: 12), Text( isAr ? 'تأكيد الموقع' : 'Confirm Location', style: const TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.w800), ), ], ), ), ), // Help Tooltip Positioned( top: 140, left: 40, right: 40, child: IgnorePointer( child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), decoration: BoxDecoration( color: Colors.black.withOpacity(0.7), borderRadius: BorderRadius.circular(30), ), child: Text( isAr ? "حرك الخريطة لتحديد موقع المكان" : "Move map to pick place location", textAlign: TextAlign.center, style: const TextStyle(color: Colors.white, fontSize: 13), ), ), ), ), // Cancel Button Positioned( top: 60, right: 20, child: FloatingActionButton.small( backgroundColor: Colors.white, elevation: 4, child: const Icon(Icons.close_rounded, color: Colors.black87), onPressed: () => controller.togglePlaceSelectionMode(), ), ), ], ); } } 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(); // Dynamic border color based on speed final Color borderColor = kmh > 80 ? const Color(0xFFFF5252) : const Color(0xFFE53935); // Positioned at the bottom (above the HUD) return Positioned( bottom: 120, // Above the _ActiveBottomHUD left: 24, child: Container( width: 80, height: 80, decoration: BoxDecoration( color: _kSurfaceContainerLowest, shape: BoxShape.circle, border: Border.all(color: borderColor, width: 4), // Red border boxShadow: const [ BoxShadow( color: Color(0x0F000000), blurRadius: 32, offset: Offset(0, 8)) ], ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('$kmh', style: TextStyle( color: borderColor, // Speed number in red 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 _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: 50, // Slightly smaller height: 50, 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: 24), ), ), ), ); } } class _ExploreActionRow extends StatelessWidget { final NavigationController controller; const _ExploreActionRow({required this.controller}); @override Widget build(BuildContext context) { final bool hasRoutes = controller.routes.isNotEmpty; final bool hasRecents = controller.recentLocations.isNotEmpty; final isAr = box.read(BoxName.lang) == 'ar'; final double safeBottom = MediaQuery.of(context).padding.bottom; final double bottomOffset = safeBottom + 20; return AnimatedPositioned( duration: const Duration(milliseconds: 300), curve: Curves.easeOutCubic, bottom: bottomOffset, left: 0, right: 0, child: Container( color: Colors.transparent, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.end, children: [ // Row 1: Favorites SingleChildScrollView( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 16), physics: const BouncingScrollPhysics(), child: Row( children: [ _ActionCapsule( icon: Icons.add_rounded, label: isAr ? 'إضافة' : 'Add', onTap: () => controller.togglePlaceSelectionMode(), isPrimary: true, ), _ActionCapsule( icon: Icons.home_rounded, label: isAr ? 'المنزل' : 'Home', onTap: () => controller.goToFavorite('home'), ), _ActionCapsule( icon: Icons.work_rounded, label: isAr ? 'العمل' : 'Work', onTap: () => controller.goToFavorite('work'), ), _ActionCapsule( icon: Icons.bookmark_rounded, label: isAr ? 'المحفوظات' : 'Saved', onTap: () {}, ), _ActionCapsule( icon: Icons.flight_rounded, label: isAr ? 'المطار' : 'Airport', onTap: () => controller.goToFavorite('airport'), ), const SizedBox(width: 8), _MapFab( icon: Icons.my_location_rounded, iconColor: controller.isCameraLocked ? const Color(0xFF0D47A1) : Colors.grey[400]!, onTap: () { HapticFeedback.lightImpact(); controller.relockCameraToUser(); }, ), ], ), ), // Row 2: Recent History (if any) if (hasRecents && !hasRoutes) ...[ const SizedBox(height: 12), SingleChildScrollView( scrollDirection: Axis.horizontal, physics: const BouncingScrollPhysics(), child: Row( children: controller.recentLocations.take(5).map((place) { return _ActionCapsule( icon: Icons.history_rounded, label: place['name'] ?? '', onTap: () => controller.selectDestination(place), isRecent: true, ); }).toList(), ), ), ], const SizedBox(height: 12), ], ), ), ); } } class _ActionCapsule extends StatelessWidget { final IconData icon; final String label; final VoidCallback onTap; final bool isPrimary; final bool isRecent; const _ActionCapsule({ required this.icon, required this.label, required this.onTap, this.isPrimary = false, this.isRecent = false, }); @override Widget build(BuildContext context) { Color bgColor; Color textColor; if (isPrimary) { bgColor = const Color(0xFF0D47A1).withOpacity(0.9); textColor = Colors.white; } else if (isRecent) { bgColor = _kSurfaceContainerHigh.withOpacity(0.8); textColor = _kOnSurfaceVariant; } else { bgColor = _kSurfaceContainerLowest.withOpacity(0.85); textColor = _kOnSurface; } return Padding( padding: const EdgeInsets.only(right: 8), child: GestureDetector( onTap: () { HapticFeedback.lightImpact(); onTap(); }, child: ClipRRect( borderRadius: BorderRadius.circular(50), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 12, sigmaY: 12), child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( color: bgColor, borderRadius: BorderRadius.circular(50), border: Border.all( color: isRecent ? Colors.transparent : Colors.white.withOpacity(0.2), width: 1.2), boxShadow: const [ BoxShadow( color: Color(0x14000000), blurRadius: 8, offset: Offset(0, 4)) ], ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, size: 18, color: textColor), const SizedBox(width: 8), Text( label, style: TextStyle( fontSize: 14, fontWeight: FontWeight.w700, color: textColor, ), ), ], ), ), ), ), ), ); } } 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)), ], ), ), ), ), ), ); } }