From 8353c4cef413782c4abd98f2d58ce70a6ff52a01 Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Sat, 11 Jul 2026 19:26:05 +0300 Subject: [PATCH] Update: 2026-07-11 19:26:05 --- .../home/route_planner/rp_location_field.dart | 6 +- .../home/route_planner/rp_search_dialog.dart | 171 ++++++++++++++++++ .../views/home/route_planner/rp_sheet.dart | 31 +++- 3 files changed, 205 insertions(+), 3 deletions(-) create mode 100644 siro_rider/lib/views/home/route_planner/rp_search_dialog.dart diff --git a/siro_rider/lib/views/home/route_planner/rp_location_field.dart b/siro_rider/lib/views/home/route_planner/rp_location_field.dart index e8a3224f..a6cb9ca2 100644 --- a/siro_rider/lib/views/home/route_planner/rp_location_field.dart +++ b/siro_rider/lib/views/home/route_planner/rp_location_field.dart @@ -40,6 +40,9 @@ class RpLocationField extends StatefulWidget { /// Optional trailing "remove" affordance (used by stops). final VoidCallback? onRemove; + /// Optional callback to fire when tapping the text field. + final VoidCallback? onTapField; + /// Whether the field participates in search. When false it behaves as a /// read-only value chip that still opens the map picker on tap. final bool searchable; @@ -58,6 +61,7 @@ class RpLocationField extends StatefulWidget { this.badge, this.onResultFavorite, this.onRemove, + this.onTapField, this.searchable = true, }); @@ -123,7 +127,7 @@ class _RpLocationFieldState extends State { controller: widget.controller, onChanged: widget.searchable ? _onChanged : null, readOnly: !widget.searchable, - onTap: widget.searchable ? null : widget.onPickOnMap, + onTap: widget.onTapField ?? (widget.searchable ? null : widget.onPickOnMap), style: RP.fieldValue, cursorColor: widget.accent, textInputAction: TextInputAction.search, diff --git a/siro_rider/lib/views/home/route_planner/rp_search_dialog.dart b/siro_rider/lib/views/home/route_planner/rp_search_dialog.dart new file mode 100644 index 00000000..390bf3b0 --- /dev/null +++ b/siro_rider/lib/views/home/route_planner/rp_search_dialog.dart @@ -0,0 +1,171 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:siro_rider/controller/home/map/location_search_controller.dart'; +import 'package:siro_rider/controller/home/map/map_engine_controller.dart'; +import 'package:siro_rider/controller/home/map/ride_lifecycle_controller.dart'; + +import 'rp_actions.dart'; +import 'rp_location_field.dart'; +import 'rp_search_results.dart'; +import 'rp_theme.dart'; + +class RpSearchDialog extends StatelessWidget { + final Color accent; + final String hintText; + final bool isOrigin; + + const RpSearchDialog({ + super.key, + required this.accent, + required this.hintText, + required this.isOrigin, + }); + + static void show({ + required BuildContext context, + required Color accent, + required String hintText, + required bool isOrigin, + }) { + showGeneralDialog( + context: context, + barrierDismissible: true, + barrierLabel: 'Search', + barrierColor: RP.sheet, + transitionDuration: const Duration(milliseconds: 300), + pageBuilder: (context, animation, secondaryAnimation) { + return RpSearchDialog( + accent: accent, + hintText: hintText, + isOrigin: isOrigin, + ); + }, + transitionBuilder: (context, animation, secondaryAnimation, child) { + return SlideTransition( + position: Tween( + begin: const Offset(0, 1), + end: Offset.zero, + ).animate(CurvedAnimation( + parent: animation, + curve: Curves.easeOutCubic, + )), + child: child, + ); + }, + ); + } + + void _onSelect(int index, dynamic place, BuildContext context) { + Navigator.of(context).pop(); + + if (isOrigin) { + RpActions.selectStart(place); + } else { + RpActions.selectDestination(index, place); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: RP.sheet, + body: SafeArea( + child: GetBuilder( + builder: (s) { + return Column( + children: [ + // Header + Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + IconButton( + icon: const Icon(Icons.arrow_back_rounded), + onPressed: () => Navigator.of(context).pop(), + ), + const SizedBox(width: 8), + Expanded( + child: Text( + hintText, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w700, + color: RP.textStrong, + ), + ), + ), + ], + ), + ), + // Field + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: RpLocationField( + accent: accent, + icon: isOrigin ? Icons.circle : Icons.flag_rounded, + controller: isOrigin + ? s.placeStartController + : s.placeDestinationController, + hintText: hintText, + results: isOrigin ? s.placesStart : s.placesDestination, + searchable: true, + onQuery: (_) { + if (isOrigin) { + s.getPlacesStart(); + } else { + s.getPlaces(); + } + }, + onEmpty: () { + if (isOrigin) { + s.clearPlacesStart(); + } else { + s.clearPlacesDestination(); + } + }, + onPickOnMap: () { + Navigator.of(context).pop(); + if (isOrigin) { + RpActions.pickOtherPickupOnMap(); + } else { + RpActions.pickDestinationOnMap(); + } + }, + onResultTap: (i) => _onSelect(i, isOrigin ? s.placesStart[i] : s.placesDestination[i], context), + onResultFavorite: (i) { + final res = isOrigin ? s.placesStart[i] : s.placesDestination[i]; + RpActions.addFavorite( + context, + res['latitude'], + res['longitude'], + (res['name_ar'] ?? res['name'] ?? 'Unknown Place').toString(), + ); + }, + ), + ), + const SizedBox(height: 16), + // Results + Expanded( + child: RpResultsList( + results: isOrigin ? s.placesStart : s.placesDestination, + accent: accent, + onTap: (i) => _onSelect(i, isOrigin ? s.placesStart[i] : s.placesDestination[i], context), + onFavorite: (i) { + final res = isOrigin ? s.placesStart[i] : s.placesDestination[i]; + RpActions.addFavorite( + context, + res['latitude'], + res['longitude'], + (res['name_ar'] ?? res['name'] ?? 'Unknown Place').toString(), + ); + }, + ), + ), + ], + ); + }, + ), + ), + ); + } +} diff --git a/siro_rider/lib/views/home/route_planner/rp_sheet.dart b/siro_rider/lib/views/home/route_planner/rp_sheet.dart index d5bece12..a0ca4032 100644 --- a/siro_rider/lib/views/home/route_planner/rp_sheet.dart +++ b/siro_rider/lib/views/home/route_planner/rp_sheet.dart @@ -13,6 +13,7 @@ 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. /// @@ -267,6 +268,15 @@ class _Expanded extends StatelessWidget { onEmpty: s.clearPlacesStart, onPickOnMap: RpActions.pickOtherPickupOnMap, onResultTap: (i) => RpActions.selectStart(s.placesStart[i]), + 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. @@ -276,11 +286,19 @@ class _Expanded extends StatelessWidget { controller: s.placeStartController, hintText: s.currentLocationString, results: const [], + onPickOnMap: RpActions.pickMyStartOnMap, + onResultTap: (_) {}, searchable: false, onQuery: (_) {}, onEmpty: () {}, - onPickOnMap: RpActions.pickMyStartOnMap, - onResultTap: (_) {}, + onTapField: () { + RpSearchDialog.show( + context: Get.context!, + accent: RP.origin, + hintText: s.currentLocationString, + isOrigin: true, + ); + }, ); } @@ -303,6 +321,15 @@ class _Expanded extends StatelessWidget { }, onPickOnMap: RpActions.pickDestinationOnMap, onResultTap: (i) => RpActions.selectDestination(i, s.placesDestination[i]), + 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(