Update: 2026-07-11 19:26:05
This commit is contained in:
@@ -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<RpLocationField> {
|
||||
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,
|
||||
|
||||
@@ -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<Offset>(
|
||||
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<LocationSearchController>(
|
||||
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(),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user