26-1-20/1

This commit is contained in:
Hamza-Ayed
2026-01-20 10:11:10 +03:00
parent 374f9e9bf3
commit 3c0ae4cf2f
53 changed files with 89652 additions and 6861 deletions

View File

@@ -1,10 +1,10 @@
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:flutter/material.dart';
import 'package:get/get.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';
@@ -15,6 +15,7 @@ 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});
@@ -22,26 +23,23 @@ class PassengerLocationMapPage extends StatelessWidget {
final MapDriverController mapDriverController =
Get.put(MapDriverController());
// Helper function to show exit confirmation dialog
// دالة ديالوج الخروج
Future<bool> showExitDialog() async {
bool? result = await Get.defaultDialog(
title: "Warning".tr,
titleStyle: AppStyle.title.copyWith(color: AppColor.redColor),
middleText:
"You are in an active ride. Leaving this screen might stop tracking. Are you sure you want to exit?"
.tr,
middleTextStyle: AppStyle.title,
"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), // Return false (Don't pop)
),
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 true (Allow pop)
),
title: 'Exit'.tr,
kolor: AppColor.redColor,
onPressed: () => Get.back(result: true)),
);
return result ?? false;
}
@@ -52,202 +50,205 @@ class PassengerLocationMapPage extends StatelessWidget {
if (Get.arguments != null && Get.arguments is Map<String, dynamic>) {
mapDriverController.argumentLoading();
mapDriverController.startTimerToShowPassengerInfoWindowFromDriver();
// 2. فرض التحديث لكل المعرفات (IDs) لضمان ظهورها
// لأن argumentLoading قد تستدعي update() العادية التي لا تؤثر على هؤلاء
mapDriverController
.update(['PassengerInfo', 'DriverEndBar', 'SosConnect']);
}
});
// ✅ Added PopScope to intercept back button
return PopScope(
canPop: false, // Prevents immediate popping
canPop: false,
onPopInvokedWithResult: (didPop, result) async {
if (didPop) {
return;
}
// Show dialog
if (didPop) return;
final shouldExit = await showExitDialog();
if (shouldExit) {
Get.back(); // Manually pop if confirmed
}
if (shouldExit) Get.back();
},
child: Scaffold(
body: SafeArea(
child: Stack(
resizeToAvoidBottomInset: false,
body: Stack(
children: [
// 1. Map
GoogleDriverMap(locationController: locationController),
// 1. الخريطة (الخلفية)
Positioned.fill(
child: GoogleDriverMap(locationController: locationController)),
// 2. Instructions
const InstructionsOfRoads(),
// 2. واجهة المستخدم (فوق الخريطة)
SafeArea(
child: Stack(
children: [
// أ) زر الإلغاء (أعلى اليسار)
CancelWidget(mapDriverController: mapDriverController),
// 3. Passenger Info
Positioned(
top: 0,
left: 0,
right: 0,
child: PassengerInfoWindow(),
// ب) شريط إنهاء الرحلة (أعلى الوسط)
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(), // ويدجت نظيفة
],
),
);
},
),
),
],
),
),
// 4. Cancel Widget
CancelWidget(mapDriverController: mapDriverController),
// 5. End Ride Bar
driverEndRideBar(),
// 6. SOS
SosConnect(),
// 7. Speed
speedCircle(),
// 8. External Map
Positioned(
bottom: 100,
right: 10,
child: GoogleMapApp(),
),
// 9. Prices Window
// 3. النوافذ المنبثقة (Overlay)
const PricesWindow(),
],
),
)),
),
);
}
}
// ... The rest of your widgets (InstructionsOfRoads, CancelWidget, etc.) remain unchanged ...
// ... Keep the code below exactly as you had it in the previous snippet ...
// ---------------------------------------------------------------------------
// 1. ويدجت شريط التعليمات (InstructionsOfRoads)
// ---------------------------------------------------------------------------
class InstructionsOfRoads extends StatelessWidget {
const InstructionsOfRoads({super.key});
@override
Widget build(BuildContext context) {
return Positioned(
bottom: 10,
left: MediaQuery.of(context).size.width * 0.15,
right: MediaQuery.of(context).size.width * 0.15,
child: GetBuilder<MapDriverController>(
builder: (controller) => controller.currentInstruction.isNotEmpty
? AnimatedContainer(
duration: const Duration(milliseconds: 300),
padding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(30),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 8,
offset: const Offset(0, 4),
),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.directions, color: AppColor.primaryColor),
const SizedBox(width: 10),
Expanded(
child: Text(
controller.currentInstruction,
style: AppStyle.title.copyWith(fontSize: 16),
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 10),
InkWell(
onTap: () {
controller.toggleTts();
},
child: Icon(
controller.isTtsEnabled
? Icons.volume_up
: Icons.volume_off,
color: controller.isTtsEnabled
? AppColor.greenColor
: Colors.grey,
),
),
],
),
)
: const SizedBox(),
),
);
}
}
class CancelWidget extends StatelessWidget {
const CancelWidget({
super.key,
required this.mapDriverController,
});
final MapDriverController mapDriverController;
@override
Widget build(BuildContext context) {
return Positioned(
top: 70,
left: 10,
bottom: 20,
left: 15,
right: 15,
child: GetBuilder<MapDriverController>(
builder: (controller) {
if (controller.isRideFinished) return const SizedBox.shrink();
// إخفاء الشريط إذا لم يكن هناك تعليمات
if (controller.currentInstruction.isEmpty) return const SizedBox();
return GestureDetector(
onTap: () {
Get.defaultDialog(
title: "Are you sure you want to cancel this trip?".tr,
titleStyle: AppStyle.title,
content: Column(
children: [
Text("Why do you want to cancel this trip?".tr),
Form(
key: mapDriverController.formKeyCancel,
child: MyTextForm(
controller: mapDriverController.cancelTripCotroller,
label: "Write the reason for canceling the trip".tr,
hint: "Write the reason for canceling the trip".tr,
type: TextInputType.name,
))
],
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,
),
),
),
),
],
),
),
confirm: MyElevatedButton(
title: 'Ok'.tr,
kolor: AppColor.redColor,
onPressed: () async {
await mapDriverController
.cancelTripFromDriverAfterApplied();
Get.back();
}),
cancel: MyElevatedButton(
title: 'No'.tr,
onPressed: () {
Get.back();
}));
},
child: Container(
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 5,
),
],
),
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Icon(
Icons.clear,
size: 30,
color: AppColor.redColor,
),
),
),
);
},
);
},
),
@@ -255,56 +256,193 @@ class CancelWidget extends StatelessWidget {
}
}
class PricesWindow extends StatelessWidget {
const PricesWindow({
super.key,
});
// ---------------------------------------------------------------------------
// 2. ويدجت زر الإلغاء (CancelWidget) - كامل
// ---------------------------------------------------------------------------
class CancelWidget extends StatelessWidget {
const CancelWidget({super.key, required this.mapDriverController});
final MapDriverController mapDriverController;
@override
Widget build(BuildContext context) {
return GetBuilder<MapDriverController>(builder: (mapDriverController) {
return mapDriverController.isPriceWindow
? Container(
color: Colors.black.withOpacity(0.5),
child: Center(
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.8,
padding: const EdgeInsets.all(24),
width: Get.width * 0.85,
padding: const EdgeInsets.all(30),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
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 is '.tr,
style: AppStyle.headTitle2,
textAlign: TextAlign.center,
'Total Price'.tr,
style: AppStyle.headTitle2
.copyWith(fontSize: 18, color: Colors.grey[600]),
),
const SizedBox(height: 10),
Text(
'${mapDriverController.totalPricePassenger} ${'\$'.tr}',
'${controller.totalCost} ${'\$'.tr}',
style: AppStyle.headTitle2.copyWith(
color: AppColor.primaryColor, fontSize: 36),
color: Colors.black87,
fontSize: 42,
fontWeight: FontWeight.w900,
),
),
const SizedBox(
height: 20,
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
});
},
),
),
MyElevatedButton(
title: 'ok'.tr,
onPressed: () =>
Get.to(() => RatePassenger(), arguments: {
'rideId': mapDriverController.rideId,
'passengerId': mapDriverController.passengerId,
'driverId': mapDriverController.driverId
}))
],
),
),
),
)
: const SizedBox();
);
},
),
),
);
});
}
}