Files
intaleq/lib/views/home/map_widget.dart/main_bottom_Menu_map.dart
2026-04-04 14:08:07 +03:00

1287 lines
52 KiB
Dart

import 'package:Intaleq/views/widgets/my_textField.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:Intaleq/constant/box_name.dart';
import 'package:Intaleq/constant/style.dart';
import 'package:Intaleq/controller/home/map_passenger_controller.dart';
import 'package:Intaleq/main.dart';
import 'package:Intaleq/views/home/map_widget.dart/form_search_places_destenation.dart';
import 'package:Intaleq/views/widgets/elevated_btn.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
import '../../../constant/colors.dart';
import '../../../constant/table_names.dart';
import '../../../controller/functions/toast.dart';
import '../../../controller/functions/tts.dart';
import '../../widgets/error_snakbar.dart';
import '../../widgets/mydialoug.dart';
import 'form_search_start.dart';
// ─────────────────────────────────────────────────────────────────────────────
// MAIN BOTTOM MENU MAP
// ─────────────────────────────────────────────────────────────────────────────
class MainBottomMenuMap extends StatelessWidget {
const MainBottomMenuMap({super.key});
@override
Widget build(BuildContext context) {
Get.put(MapPassengerController());
return GetBuilder<MapPassengerController>(
builder: (controller) {
// ─── حالة: يتم تحديد موقع على الخريطة (وضع الـ Picker) ───────────────
if (controller.isPickerShown) {
return _MapPickerOverlay(controller: controller);
}
// ─── الحالة العادية: القائمة السفلية ────────────────────────────────
return Positioned(
bottom: Get.height * .03,
left: 12,
right: 12,
child: AnimatedContainer(
duration: const Duration(milliseconds: 350),
curve: Curves.easeOutCubic,
height: controller.mainBottomMenuMapHeight,
decoration: BoxDecoration(
color: AppColor.secondaryColor,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.15),
blurRadius: 24,
offset: const Offset(0, 8),
),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: SingleChildScrollView(
physics: const NeverScrollableScrollPhysics(),
child: controller.isMainBottomMenuMap
? _CollapsedView(controller: controller)
: _ExpandedView(controller: controller, context: context),
),
),
),
);
},
);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// COLLAPSED VIEW (isMainBottomMenuMap = true)
// ─────────────────────────────────────────────────────────────────────────────
class _CollapsedView extends StatelessWidget {
final MapPassengerController controller;
const _CollapsedView({required this.controller});
@override
Widget build(BuildContext context) {
final String firstName = box.read(BoxName.name).toString().split(' ').first;
return Column(
mainAxisSize: MainAxisSize.min,
children: [
// ── شريط العنوان ──────────────────────────────────────────────────
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: controller.changeMainBottomMenuMap,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 14),
child: Row(
children: [
// أيقونة الموقع بدائرة ملونة
Container(
width: 38,
height: 38,
decoration: BoxDecoration(
color: AppColor.primaryColor.withOpacity(0.12),
shape: BoxShape.circle,
),
child: Icon(Icons.search_rounded,
color: AppColor.primaryColor, size: 20),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${'Where to'.tr} $firstName؟',
style: AppStyle.title.copyWith(
fontWeight: FontWeight.w700,
fontSize: 15,
),
),
if (!controller.noCarString)
Text(
'Tap to search your destination'.tr,
style: AppStyle.subtitle.copyWith(
fontSize: 11,
color: Colors.grey.shade500,
),
),
],
),
),
Container(
padding:
const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: AppColor.primaryColor,
borderRadius: BorderRadius.circular(20),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.keyboard_arrow_up_rounded,
color: Colors.white, size: 18),
Text(
'Open'.tr,
style: const TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.w600),
),
],
),
),
],
),
),
),
// ── الأماكن الأخيرة ───────────────────────────────────────────────
if (controller.recentPlaces.isNotEmpty)
Padding(
padding: const EdgeInsets.only(bottom: 10, left: 8, right: 8),
child: SizedBox(
height: 32,
child: ListView.separated(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 4),
itemCount: controller.recentPlaces.length,
separatorBuilder: (_, __) => const SizedBox(width: 6),
itemBuilder: (context, index) =>
_RecentPlaceChip(controller: controller, index: index),
),
),
),
],
);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// EXPANDED VIEW (isMainBottomMenuMap = false)
// ─────────────────────────────────────────────────────────────────────────────
class _ExpandedView extends StatelessWidget {
final MapPassengerController controller;
final BuildContext context;
const _ExpandedView({required this.controller, required this.context});
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// ── Header ────────────────────────────────────────────────────────
Padding(
padding: const EdgeInsets.fromLTRB(18, 14, 12, 8),
child: Row(
children: [
Text(
'Plan Your Route'.tr,
style: AppStyle.title.copyWith(
fontWeight: FontWeight.w700,
fontSize: 16,
),
),
const Spacer(),
GestureDetector(
onTap: controller.changeMainBottomMenuMap,
child: Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: Colors.grey.shade200,
shape: BoxShape.circle,
),
child: Icon(Icons.keyboard_arrow_down_rounded,
size: 22, color: Colors.grey.shade700),
),
),
],
),
),
const Divider(height: 1, thickness: 0.5),
const SizedBox(height: 12),
// ── Route Timeline (no IntrinsicHeight) ─────────────────────────
// Start location row with green dot
_buildTimelineItem(
dotColor: AppColor.primaryColor,
showTopLine: false,
showBottomLine: true,
child: !controller.isAnotherOreder
? _TimelineRow(
icon: Icons.my_location_rounded,
iconColor: AppColor.primaryColor,
bgColor: AppColor.primaryColor,
label: controller.currentLocationString,
)
: Padding(
padding: const EdgeInsets.only(right: 16),
child: formSearchPlacesStart(),
),
),
// Waypoint stop rows
...List.generate(controller.activeMenuWaypointCount, (index) {
final wpName = controller.menuWaypointNames[index];
final isSet = controller.menuWaypoints[index] != null;
// Stop 1 = amber/orange, Stop 2 = deep purple
final Color dotColor =
index == 0 ? Colors.amber.shade700 : Colors.deepPurple.shade400;
return _buildTimelineItem(
dotColor: dotColor,
showTopLine: true,
showBottomLine: true,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
decoration: BoxDecoration(
color: index == 0
? Colors.amber.shade50
: Colors.deepPurple.shade50,
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: isSet
? (index == 0
? Colors.amber.shade300
: Colors.deepPurple.shade200)
: Colors.grey.shade200,
),
),
child: Row(
children: [
// Numbered badge
Container(
width: 20,
height: 20,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: index == 0
? [Colors.amber.shade500, Colors.amber.shade700]
: [
Colors.deepPurple.shade300,
Colors.deepPurple.shade500
],
),
shape: BoxShape.circle,
),
child: Center(
child: Text(
'${index + 1}',
style: const TextStyle(
color: Colors.white,
fontSize: 10,
fontWeight: FontWeight.w700,
),
),
),
),
const SizedBox(width: 10),
Expanded(
child: GestureDetector(
onTap: () {
controller.changeMainBottomMenuMap();
controller.startPickingWaypointOnMap(index);
},
child: Text(
isSet ? wpName : '${'Stop'.tr} ${index + 1}',
style: TextStyle(
fontSize: 13,
color: isSet
? (index == 0
? Colors.amber.shade900
: Colors.deepPurple.shade700)
: Colors.grey.shade400,
fontWeight: isSet ? FontWeight.w500 : FontWeight.w400,
fontStyle:
isSet ? FontStyle.normal : FontStyle.italic,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
),
GestureDetector(
onTap: () {
controller.changeMainBottomMenuMap();
controller.startPickingWaypointOnMap(index);
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 6),
child: Icon(Icons.map_outlined,
color: index == 0
? Colors.amber.shade600
: Colors.deepPurple.shade400,
size: 18),
),
),
GestureDetector(
onTap: () => controller.removeMenuWaypoint(index),
child: Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: Colors.red.shade50,
shape: BoxShape.circle,
),
child: Icon(Icons.close_rounded,
color: Colors.red.shade400, size: 13),
),
),
],
),
),
);
}),
// Add Stop button
if (controller.activeMenuWaypointCount < 2)
_buildTimelineItem(
dotColor: Colors.grey.shade300,
isDotDashed: true,
showTopLine: true,
showBottomLine: true,
child: GestureDetector(
onTap: () => controller.addMenuWaypoint(),
child: Container(
padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 9),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.orange.shade200),
color: Colors.orange.shade50.withAlpha(100),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.add_circle_outline_rounded,
color: Colors.orange.shade500, size: 16),
const SizedBox(width: 6),
Text(
'Add a Stop'.tr,
style: TextStyle(
color: Colors.orange.shade700,
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
const SizedBox(width: 6),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 5, vertical: 1),
decoration: BoxDecoration(
color: Colors.orange.shade100,
borderRadius: BorderRadius.circular(6),
),
child: Text(
'+5 ${'min'.tr}',
style: TextStyle(
color: Colors.orange.shade700,
fontSize: 9,
fontWeight: FontWeight.w600,
),
),
),
],
),
),
),
),
// Destination row with red dot
_buildTimelineItem(
dotColor: Colors.red.shade500,
showTopLine: true,
showBottomLine: false,
child: Padding(
padding: const EdgeInsets.only(right: 16),
child: formSearchPlacesDestenation(),
),
),
// ── Surcharge info ───────────────────────────────────────────────
if (controller.activeMenuWaypointCount > 0)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 22, vertical: 6),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: Colors.orange.shade50,
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.schedule_rounded,
size: 14, color: Colors.orange.shade600),
const SizedBox(width: 6),
Text(
'${controller.activeMenuWaypointCount} ${'stop(s)'.tr} · +${controller.activeMenuWaypointCount * 5} ${'min added to fare'.tr}',
style: TextStyle(
fontSize: 11,
color: Colors.orange.shade700,
fontWeight: FontWeight.w500,
),
),
],
),
),
),
const SizedBox(height: 10),
// ── WhatsApp ─────────────────────────────────────────────────────
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: _WhatsAppLinkButton(controller: controller),
),
const SizedBox(height: 10),
// ── Order type ───────────────────────────────────────────────────
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: _OrderTypeButton(controller: controller),
),
const SizedBox(height: 14),
],
);
}
/// Builds a single timeline row: [dot + line] | [child widget]
Widget _buildTimelineItem({
required Color dotColor,
required bool showTopLine,
required bool showBottomLine,
required Widget child,
bool isDotDashed = false,
}) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Timeline indicator column (dot + lines)
SizedBox(
width: 24,
child: Column(
children: [
// Top connecting line
if (showTopLine)
Container(
width: 2,
height: 8,
color: Colors.grey.shade300,
),
// Dot
Container(
width: 12,
height: 12,
decoration: BoxDecoration(
color: isDotDashed ? Colors.transparent : dotColor,
shape: BoxShape.circle,
border: Border.all(color: dotColor, width: 2),
),
),
// Bottom connecting line
if (showBottomLine)
Container(
width: 2,
height: 8,
color: Colors.grey.shade300,
),
],
),
),
const SizedBox(width: 10),
// Content
Expanded(child: child),
],
),
);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// MAP PICKER OVERLAY (isPickerShown = true)
// الواجهة التي تظهر عندما يختار المستخدم موقعاً على الخريطة
// ─────────────────────────────────────────────────────────────────────────────
class _MapPickerOverlay extends StatelessWidget {
final MapPassengerController controller;
const _MapPickerOverlay({required this.controller});
// ── الحصول على نص الحالة الحالية ─────────────────────────────────────────
String _getModeTitle(BuildContext context) {
if (controller.isPickingWaypoint) {
return 'Move map to set stop'.tr +
' ${controller.pickingWaypointIndex + 1}'.tr;
}
if (controller.passengerStartLocationFromMap) {
return 'Move map to your pickup point'.tr;
} else if (controller.startLocationFromMap) {
return 'Move map to set start location'.tr;
} else if (controller.workLocationFromMap) {
return 'Move map to your work location'.tr;
} else if (controller.homeLocationFromMap) {
return 'Move map to your home location'.tr;
}
return 'Move map to select destination'.tr;
}
String _getConfirmLabel(BuildContext context) {
if (controller.isPickingWaypoint) {
return 'Set as Stop'.tr;
}
if (controller.passengerStartLocationFromMap) {
return 'Confirm Pickup Location'.tr;
} else if (controller.workLocationFromMap) {
return 'Set as Work'.tr;
} else if (controller.homeLocationFromMap) {
return 'Set as Home'.tr;
}
return 'Set Destination'.tr;
}
IconData _getModeIcon() {
if (controller.isPickingWaypoint) return Icons.add_location_alt_rounded;
if (controller.passengerStartLocationFromMap)
return Icons.person_pin_circle_rounded;
if (controller.workLocationFromMap) return Icons.work_rounded;
if (controller.homeLocationFromMap) return Icons.home_rounded;
return Icons.location_on_rounded;
}
Color _getModeColor() {
if (controller.isPickingWaypoint) return Colors.orange.shade600;
if (controller.passengerStartLocationFromMap) return Colors.green.shade600;
if (controller.workLocationFromMap) return Colors.blue.shade600;
if (controller.homeLocationFromMap) return Colors.orange.shade600;
return AppColor.primaryColor;
}
@override
Widget build(BuildContext context) {
final modeColor = _getModeColor();
return Positioned(
bottom: Get.height * .03,
left: 12,
right: 12,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// ── بانر التعليمات ───────────────────────────────────────────────
Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
decoration: BoxDecoration(
color: modeColor,
borderRadius: BorderRadius.circular(14),
boxShadow: [
BoxShadow(
color: modeColor.withOpacity(0.4),
blurRadius: 12,
offset: const Offset(0, 4),
)
],
),
child: Row(
children: [
Icon(_getModeIcon(), color: Colors.white, size: 20),
const SizedBox(width: 10),
Expanded(
child: Text(
_getModeTitle(context),
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.w600,
fontSize: 13,
),
),
),
],
),
),
const SizedBox(height: 8),
// ── بطاقة الإحداثيات + زر التأكيد ──────────────────────────────
Container(
decoration: BoxDecoration(
color: AppColor.secondaryColor,
borderRadius: BorderRadius.circular(18),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.12),
blurRadius: 20,
offset: const Offset(0, 6),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// ── الإحداثيات الحالية ────────────────────────────────────
Padding(
padding: const EdgeInsets.fromLTRB(16, 14, 16, 0),
child: Row(
children: [
Container(
width: 8,
height: 8,
decoration: BoxDecoration(
color: modeColor,
shape: BoxShape.circle,
),
),
const SizedBox(width: 10),
Expanded(
child: Text(
'${controller.newMyLocation.latitude.toStringAsFixed(5)}'
', ${controller.newMyLocation.longitude.toStringAsFixed(5)}',
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
fontFeatures: const [FontFeature.tabularFigures()],
),
),
),
],
),
),
const SizedBox(height: 10),
const Divider(height: 1, thickness: 0.5),
// ── الأزرار ───────────────────────────────────────────────
Padding(
padding: const EdgeInsets.fromLTRB(12, 10, 12, 14),
child: Row(
children: [
// زر إلغاء
Expanded(
flex: 2,
child: OutlinedButton.icon(
onPressed: () {
controller.isPickerShown = false;
controller.passengerStartLocationFromMap = false;
controller.startLocationFromMap = false;
controller.workLocationFromMap = false;
controller.homeLocationFromMap = false;
controller.isPickingWaypoint = false;
controller.pickingWaypointIndex = -1;
// أعد الخريطة لحالتها المنهارة
if (!controller.isMainBottomMenuMap) {
controller.isMainBottomMenuMap = true;
controller.mainBottomMenuMapHeight =
Get.height * .22;
}
controller.update();
},
style: OutlinedButton.styleFrom(
foregroundColor: Colors.grey.shade600,
side: BorderSide(color: Colors.grey.shade300),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
padding: const EdgeInsets.symmetric(vertical: 12),
),
icon: const Icon(Icons.close_rounded, size: 16),
label: Text('Cancel'.tr,
style: const TextStyle(fontSize: 13)),
),
),
const SizedBox(width: 10),
// زر التأكيد
Expanded(
flex: 3,
child: ElevatedButton.icon(
onPressed: () => _onConfirmTap(controller, context),
style: ElevatedButton.styleFrom(
backgroundColor: modeColor,
foregroundColor: Colors.white,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
padding: const EdgeInsets.symmetric(vertical: 13),
),
icon: Icon(_getModeIcon(), size: 18),
label: Text(
_getConfirmLabel(context),
style: const TextStyle(
fontWeight: FontWeight.w600, fontSize: 13),
),
),
),
],
),
),
],
),
),
],
),
);
}
// ──────────────────────────────────────────────────────────────────────────
// منطق التأكيد - هنا يتم إصلاح البق الأساسي
// ──────────────────────────────────────────────────────────────────────────
Future<void> _onConfirmTap(
MapPassengerController controller, BuildContext context) async {
// ⚠️ ROOT FIX: onCameraMoveThrottled يعمل بنظام Throttle (يحفظ أول موقع)
// لكن نحتاج Debounce (يحفظ آخر موقع بعد توقف الكاميرا).
// نضيف delay 280ms لضمان أن الكاميرا توقفت وأن الـ timer (160ms) انتهى.
// هذا يضمن أن newMyLocation = الموقع الفعلي الأخير للكاميرا. ✅
await Future.delayed(const Duration(milliseconds: 280));
// التقاط snapshot آمن بعد انتهاء الـ debounce timer
final LatLng currentCameraPosition = LatLng(
controller.newMyLocation.latitude,
controller.newMyLocation.longitude,
);
print('🌐 MAP PICKER CENTER: ${currentCameraPosition.latitude}, ${currentCameraPosition.longitude}');
print('✅ _onConfirmTap confirmed coordinates: ${currentCameraPosition.latitude}, ${currentCameraPosition.longitude}');
// ── CASE 0: Waypoint picker mode ──────────────────────────────────────
if (controller.isPickingWaypoint && controller.pickingWaypointIndex >= 0) {
final int wpIndex = controller.pickingWaypointIndex;
controller.setMenuWaypointFromMap(wpIndex, currentCameraPosition);
Get.snackbar(
'Stop ${wpIndex + 1} Set'.tr,
'Waypoint has been set successfully'.tr,
backgroundColor: Colors.orange.shade600,
colorText: Colors.white,
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 2),
margin: const EdgeInsets.all(12),
borderRadius: 12,
);
return;
}
controller.clearPolyline();
controller.data = [];
// ── CASE 1: تأكيد نقطة الانطلاق (بعد تحديد الهدف سابقاً) ─────────────
if (controller.passengerStartLocationFromMap) {
// حفظ نقطة الانطلاق من موقع الكاميرا الحالي (snapshot آمن)
final LatLng start = currentCameraPosition;
controller.newStartPointLocation = start;
// تنظيف الحالة قبل استدعاء getDirectionMap
controller.passengerStartLocationFromMap = false;
controller.isPickerShown = false;
controller.currentLocationToFormPlaces = false;
controller.placesDestination = [];
controller.clearPlacesStart();
controller.clearPlacesDestination();
// العودة لحالة القائمة المنهارة
controller.isMainBottomMenuMap = true;
controller.mainBottomMenuMapHeight = Get.height * .22;
controller.update();
// 🔑 الـ destination محفوظة مسبقاً في myDestination - لا تلمسها هنا
await controller.getDirectionMap(
'${start.latitude},${start.longitude}',
'${controller.myDestination.latitude},${controller.myDestination.longitude}',
);
controller.showBottomSheet1();
return;
}
// ── CASE 2: تأكيد نقطة الانطلاق العادية (startLocationFromMap) ─────────
if (controller.startLocationFromMap) {
final LatLng start = currentCameraPosition;
controller.newMyLocation = start;
controller.newStartPointLocation = start;
controller.hintTextStartPoint =
'${start.latitude.toStringAsFixed(4)} , ${start.longitude.toStringAsFixed(4)}';
controller.startLocationFromMap = false;
controller.isPickerShown = false;
controller.update();
return;
}
// ── CASE 3: حفظ موقع العمل ───────────────────────────────────────────
if (controller.workLocationFromMap) {
final LatLng work = currentCameraPosition;
box.write(BoxName.addWork,
'${work.latitude.toStringAsFixed(4)} , ${work.longitude.toStringAsFixed(4)}');
controller.hintTextDestinationPoint = 'To Work'.tr;
controller.workLocationFromMap = false;
controller.isPickerShown = false;
controller.update();
Get.snackbar('Work Saved'.tr, '',
backgroundColor: AppColor.greenColor,
colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM);
return;
}
// ── CASE 4: حفظ موقع المنزل ─────────────────────────────────────────
if (controller.homeLocationFromMap) {
final LatLng home = currentCameraPosition;
box.write(BoxName.addHome,
'${home.latitude.toStringAsFixed(4)} , ${home.longitude.toStringAsFixed(4)}');
controller.hintTextDestinationPoint = 'To Home'.tr;
controller.homeLocationFromMap = false;
controller.isPickerShown = false;
controller.update();
Get.snackbar('Home Saved'.tr, '',
backgroundColor: AppColor.greenColor,
colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM);
return;
}
// ── CASE 5 (DEFAULT): تحديد الوجهة - المرحلة الأولى ──────────────────
// currentCameraPosition محفوظ بأمان في بداية الدالة بعد الـ delay ✅
final LatLng confirmedDestination = currentCameraPosition;
// ─── 1. حفظ الوجهة بأمان ─────────────────────────────────────────────
controller.myDestination = confirmedDestination;
controller.hintTextDestinationPoint =
'${confirmedDestination.latitude.toStringAsFixed(4)} , ${confirmedDestination.longitude.toStringAsFixed(4)}';
controller.placesDestination = [];
controller.placeDestinationController.clear();
// ─── 2. الانتقال لمرحلة تحديد نقطة الانطلاق ─────────────────────────
controller.passengerStartLocationFromMap = true;
// isPickerShown يبقى true لأننا لا زلنا في وضع الـ picker
controller.update();
// ─── 3. العمليات الـ async بعد حفظ الوجهة بأمان ──────────────────────
try {
if (controller.isAnotherOreder) {
// ✅ "Order for someone else": move camera to the OTHER person's
// start location (set via the start search form)
await controller.mapController?.animateCamera(
CameraUpdate.newLatLng(LatLng(
controller.newStartPointLocation.latitude,
controller.newStartPointLocation.longitude,
)),
);
} else {
// Normal flow: move camera to the passenger's own location
await controller.mapController?.animateCamera(
CameraUpdate.newLatLng(LatLng(
controller.passengerLocation.latitude,
controller.passengerLocation.longitude,
)),
);
}
} catch (_) {
// Guard against disposed GoogleMapController
}
// ─── 4. إشعار المستخدم ──────────────────────────────────────────────
Get.snackbar(
'Destination Set'.tr,
controller.isAnotherOreder
? 'Now set the pickup point for the other person'.tr
: 'Now move the map to your pickup point'.tr,
backgroundColor: Colors.green.shade600,
colorText: Colors.white,
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 2),
margin: const EdgeInsets.all(12),
borderRadius: 12,
);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// WIDGETS المساعدة
// ─────────────────────────────────────────────────────────────────────────────
/// صف موقع البداية (للعرض فقط)
class _LocationRow extends StatelessWidget {
final IconData icon;
final Color iconColor;
final String label;
final bool isStart;
const _LocationRow({
required this.icon,
required this.iconColor,
required this.label,
this.isStart = false,
});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 11),
decoration: BoxDecoration(
color: isStart
? AppColor.primaryColor.withOpacity(0.05)
: Colors.transparent,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey.shade200),
),
child: Row(
children: [
Icon(icon, color: iconColor, size: 18),
const SizedBox(width: 10),
Expanded(
child: Text(
label,
style: AppStyle.subtitle.copyWith(fontSize: 13),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
);
}
}
/// صف في التايملاين (للعرض داخل عمود المسار)
class _TimelineRow extends StatelessWidget {
final IconData icon;
final Color iconColor;
final Color bgColor;
final String label;
const _TimelineRow({
required this.icon,
required this.iconColor,
required this.bgColor,
required this.label,
});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
decoration: BoxDecoration(
color: bgColor.withAlpha(15),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: bgColor.withAlpha(40)),
),
child: Row(
children: [
Icon(icon, color: iconColor, size: 16),
const SizedBox(width: 8),
Expanded(
child: Text(
label,
style: AppStyle.subtitle.copyWith(fontSize: 12.5),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
);
}
}
/// شريحة الأماكن الأخيرة
class _RecentPlaceChip extends StatelessWidget {
final MapPassengerController controller;
final int index;
const _RecentPlaceChip({required this.controller, required this.index});
@override
Widget build(BuildContext context) {
final place = controller.recentPlaces[index];
return GestureDetector(
onTap: () {
MyDialog().getDialog(
'Are you want to go this site'.tr,
' ',
() async {
Get.back();
await controller.getLocation();
await controller.getDirectionMap(
'${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}',
'${place['latitude']},${place['longitude']}',
);
controller.showBottomSheet1();
},
);
},
onLongPress: () {
MyDialog().getDialog(
'Are you sure to delete this location?'.tr,
'',
() {
sql.deleteData(TableName.recentLocations, place['id']);
controller.getFavioratePlaces();
controller.update();
Get.back();
mySnackbarSuccess('deleted'.tr);
},
);
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: AppColor.primaryColor.withOpacity(0.07),
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: AppColor.primaryColor.withOpacity(0.2), width: 1),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.history_rounded,
size: 13, color: AppColor.primaryColor.withOpacity(0.7)),
const SizedBox(width: 5),
Text(
place['name'] ?? '',
style: TextStyle(
fontSize: 12,
color: AppColor.primaryColor.withOpacity(0.85),
fontWeight: FontWeight.w500,
),
),
],
),
),
);
}
}
/// زر رابط الواتس أب
class _WhatsAppLinkButton extends StatelessWidget {
final MapPassengerController controller;
const _WhatsAppLinkButton({required this.controller});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
Get.dialog(
AlertDialog(
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
title: Text('WhatsApp Location Extractor'.tr),
content: Form(
key: controller.sosFormKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
MyTextForm(
controller: controller.whatsAppLocationText,
label: 'Location Link'.tr,
hint: 'Paste location link here'.tr,
type: TextInputType.url,
),
const SizedBox(height: 16),
MyElevatedButton(
title: 'Go to this location'.tr,
onPressed: () => controller.goToWhatappLocation(),
),
],
),
),
),
);
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
decoration: BoxDecoration(
color: Colors.green.shade50,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.green.shade200),
),
child: Row(
children: [
Icon(Icons.link_rounded, color: Colors.green.shade700, size: 18),
const SizedBox(width: 10),
Expanded(
child: Text(
'Paste WhatsApp location link'.tr,
style: TextStyle(
color: Colors.green.shade700,
fontSize: 13,
fontWeight: FontWeight.w500),
),
),
Icon(Icons.arrow_forward_ios_rounded,
size: 13, color: Colors.green.shade400),
],
),
),
);
}
}
/// زر نوع الطلب
class _OrderTypeButton extends StatelessWidget {
final MapPassengerController controller;
const _OrderTypeButton({required this.controller});
@override
Widget build(BuildContext context) {
return OutlinedButton.icon(
onPressed: () {
showCupertinoModalPopup(
context: context,
builder: (ctx) => CupertinoActionSheet(
title: Text('Select Order Type'.tr),
message: Text('Choose who this order is for'.tr),
actions: [
CupertinoActionSheetAction(
child: Text('I want to order for myself'.tr),
onPressed: () {
controller.changeisAnotherOreder(false);
Navigator.pop(ctx);
},
),
CupertinoActionSheetAction(
child: Text('I want to order for someone else'.tr),
onPressed: () {
controller.changeisAnotherOreder(true);
Navigator.pop(ctx);
},
),
],
cancelButton: CupertinoActionSheetAction(
isDefaultAction: true,
onPressed: () => Navigator.pop(ctx),
child: Text('Cancel'.tr),
),
),
);
},
style: OutlinedButton.styleFrom(
foregroundColor: AppColor.primaryColor,
side: BorderSide(color: AppColor.primaryColor.withOpacity(0.4)),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 11),
),
icon: Icon(
controller.isAnotherOreder ? Icons.person_rounded : Icons.group_rounded,
size: 17,
),
label: Text(
controller.isAnotherOreder
? 'Order for myself'.tr
: 'Order for someone else'.tr,
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
),
);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// FAVOURITE PLACES DIALOG
// ─────────────────────────────────────────────────────────────────────────────
class FaviouratePlacesDialog extends StatelessWidget {
const FaviouratePlacesDialog({super.key});
@override
Widget build(BuildContext context) {
Get.put(MapPassengerController());
return GetBuilder<MapPassengerController>(
builder: (controller) => Center(
child: InkWell(
borderRadius: BorderRadius.circular(12),
onTap: () async {
final List favoritePlaces =
await sql.getAllData(TableName.placesFavorite);
Get.defaultDialog(
title: 'Favorite Places'.tr,
titleStyle: AppStyle.title,
content: SizedBox(
width: Get.width * .85,
height: 300,
child: favoritePlaces.isEmpty
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.star_border_rounded,
size: 64, color: AppColor.accentColor),
const SizedBox(height: 12),
Text('No favorite places yet!'.tr,
style: AppStyle.title),
],
),
)
: ListView.separated(
itemCount: favoritePlaces.length,
separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (context, index) => ListTile(
leading: const Icon(Icons.star, color: Colors.amber),
title: Text(favoritePlaces[index]['name'],
style: AppStyle.title),
trailing: IconButton(
icon: const Icon(Icons.delete_outline,
color: Colors.redAccent),
onPressed: () async {
await sql.deleteData(TableName.placesFavorite,
favoritePlaces[index]['id']);
Get.back();
Toast.show(
context,
'${'Deleted'.tr} ${favoritePlaces[index]['name']}',
AppColor.redColor,
);
},
),
onTap: () async {
Get.back();
await controller.getLocation();
await controller.getDirectionMap(
'${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}',
'${favoritePlaces[index]['latitude']},${favoritePlaces[index]['longitude']}',
);
controller.showBottomSheet1();
},
),
),
),
confirm: MyElevatedButton(
title: 'Back'.tr, onPressed: () => Get.back()),
);
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.star_border_rounded,
color: AppColor.accentColor, size: 20),
const SizedBox(width: 8),
Text('Favorite Places'.tr, style: AppStyle.title),
],
),
),
),
),
);
}
}