import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:sefer_driver/constant/colors.dart'; import 'package:sefer_driver/controller/home/captin/map_driver_controller.dart'; import '../../../../constant/box_name.dart'; import '../../../../constant/style.dart'; import 'package:sefer_driver/views/widgets/mydialoug.dart'; import '../../../../controller/voice_call_controller.dart'; import '../../../../controller/functions/launch.dart'; import '../../../../controller/functions/location_controller.dart'; import '../../../../main.dart'; import '../../../widgets/error_snakbar.dart'; import 'package:flutter_font_icons/flutter_font_icons.dart'; import '../../../../controller/firebase/notification_service.dart'; import '../../../../controller/functions/crud.dart'; import '../../../../constant/links.dart'; import '../../../widgets/my_textField.dart'; class PassengerInfoWindow extends StatelessWidget { const PassengerInfoWindow({super.key}); @override Widget build(BuildContext context) { // 1. حساب الهوامش الآمنة (SafeArea) من الأسفل final double safeBottomPadding = MediaQuery.of(context).padding.bottom; return GetBuilder( // id: 'PassengerInfo', builder: (controller) { // --- 1. تجهيز بيانات العرض --- String displayName = controller.passengerName ?? "Unknown"; String avatarText = ""; // التحقق من اللغة (عربي/إنجليزي) للاسم المختصر bool isArabic = RegExp(r'[\u0600-\u06FF]').hasMatch(displayName); if (displayName.isNotEmpty) { if (isArabic) { avatarText = displayName.split(' ').first; if (avatarText.length > 4) { avatarText = avatarText.substring(0, 4); } } else { avatarText = displayName[0].toUpperCase(); } } // --- 2. المنطق الذكي للموقع (Smart Positioning) --- // نرفع النافذة إذا ظهر شريط التعليمات في الأسفل لتجنب التغطية bool hasInstructions = controller.currentInstruction.isNotEmpty; double instructionsHeight = hasInstructions ? 110.0 : 0.0; // الموقع النهائي: إذا كانت مفعلة تظهر، وإلا تختفي للأسفل double finalBottomPosition = controller.isPassengerInfoWindow ? (safeBottomPadding + 10 + instructionsHeight) : -450.0; return AnimatedPositioned( duration: const Duration(milliseconds: 500), curve: Curves.fastOutSlowIn, bottom: finalBottomPosition, left: 12.0, right: 12.0, child: Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(24), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.15), blurRadius: 25, offset: const Offset(0, 8), spreadRadius: 2, ) ], ), child: Column( mainAxisSize: MainAxisSize.min, children: [ // --- مقبض السحب (Visual Handle) --- Center( child: Container( margin: const EdgeInsets.only(top: 8, bottom: 4), width: 40, height: 4, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(10), ), ), ), Padding( padding: const EdgeInsets.fromLTRB(16, 4, 16, 16), child: Column( children: [ // --- الصف العلوي: معلومات الراكب --- Row( children: [ // الصورة الرمزية Container( width: 52, height: 52, decoration: BoxDecoration( shape: BoxShape.circle, color: AppColor.primaryColor.withOpacity(0.1), border: Border.all( color: AppColor.primaryColor.withOpacity(0.2)), ), child: Center( child: Text( avatarText, style: TextStyle( color: AppColor.primaryColor, fontWeight: FontWeight.bold, fontSize: isArabic ? 14 : 20, ), ), ), ), const SizedBox(width: 12), // النصوص (الاسم والمسافة) Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( displayName, style: AppStyle.title.copyWith( fontWeight: FontWeight.w800, fontSize: 16), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), Row( children: [ Icon(Icons.location_on, size: 14, color: Colors.grey[600]), const SizedBox(width: 4), // 🔥 [Fix Overflow] Flexible لمنع الـ overflow + تحويل المسافة // السيرفر يُرجع المسافة بالأمتار (5864.022) Flexible( child: Text( _formatDistanceDisplay( controller.distance), style: TextStyle( color: Colors.grey[700], fontSize: 13, fontWeight: FontWeight.w600), overflow: TextOverflow.ellipsis, maxLines: 1, ), ), const SizedBox(width: 10), Icon(Icons.access_time_filled, size: 14, color: Colors.grey[600]), const SizedBox(width: 4), Text( controller.hours > 0 ? '${controller.hours}h ${controller.minutes}m' : '${controller.minutes} min', style: TextStyle( color: Colors.grey[700], fontSize: 13, fontWeight: FontWeight.w600), ), ], ), ], ), ), // أزرار جانبية (سرعة + اتصال) Row( children: [ _buildSpeedCircle(), const SizedBox(width: 10), InkWell( onTap: () async { controller.isSocialPressed = true; // نفحص النتيجة: هل مسموح له يتصل؟ bool canCall = await controller.driverCallPassenger(); if (canCall) { _showCallSelectionDialog( context, controller); } else { // هنا ممكن تظهر رسالة: تم منع الاتصال بسبب كثرة الإلغاءات mySnackeBarError( "You cannot call the passenger due to policy violations" .tr); } }, borderRadius: BorderRadius.circular(50), child: Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: Colors.green.withOpacity(0.1), shape: BoxShape.circle, border: Border.all( color: Colors.green.withOpacity(0.2)), ), child: const Icon(Icons.phone, color: Colors.green, size: 22), ), ), const SizedBox(width: 8), InkWell( onTap: () => _showMessageOptions(context, controller), borderRadius: BorderRadius.circular(50), child: Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: Colors.grey.shade100, shape: BoxShape.circle, border: Border.all(color: Colors.grey.shade300), ), child: Icon( MaterialCommunityIcons .message_text_outline, color: AppColor.primaryColor, size: 22), ), ), ], ), ], ), // خط فاصل Padding( padding: const EdgeInsets.symmetric(vertical: 12), child: Divider(height: 1, color: Colors.grey.shade100), ), // --- مؤشر الانتظار (يظهر عند الوصول) --- if (controller.remainingTimeInPassengerLocatioWait < 300 && controller.remainingTimeInPassengerLocatioWait != 0 && !controller.isRideBegin) ...[ _buildWaitingIndicator(controller), const SizedBox(height: 12), ], // --- الأزرار الرئيسية (وصلت / ابدأ) --- if (!controller.isRideBegin) _buildActionButtons(controller), // --- زر الإلغاء المدفوع (بعد انتهاء وقت الانتظار) --- if (controller.isdriverWaitTimeEnd && !controller.isRideBegin) Padding( padding: const EdgeInsets.only(top: 10), child: SizedBox( width: double.infinity, height: 48, child: ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFFFFF0F0), foregroundColor: Colors.red, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), side: const BorderSide( color: Color(0xFFFFCDCD)), ), ), onPressed: () { MyDialog().getDialog( 'Confirm Cancellation'.tr, 'Are you sure you want to cancel and collect the fee?' .tr, () async { // كود الإلغاء Get.back(); controller .addWaitingTimeCostFromPassengerToDriverWallet(); }); }, icon: const Icon(Icons.money_off, size: 20), label: Text('Cancel & Collect Fee'.tr, style: const TextStyle( fontWeight: FontWeight.bold)), ), ), ), ], ), ), ], ), ), ); }, ); } // --- Widgets مساعدة --- Widget _buildSpeedCircle() { return GetBuilder(builder: (locController) { int speedKmh = (locController.speed * 3.6).round(); Color color = speedKmh > 100 ? Colors.red : const Color(0xFF0D47A1); return Container( width: 42, height: 42, decoration: BoxDecoration( color: Colors.white, shape: BoxShape.circle, border: Border.all(color: color.withOpacity(0.3), width: 2), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('$speedKmh', style: TextStyle( color: color, fontWeight: FontWeight.w900, fontSize: 13, height: 1)), Text('km/h', style: TextStyle( color: color.withOpacity(0.7), fontSize: 8, height: 1)), ], ), ); }); } Widget _buildWaitingIndicator(MapDriverController controller) { bool isUrgent = controller.remainingTimeInPassengerLocatioWait < 60; return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: Colors.grey.shade50, borderRadius: BorderRadius.circular(8), ), child: Row( children: [ Icon(Icons.timer_outlined, size: 16, color: isUrgent ? Colors.red : Colors.green), const SizedBox(width: 8), Expanded( child: ClipRRect( borderRadius: BorderRadius.circular(4), child: LinearProgressIndicator( value: controller.progressInPassengerLocationFromDriver.toDouble(), backgroundColor: Colors.grey[200], color: isUrgent ? Colors.red : Colors.green, minHeight: 6, ), ), ), const SizedBox(width: 10), Text( controller.stringRemainingTimeWaitingPassenger, style: TextStyle( fontWeight: FontWeight.w900, color: isUrgent ? Colors.red : Colors.green, fontFamily: 'monospace'), ), ], ), ); } Widget _buildActionButtons(MapDriverController controller) { if (controller.isArrivedSend) { return SizedBox( width: double.infinity, height: 50, child: ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFFF1C40F), foregroundColor: Colors.white, elevation: 2, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), onPressed: () async { await controller.markDriverAsArrived(); }, icon: const Icon(Icons.near_me_rounded), label: Text('I Have Arrived'.tr, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16)), ), ); } else { return SizedBox( width: double.infinity, height: 50, child: ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF27AE60), foregroundColor: Colors.white, elevation: 2, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), onPressed: () { // 🔥 [Fix Start-Ride] استخدام Get.defaultDialog بدلاً من MyDialog // لأن MyDialog يستخدم Navigator.of(context, rootNavigator: true).pop() // الذي يتعارض مع Get.dialog() المستخدم في startRideFromDriver() // وقد يُسبب Get.back() اللاحق إغلاق صفحة الماب بدلاً من الـ loading dialog Get.defaultDialog( title: "Start Trip?".tr, titleStyle: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), middleText: "Ensure the passenger is in the car.".tr, barrierDismissible: true, radius: 14, confirm: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF27AE60), foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10)), ), onPressed: () async { // نُغلق الديالوج بـ Get.back() لضمان أن GetX يعرف أنه أُغلق Get.back(); // ثم نُنفذ startRideFromDriver الذي يستخدم Get.dialog و Get.back بأمان await controller.startRideFromDriver(); }, child: Text('Start'.tr, style: const TextStyle(fontWeight: FontWeight.bold)), ), cancel: TextButton( onPressed: () => Get.back(), child: Text('Cancel'.tr, style: const TextStyle(color: Colors.grey)), ), ); }, icon: const Icon(Icons.play_circle_fill_rounded), label: Text('Start Ride'.tr, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16)), ), ); } } void _showCallSelectionDialog( BuildContext context, MapDriverController controller) { Get.dialog( Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), child: Padding( padding: const EdgeInsets.all(20), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text( 'Call Options'.tr, style: AppStyle.title .copyWith(fontSize: 18, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), Text( 'Choose how you want to call the passenger'.tr, style: const TextStyle(color: Colors.grey, fontSize: 14), textAlign: TextAlign.center, ), const SizedBox(height: 20), ListTile( leading: CircleAvatar( backgroundColor: Colors.green.withOpacity(0.1), child: const Icon(Icons.phone_android_rounded, color: Colors.green), ), title: Text('Standard Call'.tr, style: const TextStyle(fontWeight: FontWeight.bold)), subtitle: Text('Uses cellular network'.tr, style: const TextStyle(fontSize: 12)), onTap: () { Get.back(); makePhoneCall(controller.passengerPhone.toString()); }, ), const Divider(), ListTile( leading: CircleAvatar( backgroundColor: AppColor.primaryColor.withOpacity(0.1), child: Icon(Icons.wifi_calling_3_rounded, color: AppColor.primaryColor), ), title: Text('Free Call'.tr, style: const TextStyle(fontWeight: FontWeight.bold)), subtitle: Text('Voice call over internet'.tr, style: const TextStyle(fontSize: 12)), onTap: () { Get.back(); final voiceCtrl = Get.find(); final driverId = box.read(BoxName.driverID).toString(); voiceCtrl.startCall( rideIdVal: controller.rideId, driverId: driverId, passengerId: controller.passengerId, remoteNameVal: controller.passengerName ?? "Passenger", ); }, ), ], ), ), ), ); } void _showMessageOptions( BuildContext context, MapDriverController controller) { Get.bottomSheet( Container( padding: const EdgeInsets.all(20), decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(25)), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text('Quick Messages'.tr, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), const SizedBox(height: 15), _buildQuickMessageItem("Where are you, sir?".tr, controller), _buildQuickMessageItem("I've arrived.".tr, controller), const Divider(), Row( children: [ Expanded( child: TextField( controller: controller.messageToPassenger, decoration: InputDecoration(hintText: 'Type a message...'.tr), ), ), IconButton( icon: const Icon(Icons.send), onPressed: () { _sendMessage(controller, controller.messageToPassenger.text, 'cancel'); controller.messageToPassenger.clear(); Get.back(); }, ), ], ), ], ), ), ); } Widget _buildQuickMessageItem(String text, MapDriverController controller) { return ListTile( title: Text(text), onTap: () { _sendMessage(controller, text, 'ding'); Get.back(); }, ); } void _sendMessage( MapDriverController controller, String body, String tone) async { try { await CRUD().post( link: AppLink.sendChatMessage, payload: { 'ride_id': controller.rideId.toString(), 'sender_id': box.read(BoxName.driverID).toString(), 'receiver_id': controller.passengerId.toString(), 'sender_type': 'driver', 'message_content': body, }, ); } catch (e) { // Ignore or log error } NotificationService.sendNotification( target: controller.tokenPassenger.toString(), title: 'Driver Message'.tr, body: body, isTopic: false, tone: tone, driverList: [], category: 'message From Driver', ); } } /// تحويل المسافة من الأمتار إلى عرض مقروء /// السيرفر يُرجع المسافة بالأمتار (مثال: 5864.022) /// النتيجة: "5.9 km" أو "250 م" String _formatDistanceDisplay(String rawDistance) { final meters = double.tryParse(rawDistance) ?? 0.0; if (meters >= 1000) { return '${(meters / 1000).toStringAsFixed(1)} km'; } else if (meters > 0) { return '${meters.toStringAsFixed(0)} م'; } return rawDistance; // fallback للقيمة الأصلية }