feat: N1d/N2/N3 + قانون فلاتر وإعادة ضبط تطبيق السائق

الباك إند:
- تعيين أول أدمن صار يُنشئ المستخدم إن لم يوجد — كان يشترط دخولاً سابقاً،
  وهي بيضة ودجاجة تمنع الدخول إلى لوحة أي مستأجر جديد أصلاً.
- OTP_DEV_MODE كان يفشل مفتوحاً (`!== 'false'`): غياب المتغيّر أو خطأ مطبعي
  يترك الإنتاج برمز ثابت يفتح كل حساب، ويحرس السحب المالي كذلك. صار
  `=== 'true'` في الإعداد وفي قارئَيه، مع تحذير عند الإقلاع.
- N2: التحقّق من اللوغو عند الرفع (PNG/JPEG · 512+ · مربّع · سقف 5MB) عبر
  قراءة الترويسة بلا اعتمادية — بدل اكتشاف أيقونة ممطوطة بعد النشر.

N3: السكربت يولّد build_config.dart بأعلام const (طبقات docs/22 §1.5)،
ويضبط bundle IDs واسم التطبيق والأيقونات/splash، و--build يشغّل Shorebird.

فلاتر:
- docs/23: قانون مُلزِم للتطبيقين (البنية · Cubit · طبقة الشبكة · الأعلام).
- السائق: أُزيل Dart القديم (GetX) مع الإبقاء على الأصيل والإضافات وشهادات
  التوقيع وShorebird وFirebase الخاص به، وأُعيد هيكلته مطابقاً للراكب.
