Update: 2026-06-10 18:11:50

This commit is contained in:
Hamza-Ayed
2026-06-10 18:11:50 +03:00
parent a0473a8b0f
commit 977adfe99d
27 changed files with 3946 additions and 206 deletions

View File

@@ -87,12 +87,6 @@ class FirebaseMessagesController extends GetxController {
fireBaseTitles(message);
}
});
FirebaseMessaging.onBackgroundMessage((RemoteMessage message) async {
// Handle background message
if (message.data.isNotEmpty) {
fireBaseTitles(message);
}
});
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
if (message.data.isNotEmpty && message.notification != null) {

View File

@@ -283,7 +283,7 @@ class MapSocketController extends GetxController {
}
final dynamic distanceValue =
data['distance_m'] ?? data['distance_meters'] ?? data['distance'];
data['distance_m'] ?? data['distance_meters'];
final double? distanceMeters =
double.tryParse(distanceValue?.toString() ?? '');
final int? etaSeconds = data['eta_seconds'] == null

View File

@@ -112,6 +112,7 @@ class RideLifecycleController extends GetxController {
late String driverId = '';
late String make = '';
late String model = '';
late String gender = '';
late String carColor = '';
late String licensePlate = '';
late String driverName = '';
@@ -120,6 +121,9 @@ class RideLifecycleController extends GetxController {
late String colorHex = '';
late String carYear = '';
late String driverRate = '5.0';
late String driverRatingCount = '0';
late String driverCompletedRides = '0';
late String driverTier = 'Verified driver';
late String driverToken = '';
double kazan = 8;
@@ -1481,7 +1485,8 @@ class RideLifecycleController extends GetxController {
// إيقاف جلب السيارات المجاورة ومسحها، باستثناء السائق الذي قبل الطلب
mapEngine.reloadStartApp = false;
mapEngine.markers.removeWhere((marker) => marker.markerId.value != driverId.toString());
mapEngine.markers
.removeWhere((marker) => marker.markerId.value != driverId.toString());
mapEngine.update();
await getDriverCarsLocationToPassengerAfterApplied();
@@ -1490,8 +1495,7 @@ class RideLifecycleController extends GetxController {
LatLng driverPos = driverCarsLocationToPassengerAfterApplied.last;
Log.print(
'[rideAppliedFromDriver] 📍 Driver at: $driverPos, Passenger at: $passengerLocation');
await getInitialDriverDistanceAndDuration(driverPos, passengerLocation);
await drawDriverPathOnly(driverPos, passengerLocation);
await calculateDriverToPassengerRoute(driverPos, passengerLocation);
mapEngine.fitCameraToPoints(driverPos, passengerLocation);
}
@@ -1656,6 +1660,9 @@ class RideLifecycleController extends GetxController {
driverToken = data['token']?.toString() ?? '';
carYear = data['year']?.toString() ?? '';
driverRate = data['ratingDriver']?.toString() ?? '5.0';
driverRatingCount = data['ratingCount']?.toString() ?? '0';
driverCompletedRides = data['completedRides']?.toString() ?? '0';
driverTier = data['driverTier']?.toString() ?? 'Verified driver';
update();
}
@@ -2221,6 +2228,15 @@ class RideLifecycleController extends GetxController {
polyLines = polyLines
.where((p) => !p.polylineId.value.startsWith('driver_route'))
.toSet();
polyLines = {
...polyLines,
Polyline(
polylineId: const PolylineId('main_route'),
points: decodedPoints,
color: const Color(0xFF2196F3),
width: 6,
)
};
} else {
// مسح السلمات القديمة أولاً
polyLines = polyLines
@@ -2290,7 +2306,9 @@ class RideLifecycleController extends GetxController {
_routeHeadingMismatchCount = 0;
_isRecalculatingRoute = true;
if (statusRide == 'Begin' ||
currentRideState.value == RideState.inProgress) {
statusRide == 'Arrived' ||
currentRideState.value == RideState.inProgress ||
currentRideState.value == RideState.driverArrived) {
await calculateDriverToPassengerRoute(driverPos, myDestination,
isBeginPhase: true);
} else {
@@ -2504,6 +2522,8 @@ class RideLifecycleController extends GetxController {
String icon;
if (model.contains('دراجة') || make.contains('دراجة')) {
icon = mapEngine.motoIcon;
} else if (gender == 'Female') {
icon = mapEngine.ladyIcon;
} else {
icon = mapEngine.carIcon;
}
@@ -3026,6 +3046,17 @@ class RideLifecycleController extends GetxController {
mapEngine.playRouteAnimation(
mapEngine.polylineCoordinates, mapEngine.lastComputedBounds);
}
if (driverCarsLocationToPassengerAfterApplied.isNotEmpty &&
myDestination.latitude != 0 &&
myDestination.longitude != 0) {
await calculateDriverToPassengerRoute(
driverCarsLocationToPassengerAfterApplied.last,
myDestination,
isBeginPhase: true,
);
}
update();
}
@@ -3903,12 +3934,37 @@ class RideLifecycleController extends GetxController {
make = data['make']?.toString() ?? '';
model = data['model']?.toString() ?? '';
gender = data['gender']?.toString() ?? '';
carColor = data['color']?.toString() ?? '';
colorHex = data['color_hex']?.toString() ?? '';
licensePlate = data['car_plate']?.toString() ?? '';
carYear = data['year']?.toString() ?? '';
// المحاولة الفورية لرسم السائق إذا توفرت الإحداثيات في البيانات
double lat = double.tryParse(
data['latitude']?.toString() ?? data['lat']?.toString() ?? '0') ??
0;
double lng = double.tryParse(data['longitude']?.toString() ??
data['lng']?.toString() ??
'0') ??
0;
double heading = double.tryParse(data['heading']?.toString() ?? '0') ?? 0;
if (lat != 0 && lng != 0) {
LatLng initialPos = LatLng(lat, lng);
if (driverCarsLocationToPassengerAfterApplied.isEmpty) {
driverCarsLocationToPassengerAfterApplied.add(initialPos);
} else {
driverCarsLocationToPassengerAfterApplied[0] = initialPos;
}
// تحديث الماركر فوراً لضمان ظهوره بشكل موثوق
updateDriverMarker(initialPos, heading);
}
driverRate = data['ratingDriver']?.toString() ?? '5.0';
driverRatingCount = data['ratingCount']?.toString() ?? '0';
driverCompletedRides = data['completedRides']?.toString() ?? '0';
driverTier = data['driverTier']?.toString() ?? 'Verified driver';
driverToken = data['token']?.toString() ?? '';
update();
@@ -4185,55 +4241,6 @@ class RideLifecycleController extends GetxController {
);
}
Future<void> getDistanceFromDriverAfterAcceptedRide(
String origin, String destination) async {
String apiKey = Env.mapKeyOsm;
if (origin.isEmpty) {
origin = '${passengerLocation.latitude},${passengerLocation.longitude}';
}
var uri = Uri.parse(
'$dynamicApiUrl?origin=$origin&destination=$destination&steps=false&overview=false');
Log.print('uri: $uri');
http.Response response;
Map<String, dynamic> responseData;
try {
response = await http.get(
uri,
headers: {
'X-API-KEY': apiKey,
},
).timeout(const Duration(seconds: 20));
if (response.statusCode != 200) {
Log.print('Error from API: ${response.statusCode}');
isLoading = false;
update();
return;
}
if (Get.isBottomSheetOpen ?? false) {
Get.back();
}
isDrawingRoute = false;
responseData = json.decode(response.body);
Log.print('responseData: $responseData');
if (responseData['status'] != 'ok') {
Log.print('API returned an error: ${responseData['message']}');
isLoading = false;
update();
return;
}
} catch (e) {
Log.print('Failed to get directions: $e');
isLoading = false;
update();
return;
}
}
Future<void> _stageNiceToHave() async {
Log.print('🚀 MapPassengerController: Starting _stageNiceToHave');

View File

@@ -4,7 +4,6 @@ import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import '../../../constant/box_name.dart';
import '../../../constant/colors.dart';
@@ -15,19 +14,13 @@ import '../../../main.dart'; // contains global 'box'
import '../../../print.dart';
import '../../../services/emergency_signal_service.dart';
import '../../../views/widgets/elevated_btn.dart';
import '../../../views/widgets/mydialoug.dart';
import '../../../views/widgets/my_textField.dart';
import '../../../views/home/map_page_passenger.dart';
import '../../../views/widgets/error_snakbar.dart';
import '../../../models/model/painter_copoun.dart';
import '../../functions/launch.dart';
import '../../firebase/local_notification.dart';
import '../../firebase/notification_service.dart';
import '../../functions/crud.dart';
import '../../functions/tts.dart';
import 'ride_lifecycle_controller.dart';
import 'location_search_controller.dart';
import 'map_engine_controller.dart';
class UiInteractionsController extends GetxController {
TextEditingController sosPhonePassengerProfile = TextEditingController();
@@ -56,54 +49,54 @@ class UiInteractionsController extends GetxController {
sosPhonePassengerProfile.clear();
Get.defaultDialog(
title: 'Add SOS Phone'.tr,
titleStyle: AppStyle.title,
content: Form(
key: sosFormKey,
child: Column(
children: [
MyTextForm(
controller: sosPhonePassengerProfile,
label: 'insert sos phone'.tr,
hint: 'e.g. 0912345678 (Default +963)'.tr,
type: TextInputType.phone,
),
const SizedBox(height: 10),
Text(
"Note: If no country code is entered, it will be saved as Syrian (+963).".tr,
style: TextStyle(fontSize: 12, color: Colors.grey),
textAlign: TextAlign.center,
),
],
title: 'Add SOS Phone'.tr,
titleStyle: AppStyle.title,
content: Form(
key: sosFormKey,
child: Column(
children: [
MyTextForm(
controller: sosPhonePassengerProfile,
label: 'insert sos phone'.tr,
hint: 'e.g. 0912345678 (Default +963)'.tr,
type: TextInputType.phone,
),
const SizedBox(height: 10),
Text(
"Note: If no country code is entered, it will be saved as Syrian (+963)."
.tr,
style: TextStyle(fontSize: 12, color: Colors.grey),
textAlign: TextAlign.center,
),
],
),
),
),
confirm: MyElevatedButton(
title: 'Save'.tr,
onPressed: () async {
if (sosFormKey.currentState!.validate()) {
Get.back();
var numberPhone =
formatSyrianPhoneNumber(sosPhonePassengerProfile.text);
confirm: MyElevatedButton(
title: 'Save'.tr,
onPressed: () async {
if (sosFormKey.currentState!.validate()) {
Get.back();
var numberPhone =
formatSyrianPhoneNumber(sosPhonePassengerProfile.text);
await CRUD().post(
link: AppLink.updateprofile,
payload: {
'id': box.read(BoxName.passengerID),
'sosPhone': numberPhone,
},
);
await CRUD().post(
link: AppLink.updateprofile,
payload: {
'id': box.read(BoxName.passengerID),
'sosPhone': numberPhone,
},
);
box.write(BoxName.sosPhonePassenger, numberPhone);
onSuccess();
}
},
),
cancel: MyElevatedButton(
title: 'Cancel'.tr,
onPressed: () => Get.back(),
kolor: AppColor.redColor,
)
);
box.write(BoxName.sosPhonePassenger, numberPhone);
onSuccess();
}
},
),
cancel: MyElevatedButton(
title: 'Cancel'.tr,
onPressed: () => Get.back(),
kolor: AppColor.redColor,
));
}
void sosPassenger() {
@@ -114,10 +107,12 @@ class UiInteractionsController extends GetxController {
titleStyle: AppStyle.title.copyWith(color: AppColor.redColor),
content: Column(
children: [
Icon(Icons.warning_amber_rounded, size: 50, color: AppColor.redColor),
Icon(Icons.warning_amber_rounded,
size: 50, color: AppColor.redColor),
const SizedBox(height: 10),
Text(
"Do you want to send an emergency message to your SOS contact?".tr,
"Do you want to send an emergency message to your SOS contact?"
.tr,
textAlign: TextAlign.center,
style: AppStyle.title,
),

View File

@@ -42,6 +42,7 @@ class MyTranslation extends Translations {
"Arrived": "وصلنا",
"Audio Recording": "تسجيل صوتي",
"Call": "اتصال",
"Call Options": "خيارات الاتصال",
"Call Connected": "تم فتح الاتصال",
"Call Support": "اتصل بالدعم",
"Call left": "مكالمات متبقية",
@@ -49,6 +50,8 @@ class MyTranslation extends Translations {
"Change Photo": "تغيير الصورة",
"Captain": "الكابتن",
"Choose from Gallery": "اختر من المعرض",
"Choose how you want to call the driver":
"اختر طريقة الاتصال بالكابتن",
"Choose from contact": "اختر من جهات الاتصال",
"Click to track the trip": "اضغط لتتبع المشوار",
"Close panel": "إغلاق اللوحة",
@@ -92,6 +95,9 @@ class MyTranslation extends Translations {
"Finished": "انتهى",
"Fixed Price": "سعر ثابت",
"Free Call": "مكالمة مجانية",
"Professional driver": "كابتن محترف",
"Trusted driver": "كابتن موثوق",
"Verified driver": "كابتن موثق",
"General": "عام",
"Grant": "منح الإذن",
"Have a Promo Code?": "معك كود خصم؟",
@@ -178,6 +184,7 @@ class MyTranslation extends Translations {
"Preferences": "التفضيلات",
"Profile photo updated": "تم تحديث صورة الغلاف",
"Quick Message": "رسالة سريعة",
"reviews": "تقييم",
"Rating is": "التقييم هو",
"Received empty route data.": "تم استلام بيانات طريق فارغة.",
"Record": "تسجيل",
@@ -211,6 +218,7 @@ class MyTranslation extends Translations {
"Set as Work": "تحديد كالشغل",
"Share": "مشاركة",
"Share Trip": "مشاركة المشوار",
"Standard Call": "اتصال عادي",
"Share your experience to help us improve...":
"شاركنا تجربتك لنحسن خدمتنا...",
"Something went wrong. Please try again.": "صار غلط. جرب مرة تانية.",
@@ -271,6 +279,8 @@ class MyTranslation extends Translations {
"to arrive you.": "ليوصلك.",
"unknown": "غير معروف",
"wait 1 minute to recive message": "استنى دقيقة لتستلم الرسالة",
"Uses cellular network": "يستخدم شبكة الهاتف",
"Voice call over internet": "مكالمة صوتية عبر الإنترنت",
"with license plate": "برقم اللوحة",
"witout zero": "بدون صفر",
"you must insert token code": "لازم تدخل الكود",
@@ -16885,7 +16895,8 @@ class MyTranslation extends Translations {
"Support is Away": "سپورٹ اب دستیاب نہیں ہے",
"Support is currently Online": "سپورٹ اب آن لائن ہے",
"Voice Call": "صوتی کال",
"We're here to help you 24/7": "ہم چوبیس گھنٹے آپ کی مدد کے لیے حاضر ہیں",
"We're here to help you 24/7":
"ہم چوبیس گھنٹے آپ کی مدد کے لیے حاضر ہیں",
"Working Hours:": "کام کے اوقات:",
"1 Passenger": "1 Passenger",
"2 Passengers": "2 Passengers",
@@ -18446,7 +18457,8 @@ class MyTranslation extends Translations {
"Support is Away": "सहायता अभी उपलब्ध नहीं है",
"Support is currently Online": "सहायता अभी ऑनलाइन है",
"Voice Call": "वॉइस कॉल",
"We're here to help you 24/7": "हम आपकी सहायता के लिए 24/7 उपलब्ध हैं",
"We're here to help you 24/7":
"हम आपकी सहायता के लिए 24/7 उपलब्ध हैं",
"Working Hours:": "कार्य समय:",
"1 Passenger": "1 Passenger",
"2 Passengers": "2 Passengers",

View File

@@ -250,19 +250,23 @@ class ApplyOrderWidget extends StatelessWidget {
Row(
children: [
// صورة السائق (أصغر)
Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: AppColor.primaryColor.withOpacity(0.2), width: 2),
),
child: CircleAvatar(
radius: 22, // تصغير من 28 إلى 22
backgroundColor: Colors.grey[200],
backgroundImage: NetworkImage(
'${AppLink.server}/portrate_captain_image/${controller.driverId}.jpg'),
onBackgroundImageError: (_, __) =>
const Icon(Icons.person, color: Colors.grey, size: 20),
GestureDetector(
onTap: () => _showDriverAvatarDialog(context, controller),
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: AppColor.primaryColor.withOpacity(0.2),
width: 2),
),
child: CircleAvatar(
radius: 22, // تصغير من 28 إلى 22
backgroundColor: Colors.grey[200],
backgroundImage: NetworkImage(
'${AppLink.server}/portrate_captain_image/${controller.driverId}.jpg'),
onBackgroundImageError: (_, __) =>
const Icon(Icons.person, color: Colors.grey, size: 20),
),
),
),
@@ -299,6 +303,32 @@ class ApplyOrderWidget extends StatelessWidget {
),
],
),
const SizedBox(height: 5),
Wrap(
spacing: 6,
runSpacing: 4,
children: [
_buildDriverBadge(
icon: Icons.verified_rounded,
text: controller.driverTier.tr,
color: AppColor.primaryColor,
),
if (controller.driverCompletedRides != '0')
_buildDriverBadge(
icon: Icons.route_rounded,
text:
'${controller.driverCompletedRides} ${'rides'.tr}',
color: Colors.teal,
),
if (controller.driverRatingCount != '0')
_buildDriverBadge(
icon: Icons.reviews_rounded,
text:
'${controller.driverRatingCount} ${'reviews'.tr}',
color: Colors.amber.shade800,
),
],
),
],
),
),
@@ -320,6 +350,11 @@ class ApplyOrderWidget extends StatelessWidget {
Widget _buildMicroCarIcon(
RideLifecycleController controller, Color Function(String) parseColor) {
Color carColor = parseColor(controller.colorHex);
final String vehicleText =
'${controller.model} ${controller.make}'.toLowerCase();
final bool isBike = vehicleText.contains('scooter') ||
vehicleText.contains('bike') ||
vehicleText.contains('دراجة');
return Container(
height: 40, // تصغير من 50
width: 40,
@@ -331,7 +366,8 @@ class ApplyOrderWidget extends StatelessWidget {
child: ColorFiltered(
colorFilter: ColorFilter.mode(carColor, BlendMode.srcIn),
child: Image.asset(
box.read(BoxName.carType) == 'Scooter' ||
isBike ||
box.read(BoxName.carType) == 'Scooter' ||
box.read(BoxName.carType) == 'Pink Bike'
? 'assets/images/moto.png'
: 'assets/images/car3.png',
@@ -341,6 +377,81 @@ class ApplyOrderWidget extends StatelessWidget {
);
}
Widget _buildDriverBadge({
required IconData icon,
required String text,
required Color color,
}) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 3),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 11, color: color),
const SizedBox(width: 4),
Text(
text,
style: TextStyle(
color: color,
fontSize: 10.5,
fontWeight: FontWeight.w800,
),
),
],
),
);
}
void _showDriverAvatarDialog(
BuildContext context, RideLifecycleController controller) {
final imageUrl =
'${AppLink.server}/portrate_captain_image/${controller.driverId}.jpg';
Get.dialog(
Dialog(
insetPadding: const EdgeInsets.symmetric(horizontal: 38),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(18)),
child: Padding(
padding: const EdgeInsets.fromLTRB(18, 20, 18, 18),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircleAvatar(
radius: 58,
backgroundColor: Colors.grey[200],
backgroundImage: NetworkImage(imageUrl),
onBackgroundImageError: (_, __) {},
),
const SizedBox(height: 14),
Text(
controller.driverName,
textAlign: TextAlign.center,
style: AppStyle.title.copyWith(
fontSize: 18,
fontWeight: FontWeight.w900,
),
),
const SizedBox(height: 6),
Text(
'${controller.driverTier.tr}${controller.driverRate}',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.grey[700],
fontSize: 13,
fontWeight: FontWeight.w600,
),
),
],
),
),
),
barrierDismissible: true,
);
}
Widget _buildSlimLicensePlate(String plateNumber) {
return Container(
width: double.infinity,