import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../../../constant/box_name.dart'; import '../../../controller/home/map/location_search_controller.dart'; import '../../../controller/home/map/map_engine_controller.dart'; import '../../../controller/home/map/ride_lifecycle_controller.dart'; import '../../../main.dart'; import '../../widgets/elevated_btn.dart'; import '../../widgets/my_textField.dart'; import 'rp_actions.dart'; import 'rp_favorites.dart'; import 'rp_location_field.dart'; import 'rp_map_pick_overlay.dart'; import 'rp_theme.dart'; import 'rp_search_dialog.dart'; /// The route-planning surface anchored to the bottom of the map. /// /// • While the user drags the map to place a point → [RpMapPickOverlay]. /// • Otherwise → a collapsed "Where to?" card or the full planner. /// /// Drop-in replacement for the old `MainBottomMenuMap`. Uses only the /// existing controller APIs; nothing in the controllers changed. class RoutePlannerSheet extends StatelessWidget { const RoutePlannerSheet({super.key}); @override Widget build(BuildContext context) { return GetBuilder( builder: (mapEngine) { if (mapEngine.isPickerShown) return const RpMapPickOverlay(); return Positioned( left: 14, right: 14, bottom: Get.height * .03, child: AnimatedContainer( duration: RP.medium, curve: RP.ease, constraints: BoxConstraints( maxHeight: mapEngine.isMainBottomMenuMap ? Get.height * 0.42 : Get.height * 0.72, ), decoration: BoxDecoration( color: RP.sheet, borderRadius: BorderRadius.circular(RP.rSheet), boxShadow: RP.sheetShadow, border: Border.all(color: RP.divider), ), clipBehavior: Clip.antiAlias, child: SingleChildScrollView( physics: const BouncingScrollPhysics(), child: mapEngine.isMainBottomMenuMap ? const _Collapsed() : const _Expanded(), ), ), ); }, ); } } // ══════════════════════════════════════════════════════════════════════════ // COLLAPSED // ══════════════════════════════════════════════════════════════════════════ class _Collapsed extends StatelessWidget { const _Collapsed(); @override Widget build(BuildContext context) { final String firstName = (box.read(BoxName.name)?.toString().split(' ').first ?? '').trim(); final mapEngine = Get.find(); final rideLifecycle = Get.find(); return GetBuilder( builder: (locationSearch) { return Column( mainAxisSize: MainAxisSize.min, children: [ const SizedBox(height: 12), RP.grabber(), const SizedBox(height: 14), InkWell( onTap: mapEngine.changeMainBottomMenuMap, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), child: Row( children: [ Container( width: 48, height: 48, decoration: BoxDecoration( color: RP.brand, borderRadius: BorderRadius.circular(16), boxShadow: RP.glow(RP.brand), ), child: const Icon(Icons.search_rounded, color: Colors.white, size: 24), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text.rich( TextSpan(children: [ TextSpan( text: '${'Where to'.tr} ', style: TextStyle( fontSize: 16.5, fontWeight: FontWeight.w700, color: RP.textStrong), ), if (firstName.isNotEmpty) TextSpan( text: firstName, style: TextStyle( fontSize: 16.5, fontWeight: FontWeight.w800, color: RP.brand), ), const TextSpan(text: '؟'), ]), ), if (!rideLifecycle.noCarString) ...[ const SizedBox(height: 2), Text('Tap to search your destination'.tr, style: TextStyle(fontSize: 12, color: RP.textMuted)), ], ], ), ), Icon(Icons.keyboard_arrow_up_rounded, color: RP.textMuted, size: 26), ], ), ), ), const SizedBox(height: 14), RpRecentsRow(locationSearch: locationSearch), const SizedBox(height: 16), ], ); }, ); } } // ══════════════════════════════════════════════════════════════════════════ // EXPANDED // ══════════════════════════════════════════════════════════════════════════ class _Expanded extends StatelessWidget { const _Expanded(); @override Widget build(BuildContext context) { final mapEngine = Get.find(); return GetBuilder( builder: (s) { final rideLifecycle = Get.find(); final bool isOther = rideLifecycle.isAnotherOreder; return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const SizedBox(height: 12), Center(child: RP.grabber()), // Header Padding( padding: const EdgeInsets.fromLTRB(20, 14, 14, 6), child: Row( children: [ Text('Plan Your Route'.tr, style: RP.title), const Spacer(), _CircleIconButton( icon: Icons.keyboard_arrow_down_rounded, onTap: mapEngine.changeMainBottomMenuMap, ), ], ), ), // Order-type toggle Padding( padding: const EdgeInsets.fromLTRB(16, 6, 16, 4), child: _OrderTypeToggle(isOther: isOther), ), // Route timeline Padding( padding: const EdgeInsets.fromLTRB(16, 10, 16, 4), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _originField(s, isOther), const SizedBox(height: 10), ...List.generate( s.activeMenuWaypointCount, (i) => Padding( padding: const EdgeInsets.only(bottom: 10), child: _StopRow(index: i, s: s), ), ), if (s.activeMenuWaypointCount < 2) ...[ _AddStopButton(onTap: RpActions.addStop), const SizedBox(height: 10), ], _destinationField(s), ], ), ), const SizedBox(height: 6), _Divider(), // Quick access _SectionHeader( title: 'Quick Access'.tr, trailing: const RpFavoritesButton(), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( children: [ Expanded(child: _homeWorkButton(isWork: false)), const SizedBox(width: 12), Expanded(child: _homeWorkButton(isWork: true)), ], ), ), const SizedBox(height: 14), RpRecentsRow(locationSearch: s), const SizedBox(height: 6), _Divider(), // Advanced _SectionHeader(title: 'Advanced Tools'.tr), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: _WhatsAppLinkButton(locationSearch: s), ), const SizedBox(height: 22), ], ); }, ); } // ── Origin ────────────────────────────────────────────────────────────── Widget _originField(LocationSearchController s, bool isOther) { if (isOther) { return RpLocationField( accent: RP.origin, icon: Icons.person_pin_circle_rounded, controller: s.placeStartController, hintText: 'Search for a starting point'.tr, results: s.placesStart, onQuery: (_) => s.getPlacesStart(), onEmpty: s.clearPlacesStart, onPickOnMap: RpActions.pickOtherPickupOnMap, onResultTap: (i) => RpActions.handleLocationSelection(Get.context!, i, s.placesStart[i], true), searchable: false, onTapField: () { RpSearchDialog.show( context: Get.context!, accent: RP.origin, hintText: 'Search for a starting point'.tr, isOrigin: true, ); }, ); } // Regular order: origin = current location, changeable on the map. return RpLocationField( accent: RP.origin, icon: Icons.my_location_rounded, controller: s.placeStartController, hintText: s.currentLocationString, results: const [], onPickOnMap: RpActions.pickMyStartOnMap, onResultTap: (_) {}, searchable: false, onQuery: (_) {}, onEmpty: () {}, onTapField: () { RpSearchDialog.show( context: Get.context!, accent: RP.origin, hintText: s.currentLocationString, isOrigin: true, ); }, ); } // ── Destination ───────────────────────────────────────────────────────── Widget _destinationField(LocationSearchController s) { final mapEngine = Get.find(); return RpLocationField( accent: RP.destination, icon: Icons.flag_rounded, controller: s.placeDestinationController, hintText: s.hintTextDestinationPoint, results: s.placesDestination, onQuery: (_) { s.getPlaces(); mapEngine.changeHeightPlaces(); }, onEmpty: () { s.clearPlacesDestination(); mapEngine.changeHeightPlaces(); }, onPickOnMap: RpActions.pickDestinationOnMap, onResultTap: (i) => RpActions.handleLocationSelection(Get.context!, i, s.placesDestination[i], false), searchable: false, onTapField: () { RpSearchDialog.show( context: Get.context!, accent: RP.destination, hintText: s.hintTextDestinationPoint, isOrigin: false, ); }, onResultFavorite: (i) { final res = s.placesDestination[i]; RpActions.addFavorite( Get.context!, res['latitude'], res['longitude'], (res['name_ar'] ?? res['name'] ?? 'Unknown Place').toString(), ); }, ); } Widget _homeWorkButton({required bool isWork}) { final String saved = isWork ? (box.read(BoxName.addWork)?.toString() ?? 'addWork') : (box.read(BoxName.addHome)?.toString() ?? 'addHome'); final bool isSet = isWork ? saved != 'addWork' : saved != 'addHome'; final Color accent = isWork ? const Color(0xFF2563EB) : const Color(0xFFF59E0B); return _QuickTile( icon: isWork ? Icons.work_rounded : Icons.home_rounded, accent: accent, label: isWork ? (isSet ? 'To Work'.tr : 'Add Work'.tr) : (isSet ? 'To Home'.tr : 'Add Home'.tr), onTap: () { if (isSet) { RpActions.goToSaved( isWork ? BoxName.addWork : BoxName.addHome, isWork ? 'To Work' : 'To Home'); } else { RpActions.pickHomeOrWork(isWork: isWork); } }, onLongPress: () => RpActions.changeHomeOrWork(isWork: isWork), ); } } // ── Stop row ──────────────────────────────────────────────────────────────── class _StopRow extends StatelessWidget { final int index; final LocationSearchController s; const _StopRow({required this.index, required this.s}); @override Widget build(BuildContext context) { final Color accent = RP.stopColor(index); final bool isSet = s.menuWaypoints[index] != null; final String name = isSet ? s.menuWaypointNames[index] : '${'Stop'.tr} ${index + 1}'; return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), decoration: BoxDecoration( color: RP.fieldFill, borderRadius: BorderRadius.circular(RP.rField), border: Border.all( color: isSet ? accent.withValues(alpha: 0.45) : Colors.transparent, width: 1.4, ), ), child: Row( children: [ Container( width: 34, height: 34, decoration: BoxDecoration( color: accent, shape: BoxShape.circle, boxShadow: RP.glow(accent), ), child: Center( child: Text('${index + 1}', style: const TextStyle( color: Colors.white, fontSize: 13, fontWeight: FontWeight.w800)), ), ), const SizedBox(width: 10), Expanded( child: InkWell( onTap: () => RpActions.pickStopOnMap(index), child: Text( name, maxLines: 1, overflow: TextOverflow.ellipsis, style: isSet ? RP.fieldValue : RP.fieldHint, ), ), ), InkWell( onTap: () => RpActions.removeStop(index), borderRadius: BorderRadius.circular(RP.rChip), child: Container( width: 34, height: 34, alignment: Alignment.center, child: Icon(Icons.delete_outline_rounded, color: RP.destination, size: 19), ), ), InkWell( onTap: () => RpActions.pickStopOnMap(index), borderRadius: BorderRadius.circular(RP.rChip), child: Container( width: 34, height: 34, decoration: BoxDecoration( color: accent.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(RP.rChip), ), child: Icon(Icons.map_rounded, color: accent, size: 19), ), ), ], ), ); } } class _AddStopButton extends StatelessWidget { final VoidCallback onTap; const _AddStopButton({required this.onTap}); @override Widget build(BuildContext context) { return InkWell( onTap: onTap, borderRadius: BorderRadius.circular(RP.rField), child: Container( padding: const EdgeInsets.symmetric(vertical: 13), decoration: BoxDecoration( borderRadius: BorderRadius.circular(RP.rField), border: Border.all( color: RP.stop1.withValues(alpha: 0.5), width: 1.4, style: BorderStyle.solid), color: RP.stop1.withValues(alpha: 0.06), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.add_location_alt_outlined, color: RP.stop1, size: 18), const SizedBox(width: 8), Text('Add a Stop'.tr, style: TextStyle( color: RP.stop1, fontSize: 13.5, fontWeight: FontWeight.w700)), ], ), ), ); } } // ── Order-type toggle ──────────────────────────────────────────────────────── class _OrderTypeToggle extends StatelessWidget { final bool isOther; const _OrderTypeToggle({required this.isOther}); @override Widget build(BuildContext context) { final mapEngine = Get.find(); final rideLifecycle = Get.find(); void select(bool other) { if (mapEngine.isAnotherOreder == other) return; mapEngine.changeisAnotherOreder(other); rideLifecycle.isAnotherOreder = other; // The expanded planner rebuilds on LocationSearchController updates, so // refresh it here to swap the origin field (self ⇄ other) instantly. Get.find().update(); } return Container( padding: const EdgeInsets.all(4), decoration: BoxDecoration( color: RP.chipFill, borderRadius: BorderRadius.circular(RP.rField), ), child: Row( children: [ _seg( selected: !isOther, icon: Icons.person_rounded, label: 'Order for myself'.tr, onTap: () => select(false), ), const SizedBox(width: 4), _seg( selected: isOther, icon: Icons.group_rounded, label: 'Order for someone else'.tr, onTap: () => select(true), ), ], ), ); } Widget _seg({ required bool selected, required IconData icon, required String label, required VoidCallback onTap, }) { return Expanded( child: Material( color: Colors.transparent, child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(RP.rChip), child: AnimatedContainer( duration: RP.fast, padding: const EdgeInsets.symmetric(vertical: 11), decoration: BoxDecoration( color: selected ? const Color(0xFF4F46E5) : Colors.transparent, borderRadius: BorderRadius.circular(RP.rChip), boxShadow: selected ? RP.glow(const Color(0xFF4F46E5), intensity: 0.35) : null, ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(icon, size: 16, color: selected ? Colors.white : RP.textMuted), const SizedBox(width: 7), Flexible( child: Text( label, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 12.5, fontWeight: selected ? FontWeight.w700 : FontWeight.w600, color: selected ? Colors.white : RP.textBody, ), ), ), ], ), ), ), ), ); } } // ── Quick tile (home/work) ─────────────────────────────────────────────────── class _QuickTile extends StatelessWidget { final IconData icon; final Color accent; final String label; final VoidCallback onTap; final VoidCallback? onLongPress; const _QuickTile({ required this.icon, required this.accent, required this.label, required this.onTap, this.onLongPress, }); @override Widget build(BuildContext context) { return InkWell( onTap: onTap, onLongPress: onLongPress, borderRadius: BorderRadius.circular(RP.rField), child: Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 13), decoration: BoxDecoration( color: accent.withValues(alpha: 0.10), borderRadius: BorderRadius.circular(RP.rField), ), child: Row( children: [ Icon(icon, color: accent, size: 20), const SizedBox(width: 10), Flexible( child: Text(label, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( color: accent, fontSize: 13.5, fontWeight: FontWeight.w700)), ), ], ), ), ); } } // ── WhatsApp link ───────────────────────────────────────────────────────────── class _WhatsAppLinkButton extends StatelessWidget { final LocationSearchController locationSearch; const _WhatsAppLinkButton({required this.locationSearch}); @override Widget build(BuildContext context) { return InkWell( borderRadius: BorderRadius.circular(RP.rField), onTap: () { Get.dialog( AlertDialog( backgroundColor: RP.sheetElevated, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(RP.rCard)), title: Text('WhatsApp Location Extractor'.tr, style: RP.title), content: Form( key: locationSearch.sosFormKey, child: Column( mainAxisSize: MainAxisSize.min, children: [ MyTextForm( controller: locationSearch.whatsAppLocationText, label: 'Location Link'.tr, type: TextInputType.url, hint: 'https://maps.app.goo.gl/...'), const SizedBox(height: 16), MyElevatedButton( title: 'Go to this location'.tr, onPressed: () => locationSearch.goToWhatappLocation()), ], ), ), ), ); }, child: Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14), decoration: BoxDecoration( color: const Color(0xFF25D366).withValues(alpha: 0.10), borderRadius: BorderRadius.circular(RP.rField), ), child: Row( children: [ const Icon(Icons.link_rounded, color: Color(0xFF1FA855), size: 20), const SizedBox(width: 12), Expanded( child: Text('Paste WhatsApp location link'.tr, style: const TextStyle( color: Color(0xFF1FA855), fontSize: 13.5, fontWeight: FontWeight.w700)), ), ], ), ), ); } } // ── Small shared bits ───────────────────────────────────────────────────────── class _SectionHeader extends StatelessWidget { final String title; final Widget? trailing; const _SectionHeader({required this.title, this.trailing}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.fromLTRB(20, 4, 12, 10), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(title.toUpperCase(), style: RP.sectionLabel), if (trailing != null) trailing!, ], ), ); } } class _Divider extends StatelessWidget { @override Widget build(BuildContext context) { return Container( height: 1, margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), color: RP.divider, ); } } class _CircleIconButton extends StatelessWidget { final IconData icon; final VoidCallback onTap; const _CircleIconButton({required this.icon, required this.onTap}); @override Widget build(BuildContext context) { return InkWell( onTap: onTap, borderRadius: BorderRadius.circular(RP.rPill), child: Container( width: 38, height: 38, decoration: BoxDecoration(color: RP.fieldFill, shape: BoxShape.circle), child: Icon(icon, size: 24, color: RP.textBody), ), ); } }