- طبقة الشبكة ترسل x-device-id وتجدّد التوكن عند 401 مرة واحدة فقط.
- الحزم موحّدة بين التطبيقين، والـAPI https حصراً في الاثنين.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Hamza-Ayed
2026-07-18 13:06:46 +03:00
co-authored by Claude Fable 5
parent 3ab74a13a8
commit de4bbd00ac
271 changed files with 1333 additions and 81068 deletions
@@ -1,218 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:slide_to_act/slide_to_act.dart';
import '../../../../constant/colors.dart';
import '../../../../controller/home/captin/map_driver_controller.dart';
Widget driverEndRideBar() {
return GetBuilder<MapDriverController>(
builder: (controller) {
// 🔥 فحص هل السعر ثابت للعرض
final String carType = controller.carType;
final bool isFixed = (carType == 'Speed' ||
carType == 'Awfar' ||
carType == 'Fixed Price');
return AnimatedContainer(
duration: const Duration(milliseconds: 400),
curve: Curves.easeOutBack,
transform: Matrix4.translationValues(
0,
controller.isRideStarted ? 0 : -300,
0,
), // Matrix4.translationValues مستخدمة للإزاحة [web:28]
margin: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
padding: const EdgeInsets.all(15),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 20,
offset: const Offset(0, 8),
)
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// لوحة العدادات
Container(
padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 10),
decoration: BoxDecoration(
color: const Color(0xFFF8F9FA),
borderRadius: BorderRadius.circular(18),
border: Border.all(color: Colors.grey.shade200),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// 1) المسافة (تتغير دائماً)
_buildLiveMetric(
icon: Icons.alt_route_rounded,
iconColor: Colors.blueAccent,
value: controller.currentRideDistanceKm.toStringAsFixed(2),
unit: 'KM'.tr,
label: 'Traveled'.tr,
),
_buildVerticalDivider(),
// 2) السعر (ثابت أو متغير)
_buildLiveMetric(
icon: isFixed
? Icons.lock_outline_rounded
: Icons.attach_money_rounded,
iconColor: isFixed ? Colors.grey : AppColor.primaryColor,
value: NumberFormat('#,##0').format(
double.tryParse(controller.price.toString()) ?? 0,
),
unit: 'SYP'.tr,
label: isFixed ? 'Fixed Price'.tr : 'Meter Fare'.tr,
isHighlight: true,
isFixedStyle: isFixed,
),
_buildVerticalDivider(),
// 3) الوقت (تصغير الخط + إخفاء الساعات إذا 0)
_buildLiveMetric(
icon: Icons.timer_outlined,
iconColor: Colors.orange,
value: _formatDurationFromStart(controller),
unit: '',
label: 'Duration'.tr,
valueFontSize: 14, // ✅ تصغير خط “المدة”
),
],
),
),
const SizedBox(height: 20),
// زر الإنهاء
SlideAction(
key: ValueKey(controller.isRideFinished),
height: 60,
borderRadius: 18,
elevation: 0,
text: 'Swipe to End Trip'.tr,
textStyle: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white,
),
outerColor: AppColor.redColor,
innerColor: Colors.white,
sliderButtonIcon: const Icon(
Icons.stop_circle_outlined,
color: AppColor.redColor,
size: 32,
),
onSubmit: () async {
await controller.finishRideFromDriver(isFromSlider: true);
return null;
},
), // SlideAction مثال الاستخدام موجود في صفحة الحزمة [web:19]
],
),
);
},
); // GetBuilder يعيد البناء عند update() من الكنترولر [web:21]
}
/// دالة تنسيق المدة:
/// - أقل من ساعة: mm:ss
/// - ساعة فأكثر: h:mm:ss (خانة واحدة للساعات بدون leading zero)
String _formatDurationFromStart(MapDriverController controller) {
if (controller.rideStartTime == null) return "00:00";
final d = DateTime.now().difference(controller.rideStartTime!);
String twoDigits(int n) => n.toString().padLeft(2, "0");
final hours = d.inHours;
final minutes = d.inMinutes.remainder(60);
final seconds = d.inSeconds.remainder(60);
if (hours == 0) {
// mm:ss
final totalMinutes = d.inMinutes;
return "${twoDigits(totalMinutes)}:${twoDigits(seconds)}";
}
// h:mm:ss
return "$hours:${twoDigits(minutes)}:${twoDigits(seconds)}";
} // Duration وتفكيكه (inHours/inMinutes/inSeconds) من أساسيات Dart [web:11]
Widget _buildLiveMetric({
required IconData icon,
required Color iconColor,
required String value,
required String unit,
required String label,
bool isHighlight = false,
bool isFixedStyle = false,
double? valueFontSize, // ✅ جديد: حجم خط القيمة فقط
}) {
final effectiveFontSize = valueFontSize ?? (isHighlight ? 20 : 18);
return Expanded(
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, size: 14, color: iconColor),
const SizedBox(width: 4),
Text(
label,
style: TextStyle(
fontSize: 10,
color: Colors.grey[600],
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 6),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
children: [
Text(
value,
style: TextStyle(
fontSize: effectiveFontSize, // ✅ هنا صار التحكم
fontWeight: FontWeight.w900,
color: isFixedStyle
? Colors.grey[800]
: (isHighlight ? AppColor.primaryColor : Colors.black87),
fontFamily: 'monospace',
),
),
if (unit.isNotEmpty) ...[
const SizedBox(width: 2),
Text(
unit,
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
color: Colors.grey[600],
),
),
]
],
),
],
),
);
}
Widget _buildVerticalDivider() {
return Container(height: 35, width: 1, color: Colors.grey.shade300);
}
@@ -1,117 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import '../../../../controller/functions/location_controller.dart';
import '../../../../controller/home/captin/map_driver_controller.dart';
class GoogleDriverMap extends StatelessWidget {
const GoogleDriverMap({
super.key,
required this.locationController,
});
final LocationController locationController;
@override
Widget build(BuildContext context) {
final MapDriverController controller = Get.put(MapDriverController());
// New: تحديد قيمة الـ padding لتحريك مركز الخريطة للأعلى
final double mapPaddingBottom = MediaQuery.of(context).size.height * 0.3;
return GetBuilder<MapDriverController>(
builder: (controller) => GoogleMap(
onMapCreated: (mapController) {
controller.onMapCreated(mapController);
// New: تطبيق الـ padding بعد إنشاء الخريطة مباشرة
mapController.setMapStyle('[]'); // يمكنك إضافة تصميم مخصص للخريطة هنا
// يمكنك استخدام CameraUpdate.scrollBy لتحريك الكاميرا إذا رغبت بذلك:
// controller.mapController!.animateCamera(CameraUpdate.scrollBy(0, mapPaddingBottom));
},
// New: إضافة padding لتحريك مركز الخريطة للأعلى، مما يجعل أيقونة السائق تظهر في الأسفل
zoomControlsEnabled: false, // Changed: تم إخفاء أزرار الزوم الافتراضية
initialCameraPosition: CameraPosition(
target: locationController.myLocation,
zoom: 17,
bearing: locationController.heading, // استخدام اتجاه السائق
tilt: 60, // زاوية ميل
),
onCameraMove: (position) {
CameraPosition(
target: locationController.myLocation,
zoom: 17.5,
tilt: 50.0,
bearing: locationController.heading,
);
},
padding: EdgeInsets.only(bottom: 50, top: Get.height * 0.7),
minMaxZoomPreference: const MinMaxZoomPreference(8, 18),
myLocationEnabled: false, // Changed: تم الاعتماد على ماركر مخصص
myLocationButtonEnabled: true,
compassEnabled: true,
mapType: MapType.terrain,
trafficEnabled: true, // Changed: تفعيل عرض حركة المرور
buildingsEnabled: true,
polylines: {
// Polyline(
// zIndex: 2,
// polylineId: const PolylineId('route1'),
// points: controller.polylineCoordinates,
// color: const Color.fromARGB(255, 163, 81, 246),
// width: 6, // Changed: زيادة عرض الخط
// startCap: Cap.roundCap,
// endCap: Cap.roundCap,
// ),
// Polyline(
// zIndex: 2,
// polylineId: const PolylineId('route'),
// points: controller.polylineCoordinatesDestination,
// color: const Color.fromARGB(255, 10, 29, 126),
// width: 6, // Changed: زيادة عرض الخط
// startCap: Cap.roundCap,
// endCap: Cap.roundCap,
// ),
Polyline(
polylineId: const PolylineId('upcoming_route'),
points: controller.upcomingPathPoints,
color: Colors.blue, // أو أي لون آخر تختاره للمسار
width: 8,
zIndex: 2,
),
// 2. الخط المقطوع (تحت)
Polyline(
polylineId: const PolylineId('traveled_route'),
points: controller.traveledPathPoints,
color: Colors.grey.withOpacity(0.8),
width: 7,
zIndex: 1,
),
},
markers: {
Marker(
markerId: MarkerId('MyLocation'.tr),
position: locationController.myLocation,
rotation: locationController.heading,
flat: true,
anchor: const Offset(0.5, 0.5),
icon: controller.carIcon,
),
Marker(
markerId: MarkerId('start'.tr),
position: controller.latLngPassengerLocation,
icon: controller.startIcon,
),
Marker(
markerId: MarkerId('end'.tr),
position: controller.latLngPassengerDestination,
icon: controller.endIcon,
),
},
),
);
}
}
@@ -1,62 +0,0 @@
import 'package:sefer_driver/constant/colors.dart';
import 'package:sefer_driver/constant/style.dart';
import 'package:sefer_driver/controller/home/captin/map_driver_controller.dart';
import 'package:flutter/material.dart';
import 'package:flutter_font_icons/flutter_font_icons.dart';
import 'package:get/get.dart';
import 'package:url_launcher/url_launcher.dart';
class GoogleMapApp extends StatelessWidget {
const GoogleMapApp({super.key});
@override
Widget build(BuildContext context) {
return GetBuilder<MapDriverController>(
builder: (mapDriverController) {
if (!mapDriverController.isRideStarted) return const SizedBox();
// REMOVED: Positioned wrapper
return Material(
elevation: 8,
shadowColor: Colors.black26,
borderRadius: BorderRadius.circular(30),
color: Colors.white,
child: InkWell(
borderRadius: BorderRadius.circular(30),
onTap: () async {
var endLat =
mapDriverController.latLngPassengerDestination.latitude;
var endLng =
mapDriverController.latLngPassengerDestination.longitude;
String url = 'google.navigation:q=$endLat,$endLng';
if (await canLaunchUrl(Uri.parse(url))) {
await launchUrl(Uri.parse(url));
} else {
String webUrl =
'https://www.google.com/maps/dir/?api=1&destination=$endLat,$endLng';
if (await canLaunchUrl(Uri.parse(webUrl))) {
await launchUrl(Uri.parse(webUrl));
}
}
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
decoration: BoxDecoration(
color: Colors.blueAccent,
borderRadius: BorderRadius.circular(30),
border: Border.all(
color: AppColor.blueColor.withOpacity(0.2), width: 1),
),
child: const Icon(
MaterialCommunityIcons.google_maps,
size: 28,
color: AppColor.secondaryColor,
),
),
),
);
},
);
}
}
@@ -1,392 +0,0 @@
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/style.dart';
import 'package:sefer_driver/views/widgets/mydialoug.dart';
import '../../../../controller/functions/launch.dart';
import '../../../../controller/functions/location_controller.dart';
import '../../../widgets/error_snakbar.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<MapDriverController>(
// 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),
Text(
'${controller.distance} km',
style: TextStyle(
color: Colors.grey[700],
fontSize: 13,
fontWeight: FontWeight.w600),
),
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) {
makePhoneCall(
controller.passengerPhone.toString());
} 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),
),
),
],
),
],
),
// خط فاصل
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<LocationController>(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: () {
MyDialog().getDialog(
"Start Trip?".tr,
"Ensure the passenger is in the car.".tr,
() async {
await controller.startRideFromDriver();
Get.back();
},
);
},
icon: const Icon(Icons.play_circle_fill_rounded),
label: Text('Start Ride'.tr,
style:
const TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
),
);
}
}
}
@@ -1,233 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
import 'package:sefer_driver/views/widgets/my_textField.dart';
import 'package:sefer_driver/views/widgets/elevated_btn.dart'; // Checked import
import 'package:flutter_font_icons/flutter_font_icons.dart';
import '../../../../constant/box_name.dart';
import '../../../../constant/colors.dart';
import '../../../../constant/style.dart';
import '../../../../controller/firebase/notification_service.dart';
import '../../../../controller/functions/launch.dart';
import '../../../../controller/home/captin/map_driver_controller.dart';
import '../../../../main.dart';
class SosConnect extends StatelessWidget {
SosConnect({super.key});
@override
Widget build(BuildContext context) {
return GetBuilder<MapDriverController>(
id: 'SosConnect', // Keep ID for updates
builder: (controller) {
// Check visibility logic
bool showPassengerContact =
!controller.isRideBegin && controller.isPassengerInfoWindow;
bool showSos = controller.isRideStarted;
if (!showPassengerContact && !showSos) return const SizedBox();
// REMOVED: Positioned widget
return Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.95),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// === Call Button ===
if (showPassengerContact)
_buildModernActionButton(
icon: Icons.phone_in_talk,
color: Colors.white,
bgColor: AppColor.blueColor,
tooltip: 'Call Passenger',
onTap: () async {
controller.isSocialPressed = true;
bool canCall = await controller.driverCallPassenger();
if (canCall) {
makePhoneCall(controller.passengerPhone.toString());
} else {
mySnackeBarError("Policy restriction on calls".tr);
}
},
),
if (showPassengerContact) const SizedBox(height: 12),
// === Message Button ===
if (showPassengerContact)
_buildModernActionButton(
icon: MaterialCommunityIcons.message_text_outline,
color: AppColor.primaryColor,
bgColor: Colors.grey.shade100,
tooltip: 'Message Passenger',
onTap: () => _showMessageOptions(context, controller),
),
// === SOS Button ===
if (showSos)
_buildModernActionButton(
icon: MaterialIcons.warning,
color: Colors.white,
bgColor: AppColor.redColor,
tooltip: 'EMERGENCY SOS',
isPulsing: true,
onTap: () => _handleSosCall(controller),
),
],
),
);
},
);
}
Widget _buildModernActionButton({
required IconData icon,
required Color color,
required Color bgColor,
required String tooltip,
required VoidCallback onTap,
bool isPulsing = false,
}) {
return Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
child: Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: bgColor,
borderRadius: BorderRadius.circular(12),
boxShadow: isPulsing
? [
BoxShadow(
color: bgColor.withOpacity(0.4),
blurRadius: 12,
spreadRadius: 2,
)
]
: [],
),
child: Icon(icon, color: color, size: 24),
),
),
);
}
// --- Logic Functions ---
void _handleSosCall(MapDriverController mapDriverController) {
if (box.read(BoxName.sosPhoneDriver) == null) {
Get.defaultDialog(
title: 'Emergency Contact'.tr,
content: Column(
children: [
Text('Please enter the emergency number.'.tr),
Form(
key: mapDriverController.formKey1,
child: MyTextForm(
controller: mapDriverController.sosEmergincyNumberCotroller,
label: 'Phone Number'.tr,
hint: '01xxxxxxxxx',
type: TextInputType.phone,
),
),
],
),
confirm: MyElevatedButton(
title: 'Save & Call'.tr,
onPressed: () {
if (mapDriverController.formKey1.currentState!.validate()) {
box.write(BoxName.sosPhoneDriver,
mapDriverController.sosEmergincyNumberCotroller.text);
Get.back();
launchCommunication(
'phone', box.read(BoxName.sosPhoneDriver), '');
}
},
),
);
} else {
launchCommunication('phone', box.read(BoxName.sosPhoneDriver), '');
}
}
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) {
NotificationService.sendNotification(
target: controller.tokenPassenger.toString(),
title: 'Driver Message'.tr,
body: body,
isTopic: false,
tone: tone,
driverList: [],
category: 'message From Driver',
);
}
}
@@ -1,76 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../../constant/colors.dart';
import '../../../../constant/style.dart';
import '../../../../controller/home/captin/map_driver_controller.dart';
// ويدجت للعرض فقط (بدون منطق فتح نوافذ)
class SpeedCircle extends StatelessWidget {
const SpeedCircle({super.key});
@override
Widget build(BuildContext context) {
return GetBuilder<MapDriverController>(
id: 'SpeedCircle', // نحدد ID للتحديث الخفيف
builder: (controller) {
// إذا السرعة 0 أو أقل، نخفي الدائرة
if (controller.speed <= 0) return const SizedBox();
bool isSpeeding = controller.speed > 100;
return Positioned(
left: 20,
top: 100, // مكانها المناسب
child: Container(
width: 70,
height: 70,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 10,
offset: const Offset(0, 4),
)
],
border: Border.all(
color: _getSpeedColor(controller.speed),
width: 4,
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
controller.speed.toStringAsFixed(0),
style: TextStyle(
fontFamily: AppStyle.title.fontFamily,
fontSize: 22,
fontWeight: FontWeight.w900,
height: 1.0,
color: Colors.black87,
),
),
Text(
"km/h",
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
color: Colors.grey,
),
),
],
),
),
);
},
);
}
Color _getSpeedColor(double speed) {
if (speed < 60) return AppColor.greenColor;
if (speed < 100) return Colors.orange;
return Colors.red;
}
}