950 lines
39 KiB
Dart
950 lines
39 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:google_maps_flutter/google_maps_flutter.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: [
|
|
// ── هيدر "Quick Actions" ─────────────────────────────────────────
|
|
Padding(
|
|
padding: const EdgeInsets.fromLTRB(18, 14, 12, 8),
|
|
child: Row(
|
|
children: [
|
|
Text(
|
|
'Quick Actions'.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: 10),
|
|
|
|
// ── موقع البداية ─────────────────────────────────────────────────
|
|
if (!controller.isAnotherOreder)
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
child: _LocationRow(
|
|
icon: Icons.my_location_rounded,
|
|
iconColor: AppColor.primaryColor,
|
|
label: controller.currentLocationString,
|
|
isStart: true,
|
|
),
|
|
)
|
|
else
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
child: formSearchPlacesStart(),
|
|
),
|
|
|
|
const SizedBox(height: 6),
|
|
|
|
// ── حقل الوجهة ────────────────────────────────────────────────────
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
child: formSearchPlacesDestenation(),
|
|
),
|
|
|
|
const SizedBox(height: 12),
|
|
|
|
// ── زر WhatsApp ───────────────────────────────────────────────────
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
child: _WhatsAppLinkButton(controller: controller),
|
|
),
|
|
|
|
const SizedBox(height: 10),
|
|
|
|
// ── Order for someone else ────────────────────────────────────────
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
child: _OrderTypeButton(controller: controller),
|
|
),
|
|
|
|
const SizedBox(height: 14),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// MAP PICKER OVERLAY (isPickerShown = true)
|
|
// الواجهة التي تظهر عندما يختار المستخدم موقعاً على الخريطة
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
class _MapPickerOverlay extends StatelessWidget {
|
|
final MapPassengerController controller;
|
|
const _MapPickerOverlay({required this.controller});
|
|
|
|
// ── الحصول على نص الحالة الحالية ─────────────────────────────────────────
|
|
String _getModeTitle(BuildContext context) {
|
|
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.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.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.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;
|
|
// أعد الخريطة لحالتها المنهارة
|
|
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,
|
|
);
|
|
|
|
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 بعد حفظ الوجهة بأمان ──────────────────────
|
|
if (!controller.isAnotherOreder) {
|
|
// تحريك الكاميرا لموقع الراكب ليختار منه نقطة الانطلاق
|
|
// ملاحظة: هذا يُغير newMyLocation - لكن myDestination محفوظ بأمان ✅
|
|
await controller.mapController?.animateCamera(
|
|
CameraUpdate.newLatLng(LatLng(
|
|
controller.passengerLocation.latitude,
|
|
controller.passengerLocation.longitude,
|
|
)),
|
|
);
|
|
}
|
|
|
|
// ─── 4. إشعار المستخدم ──────────────────────────────────────────────
|
|
Get.snackbar(
|
|
'Destination Set'.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 _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),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|