449 lines
18 KiB
Dart
Executable File
449 lines
18 KiB
Dart
Executable File
import 'dart:ui';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:sefer_driver/constant/box_name.dart';
|
|
import 'package:sefer_driver/constant/style.dart';
|
|
import 'package:sefer_driver/views/widgets/elevated_btn.dart';
|
|
import 'package:sefer_driver/controller/home/captin/map_driver_controller.dart';
|
|
import '../../../constant/colors.dart';
|
|
import '../../../controller/functions/location_controller.dart';
|
|
import '../../../main.dart';
|
|
import '../../Rate/rate_passenger.dart';
|
|
import '../../widgets/my_textField.dart';
|
|
import 'mapDriverWidgets/driver_end_ride_bar.dart';
|
|
import 'mapDriverWidgets/google_driver_map_page.dart';
|
|
import 'mapDriverWidgets/google_map_app.dart';
|
|
import 'mapDriverWidgets/passenger_info_window.dart';
|
|
import 'mapDriverWidgets/sos_connect.dart';
|
|
import 'mapDriverWidgets/sped_circle.dart';
|
|
|
|
class PassengerLocationMapPage extends StatelessWidget {
|
|
PassengerLocationMapPage({super.key});
|
|
final LocationController locationController = Get.put(LocationController());
|
|
final MapDriverController mapDriverController =
|
|
Get.put(MapDriverController());
|
|
|
|
// دالة ديالوج الخروج
|
|
Future<bool> showExitDialog() async {
|
|
bool? result = await Get.defaultDialog(
|
|
title: "Warning".tr,
|
|
titleStyle: AppStyle.title.copyWith(color: AppColor.redColor),
|
|
middleText:
|
|
"Active ride in progress. Leaving might stop tracking. Exit?".tr,
|
|
barrierDismissible: false,
|
|
radius: 15,
|
|
confirm: MyElevatedButton(
|
|
title: 'Stay'.tr,
|
|
kolor: AppColor.greenColor,
|
|
onPressed: () => Get.back(result: false)),
|
|
cancel: MyElevatedButton(
|
|
title: 'Exit'.tr,
|
|
kolor: AppColor.redColor,
|
|
onPressed: () => Get.back(result: true)),
|
|
);
|
|
return result ?? false;
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
if (Get.arguments != null && Get.arguments is Map<String, dynamic>) {
|
|
mapDriverController.argumentLoading();
|
|
mapDriverController.startTimerToShowPassengerInfoWindowFromDriver();
|
|
// 2. فرض التحديث لكل المعرفات (IDs) لضمان ظهورها
|
|
// لأن argumentLoading قد تستدعي update() العادية التي لا تؤثر على هؤلاء
|
|
mapDriverController
|
|
.update(['PassengerInfo', 'DriverEndBar', 'SosConnect']);
|
|
}
|
|
});
|
|
|
|
return PopScope(
|
|
canPop: false,
|
|
onPopInvokedWithResult: (didPop, result) async {
|
|
if (didPop) return;
|
|
final shouldExit = await showExitDialog();
|
|
if (shouldExit) Get.back();
|
|
},
|
|
child: Scaffold(
|
|
resizeToAvoidBottomInset: false,
|
|
body: Stack(
|
|
children: [
|
|
// 1. الخريطة (الخلفية)
|
|
Positioned.fill(
|
|
child: GoogleDriverMap(locationController: locationController)),
|
|
|
|
// 2. واجهة المستخدم (فوق الخريطة)
|
|
SafeArea(
|
|
child: Stack(
|
|
children: [
|
|
// أ) زر الإلغاء (أعلى اليسار)
|
|
CancelWidget(mapDriverController: mapDriverController),
|
|
|
|
// ب) شريط إنهاء الرحلة (أعلى الوسط)
|
|
Positioned(
|
|
top: 0,
|
|
left: 0,
|
|
right: 0,
|
|
child: SafeArea(child: driverEndRideBar())),
|
|
|
|
// ج) شريط التعليمات الملاحية (الأسفل)
|
|
const InstructionsOfRoads(),
|
|
|
|
// د) نافذة معلومات الراكب (تعلو التعليمات ديناميكياً)
|
|
const PassengerInfoWindow(),
|
|
// SpeedCircle(),
|
|
Positioned(
|
|
right: 16,
|
|
bottom: 20, // أو أي مسافة تناسبك
|
|
child: GetBuilder<MapDriverController>(
|
|
// id: 'SosConnect', // لتحديث الزر عند بدء الرحلة
|
|
builder: (controller) {
|
|
// حساب الهوامش ديناميكياً لرفع الأزرار فوق النوافذ السفلية
|
|
double bottomPadding = 0;
|
|
if (controller.currentInstruction.isNotEmpty)
|
|
bottomPadding += 120;
|
|
if (controller.isPassengerInfoWindow)
|
|
bottomPadding += 220;
|
|
|
|
return AnimatedContainer(
|
|
duration: const Duration(milliseconds: 300),
|
|
margin: EdgeInsets.only(bottom: bottomPadding),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
SosConnect(), // ويدجت نظيفة
|
|
const SizedBox(height: 12),
|
|
const GoogleMapApp(), // ويدجت نظيفة
|
|
],
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// 3. النوافذ المنبثقة (Overlay)
|
|
const PricesWindow(),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 1. ويدجت شريط التعليمات (InstructionsOfRoads)
|
|
// ---------------------------------------------------------------------------
|
|
class InstructionsOfRoads extends StatelessWidget {
|
|
const InstructionsOfRoads({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Positioned(
|
|
bottom: 20,
|
|
left: 15,
|
|
right: 15,
|
|
child: GetBuilder<MapDriverController>(
|
|
builder: (controller) {
|
|
// إخفاء الشريط إذا لم يكن هناك تعليمات
|
|
if (controller.currentInstruction.isEmpty) return const SizedBox();
|
|
|
|
return TweenAnimationBuilder<double>(
|
|
tween: Tween(begin: 0.0, end: 1.0),
|
|
duration: const Duration(milliseconds: 500),
|
|
builder: (context, value, child) {
|
|
return Transform.translate(
|
|
offset: Offset(0, 50 * (1 - value)), // حركة انزلاق
|
|
child: Opacity(
|
|
opacity: value,
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 16, vertical: 12),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFF1F1F1F)
|
|
.withOpacity(0.95), // خلفية داكنة
|
|
borderRadius: BorderRadius.circular(16),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.4),
|
|
blurRadius: 15,
|
|
offset: const Offset(0, 5)),
|
|
],
|
|
border: Border.all(color: Colors.white.withOpacity(0.1)),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
// أيقونة الاتجاه
|
|
Container(
|
|
padding: const EdgeInsets.all(8),
|
|
decoration: BoxDecoration(
|
|
color: AppColor.primaryColor,
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: const Icon(Icons.turn_right_rounded,
|
|
color: Colors.white, size: 24),
|
|
),
|
|
const SizedBox(width: 14),
|
|
|
|
// نص التعليمات
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"NEXT STEP".tr,
|
|
style: TextStyle(
|
|
color: Colors.grey.shade500,
|
|
fontSize: 10,
|
|
fontWeight: FontWeight.bold,
|
|
letterSpacing: 1.2),
|
|
),
|
|
const SizedBox(height: 2),
|
|
Text(
|
|
controller.currentInstruction,
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
height: 1.2),
|
|
maxLines: 2,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// فاصل عمودي
|
|
Container(
|
|
width: 1,
|
|
height: 30,
|
|
color: Colors.white12,
|
|
margin: const EdgeInsets.symmetric(horizontal: 10)),
|
|
|
|
// زر التحكم بالصوت
|
|
Material(
|
|
color: Colors.transparent,
|
|
child: InkWell(
|
|
onTap: () => controller.toggleTts(),
|
|
borderRadius: BorderRadius.circular(20),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: Icon(
|
|
controller.isTtsEnabled
|
|
? Icons.volume_up_rounded
|
|
: Icons.volume_off_rounded,
|
|
color: controller.isTtsEnabled
|
|
? AppColor.greenColor
|
|
: Colors.grey,
|
|
size: 24,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 2. ويدجت زر الإلغاء (CancelWidget) - كامل
|
|
// ---------------------------------------------------------------------------
|
|
class CancelWidget extends StatelessWidget {
|
|
const CancelWidget({super.key, required this.mapDriverController});
|
|
final MapDriverController mapDriverController;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Positioned(
|
|
top: 10,
|
|
left: 15,
|
|
child: GetBuilder<MapDriverController>(builder: (controller) {
|
|
// نخفي الزر إذا انتهت الرحلة
|
|
if (controller.isRideFinished) return const SizedBox();
|
|
|
|
return ClipRRect(
|
|
borderRadius: BorderRadius.circular(30),
|
|
child: BackdropFilter(
|
|
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withOpacity(0.9),
|
|
borderRadius: BorderRadius.circular(30),
|
|
boxShadow: [
|
|
BoxShadow(color: Colors.black.withOpacity(0.1), blurRadius: 8)
|
|
],
|
|
),
|
|
child: Material(
|
|
color: Colors.transparent,
|
|
child: InkWell(
|
|
borderRadius: BorderRadius.circular(30),
|
|
onTap: () => _showCancelDialog(context, controller),
|
|
child: const Padding(
|
|
padding: EdgeInsets.all(10.0),
|
|
child: Icon(Icons.close_rounded,
|
|
color: AppColor.redColor, size: 26),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}),
|
|
);
|
|
}
|
|
|
|
void _showCancelDialog(BuildContext context, MapDriverController controller) {
|
|
Get.defaultDialog(
|
|
title: "Cancel Trip?".tr,
|
|
titleStyle: AppStyle.title.copyWith(fontWeight: FontWeight.bold),
|
|
radius: 16,
|
|
content: Column(
|
|
children: [
|
|
const Icon(Icons.warning_amber_rounded,
|
|
size: 50, color: Colors.orangeAccent),
|
|
const SizedBox(height: 10),
|
|
Text(
|
|
"Please tell us why you want to cancel.".tr,
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(color: Colors.grey[600]),
|
|
),
|
|
const SizedBox(height: 15),
|
|
Form(
|
|
key: controller.formKeyCancel,
|
|
child: MyTextForm(
|
|
controller: controller.cancelTripCotroller,
|
|
label: "Reason".tr,
|
|
hint: "Write your reason...".tr,
|
|
type: TextInputType.text,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
confirm: SizedBox(
|
|
width: 100,
|
|
child: MyElevatedButton(
|
|
title: 'Confirm'.tr,
|
|
kolor: AppColor.redColor,
|
|
onPressed: () async {
|
|
// استدعاء دالة الإلغاء من الكنترولر
|
|
await controller.cancelTripFromDriverAfterApplied();
|
|
// Get.back(); // عادة موجودة داخل الدالة في الكنترولر
|
|
},
|
|
),
|
|
),
|
|
cancel: SizedBox(
|
|
width: 100,
|
|
child: TextButton(
|
|
onPressed: () => Get.back(),
|
|
child: Text('Back'.tr, style: const TextStyle(color: Colors.grey)),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 3. ويدجت نافذة الأسعار (PricesWindow) - كامل
|
|
// ---------------------------------------------------------------------------
|
|
class PricesWindow extends StatelessWidget {
|
|
const PricesWindow({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return GetBuilder<MapDriverController>(builder: (controller) {
|
|
// إخفاء إذا لم تكن مفعلة
|
|
if (!controller.isPriceWindow) return const SizedBox();
|
|
|
|
return Container(
|
|
color: Colors.black.withOpacity(0.6), // خلفية معتمة
|
|
child: Center(
|
|
child: TweenAnimationBuilder<double>(
|
|
tween: Tween(begin: 0.8, end: 1.0),
|
|
duration: const Duration(milliseconds: 300),
|
|
curve: Curves.elasticOut,
|
|
builder: (context, scale, child) {
|
|
return Transform.scale(
|
|
scale: scale,
|
|
child: Container(
|
|
width: Get.width * 0.85,
|
|
padding: const EdgeInsets.all(30),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(24),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.2),
|
|
blurRadius: 20,
|
|
spreadRadius: 5,
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(15),
|
|
decoration: BoxDecoration(
|
|
color: AppColor.primaryColor.withOpacity(0.1),
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: Icon(Icons.check_circle_rounded,
|
|
color: AppColor.primaryColor, size: 50),
|
|
),
|
|
const SizedBox(height: 20),
|
|
Text(
|
|
'Total Price'.tr,
|
|
style: AppStyle.headTitle2
|
|
.copyWith(fontSize: 18, color: Colors.grey[600]),
|
|
),
|
|
const SizedBox(height: 10),
|
|
Text(
|
|
'${controller.totalCost} ${'\$'.tr}',
|
|
style: AppStyle.headTitle2.copyWith(
|
|
color: Colors.black87,
|
|
fontSize: 42,
|
|
fontWeight: FontWeight.w900,
|
|
),
|
|
),
|
|
const SizedBox(height: 30),
|
|
SizedBox(
|
|
width: double.infinity,
|
|
height: 55,
|
|
child: MyElevatedButton(
|
|
title: 'Collect Payment'.tr,
|
|
kolor: AppColor.primaryColor,
|
|
onPressed: () {
|
|
// الذهاب لصفحة التقييم
|
|
Get.to(() => RatePassenger(), arguments: {
|
|
'rideId': controller.rideId,
|
|
'passengerId': controller.passengerId,
|
|
'driverId': controller.driverId,
|
|
'price': controller.paymentAmount,
|
|
'walletChecked': controller.walletChecked
|
|
});
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
});
|
|
}
|
|
}
|