feat: refactor financial wallet UI components and add offline map service support
This commit is contained in:
@@ -8,7 +8,6 @@ import '../../../constant/links.dart';
|
||||
import '../../../main.dart';
|
||||
import '../../../views/home/Captin/history/history_details_page.dart';
|
||||
import '../../functions/crud.dart';
|
||||
import '../../functions/encrypt_decrypt.dart';
|
||||
|
||||
class HistoryCaptainController extends GetxController {
|
||||
bool isloading = false;
|
||||
|
||||
@@ -379,16 +379,20 @@ class LoginDriverController extends GetxController {
|
||||
box.read(BoxName.tokenDriver).toString() ||
|
||||
serverData['data'][0]['fingerPrint'].toString() !=
|
||||
fingerPrint.toString()) {
|
||||
await Get.defaultDialog(
|
||||
Get.defaultDialog(
|
||||
barrierDismissible: false,
|
||||
title: 'Device Change Detected'.tr,
|
||||
middleText: 'Please verify your identity'.tr,
|
||||
textConfirm: 'Verify'.tr,
|
||||
confirmTextColor: Colors.white,
|
||||
onConfirm: () {
|
||||
Get.back();
|
||||
// انتقل لصفحة OTP الجديدة
|
||||
Get.to(
|
||||
// نغلق الـ Dialog أولاً بشكل صريح
|
||||
if (Get.isDialogOpen ?? false) {
|
||||
Get.back();
|
||||
}
|
||||
|
||||
// ثم ننتقل لصفحة OTP
|
||||
Get.offAll(
|
||||
() => OtpVerificationPage(
|
||||
phone: d['phone'].toString(),
|
||||
deviceToken: fingerPrint.toString(),
|
||||
@@ -399,6 +403,9 @@ class LoginDriverController extends GetxController {
|
||||
);
|
||||
},
|
||||
);
|
||||
isloading = false;
|
||||
update();
|
||||
return true; // نخرج من الدالة هنا لنسمح لـ OTP بالتعامل مع الأمر
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:ui';
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_background_service/flutter_background_service.dart';
|
||||
import 'package:flutter_background_service_android/flutter_background_service_android.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:flutter_overlay_window/flutter_overlay_window.dart';
|
||||
import 'package:socket_io_client/socket_io_client.dart' as IO;
|
||||
import 'package:flutter_overlay_window/flutter_overlay_window.dart' as Overlay;
|
||||
import 'package:get_storage/get_storage.dart';
|
||||
import 'package:geolocator/geolocator.dart' as geo;
|
||||
import '../../constant/box_name.dart';
|
||||
import '../firebase/local_notification.dart';
|
||||
|
||||
const String notificationChannelId = 'driver_service_channel';
|
||||
const int notificationId = 888;
|
||||
@@ -35,14 +37,18 @@ Future<bool> onStart(ServiceInstance service) async {
|
||||
IO.OptionBuilder()
|
||||
.setTransports(['websocket'])
|
||||
.disableAutoConnect()
|
||||
.setQuery({'driver_id': driverId, 'token': token})
|
||||
.setQuery({
|
||||
'driver_id': driverId,
|
||||
'token': token,
|
||||
'EIO': '3', // توافقية مع Workerman
|
||||
})
|
||||
.setReconnectionAttempts(double.infinity)
|
||||
.build());
|
||||
|
||||
socket.connect();
|
||||
|
||||
socket.onConnect((_) {
|
||||
print("✅ Background Service: Socket Connected!");
|
||||
print("✅ Background Service: Socket Connected! ID: ${socket?.id}");
|
||||
if (service is AndroidServiceInstance) {
|
||||
flutterLocalNotificationsPlugin.show(
|
||||
id: notificationId,
|
||||
@@ -70,39 +76,94 @@ Future<bool> onStart(ServiceInstance service) async {
|
||||
final box = GetStorage();
|
||||
bool isAppInForeground = box.read(BoxName.isAppInForeground) ?? false;
|
||||
|
||||
// 🔥 Check إضافي: هل الـ Overlay مفتوح بالفعل؟
|
||||
bool overlayActive = await Overlay.FlutterOverlayWindow.isActive();
|
||||
// 🔥 Check إضافي: هل الـ Overlay مفتوح بالفعل؟ (للأندرويد فقط)
|
||||
bool overlayActive = false;
|
||||
if (Platform.isAndroid) {
|
||||
overlayActive = await Overlay.FlutterOverlayWindow.isActive();
|
||||
}
|
||||
|
||||
if (isAppInForeground || overlayActive) {
|
||||
print("🛑 App is FOREGROUND or Overlay already shown. Skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
// عرض الـ Overlay
|
||||
print("🚀 App is BACKGROUND. Showing Overlay...");
|
||||
try {
|
||||
await Overlay.FlutterOverlayWindow.showOverlay(
|
||||
enableDrag: true,
|
||||
overlayTitle: "طلب جديد",
|
||||
overlayContent: "لديك طلب جديد وصل للتو!",
|
||||
flag: OverlayFlag.focusPointer,
|
||||
positionGravity: PositionGravity.auto,
|
||||
height: WindowSize.matchParent,
|
||||
width: WindowSize.matchParent,
|
||||
startPosition: const OverlayPosition(0, -30),
|
||||
);
|
||||
await Overlay.FlutterOverlayWindow.shareData(data);
|
||||
} catch (e) {
|
||||
print("Overlay Error: $e");
|
||||
// عرض الـ Overlay (للأندرويد فقط)
|
||||
if (Platform.isAndroid) {
|
||||
print("🚀 App is BACKGROUND. Showing Overlay...");
|
||||
try {
|
||||
await Overlay.FlutterOverlayWindow.showOverlay(
|
||||
enableDrag: true,
|
||||
overlayTitle: "طلب جديد",
|
||||
overlayContent: "لديك طلب جديد وصل للتو!",
|
||||
flag: OverlayFlag.focusPointer,
|
||||
positionGravity: PositionGravity.auto,
|
||||
height: WindowSize.matchParent,
|
||||
width: WindowSize.matchParent,
|
||||
startPosition: const OverlayPosition(0, -30),
|
||||
);
|
||||
await Overlay.FlutterOverlayWindow.shareData(data);
|
||||
} catch (e) {
|
||||
print("Overlay Error: $e");
|
||||
}
|
||||
} else if (Platform.isIOS) {
|
||||
// على iOS، نظهر إشعاراً عادياً لأن الـ Overlay غير موجود
|
||||
flutterLocalNotificationsPlugin.show(
|
||||
id: 1002,
|
||||
title: "طلب رحلة جديد 🚖",
|
||||
body: "لديك طلب رحلة جديد، افتح التطبيق للموافقة عليه",
|
||||
notificationDetails: const NotificationDetails(
|
||||
iOS: DarwinNotificationDetails(
|
||||
presentAlert: true,
|
||||
presentBadge: true,
|
||||
presentSound: true,
|
||||
),
|
||||
),
|
||||
payload: jsonEncode(data));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
service.on('stopService').listen((event) {
|
||||
socket?.disconnect();
|
||||
socket?.clearListeners();
|
||||
socket?.dispose();
|
||||
service.stopSelf();
|
||||
});
|
||||
|
||||
// 🔥 Location management in background isolate (Using Geolocator)
|
||||
geo.Position? latestPos;
|
||||
|
||||
// Listen to location changes continuously in the background
|
||||
geo.Geolocator.getPositionStream(
|
||||
locationSettings: geo.AndroidSettings(
|
||||
accuracy: geo.LocationAccuracy.high,
|
||||
distanceFilter: 10,
|
||||
intervalDuration: const Duration(seconds: 10),
|
||||
),
|
||||
).listen((pos) {
|
||||
latestPos = pos;
|
||||
});
|
||||
|
||||
// 🔥 MERCY HEARTBEAT: Send location every 2 minutes to keep driver active in 'raids'
|
||||
Timer.periodic(const Duration(minutes: 2), (timer) async {
|
||||
if (socket != null && socket.connected && latestPos != null) {
|
||||
try {
|
||||
socket.emit('update_location', {
|
||||
'driver_id': driverId,
|
||||
'lat': latestPos!.latitude,
|
||||
'lng': latestPos!.longitude,
|
||||
'heading': latestPos!.heading,
|
||||
'speed': latestPos!.speed * 3.6,
|
||||
'status': box.read(BoxName.statusDriverLocation) ?? 'on',
|
||||
'source': 'background_heartbeat'
|
||||
});
|
||||
print(
|
||||
"💓 Background Mercy Heartbeat Sent: ${latestPos!.latitude}, ${latestPos!.longitude}");
|
||||
} catch (e) {
|
||||
print("❌ Background Heartbeat Error: $e");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Timer.periodic(const Duration(seconds: 30), (timer) async {
|
||||
if (service is AndroidServiceInstance) {
|
||||
if (await service.isForegroundService()) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/*
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
@@ -234,3 +235,4 @@ class CameraClassController extends GetxController {
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -604,6 +604,59 @@ class CRUD {
|
||||
);
|
||||
return json.decode(response.body);
|
||||
}
|
||||
|
||||
Future<dynamic> getMapSaas({
|
||||
required String link,
|
||||
}) async {
|
||||
var url = Uri.parse(link);
|
||||
try {
|
||||
var response = await http.get(
|
||||
url,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-api-key': Env.mapSaasKey,
|
||||
},
|
||||
);
|
||||
Log.print('link -MapSaas: $link');
|
||||
Log.print('response -MapSaas: ${response.body}');
|
||||
if (response.statusCode == 200) {
|
||||
return jsonDecode(response.body);
|
||||
}
|
||||
Log.print('MapSaas Error: ${response.statusCode} - ${response.body}');
|
||||
return null;
|
||||
} catch (e) {
|
||||
Log.print('MapSaas Exception: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<dynamic> postMapSaas({
|
||||
required String link,
|
||||
required Map<String, dynamic> payload,
|
||||
}) async {
|
||||
var url = Uri.parse(link);
|
||||
try {
|
||||
var response = await http.post(
|
||||
url,
|
||||
body: jsonEncode(payload),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-api-key': Env.mapSaasKey,
|
||||
},
|
||||
);
|
||||
Log.print('post -MapSaas link: $link');
|
||||
Log.print('post -MapSaas payload: $payload');
|
||||
Log.print('post -MapSaas response: ${response.body}');
|
||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||
return jsonDecode(response.body);
|
||||
}
|
||||
Log.print('MapSaas Post Error: ${response.statusCode} - ${response.body}');
|
||||
return null;
|
||||
} catch (e) {
|
||||
Log.print('MapSaas Post Exception: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class NoInternetException implements Exception {
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:get/get_core/src/get_main.dart';
|
||||
import 'package:get/get_navigation/src/extension_navigation.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
|
||||
import 'package:sefer_driver/views/widgets/mydialoug.dart';
|
||||
|
||||
import 'background_service.dart';
|
||||
|
||||
@@ -28,18 +32,19 @@ class PermissionsHelper {
|
||||
if (Platform.isAndroid) {
|
||||
final androidInfo = await DeviceInfoPlugin().androidInfo;
|
||||
|
||||
// Android 13+ (API 33+) يحتاج إذن POST_NOTIFICATIONS
|
||||
if (androidInfo.version.sdkInt >= 33) {
|
||||
final status = await Permission.notification.request();
|
||||
|
||||
if (status.isDenied) {
|
||||
print('⚠️ إذن الإشعارات مرفوض');
|
||||
mySnackbarWarning(
|
||||
"يرجى منح صلاحية الإشعارات لضمان وصول الطلبات إليك");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (status.isPermanentlyDenied) {
|
||||
print('⚠️ إذن الإشعارات مرفوض بشكل دائم - افتح الإعدادات');
|
||||
await openAppSettings();
|
||||
mySnackbarWarning('يرجى فتح الإعدادات وتفعيل صلاحية الإشعارات');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -50,25 +55,31 @@ class PermissionsHelper {
|
||||
|
||||
/// طلب جميع الإذونات المطلوبة
|
||||
static Future<bool> requestAllPermissions() async {
|
||||
// إذن الإشعارات أولاً
|
||||
bool notificationGranted = await requestNotificationPermission();
|
||||
if (!notificationGranted) return false;
|
||||
// إذن الإشعارات (اختياري)
|
||||
await requestNotificationPermission();
|
||||
|
||||
// إذن الموقع
|
||||
final locationStatus = await Permission.location.request();
|
||||
if (!locationStatus.isGranted) {
|
||||
print('⚠️ إذن الموقع مرفوض');
|
||||
// 1. طلب إذن الموقع الأساسي فقط إذا كان مرفوضاً
|
||||
var status = await Permission.location.status;
|
||||
if (status.isDenied) {
|
||||
status = await Permission.location.request();
|
||||
}
|
||||
|
||||
if (status.isPermanentlyDenied) {
|
||||
_showSettingsDialog('الموقع');
|
||||
return false;
|
||||
}
|
||||
|
||||
// إذن الموقع في الخلفية
|
||||
if (Platform.isAndroid) {
|
||||
final bgLocationStatus = await Permission.locationAlways.request();
|
||||
if (!bgLocationStatus.isGranted) {
|
||||
print('⚠️ إذن الموقع في الخلفية مرفوض');
|
||||
}
|
||||
}
|
||||
return status.isGranted || status.isLimited;
|
||||
}
|
||||
|
||||
return true;
|
||||
static void _showSettingsDialog(String permissionName) {
|
||||
MyDialog().getDialog(
|
||||
'صلاحية $permissionName مطلوبة',
|
||||
'لقد قمت برفض صلاحية $permissionName سابقاً. يرجى تفعيلها من الإعدادات لتمكين التطبيق من العمل.',
|
||||
() async {
|
||||
await openAppSettings();
|
||||
Get.back();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:geolocator/geolocator.dart' as geo;
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'package:intaleq_maps/intaleq_maps.dart';
|
||||
import 'package:location/location.dart';
|
||||
import 'package:battery_plus/battery_plus.dart';
|
||||
import 'package:permission_handler/permission_handler.dart' as ph;
|
||||
import 'package:sefer_driver/views/home/Captin/orderCaptin/order_request_page.dart';
|
||||
import 'package:socket_io_client/socket_io_client.dart' as IO;
|
||||
import 'package:sefer_driver/constant/table_names.dart';
|
||||
import 'package:trip_overlay_plugin/trip_overlay_plugin.dart';
|
||||
@@ -17,6 +15,7 @@ import '../../constant/box_name.dart';
|
||||
import '../../constant/links.dart';
|
||||
import '../../main.dart';
|
||||
import '../../print.dart';
|
||||
import '../firebase/local_notification.dart';
|
||||
import '../home/captin/home_captain_controller.dart';
|
||||
import '../home/captin/map_driver_controller.dart';
|
||||
import '../home/payment/captain_wallet_controller.dart';
|
||||
@@ -63,6 +62,7 @@ class LocationController extends GetxController with WidgetsBindingObserver {
|
||||
bool _isPowerSavingMode = false;
|
||||
|
||||
final List<Map<String, dynamic>> _trackBuffer = [];
|
||||
final List<Map<String, dynamic>> _behaviorBuffer = [];
|
||||
|
||||
LatLng? _lastPosForDistance;
|
||||
LatLng? _lastRecordedRealLoc;
|
||||
@@ -137,26 +137,22 @@ class LocationController extends GetxController with WidgetsBindingObserver {
|
||||
Log.print("📱 Lifecycle: App is in FOREGROUND");
|
||||
box.write(BoxName.isAppInForeground, true);
|
||||
|
||||
// إيقاف خدمة الخلفية لأننا في الواجهة الآن
|
||||
// إيقاف خدمة الخلفية
|
||||
BackgroundServiceHelper.stopService();
|
||||
|
||||
// التأكد من أن السوكيت متصل، وإذا لا، نعيد الاتصال فوراً
|
||||
if (socket == null || !socket!.connected) {
|
||||
Log.print("🔄 Socket disconnected in background. Reconnecting now...");
|
||||
|
||||
if (socket == null || (!socket!.connected && !_isInitializingSocket)) {
|
||||
Log.print("🔄 Initializing Socket on resume...");
|
||||
initSocket();
|
||||
} else {
|
||||
// إذا كان متصلاً، ننعش المستمعين فقط للتأكد
|
||||
_setupSocketListeners();
|
||||
}
|
||||
} else if (state == AppLifecycleState.paused ||
|
||||
state == AppLifecycleState.detached) {
|
||||
Log.print("📱 Lifecycle: App is in BACKGROUND");
|
||||
box.write(BoxName.isAppInForeground, false);
|
||||
|
||||
// تشغيل خدمة الخلفية لضمان بقاء التطبيق حياً
|
||||
BackgroundServiceHelper.startService();
|
||||
|
||||
// ملاحظة: لا نقطع السوكيت هنا، نتركه يعمل قدر الإمكان
|
||||
// تشغيل خدمة الخلفية للأندرويد لضمان بقاء التطبيق حياً
|
||||
if (!Platform.isIOS) {
|
||||
BackgroundServiceHelper.startService();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,69 +175,107 @@ class LocationController extends GetxController with WidgetsBindingObserver {
|
||||
// ====== Socket Logic (Improved) ======
|
||||
// ===================================================================
|
||||
|
||||
bool _isInitializingSocket = false;
|
||||
|
||||
void initSocket() {
|
||||
// منع الاستدعاءات المتداخلة التي تسبب قتل الاتصال قبل اكتماله
|
||||
if (_isInitializingSocket) {
|
||||
Log.print("⏳ Socket is already initializing. Skipping redundant call.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (socket != null && socket!.connected) {
|
||||
Log.print("✅ Socket is already connected. No need to re-init.");
|
||||
return;
|
||||
}
|
||||
|
||||
String driverId = box.read(BoxName.driverID).toString();
|
||||
String token = box.read(BoxName.tokenDriver).toString();
|
||||
String platform = Platform.isIOS ? 'ios' : 'android';
|
||||
|
||||
// 1. إذا كان السوكيت موجوداً، فقط تأكد من اتصاله
|
||||
_isInitializingSocket = true;
|
||||
|
||||
// تنظيف السوكيت القديم فقط إذا كان موجوداً وغير متصل
|
||||
if (socket != null) {
|
||||
if (!socket!.connected) {
|
||||
Log.print("🟡 Socket exists but disconnected. Reconnecting...");
|
||||
socket!.connect();
|
||||
}
|
||||
_setupSocketListeners(); // تحديث المستمعين
|
||||
return;
|
||||
Log.print("🧹 Cleaning up old socket instance...");
|
||||
socket!.clearListeners();
|
||||
socket!.dispose();
|
||||
socket = null;
|
||||
}
|
||||
|
||||
Log.print(
|
||||
"🟡 [LocationController] Creating NEW Socket for Driver: $driverId");
|
||||
"🟡 [LocationController] Initializing NEW Socket for Driver: $driverId");
|
||||
|
||||
// 2. إنشاء الاتصال
|
||||
socket = IO.io(
|
||||
'https://location.intaleq.xyz',
|
||||
IO.OptionBuilder()
|
||||
.setTransports(['websocket'])
|
||||
.enableAutoConnect() // تفعيل إعادة الاتصال التلقائي
|
||||
.setQuery(
|
||||
{'driver_id': driverId, 'token': token, 'platform': platform})
|
||||
.setReconnectionAttempts(double.infinity)
|
||||
.setReconnectionDelay(2000)
|
||||
.build());
|
||||
try {
|
||||
// العودة للـ Websocket حصراً لأنه الوحيد الذي ينجح في فتح القناة
|
||||
socket = IO.io(
|
||||
'https://location.intaleq.xyz',
|
||||
IO.OptionBuilder()
|
||||
.setTransports(['websocket'])
|
||||
.setQuery({'driver_id': driverId, 'token': token, 'EIO': '3'})
|
||||
.enableForceNew()
|
||||
.build());
|
||||
|
||||
socket!.connect();
|
||||
_setupSocketListeners();
|
||||
_setupSocketListeners();
|
||||
socket!.connect();
|
||||
} catch (e) {
|
||||
_isInitializingSocket = false;
|
||||
Log.print("❌ Socket Initialization Exception: $e");
|
||||
}
|
||||
}
|
||||
|
||||
// دالة منفصلة لضمان عدم تكرار المستمعين
|
||||
void _setupSocketListeners() {
|
||||
if (socket == null) return;
|
||||
|
||||
// تنظيف القديم أولاً
|
||||
socket!.off('connect');
|
||||
socket!.off('disconnect');
|
||||
socket!.off('new_ride_request');
|
||||
socket!.off('ride_cancelled');
|
||||
socket!.off('connect_error');
|
||||
socket!.off('error');
|
||||
|
||||
socket!.onConnect((_) {
|
||||
Log.print('✅ Socket Connected! ID: ${socket?.id}');
|
||||
isSocketConnected = true;
|
||||
_startHeartbeat();
|
||||
_isInitializingSocket = false;
|
||||
|
||||
// ننتظر قليلاً للتأكد من تعبئة الـ IDs
|
||||
Future.delayed(const Duration(milliseconds: 1000), () {
|
||||
String? sid = socket?.id;
|
||||
String? eid = socket?.io.engine?.id;
|
||||
|
||||
Log.print(
|
||||
'✅ Socket Connected! ID: ${sid ?? eid ?? 'N/A'} (SID: $sid, EID: $eid)');
|
||||
|
||||
if (sid != null || eid != null) {
|
||||
isSocketConnected = true;
|
||||
_startHeartbeat();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
socket!.onDisconnect((_) {
|
||||
Log.print('❌ Socket Disconnected');
|
||||
socket!.onDisconnect((data) {
|
||||
_isInitializingSocket = false;
|
||||
Log.print('❌ Socket Disconnected: $data');
|
||||
isSocketConnected = false;
|
||||
_stopHeartbeat();
|
||||
});
|
||||
|
||||
socket!.onConnectError((err) {
|
||||
_isInitializingSocket = false;
|
||||
Log.print('❌ Socket Connect Error: $err');
|
||||
});
|
||||
|
||||
socket!.onConnectTimeout((data) {
|
||||
_isInitializingSocket = false;
|
||||
Log.print('❌ Socket Connect Timeout: $data');
|
||||
});
|
||||
|
||||
socket!.onError((err) {
|
||||
_isInitializingSocket = false;
|
||||
Log.print('❌ Socket General Error: $err');
|
||||
});
|
||||
|
||||
socket!.on('reconnect_attempt', (attempt) {
|
||||
Log.print('🔄 Socket Reconnecting... Attempt: $attempt');
|
||||
});
|
||||
|
||||
// 🔥 الاستماع للطلبات الجديدة
|
||||
socket!.on('new_ride_request', (data) {
|
||||
Log.print("🔔 Socket: New Ride Request Arrived!");
|
||||
@@ -296,8 +330,17 @@ class LocationController extends GetxController with WidgetsBindingObserver {
|
||||
|
||||
if (!isAppInForeground) {
|
||||
Log.print(
|
||||
"🛑 التطبيق في الخلفية. السوكيت سيتجاهل التوجيه ويترك المهمة للـ Overlay.");
|
||||
return; // 👈 هذا السطر يمنع السوكيت من إكمال العمل وفتح الصفحة
|
||||
"📱 [LocationController] Order received in background (iOS/Android). Source: $source");
|
||||
|
||||
if (Platform.isIOS) {
|
||||
// على iOS، نقوم بإظهار إشعار محلي لأن الـ Overlay غير مدعوم
|
||||
NotificationController().showNotification(
|
||||
"طلب رحلة جديد 🚖",
|
||||
"لديك طلب رحلة جديد، افتح التطبيق للموافقة عليه",
|
||||
jsonEncode(rideData),
|
||||
'ding.wav');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -309,14 +352,23 @@ class LocationController extends GetxController with WidgetsBindingObserver {
|
||||
|
||||
// 3. تجهيز البيانات (DriverList)
|
||||
List<dynamic> driverList = [];
|
||||
if (rideData.length > 0) {
|
||||
var sortedKeys = rideData.keys.map((e) => int.tryParse(e) ?? 0).toList()
|
||||
..sort();
|
||||
if (rideData.isNotEmpty) {
|
||||
var sortedKeys = rideData.keys
|
||||
.where((e) => int.tryParse(e) != null)
|
||||
.map((e) => int.parse(e))
|
||||
.toList()..sort();
|
||||
|
||||
for (var key in sortedKeys) {
|
||||
driverList.add(rideData[key.toString()]);
|
||||
}
|
||||
}
|
||||
|
||||
// الحماية ضد البنية غير المكتملة
|
||||
if (driverList.length <= 16) {
|
||||
Log.print("❌ Socket Error: Parsed driver list is incomplete.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. إغلاق النافذة (إن وجدت بالخطأ) والتنقل
|
||||
try {
|
||||
if (await TripOverlayPlugin.isOverlayActive()) {
|
||||
@@ -459,21 +511,16 @@ class LocationController extends GetxController with WidgetsBindingObserver {
|
||||
_uploadBatchTimer?.cancel();
|
||||
_socketHeartbeat?.cancel();
|
||||
|
||||
if (socket != null && socket!.connected) {
|
||||
String driverId = box.read(BoxName.driverID).toString();
|
||||
socket!.emit('update_location', {
|
||||
'driver_id': driverId,
|
||||
'lat': myLocation.latitude,
|
||||
'lng': myLocation.longitude,
|
||||
'heading': heading,
|
||||
'speed': speed * 3.6,
|
||||
'status': 'close', // Changed to off
|
||||
'distance': totalDistance
|
||||
});
|
||||
socket!.disconnect();
|
||||
if (socket != null) {
|
||||
socket!.clearListeners();
|
||||
socket!
|
||||
.dispose(); // استخدام dispose بدلاً من disconnect لضمان تحرير الموارد على iOS
|
||||
}
|
||||
|
||||
if (!Platform.isIOS) {
|
||||
await BackgroundServiceHelper.stopService();
|
||||
}
|
||||
|
||||
await BackgroundServiceHelper.stopService();
|
||||
socket = null;
|
||||
isSocketConnected = false;
|
||||
_isReady = false;
|
||||
@@ -526,16 +573,31 @@ class LocationController extends GetxController with WidgetsBindingObserver {
|
||||
|
||||
Future<void> _flushBufferToServer() async {
|
||||
if (_trackBuffer.isEmpty) return;
|
||||
List<Map<String, dynamic>> batch = List.from(_trackBuffer);
|
||||
_trackBuffer.clear();
|
||||
|
||||
int itemsToTake = _trackBuffer.length > 100 ? 100 : _trackBuffer.length;
|
||||
List<Map<String, dynamic>> batch = _trackBuffer.sublist(0, itemsToTake);
|
||||
|
||||
final String driverId = (box.read(BoxName.driverID) ?? '').toString();
|
||||
try {
|
||||
await CRUD().post(
|
||||
var res = await CRUD().post(
|
||||
link: '${AppLink.locationServer}/add_batch.php',
|
||||
payload: {'driver_id': driverId, 'batch_data': jsonEncode(batch)},
|
||||
);
|
||||
if (res != 'failure') {
|
||||
_trackBuffer.removeRange(0, itemsToTake);
|
||||
} else {
|
||||
_enforceBufferLimit();
|
||||
}
|
||||
} catch (e) {
|
||||
Log.print('❌ Failed to upload batch: $e');
|
||||
_enforceBufferLimit();
|
||||
}
|
||||
}
|
||||
|
||||
void _enforceBufferLimit() {
|
||||
if (_trackBuffer.length > 500) {
|
||||
_trackBuffer.removeRange(0, _trackBuffer.length - 500);
|
||||
Log.print("⚠️ Buffer limit enforced. Removed oldest entries.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -547,32 +609,65 @@ class LocationController extends GetxController with WidgetsBindingObserver {
|
||||
if (level >= powerSaveExitLevel) _isPowerSavingMode = false;
|
||||
if (previousMode != _isPowerSavingMode) {
|
||||
_startBatchTimers();
|
||||
startLocationUpdates();
|
||||
_updateLocationSettings();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _updateLocationSettings() async {
|
||||
if (_locSub == null) return;
|
||||
int interval = _isPowerSavingMode ? 10000 : 5000;
|
||||
try {
|
||||
await location.changeSettings(
|
||||
accuracy: LocationAccuracy.navigation,
|
||||
interval: interval,
|
||||
distanceFilter: _isPowerSavingMode ? 20 : 10,
|
||||
);
|
||||
Log.print("🔋 Location settings updated. Power Save: $_isPowerSavingMode");
|
||||
} catch (e) {
|
||||
Log.print("❌ Failed to update location settings: $e");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _saveBehaviorIfMoved(LatLng pos, DateTime now,
|
||||
{required double currentSpeed}) async {
|
||||
final dist =
|
||||
(_lastSqlLoc == null) ? 999.0 : _calculateDistance(_lastSqlLoc!, pos);
|
||||
if (dist < 15.0) return;
|
||||
|
||||
final accel = _calcAcceleration(currentSpeed, now) ?? 0.0;
|
||||
try {
|
||||
await sql.insertData({
|
||||
'driver_id': (box.read(BoxName.driverID) ?? '').toString(),
|
||||
'latitude': pos.latitude,
|
||||
'longitude': pos.longitude,
|
||||
'acceleration': accel,
|
||||
'created_at': now.toIso8601String(),
|
||||
'updated_at': now.toIso8601String(),
|
||||
}, TableName.behavior);
|
||||
_lastSqlLoc = pos;
|
||||
} catch (e) {
|
||||
Log.print('SQLite Error: $e');
|
||||
_lastSqlLoc = pos;
|
||||
|
||||
_behaviorBuffer.add({
|
||||
'driver_id': (box.read(BoxName.driverID) ?? '').toString(),
|
||||
'latitude': pos.latitude,
|
||||
'longitude': pos.longitude,
|
||||
'acceleration': accel,
|
||||
'created_at': now.toIso8601String(),
|
||||
'updated_at': now.toIso8601String(),
|
||||
});
|
||||
|
||||
if (_behaviorBuffer.length >= 10) {
|
||||
_flushBehaviorBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
void _flushBehaviorBuffer() {
|
||||
if (_behaviorBuffer.isEmpty) return;
|
||||
List<Map<String, dynamic>> batch = List.from(_behaviorBuffer);
|
||||
_behaviorBuffer.clear();
|
||||
|
||||
Future.microtask(() async {
|
||||
try {
|
||||
for (var data in batch) {
|
||||
await sql.insertData(data, TableName.behavior);
|
||||
}
|
||||
} catch (e) {
|
||||
Log.print('SQLite Batch Insert Error: $e');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// استبدال دالة Haversine اليدوية بـ Geolocator في باقي الكود أيضاً
|
||||
// لأنها تعتمد على C++ في الأندرويد و Obj-C في الآيفون (Native Speed)
|
||||
double _calculateDistance(LatLng a, LatLng b) {
|
||||
|
||||
@@ -217,8 +217,12 @@ class SecurityHelper {
|
||||
isNotTrust = await JailbreakRootDetection.instance.isNotTrust;
|
||||
isJailBroken = await JailbreakRootDetection.instance.isJailBroken;
|
||||
isRealDevice = await JailbreakRootDetection.instance.isRealDevice;
|
||||
isOnExternalStorage =
|
||||
await JailbreakRootDetection.instance.isOnExternalStorage;
|
||||
|
||||
// This method is only relevant/implemented for Android
|
||||
if (Platform.isAndroid) {
|
||||
isOnExternalStorage =
|
||||
await JailbreakRootDetection.instance.isOnExternalStorage;
|
||||
}
|
||||
|
||||
List<JailbreakIssue> issues =
|
||||
await JailbreakRootDetection.instance.checkForIssues;
|
||||
@@ -230,7 +234,6 @@ class SecurityHelper {
|
||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
bundleId = packageInfo.packageName;
|
||||
if (bundleId.isNotEmpty) {
|
||||
// Pass the CORRECT bundle ID to isTampered
|
||||
isTampered = await JailbreakRootDetection.instance.isTampered(bundleId);
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
@@ -3,15 +3,9 @@ import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'dart:ui' as ui; // للألوان
|
||||
// import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
// import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'package:intaleq_maps/intaleq_maps.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
// import 'package:latlong2/latlong.dart'
|
||||
// as latlng; // هذا مهم جداً للتعامل مع إحداثيات OSM
|
||||
import 'package:sefer_driver/constant/box_name.dart';
|
||||
import 'package:sefer_driver/controller/home/captin/map_driver_controller.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import '../../../constant/links.dart';
|
||||
@@ -35,7 +29,8 @@ class HomeCaptainController extends GetxController {
|
||||
Timer? activeTimer;
|
||||
Map data = {};
|
||||
bool isHomeMapActive = true;
|
||||
BitmapDescriptor carIcon = BitmapDescriptor.defaultMarker;
|
||||
InlqBitmap carIcon = InlqBitmap.defaultMarker;
|
||||
bool isMapReadyForCommands = false;
|
||||
bool isLoading = true;
|
||||
late double kazan = 0;
|
||||
double latePrice = 0;
|
||||
@@ -55,7 +50,8 @@ class HomeCaptainController extends GetxController {
|
||||
String totalMoneyInSEFER = '0';
|
||||
String totalDurationToday = '0';
|
||||
Timer? timer;
|
||||
late LatLng myLocation = const LatLng(33.5138, 36.2765);
|
||||
Timer? _cameraFollowTimer;
|
||||
LatLng myLocation = const LatLng(33.5138, 36.2765);
|
||||
String totalPoints = '0';
|
||||
String countRefuse = '0';
|
||||
bool mapType = false;
|
||||
@@ -81,10 +77,13 @@ class HomeCaptainController extends GetxController {
|
||||
// دالة لتغيير حالة الهيت ماب (عرض/إخفاء)
|
||||
void toggleHeatmap() async {
|
||||
isHeatmapVisible = !isHeatmapVisible;
|
||||
print("🔥 [Heatmap] Visibility toggled to: $isHeatmapVisible");
|
||||
if (isHeatmapVisible) {
|
||||
await fetchAndDrawHeatmap();
|
||||
startHeatmapCycle();
|
||||
} else {
|
||||
_heatmapTimer?.cancel();
|
||||
heatmapPolygons.clear();
|
||||
print("🧹 [Heatmap] Polygons cleared.");
|
||||
}
|
||||
update(); // تحديث الواجهة
|
||||
}
|
||||
@@ -96,6 +95,7 @@ class HomeCaptainController extends GetxController {
|
||||
|
||||
// دالة جلب البيانات ورسم الخريطة
|
||||
Future<void> fetchAndDrawHeatmap() async {
|
||||
print("🚀 [Heatmap] Fetching live data...");
|
||||
// استخدم الرابط المباشر لملف JSON لسرعة قصوى
|
||||
final String jsonUrl =
|
||||
"https://api.intaleq.xyz/intaleq/ride/rides/heatmap_live.json";
|
||||
@@ -107,20 +107,26 @@ class HomeCaptainController extends GetxController {
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final List<dynamic> data = json.decode(response.body);
|
||||
print("✅ [Heatmap] Data received. Points count: ${data.length}");
|
||||
_generatePolygons(data);
|
||||
} else {
|
||||
print("⚠️ [Heatmap] Server error: ${response.statusCode}");
|
||||
}
|
||||
} catch (e) {
|
||||
print("Heatmap Error: $e");
|
||||
print("❌ [Heatmap] Error: $e");
|
||||
}
|
||||
}
|
||||
|
||||
void _generatePolygons(List<dynamic> data) {
|
||||
print("🎨 [Heatmap] Processing polygons...");
|
||||
Set<Polygon> tempPolygons = {};
|
||||
|
||||
// الأوفست لرسم المربع (نصف حجم الشبكة)
|
||||
// الشبكة دقتها 0.01 درجة، لذا نصفها 0.005
|
||||
double offset = 0.005;
|
||||
|
||||
int highCount = 0, medCount = 0, lowCount = 0;
|
||||
|
||||
for (var point in data) {
|
||||
double lat = double.parse(point['lat'].toString());
|
||||
double lng = double.parse(point['lng'].toString());
|
||||
@@ -133,24 +139,27 @@ class HomeCaptainController extends GetxController {
|
||||
|
||||
// 🧠 منطق الألوان: ندمج الذكاء (Intensity) مع العدد (Count)
|
||||
if (intensity == 'high' || count >= 5) {
|
||||
highCount++;
|
||||
// منطقة مشتعلة (أحمر)
|
||||
// إما فيها طلبات ضائعة (Timeout) أو فيها عدد كبير من الطلبات
|
||||
color = Colors.red.withOpacity(0.35);
|
||||
strokeColor = Colors.red.withOpacity(0.8);
|
||||
color = Colors.red.withValues(alpha: 0.35);
|
||||
strokeColor = Colors.red.withValues(alpha: 0.8);
|
||||
} else if (intensity == 'medium' || count >= 3) {
|
||||
medCount++;
|
||||
// منطقة متوسطة (برتقالي)
|
||||
color = Colors.orange.withOpacity(0.35);
|
||||
strokeColor = Colors.orange.withOpacity(0.8);
|
||||
color = Colors.orange.withValues(alpha: 0.35);
|
||||
strokeColor = Colors.orange.withValues(alpha: 0.8);
|
||||
} else {
|
||||
lowCount++;
|
||||
// منطقة خفيفة (أصفر)
|
||||
color = Colors.yellow.withOpacity(0.3);
|
||||
strokeColor = Colors.yellow.withOpacity(0.6);
|
||||
color = Colors.yellow.withValues(alpha: 0.3);
|
||||
strokeColor = Colors.yellow.withValues(alpha: 0.6);
|
||||
}
|
||||
|
||||
// رسم المربع
|
||||
tempPolygons.add(Polygon(
|
||||
polygonId: PolygonId("$lat-$lng"),
|
||||
consumeTapEvents: true, // للسماح بالضغط عليه مستقبلاً
|
||||
// consumeTapEvents: true, // للسماح بالضغط عليه مستقبلاً
|
||||
points: [
|
||||
LatLng(lat - offset, lng - offset),
|
||||
LatLng(lat + offset, lng - offset),
|
||||
@@ -164,13 +173,27 @@ class HomeCaptainController extends GetxController {
|
||||
}
|
||||
|
||||
heatmapPolygons = tempPolygons;
|
||||
print(
|
||||
"✨ [Heatmap] Rendering Done. (🔥 High: $highCount, 🟠 Med: $medCount, 🟡 Low: $lowCount)");
|
||||
print("📍 [Heatmap] Total Polygons on Map: ${heatmapPolygons.length}");
|
||||
update(); // تحديث الخريطة
|
||||
}
|
||||
|
||||
// دالة لتشغيل الخريطة الحرارية كل فترة (مثلاً عند فتح الصفحة)
|
||||
Timer? _heatmapTimer;
|
||||
|
||||
// دالة لتشغيل الخريطة الحرارية كل فترة (كل 5 دقائق) لضمان نشاط البيانات
|
||||
void startHeatmapCycle() {
|
||||
_heatmapTimer?.cancel();
|
||||
fetchAndDrawHeatmap();
|
||||
// يمكن تفعيل Timer هنا لو أردت تحديثها تلقائياً كل 5 دقائق
|
||||
|
||||
_heatmapTimer = Timer.periodic(const Duration(minutes: 5), (timer) {
|
||||
if (isHeatmapVisible) {
|
||||
print("🔄 [Heatmap] Periodic refresh started...");
|
||||
fetchAndDrawHeatmap();
|
||||
} else {
|
||||
timer.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void goToWalletFromConnect() {
|
||||
@@ -185,16 +208,8 @@ class HomeCaptainController extends GetxController {
|
||||
}
|
||||
|
||||
void addCustomCarIcon() {
|
||||
ImageConfiguration config = ImageConfiguration(
|
||||
size: const Size(30, 35), devicePixelRatio: Get.pixelRatio);
|
||||
BitmapDescriptor.asset(
|
||||
config,
|
||||
'assets/images/car.png',
|
||||
// mipmaps: false,
|
||||
).then((value) {
|
||||
carIcon = value;
|
||||
update();
|
||||
});
|
||||
carIcon = InlqBitmap.fromAsset('assets/images/car.png');
|
||||
update();
|
||||
}
|
||||
|
||||
String stringActiveDuration = '';
|
||||
@@ -259,7 +274,7 @@ class HomeCaptainController extends GetxController {
|
||||
|
||||
// 3. إظهار الديالوج المانع
|
||||
Get.defaultDialog(
|
||||
title: "حسابك مقيد مؤقتاً ⛔",
|
||||
title: "Your account is temporarily restricted ⛔".tr,
|
||||
titleStyle:
|
||||
const TextStyle(color: Colors.red, fontWeight: FontWeight.bold),
|
||||
barrierDismissible: false, // 🚫 ممنوع الإغلاق بالضغط خارجاً
|
||||
@@ -269,8 +284,9 @@ class HomeCaptainController extends GetxController {
|
||||
const Icon(Icons.timer_off_outlined,
|
||||
size: 50, color: Colors.orange),
|
||||
const SizedBox(height: 15),
|
||||
const Text(
|
||||
"لقد تجاوزت حد الإلغاء المسموح به (3 مرات).\nلا يمكنك العمل حتى انتهاء العقوبة.",
|
||||
Text(
|
||||
"You have exceeded the allowed cancellation limit (3 times).\nYou cannot work until the penalty expires."
|
||||
.tr,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
@@ -296,11 +312,11 @@ class HomeCaptainController extends GetxController {
|
||||
? () {
|
||||
Get.back(); // إغلاق الديالوج
|
||||
box.remove(BoxName.blockUntilDate); // إزالة الحظر
|
||||
Get.snackbar("أهلاً بك", "يمكنك الآن استقبال الطلبات",
|
||||
Get.snackbar("Welcome".tr, "You can now receive orders".tr,
|
||||
backgroundColor: Colors.green);
|
||||
}
|
||||
: null, // زر معطل
|
||||
child: Text(isFinished ? "Go Online" : "انتظر انتهاء الوقت"),
|
||||
child: Text(isFinished ? "Go Online".tr : "Wait for timer".tr),
|
||||
);
|
||||
}),
|
||||
);
|
||||
@@ -334,7 +350,13 @@ class HomeCaptainController extends GetxController {
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
print("🔥 [HomeCaptain] onClose called. Tearing down map resources...");
|
||||
_blockTimer?.cancel();
|
||||
activeTimer?.cancel();
|
||||
_cameraFollowTimer?.cancel();
|
||||
_heatmapTimer?.cancel();
|
||||
stopTimer();
|
||||
mapHomeCaptainController = null;
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
@@ -377,7 +399,8 @@ class HomeCaptainController extends GetxController {
|
||||
confirm: MyElevatedButton(
|
||||
title: 'Ok , See you Tomorrow'.tr,
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
// إغلاق الديالوج والعودة قسرياً
|
||||
navigatorKey.currentState?.pop();
|
||||
Get.back();
|
||||
}));
|
||||
}
|
||||
@@ -395,26 +418,40 @@ class HomeCaptainController extends GetxController {
|
||||
update();
|
||||
}
|
||||
|
||||
// late GoogleMapController mapHomeCaptainController;
|
||||
GoogleMapController? mapHomeCaptainController;
|
||||
// final locationController = Get.find<LocationController>();
|
||||
// late IntaleqMapController mapHomeCaptainController;
|
||||
IntaleqMapController? mapHomeCaptainController;
|
||||
|
||||
// --- FIX 2: Smart Map Creation ---
|
||||
void onMapCreated(GoogleMapController controller) {
|
||||
void onMapCreated(IntaleqMapController controller) {
|
||||
print("🔥 [HomeCaptain] onMapCreated started");
|
||||
mapHomeCaptainController = controller;
|
||||
|
||||
// Check actual location before moving camera
|
||||
var currentLoc = locationController.myLocation;
|
||||
if (currentLoc.latitude != 0 && currentLoc.longitude != 0) {
|
||||
controller.animateCamera(
|
||||
CameraUpdate.newLatLng(currentLoc),
|
||||
);
|
||||
} else {
|
||||
// Optional: Move to default city view instead of ocean
|
||||
controller.animateCamera(
|
||||
CameraUpdate.newLatLngZoom(myLocation, 10),
|
||||
);
|
||||
}
|
||||
// We delay the first move to ensure the native side is fully ready
|
||||
Future.delayed(const Duration(milliseconds: 800), () {
|
||||
if (isClosed || mapHomeCaptainController == null) return;
|
||||
|
||||
try {
|
||||
var currentLoc = locationController.myLocation;
|
||||
if (currentLoc.latitude != 0 &&
|
||||
currentLoc.latitude != null &&
|
||||
!currentLoc.latitude.isNaN) {
|
||||
print(
|
||||
"🔥 [HomeCaptain] Safely moving camera to: ${currentLoc.latitude}");
|
||||
mapHomeCaptainController!.moveCamera(
|
||||
CameraUpdate.newLatLngZoom(currentLoc, 15),
|
||||
);
|
||||
} else {
|
||||
print("🔥 [HomeCaptain] Safely moving to default Damascus");
|
||||
mapHomeCaptainController!.moveCamera(
|
||||
CameraUpdate.newLatLngZoom(myLocation, 12),
|
||||
);
|
||||
}
|
||||
// Mark as ready for regular listener updates
|
||||
isMapReadyForCommands = true;
|
||||
} catch (e) {
|
||||
print("❌ [HomeCaptain] Map move failed: $e");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void savePeriod(Duration period) {
|
||||
@@ -441,7 +478,7 @@ class HomeCaptainController extends GetxController {
|
||||
await getCaptainDurationOnToday();
|
||||
String? initialDurationStr = totalDurationToday;
|
||||
|
||||
if (initialDurationStr != null) {
|
||||
if (initialDurationStr != '0') {
|
||||
// تحويل النص (01:20:30) إلى كائن Duration
|
||||
List<String> parts = initialDurationStr.split(':');
|
||||
_currentDuration = Duration(
|
||||
@@ -490,16 +527,27 @@ class HomeCaptainController extends GetxController {
|
||||
getlocation() async {
|
||||
isLoading = true;
|
||||
update();
|
||||
// This ensures we try to get a fix, but map doesn't crash if it fails
|
||||
await locationController.getLocation();
|
||||
try {
|
||||
// ننتظر جلب الموقع مع مهلة 10 ثوانٍ لتجنب التعليق
|
||||
var locData = await locationController.getLocation().timeout(
|
||||
const Duration(seconds: 10),
|
||||
onTimeout: () => null,
|
||||
);
|
||||
|
||||
var loc = locationController.myLocation;
|
||||
if (loc.latitude != 0) {
|
||||
myLocation = loc;
|
||||
if (locData != null && locData.latitude != null) {
|
||||
myLocation = LatLng(locData.latitude!, locData.longitude!);
|
||||
print(
|
||||
"📍 [HomeCaptain] Location updated: ${myLocation.latitude}, ${myLocation.longitude}");
|
||||
} else {
|
||||
print(
|
||||
"⚠️ [HomeCaptain] Could not get current location, using default.");
|
||||
}
|
||||
} catch (e) {
|
||||
print("❌ Error in getlocation: $e");
|
||||
} finally {
|
||||
isLoading = false;
|
||||
update();
|
||||
}
|
||||
|
||||
isLoading = false;
|
||||
update();
|
||||
}
|
||||
|
||||
Map walletDriverPointsDate = {};
|
||||
@@ -535,27 +583,18 @@ class HomeCaptainController extends GetxController {
|
||||
// 4. دالة نستدعيها عند العودة للصفحة الرئيسية
|
||||
void resumeHomeMapUpdates() {
|
||||
isHomeMapActive = true;
|
||||
// إنعاش الخريطة عند العودة
|
||||
if (mapHomeCaptainController != null) {
|
||||
onMapCreated(mapHomeCaptainController!);
|
||||
}
|
||||
// تم حذف استدعاء onMapCreated المتكرر لمنع قفز الخريطة عند العودة
|
||||
update();
|
||||
}
|
||||
|
||||
@override
|
||||
void onInit() async {
|
||||
// ✅ طلب الإذونات أولاً
|
||||
bool permissionsGranted = await PermissionsHelper.requestAllPermissions();
|
||||
// ✅ تم إرجاعه كتعليق لمنع الديالوج عند التشغيل (كما كان في الكود الأصلي)
|
||||
// bool permissionsGranted = await PermissionsHelper.requestAllPermissions();
|
||||
// if (permissionsGranted) {
|
||||
// await BackgroundServiceHelper.startService();
|
||||
// }
|
||||
|
||||
if (permissionsGranted) {
|
||||
// ✅ بدء الخدمة بعد الحصول على الإذونات
|
||||
await BackgroundServiceHelper.startService();
|
||||
print('✅ Background service started successfully');
|
||||
} else {
|
||||
print('❌ لم يتم منح الإذونات - الخدمة لن تعمل');
|
||||
// اعرض رسالة للمستخدم
|
||||
}
|
||||
// await locationBackController.requestLocationPermission();
|
||||
Get.put(FirebaseMessagesController());
|
||||
addToken();
|
||||
await getlocation();
|
||||
@@ -575,29 +614,25 @@ class HomeCaptainController extends GetxController {
|
||||
checkAndShowBlockDialog();
|
||||
box.write(BoxName.statusDriverLocation, 'off');
|
||||
// 2. عدل الليسنر ليصبح مشروطاً
|
||||
locationController.addListener(() {
|
||||
// الشرط الذهبي: إذا كانت الصفحة غير نشطة أو الخريطة غير موجودة، لا تفعل شيئاً
|
||||
if (!isHomeMapActive || mapHomeCaptainController == null || isClosed)
|
||||
return;
|
||||
// 2. مؤقت التتبع التلقائي (كل 5 ثوانٍ كما في الكود السابق)
|
||||
_cameraFollowTimer = Timer.periodic(const Duration(seconds: 5), (timer) {
|
||||
if (isClosed ||
|
||||
!isHomeMapActive ||
|
||||
mapHomeCaptainController == null ||
|
||||
!isActive) return;
|
||||
|
||||
if (isActive) {
|
||||
// isActive الخاصة بالزر "متصل/غير متصل"
|
||||
var loc = locationController.myLocation;
|
||||
if (loc.latitude != 0 && loc.longitude != 0) {
|
||||
try {
|
||||
mapHomeCaptainController!.animateCamera(
|
||||
CameraUpdate.newCameraPosition(
|
||||
CameraPosition(
|
||||
target: loc,
|
||||
zoom: 17.5,
|
||||
tilt: 50.0,
|
||||
bearing: locationController.heading,
|
||||
),
|
||||
),
|
||||
var loc = locationController.myLocation;
|
||||
if (loc.latitude != 0 && loc.latitude != null && !loc.latitude.isNaN) {
|
||||
try {
|
||||
// 🔥 Safety double-check before animating
|
||||
if (mapHomeCaptainController != null) {
|
||||
print("🔥 [HomeCaptain] Safely moving camera to: ${loc.latitude}");
|
||||
mapHomeCaptainController?.animateCamera(
|
||||
CameraUpdate.newLatLngZoom(loc, 17.5),
|
||||
);
|
||||
} catch (e) {
|
||||
// التقاط الخطأ بصمت إذا حدث أثناء الانتقال
|
||||
}
|
||||
} catch (e) {
|
||||
print("❌ [HomeCaptain] Camera movement failed: $e");
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -724,11 +759,4 @@ class HomeCaptainController extends GetxController {
|
||||
update();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
activeTimer?.cancel();
|
||||
stopTimer();
|
||||
mapHomeCaptainController?.dispose(); // Dispose controller
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,28 +5,21 @@ import 'dart:math';
|
||||
import 'dart:math' as math;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:sefer_driver/controller/firebase/local_notification.dart';
|
||||
import 'package:sefer_driver/controller/home/captin/behavior_controller.dart';
|
||||
import 'package:sefer_driver/controller/home/captin/home_captain_controller.dart';
|
||||
import 'package:sefer_driver/controller/home/navigation/decode_polyline_isolate.dart';
|
||||
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
|
||||
import 'package:sefer_driver/views/widgets/mydialoug.dart';
|
||||
import 'package:bubble_head/bubble.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'package:google_polyline_algorithm/google_polyline_algorithm.dart';
|
||||
import 'package:intaleq_maps/intaleq_maps.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../../../constant/api_key.dart';
|
||||
import '../../../constant/box_name.dart';
|
||||
import '../../../constant/colors.dart';
|
||||
import '../../../constant/country_polygons.dart';
|
||||
import '../../../constant/links.dart';
|
||||
import '../../../constant/table_names.dart';
|
||||
import '../../../env/env.dart';
|
||||
import '../../../main.dart';
|
||||
import '../../../print.dart';
|
||||
import '../../../views/Rate/rate_passenger.dart';
|
||||
@@ -36,6 +29,7 @@ import '../../firebase/notification_service.dart';
|
||||
import '../../functions/crud.dart';
|
||||
import '../../functions/location_controller.dart';
|
||||
import '../../functions/tts.dart';
|
||||
import 'behavior_controller.dart';
|
||||
|
||||
class MapDriverController extends GetxController {
|
||||
bool isLoading = true;
|
||||
@@ -48,10 +42,10 @@ class MapDriverController extends GetxController {
|
||||
List data = [];
|
||||
List dataDestination = [];
|
||||
LatLngBounds? boundsData;
|
||||
BitmapDescriptor carIcon = BitmapDescriptor.defaultMarker;
|
||||
BitmapDescriptor passengerIcon = BitmapDescriptor.defaultMarker;
|
||||
BitmapDescriptor startIcon = BitmapDescriptor.defaultMarker;
|
||||
BitmapDescriptor endIcon = BitmapDescriptor.defaultMarker;
|
||||
InlqBitmap carIcon = InlqBitmap.defaultMarker;
|
||||
InlqBitmap passengerIcon = InlqBitmap.defaultMarker;
|
||||
InlqBitmap startIcon = InlqBitmap.defaultMarker;
|
||||
InlqBitmap endIcon = InlqBitmap.defaultMarker;
|
||||
final List<LatLng> polylineCoordinates = [];
|
||||
final List<LatLng> polylineCoordinatesDestination = [];
|
||||
List<Polyline> polyLines = [];
|
||||
@@ -104,7 +98,7 @@ class MapDriverController extends GetxController {
|
||||
int remainingTimeToPassenger = 60;
|
||||
int remainingTimeInPassengerLocatioWait = 60;
|
||||
bool isDriverNearPassengerStart = false;
|
||||
GoogleMapController? mapController;
|
||||
IntaleqMapController? mapController;
|
||||
late LatLng myLocation;
|
||||
int remainingTimeTimerRideBegin = 60;
|
||||
String stringRemainingTimeRideBegin = '';
|
||||
@@ -158,16 +152,32 @@ class MapDriverController extends GetxController {
|
||||
_posSub?.cancel();
|
||||
_posSub = null;
|
||||
|
||||
mapController?.dispose();
|
||||
// mapController?.dispose();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
void onMapCreated(GoogleMapController controller) {
|
||||
void onMapCreated(IntaleqMapController controller) {
|
||||
mapController = controller;
|
||||
if (Get.isRegistered<LocationController>()) {
|
||||
myLocation = Get.find<LocationController>().myLocation;
|
||||
controller.animateCamera(CameraUpdate.newLatLngZoom(myLocation, 16));
|
||||
}
|
||||
|
||||
// 🔥 رسم المسار فور جاهزية الخريطة
|
||||
if (isRideStarted) {
|
||||
// إذا كانت الرحلة بدأت، ارسم للمكان النهائي
|
||||
getRoute(
|
||||
origin: myLocation,
|
||||
destination: latLngPassengerDestination,
|
||||
routeColor: Colors.blue);
|
||||
} else {
|
||||
// إذا كان السائق ذاهب للراكب
|
||||
getRoute(
|
||||
origin: myLocation,
|
||||
destination: latLngPassengerLocation,
|
||||
routeColor: Colors.yellow);
|
||||
}
|
||||
|
||||
// بدء الاستماع للموقع للملاحة وتحديث الماركر
|
||||
startListeningStepNavigation();
|
||||
}
|
||||
@@ -239,7 +249,7 @@ class MapDriverController extends GetxController {
|
||||
}
|
||||
|
||||
takeSnapMap() {
|
||||
mapController!.takeSnapshot();
|
||||
// mapController!.takeSnapshot();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -254,7 +264,7 @@ class MapDriverController extends GetxController {
|
||||
_passengerTimer?.cancel();
|
||||
_waitingTimer?.cancel();
|
||||
_posSub?.cancel();
|
||||
mapController?.dispose();
|
||||
// mapController?.dispose();
|
||||
}
|
||||
|
||||
Future openGoogleMapFromDriverToPassenger() async {
|
||||
@@ -307,7 +317,9 @@ class MapDriverController extends GetxController {
|
||||
box.remove(BoxName.rideArguments);
|
||||
|
||||
// 3. عرض رسالة للسائق
|
||||
if (Get.isDialogOpen == true) Get.back(); // إغلاق أي ديالوج مفتوح
|
||||
if (Get.isDialogOpen == true) {
|
||||
navigatorKey.currentState?.pop();
|
||||
}
|
||||
|
||||
Get.defaultDialog(
|
||||
title: "تم إلغاء الرحلة".tr,
|
||||
@@ -325,7 +337,7 @@ class MapDriverController extends GetxController {
|
||||
),
|
||||
confirm: ElevatedButton(
|
||||
onPressed: () {
|
||||
Get.back(); // إغلاق الديالوج
|
||||
navigatorKey.currentState?.pop(); // إغلاق الديالوج
|
||||
Get.offAll(() => HomeCaptain()); // العودة للرئيسية
|
||||
},
|
||||
child: Text("OK".tr),
|
||||
@@ -367,8 +379,8 @@ class MapDriverController extends GetxController {
|
||||
box.write(BoxName.statusDriverLocation, 'blocked');
|
||||
|
||||
// عرض رسالة العقوبة
|
||||
Get.snackbar("تم تقييد حسابك مؤقتاً ⛔",
|
||||
"بسبب كثرة الإلغاءات (3 مرات)، تم إيقاف استقبال الطلبات لمدة 4 ساعات.",
|
||||
Get.snackbar("Your account is temporarily restricted ⛔".tr,
|
||||
"Due to excessive cancellations (3 times), receiving orders has been suspended for 4 hours.".tr,
|
||||
duration: Duration(seconds: 8),
|
||||
backgroundColor: Colors.red,
|
||||
colorText: Colors.white,
|
||||
@@ -402,15 +414,21 @@ class MapDriverController extends GetxController {
|
||||
Get.put(HomeCaptainController()).getRefusedOrderByCaptain();
|
||||
}
|
||||
|
||||
if (Get.isDialogOpen == true) Get.back();
|
||||
if (Get.isDialogOpen == true) {
|
||||
navigatorKey.currentState?.pop();
|
||||
}
|
||||
Get.offAll(
|
||||
() => HomeCaptain()); // العودة للرئيسية ليتم تطبيق الحظر هناك
|
||||
} else {
|
||||
if (Get.isDialogOpen == true) Get.back();
|
||||
if (Get.isDialogOpen == true) {
|
||||
navigatorKey.currentState?.pop();
|
||||
}
|
||||
Get.snackbar("Error", "Failed to cancel ride");
|
||||
}
|
||||
} catch (e) {
|
||||
if (Get.isDialogOpen == true) Get.back();
|
||||
if (Get.isDialogOpen == true) {
|
||||
navigatorKey.currentState?.pop();
|
||||
}
|
||||
Log.print("Error: $e");
|
||||
}
|
||||
}
|
||||
@@ -707,7 +725,9 @@ class MapDriverController extends GetxController {
|
||||
await calculateDistanceBetweenDriverAndPassengerLocation();
|
||||
|
||||
// إغلاق مؤشر التحميل لأننا حصلنا على النتيجة
|
||||
if (Get.isDialogOpen == true) Get.back();
|
||||
if (Get.isDialogOpen == true) {
|
||||
navigatorKey.currentState?.pop();
|
||||
}
|
||||
|
||||
if (distanceToPassenger < 100) {
|
||||
// زدت المسافة قليلاً لمرونة أكبر (150م)
|
||||
@@ -752,15 +772,17 @@ class MapDriverController extends GetxController {
|
||||
});
|
||||
} else {
|
||||
// --- حالة الرفض (بعيد جداً) ---
|
||||
MyDialog().getDialog(
|
||||
'You are far from passenger location'.tr,
|
||||
MyDialog().getDialog('You are far from passenger location'.tr,
|
||||
'Please go closer to the passenger location (less than 150m)'.tr,
|
||||
() => Get.back() // إغلاق الديالوج فقط
|
||||
);
|
||||
() {
|
||||
// الديالوج يغلق نفسه الآن تلقائياً
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
// تنظيف اللودينج في حال حدوث خطأ غير متوقع
|
||||
if (Get.isDialogOpen == true) Get.back();
|
||||
if (Get.isDialogOpen == true) {
|
||||
navigatorKey.currentState?.pop();
|
||||
}
|
||||
Log.print("Error starting ride: $e");
|
||||
Get.snackbar("Error", "Could not start ride. Please check internet.");
|
||||
}
|
||||
@@ -1448,56 +1470,23 @@ class MapDriverController extends GetxController {
|
||||
}
|
||||
|
||||
void addCustomCarIcon() {
|
||||
ImageConfiguration config = ImageConfiguration(
|
||||
size: const Size(30, 35), devicePixelRatio: Get.pixelRatio);
|
||||
BitmapDescriptor.asset(
|
||||
config,
|
||||
'assets/images/car.png',
|
||||
// mipmaps: false,
|
||||
).then((value) {
|
||||
carIcon = value;
|
||||
update();
|
||||
});
|
||||
carIcon = InlqBitmap.fromAsset('assets/images/car.png');
|
||||
update();
|
||||
}
|
||||
|
||||
void addCustomStartIcon() async {
|
||||
// Create the marker with the resized image
|
||||
|
||||
ImageConfiguration config = ImageConfiguration(
|
||||
size: const Size(30, 30), devicePixelRatio: Get.pixelRatio);
|
||||
BitmapDescriptor.asset(
|
||||
config,
|
||||
'assets/images/A.png',
|
||||
).then((value) {
|
||||
startIcon = value;
|
||||
update();
|
||||
});
|
||||
startIcon = InlqBitmap.fromAsset('assets/images/A.png');
|
||||
update();
|
||||
}
|
||||
|
||||
void addCustomEndIcon() {
|
||||
ImageConfiguration config = ImageConfiguration(
|
||||
size: const Size(25, 25), devicePixelRatio: Get.pixelRatio);
|
||||
BitmapDescriptor.asset(
|
||||
config,
|
||||
'assets/images/b.png',
|
||||
).then((value) {
|
||||
endIcon = value;
|
||||
update();
|
||||
});
|
||||
endIcon = InlqBitmap.fromAsset('assets/images/b.png');
|
||||
update();
|
||||
}
|
||||
|
||||
void addCustomPassengerIcon() {
|
||||
ImageConfiguration config = ImageConfiguration(
|
||||
size: const Size(30, 30), devicePixelRatio: Get.pixelRatio
|
||||
// scale: 1.0,
|
||||
);
|
||||
BitmapDescriptor.asset(
|
||||
config,
|
||||
'assets/images/picker.png',
|
||||
).then((value) {
|
||||
passengerIcon = value;
|
||||
update();
|
||||
});
|
||||
passengerIcon = InlqBitmap.fromAsset('assets/images/picker.png');
|
||||
update();
|
||||
}
|
||||
|
||||
var activeRouteSteps = <Map<String, dynamic>>[];
|
||||
@@ -1596,82 +1585,94 @@ class MapDriverController extends GetxController {
|
||||
required LatLng destination,
|
||||
required Color routeColor,
|
||||
}) async {
|
||||
// 1. استخدام الرابط الجديد والإعدادات الصحيحة
|
||||
String coordinates =
|
||||
'${origin.longitude},${origin.latitude};${destination.longitude},${destination.latitude}';
|
||||
// استخدام الرابط من الكلاس المرجعي لأنه أحدث
|
||||
var url =
|
||||
"${AppLink.mapOSM}/route/v1/driving/$coordinates?steps=true&overview=full";
|
||||
if (mapController == null) return;
|
||||
|
||||
try {
|
||||
var response = await http.get(Uri.parse(url));
|
||||
// 1. طلب المسار من الباكيج
|
||||
final response =
|
||||
await mapController!.getDirections(origin, destination, steps: true);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
var decoded = jsonDecode(response.body);
|
||||
// 2. التعامل مع الـ JSON المباشر (الذي أرسله المستخدم)
|
||||
// إذا كان الـ response يحتوي على الحقول مباشرة في الجذر
|
||||
final String? encodedPoints = response['points'];
|
||||
|
||||
if (decoded['code'] != 'Ok' || (decoded['routes'] as List).isEmpty) {
|
||||
mySnackeBarError("لم يتم العثور على مسار");
|
||||
return;
|
||||
}
|
||||
if (encodedPoints == null) {
|
||||
mySnackeBarError("No route points found".tr);
|
||||
return;
|
||||
}
|
||||
|
||||
var route = decoded['routes'][0];
|
||||
// 🔥 فك التشفير باستخدام compute لضمان أداء ممتاز
|
||||
List<LatLng> fullRoute =
|
||||
await compute(PolylineUtils.decode, encodedPoints);
|
||||
|
||||
// أ) تشغيل الـ Isolate لفك التشفير (ممتاز، ابقِ عليه)
|
||||
final String pointsString = route["geometry"];
|
||||
List<LatLng> fullRoute =
|
||||
await compute(decodePolylineIsolate, pointsString);
|
||||
if (fullRoute.isEmpty) {
|
||||
mySnackeBarError("Failed to process route points".tr);
|
||||
return;
|
||||
}
|
||||
|
||||
// ب) تهيئة المتغيرات
|
||||
upcomingPathPoints.assignAll(fullRoute);
|
||||
traveledPathPoints.clear();
|
||||
_lastTraveledIndex = 0;
|
||||
// تحديث المسافة والوقت من الـ JSON الجديد
|
||||
distance = (response['distance'] ?? 0).toString();
|
||||
duration = (response['duration'] ?? 0).toString();
|
||||
|
||||
// ج) رسم المسار الأولي
|
||||
polyLines.clear();
|
||||
polyLines.add(Polyline(
|
||||
polylineId: const PolylineId("upcoming_route"),
|
||||
points: fullRoute,
|
||||
width: 8,
|
||||
color: routeColor,
|
||||
startCap: Cap.roundCap,
|
||||
endCap: Cap.roundCap,
|
||||
));
|
||||
// ب) تهيئة المتغيرات
|
||||
upcomingPathPoints.assignAll(fullRoute);
|
||||
traveledPathPoints.clear();
|
||||
_lastTraveledIndex = 0;
|
||||
|
||||
// د) معالجة الخطوات (Legs & Steps)
|
||||
List<dynamic> legs = route['legs'];
|
||||
if (legs.isNotEmpty) {
|
||||
final stepsList = List<Map<String, dynamic>>.from(legs[0]['steps']);
|
||||
// ج) رسم المسار الأولي
|
||||
polyLines.clear();
|
||||
polyLines.add(Polyline(
|
||||
polylineId: const PolylineId("upcoming_route"),
|
||||
points: fullRoute,
|
||||
width: 8,
|
||||
color: routeColor,
|
||||
));
|
||||
|
||||
// 🔥 استخدام دالة الترجمة المحسنة من الكلاس المرجعي
|
||||
for (var step in stepsList) {
|
||||
step['html_instructions'] =
|
||||
_createInstructionFromManeuverSmart(step);
|
||||
// تصحيح مواقع المناورات
|
||||
if (step['maneuver'] != null &&
|
||||
step['maneuver']['location'] != null) {
|
||||
var loc = step['maneuver']['location'];
|
||||
// د) معالجة الخطوات (Legs & Steps) إذا توفرت في الـ JSON
|
||||
List<dynamic> legs = response['legs'] ?? [];
|
||||
if (legs.isNotEmpty) {
|
||||
final stepsList =
|
||||
List<Map<String, dynamic>>.from(legs[0]['steps'] ?? []);
|
||||
|
||||
for (var step in stepsList) {
|
||||
step['html_instructions'] = _createInstructionFromManeuverSmart(step);
|
||||
|
||||
if (step['maneuver'] != null &&
|
||||
step['maneuver']['location'] != null) {
|
||||
var loc = step['maneuver']['location'];
|
||||
// التعامل مع تنسيق OSRM [lng, lat]
|
||||
if (loc is List && loc.length >= 2) {
|
||||
step['end_location'] = {'lat': loc[1], 'lng': loc[0]};
|
||||
} else if (loc is Map) {
|
||||
step['end_location'] = loc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
routeSteps = stepsList;
|
||||
currentStepIndex = 0;
|
||||
routeSteps = stepsList;
|
||||
currentStepIndex = 0;
|
||||
|
||||
// نطق أول تعليمة
|
||||
if (routeSteps.isNotEmpty) {
|
||||
currentInstruction = routeSteps[0]['html_instructions'];
|
||||
// نطق أول تعليمة
|
||||
if (routeSteps.isNotEmpty) {
|
||||
currentInstruction = routeSteps[0]['html_instructions'];
|
||||
if (Get.isRegistered<TextToSpeechController>()) {
|
||||
Get.find<TextToSpeechController>().speakText(currentInstruction);
|
||||
}
|
||||
}
|
||||
|
||||
// هـ) تحريك الكاميرا لتشمل المسار
|
||||
if (fullRoute.isNotEmpty) {
|
||||
final bounds = _boundsFromLatLngList(fullRoute);
|
||||
safeAnimateCamera(CameraUpdate.newLatLngBounds(bounds, 80));
|
||||
}
|
||||
|
||||
update();
|
||||
} else {
|
||||
// في حال عدم وجود steps، نقوم بتصفيرها
|
||||
routeSteps = [];
|
||||
currentInstruction = "";
|
||||
}
|
||||
|
||||
// هـ) تحريك الكاميرا لتشمل المسار
|
||||
if (fullRoute.isNotEmpty) {
|
||||
final bounds = _boundsFromLatLngList(fullRoute);
|
||||
safeAnimateCamera(CameraUpdate.newLatLngBounds(bounds,
|
||||
left: 80, top: 80, right: 80, bottom: 80));
|
||||
}
|
||||
|
||||
update();
|
||||
} catch (e) {
|
||||
Log.print("Route Error: $e");
|
||||
}
|
||||
@@ -1679,7 +1680,7 @@ class MapDriverController extends GetxController {
|
||||
|
||||
// 🔥 دالة الترجمة المحسنة (من NavigationController)
|
||||
String _createInstructionFromManeuverSmart(Map<String, dynamic> step) {
|
||||
if (step['maneuver'] == null) return "تابع المسير";
|
||||
if (step['maneuver'] == null) return "Continue straight".tr;
|
||||
|
||||
final maneuver = step['maneuver'];
|
||||
final type = maneuver['type'] ?? 'continue';
|
||||
@@ -1690,10 +1691,10 @@ class MapDriverController extends GetxController {
|
||||
|
||||
switch (type) {
|
||||
case 'depart':
|
||||
instruction = "انطلق";
|
||||
instruction = "Go".tr;
|
||||
break;
|
||||
case 'arrive':
|
||||
return "لقد وصلت إلى وجهتك، $name";
|
||||
return "You have arrived at your destination, @name".trParams({'name': name});
|
||||
case 'turn':
|
||||
case 'fork':
|
||||
case 'roundabout':
|
||||
@@ -1705,14 +1706,14 @@ class MapDriverController extends GetxController {
|
||||
_getTurnInstruction(modifier); // استخدم نفس دالتك المساعدة هنا
|
||||
break;
|
||||
default:
|
||||
instruction = "تابع المسير";
|
||||
instruction = "Continue straight".tr;
|
||||
}
|
||||
|
||||
if (name.isNotEmpty) {
|
||||
if (type == 'continue') {
|
||||
instruction += " على $name";
|
||||
instruction += " ${"on".tr} $name";
|
||||
} else {
|
||||
instruction += " نحو $name";
|
||||
instruction += " ${"towards".tr} $name";
|
||||
}
|
||||
}
|
||||
return instruction;
|
||||
@@ -1728,10 +1729,10 @@ class MapDriverController extends GetxController {
|
||||
|
||||
switch (type) {
|
||||
case 'depart':
|
||||
instruction = "انطلق";
|
||||
instruction = "Go".tr;
|
||||
break;
|
||||
case 'arrive':
|
||||
instruction = "لقد وصلت إلى وجهتك";
|
||||
instruction = "You have arrived at your destination".tr;
|
||||
if (name.isNotEmpty) instruction += "، $name";
|
||||
return instruction;
|
||||
case 'turn':
|
||||
@@ -1742,20 +1743,20 @@ class MapDriverController extends GetxController {
|
||||
instruction = _getTurnInstruction(modifier);
|
||||
break;
|
||||
case 'continue':
|
||||
instruction = "استمر";
|
||||
instruction = "Continue".tr;
|
||||
break;
|
||||
default:
|
||||
instruction = "اتجه";
|
||||
instruction = "Head".tr;
|
||||
}
|
||||
|
||||
if (name.isNotEmpty) {
|
||||
if (instruction == "استمر") {
|
||||
instruction += " على $name";
|
||||
instruction += " ${"on".tr} $name";
|
||||
} else {
|
||||
instruction += " إلى $name";
|
||||
instruction += " ${"to".tr} $name";
|
||||
}
|
||||
} else if (type == 'continue' && modifier == 'straight') {
|
||||
instruction = "استمر بشكل مستقيم";
|
||||
instruction = "Continue straight".tr;
|
||||
}
|
||||
|
||||
return instruction;
|
||||
@@ -1767,23 +1768,23 @@ class MapDriverController extends GetxController {
|
||||
String _getTurnInstruction(String modifier) {
|
||||
switch (modifier) {
|
||||
case 'uturn':
|
||||
return "قم بالاستدارة والعودة";
|
||||
return "Make a U-turn".tr;
|
||||
case 'sharp right':
|
||||
return "انعطف يمينًا بحدة";
|
||||
return "Turn sharp right".tr;
|
||||
case 'right':
|
||||
return "انعطف يمينًا";
|
||||
return "Turn right".tr;
|
||||
case 'slight right':
|
||||
return "انعطف يمينًا قليلاً";
|
||||
return "Turn slight right".tr;
|
||||
case 'straight':
|
||||
return "استمر بشكل مستقيم";
|
||||
return "Continue straight".tr;
|
||||
case 'slight left':
|
||||
return "انعطف يسارًا قليلاً";
|
||||
return "Turn slight left".tr;
|
||||
case 'left':
|
||||
return "انعطف يسارًا";
|
||||
return "Turn left".tr;
|
||||
case 'sharp left':
|
||||
return "انعطف يسارًا بحدة";
|
||||
return "Turn sharp left".tr;
|
||||
default:
|
||||
return "اتجه";
|
||||
return "Head".tr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1850,7 +1851,7 @@ class MapDriverController extends GetxController {
|
||||
void _advanceStep() {
|
||||
if (currentStepIndex >= _stepBounds.length - 1) {
|
||||
// وصل للنهاية
|
||||
currentInstruction = "لقد وصلت إلى وجهتك";
|
||||
currentInstruction = "You have arrived at your destination".tr;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1909,11 +1910,11 @@ class MapDriverController extends GetxController {
|
||||
routeColor: Colors.blue);
|
||||
} else {
|
||||
if (Get.isDialogOpen == true) Get.back();
|
||||
mySnackeBarError("يجب أن تكون أقرب من 100 متر للوصول");
|
||||
mySnackeBarError("You must be closer than 100 meters to arrive".tr);
|
||||
}
|
||||
} catch (e) {
|
||||
if (Get.isDialogOpen == true) Get.back();
|
||||
mySnackeBarError("حدث خطأ في الاتصال");
|
||||
mySnackeBarError("A connection error occurred".tr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1941,9 +1942,7 @@ class MapDriverController extends GetxController {
|
||||
|
||||
// 2. فك تشفير البوليلاين الخاص بالخطوة وتحويله إلى LatLng
|
||||
// -->> هنا تم التصحيح <<--
|
||||
List<LatLng> pts = decodePolyline(s['polyline']['points'])
|
||||
.map((point) => LatLng(point[0].toDouble(), point[1].toDouble()))
|
||||
.toList();
|
||||
List<LatLng> pts = PolylineUtils.decode(s['polyline']['points']);
|
||||
|
||||
// أضف نقاط البداية والنهاية إذا لم تكن موجودة في البوليلاين لضمان دقة الحدود
|
||||
if (pts.isNotEmpty) {
|
||||
@@ -1962,14 +1961,9 @@ class MapDriverController extends GetxController {
|
||||
// A helper function to decode and convert the polyline string
|
||||
List<LatLng> decodePolylineToLatLng(String polylineString) {
|
||||
// 1. Decode the string into a list of number lists (e.g., [[lat, lng], ...])
|
||||
List<List<num>> decodedPoints = decodePolyline(polylineString);
|
||||
List<LatLng> decodedPoints = PolylineUtils.decode(polylineString);
|
||||
|
||||
// 2. Map each [lat, lng] pair to a LatLng object, ensuring conversion to double
|
||||
List<LatLng> latLngPoints = decodedPoints
|
||||
.map((point) => LatLng(point[0].toDouble(), point[1].toDouble()))
|
||||
.toList();
|
||||
|
||||
return latLngPoints;
|
||||
return decodedPoints;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
@@ -1986,11 +1980,11 @@ class MapDriverController extends GetxController {
|
||||
|
||||
void _suggestOptimization() {
|
||||
Get.snackbar(
|
||||
"تحسين أداء التطبيق",
|
||||
"لضمان أفضل تجربة، نقترح تعديل الإعدادات لتناسب جهازك. هل تود المتابعة؟",
|
||||
"Improve app performance".tr,
|
||||
"To ensure the best experience, we suggest adjusting the settings to suit your device. Would you like to proceed?".tr,
|
||||
duration: const Duration(seconds: 15),
|
||||
mainButton: TextButton(
|
||||
child: const Text("نعم، قم بالتحسين"),
|
||||
child: Text("Yes, optimize".tr),
|
||||
onPressed: () {
|
||||
updateInterval.value = 8; // غير الفترة إلى 8 ثوانٍ
|
||||
// save setting to shared_preferences
|
||||
@@ -2017,7 +2011,8 @@ class MapDriverController extends GetxController {
|
||||
|
||||
Future<void> _fitToBounds(LatLngBounds b, {double padding = 60}) async {
|
||||
// نستخدم الدالة الآمنة التي أنشأناها
|
||||
await safeAnimateCamera(CameraUpdate.newLatLngBounds(b, padding));
|
||||
await safeAnimateCamera(CameraUpdate.newLatLngBounds(b,
|
||||
left: padding, top: padding, right: padding, bottom: padding));
|
||||
}
|
||||
|
||||
double distanceBetweenDriverAndPassengerWhenConfirm = 0;
|
||||
@@ -2154,7 +2149,8 @@ class MapDriverController extends GetxController {
|
||||
LatLngBounds(northeast: northeast, southwest: southwest);
|
||||
|
||||
// Fit the camera to the bounds
|
||||
var cameraUpdate = CameraUpdate.newLatLngBounds(boundsData, 140);
|
||||
var cameraUpdate = CameraUpdate.newLatLngBounds(boundsData,
|
||||
left: 140, top: 140, right: 140, bottom: 140);
|
||||
safeAnimateCamera(cameraUpdate);
|
||||
}
|
||||
|
||||
@@ -2395,22 +2391,19 @@ class MapDriverController extends GetxController {
|
||||
polyLines.removeWhere((p) => p.polylineId.value == 'upcoming_route');
|
||||
polyLines.removeWhere((p) => p.polylineId.value == 'traveled_route');
|
||||
|
||||
// المسار المتبقي (أزرق)
|
||||
// المسار المتبقي
|
||||
polyLines.add(Polyline(
|
||||
polylineId: const PolylineId('upcoming_route'),
|
||||
polylineId: const PolylineId("upcoming_route"),
|
||||
points: remaining,
|
||||
color: Colors.blue,
|
||||
width: 8,
|
||||
zIndex: 2,
|
||||
startCap: Cap.roundCap,
|
||||
endCap: Cap.roundCap,
|
||||
color: isRideStarted ? Colors.blue : Colors.yellow,
|
||||
));
|
||||
|
||||
// المسار المقطوع (رمادي)
|
||||
polyLines.add(Polyline(
|
||||
polylineId: const PolylineId('traveled_route'),
|
||||
points: traveled,
|
||||
color: Colors.grey.withOpacity(0.8),
|
||||
color: Colors.grey.withValues(alpha: 0.8),
|
||||
width: 7,
|
||||
zIndex: 1,
|
||||
));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'package:intaleq_maps/intaleq_maps.dart';
|
||||
|
||||
class NavigationStep {
|
||||
final String instruction;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'package:google_polyline_algorithm/google_polyline_algorithm.dart';
|
||||
import 'package:intaleq_maps/intaleq_maps.dart';
|
||||
import 'package:sefer_driver/constant/api_key.dart';
|
||||
import 'package:sefer_driver/constant/box_name.dart';
|
||||
import 'package:sefer_driver/constant/links.dart';
|
||||
@@ -19,10 +18,10 @@ class NavigationService extends GetxService {
|
||||
final RxSet<Polyline> polylines = <Polyline>{}.obs;
|
||||
final RxString currentInstruction = "".obs;
|
||||
|
||||
BitmapDescriptor carIcon = BitmapDescriptor.defaultMarker;
|
||||
BitmapDescriptor passengerIcon = BitmapDescriptor.defaultMarker;
|
||||
BitmapDescriptor startIcon = BitmapDescriptor.defaultMarker;
|
||||
BitmapDescriptor endIcon = BitmapDescriptor.defaultMarker;
|
||||
InlqBitmap carIcon = InlqBitmap.defaultMarker;
|
||||
InlqBitmap passengerIcon = InlqBitmap.defaultMarker;
|
||||
InlqBitmap startIcon = InlqBitmap.defaultMarker;
|
||||
InlqBitmap endIcon = InlqBitmap.defaultMarker;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@@ -30,19 +29,11 @@ class NavigationService extends GetxService {
|
||||
_loadCustomIcons();
|
||||
}
|
||||
|
||||
void _loadCustomIcons() async {
|
||||
carIcon = await _createBitmapDescriptor('assets/images/car.png');
|
||||
passengerIcon = await _createBitmapDescriptor('assets/images/picker.png');
|
||||
startIcon = await _createBitmapDescriptor('assets/images/A.png');
|
||||
endIcon = await _createBitmapDescriptor('assets/images/b.png');
|
||||
}
|
||||
|
||||
Future<BitmapDescriptor> _createBitmapDescriptor(String assetName) {
|
||||
return BitmapDescriptor.fromAssetImage(
|
||||
ImageConfiguration(
|
||||
size: const Size(30, 35), devicePixelRatio: Get.pixelRatio),
|
||||
assetName,
|
||||
);
|
||||
void _loadCustomIcons() {
|
||||
carIcon = InlqBitmap.fromAsset('assets/images/car.png');
|
||||
passengerIcon = InlqBitmap.fromAsset('assets/images/picker.png');
|
||||
startIcon = InlqBitmap.fromAsset('assets/images/A.png');
|
||||
endIcon = InlqBitmap.fromAsset('assets/images/b.png');
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>?> getRoute({
|
||||
@@ -62,9 +53,7 @@ class NavigationService extends GetxService {
|
||||
|
||||
void drawRoute(Map<String, dynamic> routeData, {Color color = Colors.blue}) {
|
||||
final pointsString = routeData["overview_polyline"]["points"];
|
||||
final points = decodePolyline(pointsString)
|
||||
.map((p) => LatLng(p[0].toDouble(), p[1].toDouble()))
|
||||
.toList();
|
||||
final points = PolylineUtils.decode(pointsString);
|
||||
|
||||
final polyline = Polyline(
|
||||
polylineId: PolylineId(routeData["summary"] ?? DateTime.now().toString()),
|
||||
|
||||
@@ -5,14 +5,16 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_overlay_window/flutter_overlay_window.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'package:intaleq_maps/intaleq_maps.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:just_audio/just_audio.dart';
|
||||
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import '../../../constant/box_name.dart';
|
||||
import '../../../constant/links.dart';
|
||||
import '../../../env/env.dart';
|
||||
import '../../../main.dart';
|
||||
import '../../../print.dart';
|
||||
import '../../../views/home/Captin/driver_map_page.dart';
|
||||
@@ -22,8 +24,6 @@ import '../../firebase/local_notification.dart';
|
||||
import '../../functions/crud.dart';
|
||||
import '../../functions/location_controller.dart';
|
||||
import '../../home/captin/home_captain_controller.dart';
|
||||
import '../../firebase/notification_service.dart';
|
||||
import '../navigation/decode_polyline_isolate.dart';
|
||||
|
||||
class OrderRequestController extends GetxController
|
||||
with WidgetsBindingObserver {
|
||||
@@ -40,7 +40,7 @@ class OrderRequestController extends GetxController
|
||||
bool _isRideTakenHandled = false;
|
||||
|
||||
// --- الأيقونات والماركرز ---
|
||||
BitmapDescriptor? driverIcon;
|
||||
InlqBitmap? driverIcon;
|
||||
Map<MarkerId, Marker> markersMap = {};
|
||||
Set<Marker> get markers => markersMap.values.toSet();
|
||||
|
||||
@@ -49,7 +49,7 @@ class OrderRequestController extends GetxController
|
||||
List<dynamic>? myList;
|
||||
Map<dynamic, dynamic>? myMapData;
|
||||
|
||||
GoogleMapController? mapController;
|
||||
IntaleqMapController? mapController;
|
||||
|
||||
// الإحداثيات (أزلنا late لتجنب الأخطاء القاتلة)
|
||||
double latPassenger = 0.0;
|
||||
@@ -64,7 +64,7 @@ class OrderRequestController extends GetxController
|
||||
String totalTripDuration = "--";
|
||||
String tripPrice = "--";
|
||||
|
||||
String timeToPassenger = "جاري الحساب...";
|
||||
String timeToPassenger = "Calculating...".tr;
|
||||
String distanceToPassenger = "--";
|
||||
|
||||
// --- الخريطة ---
|
||||
@@ -193,9 +193,25 @@ class OrderRequestController extends GetxController
|
||||
void _parseExtraData() {
|
||||
passengerRating = _safeGet(33).isEmpty ? "5.0" : _safeGet(33);
|
||||
tripType = _safeGet(31);
|
||||
totalTripDistance = _safeGet(5);
|
||||
totalTripDuration = _safeGet(19);
|
||||
tripPrice = _safeGet(2);
|
||||
|
||||
// Format numbers to avoid many decimal places
|
||||
String rawDist = _safeGet(5);
|
||||
if (rawDist.isNotEmpty) {
|
||||
double? d = double.tryParse(rawDist);
|
||||
totalTripDistance = d != null ? "${d.toStringAsFixed(1)} km" : rawDist;
|
||||
}
|
||||
|
||||
String rawDur = _safeGet(19);
|
||||
if (rawDur.isNotEmpty) {
|
||||
double? d = double.tryParse(rawDur);
|
||||
totalTripDuration = d != null ? "${d.toStringAsFixed(0)} min" : rawDur;
|
||||
}
|
||||
|
||||
String rawPrice = _safeGet(2);
|
||||
if (rawPrice.isNotEmpty) {
|
||||
double? p = double.tryParse(rawPrice);
|
||||
tripPrice = p != null ? p.toStringAsFixed(0) : rawPrice;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
@@ -203,6 +219,8 @@ class OrderRequestController extends GetxController
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
Future<void> _calculateFullJourney() async {
|
||||
if (mapController == null) return; // Wait for controller to draw
|
||||
|
||||
try {
|
||||
Position driverPos = await Geolocator.getCurrentPosition(
|
||||
desiredAccuracy: LocationAccuracy.high);
|
||||
@@ -210,6 +228,9 @@ class OrderRequestController extends GetxController
|
||||
|
||||
updateDriverLocation(driverLatLng, driverPos.heading);
|
||||
|
||||
// Clear old polylines to avoid "ghost lines"
|
||||
polylines.clear();
|
||||
|
||||
var pickupFuture = _fetchRouteData(
|
||||
start: driverLatLng,
|
||||
end: LatLng(latPassenger, lngPassenger),
|
||||
@@ -246,7 +267,8 @@ class OrderRequestController extends GetxController
|
||||
destTime: totalTripDuration,
|
||||
destDist: totalTripDistance);
|
||||
|
||||
zoomToFitRide(driverLatLng);
|
||||
// Now zoom to fit all polylines and markers
|
||||
zoomToFitRide();
|
||||
|
||||
update();
|
||||
} catch (e) {
|
||||
@@ -254,6 +276,22 @@ class OrderRequestController extends GetxController
|
||||
}
|
||||
}
|
||||
|
||||
String _formatDistance(dynamic rawDist) {
|
||||
if (rawDist == null || rawDist.toString().isEmpty) return "--";
|
||||
double dist = double.tryParse(rawDist.toString()) ?? 0.0;
|
||||
if (dist <= 0) return "--";
|
||||
if (dist < 1000) return "${dist.toStringAsFixed(0)} m";
|
||||
return "${(dist / 1000).toStringAsFixed(1)} km";
|
||||
}
|
||||
|
||||
String _formatDuration(dynamic rawDur) {
|
||||
if (rawDur == null || rawDur.toString().isEmpty) return "--";
|
||||
double dur = double.tryParse(rawDur.toString()) ?? 0.0;
|
||||
if (dur <= 0) return "1 min"; // Minimum 1 min for UI
|
||||
if (dur < 60) return "${dur.toStringAsFixed(0)} sec";
|
||||
return "${(dur / 60).toStringAsFixed(0)} min";
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>?> _fetchRouteData(
|
||||
{required LatLng start,
|
||||
required LatLng end,
|
||||
@@ -261,46 +299,56 @@ class OrderRequestController extends GetxController
|
||||
required String id,
|
||||
bool isDashed = false}) async {
|
||||
try {
|
||||
// حماية من الإحداثيات الصفرية
|
||||
if (start.latitude == 0 || end.latitude == 0) return null;
|
||||
if (mapController == null) return null;
|
||||
|
||||
String apiUrl = "https://routesy.intaleq.xyz/route/v1/driving";
|
||||
String coords =
|
||||
"${start.longitude},${start.latitude};${end.longitude},${end.latitude}";
|
||||
String url = "$apiUrl/$coords?steps=false&overview=full";
|
||||
final saasUrl = Uri.parse(AppLink.mapSaasRoute).replace(queryParameters: {
|
||||
'fromLat': start.latitude.toString(),
|
||||
'fromLng': start.longitude.toString(),
|
||||
'toLat': end.latitude.toString(),
|
||||
'toLng': end.longitude.toString(),
|
||||
'steps': 'false',
|
||||
'alternatives': 'false',
|
||||
});
|
||||
|
||||
var response = await http.get(Uri.parse(url));
|
||||
if (response.statusCode == 200) {
|
||||
var json = jsonDecode(response.body);
|
||||
if (json['code'] == 'Ok' && json['routes'].isNotEmpty) {
|
||||
var route = json['routes'][0];
|
||||
final response = await http.get(saasUrl, headers: {
|
||||
'x-api-key': Env.mapSaasKey,
|
||||
'Content-Type': 'application/json',
|
||||
});
|
||||
|
||||
double distM = double.parse(route['distance'].toString());
|
||||
double durS = double.parse(route['duration'].toString());
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception("Routing request failed: ${response.statusCode}");
|
||||
}
|
||||
|
||||
String distText = "${(distM / 1000).toStringAsFixed(1)} كم";
|
||||
String durText = "${(durS / 60).toStringAsFixed(0)} دقيقة";
|
||||
final data = jsonDecode(response.body);
|
||||
print("🛣️ Route API Response [$id]: ${data}");
|
||||
|
||||
String geometry = route['geometry'];
|
||||
List<LatLng> points = await compute(decodePolylineIsolate, geometry);
|
||||
// The map-saas API returns the route data directly at the root,
|
||||
// with 'points' being an encoded polyline string.
|
||||
final String? encodedPoints = data['points']?.toString();
|
||||
|
||||
Polyline polyline = Polyline(
|
||||
polylineId: PolylineId(id),
|
||||
color: color,
|
||||
width: 5,
|
||||
points: points,
|
||||
patterns:
|
||||
isDashed ? [PatternItem.dash(10), PatternItem.gap(5)] : [],
|
||||
startCap: Cap.roundCap,
|
||||
endCap: Cap.roundCap,
|
||||
);
|
||||
if (encodedPoints != null && encodedPoints.isNotEmpty) {
|
||||
List<LatLng> path = controllerDecodePolyline(encodedPoints);
|
||||
print("📍 Path for [$id] has ${path.length} points.");
|
||||
|
||||
return {
|
||||
'distance_text': distText,
|
||||
'duration_text': durText,
|
||||
'polyline': polyline
|
||||
};
|
||||
}
|
||||
final num? rawDist = data['distance'] is num ? data['distance'] : null;
|
||||
final num? rawDur = data['duration'] is num ? data['duration'] : null;
|
||||
|
||||
final distanceText = data['distance_text'] ?? _formatDistance(rawDist);
|
||||
final durationText = data['duration_text'] ?? _formatDuration(rawDur);
|
||||
|
||||
Polyline polyline = Polyline(
|
||||
polylineId: PolylineId(id),
|
||||
color: color,
|
||||
width: 5,
|
||||
points: path,
|
||||
);
|
||||
|
||||
return {
|
||||
'distance_text': distanceText,
|
||||
'duration_text': durationText,
|
||||
'polyline': polyline
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
print("Route Fetch Error: $e");
|
||||
@@ -308,36 +356,47 @@ class OrderRequestController extends GetxController
|
||||
return null;
|
||||
}
|
||||
|
||||
void zoomToFitRide(LatLng driverPos) {
|
||||
void zoomToFitRide() {
|
||||
if (mapController == null) return;
|
||||
|
||||
// حماية من النقاط الصفرية
|
||||
if (latPassenger == 0 || latDestination == 0) return;
|
||||
List<LatLng> allPoints = [];
|
||||
|
||||
List<LatLng> points = [
|
||||
driverPos,
|
||||
LatLng(latPassenger, lngPassenger),
|
||||
LatLng(latDestination, lngDestination),
|
||||
];
|
||||
// Add all polyline points to the bounds calculation
|
||||
for (var polyline in polylines) {
|
||||
allPoints.addAll(polyline.points);
|
||||
}
|
||||
|
||||
double minLat = points.first.latitude;
|
||||
double maxLat = points.first.latitude;
|
||||
double minLng = points.first.longitude;
|
||||
double maxLng = points.first.longitude;
|
||||
// Fallback to basic markers if polylines are empty
|
||||
if (allPoints.isEmpty) {
|
||||
allPoints.addAll([
|
||||
LatLng(latPassenger, lngPassenger),
|
||||
LatLng(latDestination, lngDestination),
|
||||
]);
|
||||
}
|
||||
|
||||
for (var p in points) {
|
||||
if (allPoints.isEmpty) return;
|
||||
|
||||
double minLat = allPoints.first.latitude;
|
||||
double maxLat = allPoints.first.latitude;
|
||||
double minLng = allPoints.first.longitude;
|
||||
double maxLng = allPoints.first.longitude;
|
||||
|
||||
for (var p in allPoints) {
|
||||
if (p.latitude < minLat) minLat = p.latitude;
|
||||
if (p.latitude > maxLat) maxLat = p.latitude;
|
||||
if (p.longitude < minLng) minLng = p.longitude;
|
||||
if (p.longitude > maxLng) maxLng = p.longitude;
|
||||
}
|
||||
|
||||
// Add some padding to the bounds
|
||||
double latPad = (maxLat - minLat) * 0.25;
|
||||
double lngPad = (maxLng - minLng) * 0.2;
|
||||
|
||||
mapController!.animateCamera(CameraUpdate.newLatLngBounds(
|
||||
LatLngBounds(
|
||||
southwest: LatLng(minLat, minLng),
|
||||
northeast: LatLng(maxLat, maxLng),
|
||||
southwest: LatLng(minLat - latPad, minLng - lngPad),
|
||||
northeast: LatLng(maxLat + latPad, maxLng + lngPad),
|
||||
),
|
||||
100.0,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -357,15 +416,15 @@ class OrderRequestController extends GetxController
|
||||
// حماية إذا لم يتم جلب الإحداثيات
|
||||
if (latPassenger == 0 || latDestination == 0) return;
|
||||
|
||||
final BitmapDescriptor pickupIcon =
|
||||
final InlqBitmap pickupIcon =
|
||||
await MarkerGenerator.createCustomMarkerBitmap(
|
||||
title: paxTime,
|
||||
subtitle: paxDist,
|
||||
color: Colors.green.shade700,
|
||||
color: Colors.amber.shade900, // Matching the amber pickup line
|
||||
iconData: Icons.person_pin_circle,
|
||||
);
|
||||
|
||||
final BitmapDescriptor dropoffIcon =
|
||||
final InlqBitmap dropoffIcon =
|
||||
await MarkerGenerator.createCustomMarkerBitmap(
|
||||
title: destTime ?? totalTripDuration,
|
||||
subtitle: destDist ?? totalTripDistance,
|
||||
@@ -411,10 +470,9 @@ class OrderRequestController extends GetxController
|
||||
points: [driverLatLng, LatLng(latPassenger, lngPassenger)],
|
||||
color: Colors.grey,
|
||||
width: 2,
|
||||
patterns: [PatternItem.dash(10), PatternItem.gap(10)],
|
||||
));
|
||||
|
||||
zoomToFitRide(driverLatLng);
|
||||
zoomToFitRide();
|
||||
}
|
||||
|
||||
update();
|
||||
@@ -435,8 +493,9 @@ class OrderRequestController extends GetxController
|
||||
}
|
||||
}
|
||||
|
||||
void onMapCreated(GoogleMapController controller) {
|
||||
void onMapCreated(IntaleqMapController controller) {
|
||||
mapController = controller;
|
||||
_calculateFullJourney();
|
||||
}
|
||||
|
||||
// --- قبول الطلب وإدارة التايمر ---
|
||||
@@ -488,10 +547,13 @@ class OrderRequestController extends GetxController
|
||||
// 1. حذف الإشعار من شريط التنبيهات فوراً
|
||||
NotificationController().cancelOrderNotification();
|
||||
if (Get.isSnackbarOpen) Get.closeCurrentSnackbar();
|
||||
if (Get.isDialogOpen ?? false) Get.back();
|
||||
|
||||
// إغلاق أي ديالوج مفتوح قسرياً
|
||||
if (Get.isDialogOpen ?? false) {
|
||||
navigatorKey.currentState?.pop();
|
||||
}
|
||||
Get.back();
|
||||
Get.snackbar("تنبيه", "تم قبول الطلب من قبل سائق آخر",
|
||||
backgroundColor: Colors.orange, colorText: Colors.white);
|
||||
mySnackbarInfo("The order has been accepted by another driver.".tr);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -546,9 +608,10 @@ class OrderRequestController extends GetxController
|
||||
|
||||
if (isFailure) {
|
||||
// ⛔ حالة الفشل: الطلب مأخوذ
|
||||
MyDialog().getDialog("عذراً، الطلب أخذه سائق آخر.", '', () {
|
||||
Get.back(); // إغلاق الديالوج
|
||||
Get.back(); // العودة للصفحة الرئيسية (إغلاق صفحة الطلب)
|
||||
MyDialog().getDialog(
|
||||
"Sorry, the order was taken by another driver.".tr, '', () {
|
||||
// بما أن MyDialog يغلق نفسه الآن، نحتاج Get.back() واحدة فقط لإغلاق صفحة الطلب
|
||||
Get.back();
|
||||
});
|
||||
} else {
|
||||
// ✅ حالة النجاح
|
||||
@@ -611,7 +674,37 @@ class OrderRequestController extends GetxController
|
||||
audioPlayer.dispose();
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
_timer?.cancel();
|
||||
mapController?.dispose();
|
||||
// mapController?.dispose();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
List<LatLng> controllerDecodePolyline(String encoded) {
|
||||
List<LatLng> points = [];
|
||||
int index = 0, len = encoded.length;
|
||||
int lat = 0, lng = 0;
|
||||
|
||||
while (index < len) {
|
||||
int b, shift = 0, result = 0;
|
||||
do {
|
||||
b = encoded.codeUnitAt(index++) - 63;
|
||||
result |= (b & 0x1f) << shift;
|
||||
shift += 5;
|
||||
} while (b >= 0x20);
|
||||
int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
|
||||
lat += dlat;
|
||||
|
||||
shift = 0;
|
||||
result = 0;
|
||||
do {
|
||||
b = encoded.codeUnitAt(index++) - 63;
|
||||
result |= (b & 0x1f) << shift;
|
||||
shift += 5;
|
||||
} while (b >= 0x20);
|
||||
int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
|
||||
lng += dlng;
|
||||
|
||||
points.add(LatLng(lat / 1E5, lng / 1E5));
|
||||
}
|
||||
return points;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,6 @@
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
|
||||
// تم تعديل الدالة لتقبل وسيط من نوع `dynamic` لحل مشكلة عدم تطابق الأنواع مع دالة `compute`.
|
||||
// هذه الدالة لا تزال تعمل كدالة من المستوى الأعلى (Top-level function)
|
||||
// وهو شرط أساسي لاستخدامها مع دالة compute.
|
||||
List<LatLng> decodePolylineIsolate(dynamic encodedMessage) {
|
||||
// التأكد من أن الرسالة المستقبلة هي من نوع String
|
||||
if (encodedMessage is! String) {
|
||||
// إرجاع قائمة فارغة أو إظهار خطأ إذا كان النوع غير صحيح
|
||||
return [];
|
||||
}
|
||||
final String encoded = encodedMessage;
|
||||
import 'package:intaleq_maps/intaleq_maps.dart';
|
||||
|
||||
List<LatLng> decodePolylineIsolate(String encoded) {
|
||||
List<LatLng> points = [];
|
||||
int index = 0, len = encoded.length;
|
||||
int lat = 0, lng = 0;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:isolate';
|
||||
import 'dart:typed_data';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'package:intaleq_maps/intaleq_maps.dart';
|
||||
import 'dart:math';
|
||||
|
||||
/// Worker entrypoint (spawnUri/spawn).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'package:intaleq_maps/intaleq_maps.dart';
|
||||
import 'package:sefer_driver/constant/style.dart';
|
||||
|
||||
import '../../constant/api_key.dart';
|
||||
|
||||
@@ -4,109 +4,57 @@ import 'package:get/get.dart';
|
||||
import '../../constant/box_name.dart';
|
||||
import '../../main.dart';
|
||||
import '../themes/themes.dart';
|
||||
import '../profile/setting_controller.dart';
|
||||
|
||||
class LocaleController extends GetxController {
|
||||
Locale? language;
|
||||
String countryCode = '';
|
||||
|
||||
ThemeData appTheme = lightThemeEnglish;
|
||||
ThemeData get appTheme {
|
||||
String lang = box.read(BoxName.lang) ?? 'en';
|
||||
bool isDarkMode = box.read('isDarkMode') ?? false;
|
||||
return _getThemeFor(lang, isDarkMode);
|
||||
}
|
||||
|
||||
ThemeData get lightTheme {
|
||||
String lang = box.read(BoxName.lang) ?? 'en';
|
||||
return _getThemeFor(lang, false);
|
||||
}
|
||||
|
||||
ThemeData get darkTheme {
|
||||
String lang = box.read(BoxName.lang) ?? 'en';
|
||||
return _getThemeFor(lang, true);
|
||||
}
|
||||
|
||||
ThemeData _getThemeFor(String lang, bool isDarkMode) {
|
||||
if (lang.startsWith('ar')) {
|
||||
return isDarkMode ? darkThemeArabic : lightThemeArabic;
|
||||
} else {
|
||||
return isDarkMode ? darkThemeEnglish : lightThemeEnglish;
|
||||
}
|
||||
}
|
||||
|
||||
void refreshTheme() {
|
||||
update();
|
||||
}
|
||||
|
||||
void changeLang(String langcode) {
|
||||
Locale locale;
|
||||
switch (langcode) {
|
||||
case "ar":
|
||||
locale = const Locale("ar");
|
||||
appTheme = lightThemeArabic;
|
||||
box.write(BoxName.lang, 'ar');
|
||||
break;
|
||||
case "en":
|
||||
locale = const Locale("en");
|
||||
appTheme = lightThemeEnglish;
|
||||
box.write(BoxName.lang, 'en');
|
||||
break;
|
||||
case "tr":
|
||||
locale = const Locale("tr");
|
||||
appTheme = lightThemeEnglish;
|
||||
box.write(BoxName.lang, 'tr');
|
||||
break;
|
||||
case "fr":
|
||||
locale = const Locale("fr");
|
||||
appTheme = lightThemeEnglish;
|
||||
box.write(BoxName.lang, 'fr');
|
||||
break;
|
||||
case "it":
|
||||
locale = const Locale("it");
|
||||
appTheme = lightThemeEnglish;
|
||||
box.write(BoxName.lang, 'it');
|
||||
break;
|
||||
case "de":
|
||||
locale = const Locale("de");
|
||||
appTheme = lightThemeEnglish;
|
||||
box.write(BoxName.lang, 'de');
|
||||
break;
|
||||
case "el":
|
||||
locale = const Locale("el");
|
||||
appTheme = lightThemeEnglish;
|
||||
box.write(BoxName.lang, 'el');
|
||||
break;
|
||||
case "es":
|
||||
locale = const Locale("es");
|
||||
appTheme = lightThemeEnglish;
|
||||
box.write(BoxName.lang, 'es');
|
||||
break;
|
||||
case "fa":
|
||||
locale = const Locale("fa");
|
||||
appTheme = lightThemeEnglish;
|
||||
box.write(BoxName.lang, 'fa');
|
||||
break;
|
||||
case "zh":
|
||||
locale = const Locale("zh");
|
||||
appTheme = lightThemeEnglish;
|
||||
box.write(BoxName.lang, 'zh');
|
||||
break;
|
||||
case "ru":
|
||||
locale = const Locale("ru");
|
||||
appTheme = lightThemeEnglish;
|
||||
box.write(BoxName.lang, 'ru');
|
||||
break;
|
||||
case "hi":
|
||||
locale = const Locale("hi");
|
||||
appTheme = lightThemeEnglish;
|
||||
box.write(BoxName.lang, 'hi');
|
||||
break;
|
||||
case "ar-ma":
|
||||
locale = const Locale("ar-ma");
|
||||
appTheme = lightThemeArabic;
|
||||
box.write(BoxName.lang, 'ar-ma');
|
||||
break;
|
||||
case "ar-gulf":
|
||||
locale = const Locale("ar-gulf");
|
||||
appTheme = lightThemeArabic;
|
||||
box.write(BoxName.lang, 'ar-gulf');
|
||||
break;
|
||||
default:
|
||||
locale = Locale(Get.deviceLocale!.languageCode);
|
||||
box.write(BoxName.lang, Get.deviceLocale!.languageCode);
|
||||
appTheme = lightThemeEnglish;
|
||||
break;
|
||||
}
|
||||
|
||||
Locale locale = Locale(langcode);
|
||||
box.write(BoxName.lang, langcode);
|
||||
Get.changeTheme(appTheme);
|
||||
Get.updateLocale(locale);
|
||||
update();
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
String? storedLang = box.read(BoxName.lang);
|
||||
if (storedLang == null) {
|
||||
// Use device language if no language is stored
|
||||
storedLang = Get.deviceLocale!.languageCode;
|
||||
box.write(BoxName.lang, storedLang);
|
||||
}
|
||||
|
||||
changeLang(storedLang);
|
||||
language = Locale(storedLang);
|
||||
super.onInit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,9 +18,7 @@ import '../../constant/colors.dart';
|
||||
import '../../constant/info.dart';
|
||||
import '../../constant/links.dart';
|
||||
import '../../main.dart';
|
||||
import '../../print.dart';
|
||||
import '../functions/crud.dart';
|
||||
import '../functions/encrypt_decrypt.dart';
|
||||
import '../functions/toast.dart';
|
||||
import 'paymob/paymob_wallet.dart';
|
||||
|
||||
@@ -170,8 +168,8 @@ class PaymentController extends GetxController {
|
||||
)),
|
||||
allowsDelayedPaymentMethods: true,
|
||||
customerEphemeralKeySecret: Stripe.merchantIdentifier,
|
||||
appearance: const PaymentSheetAppearance(
|
||||
shapes: PaymentSheetShape(borderRadius: 12),
|
||||
appearance: PaymentSheetAppearance(
|
||||
shapes: const PaymentSheetShape(borderRadius: 12),
|
||||
colors: PaymentSheetAppearanceColors(
|
||||
background: AppColor.secondaryColor,
|
||||
),
|
||||
@@ -725,7 +723,6 @@ class PaymentController extends GetxController {
|
||||
box.write(BoxName.passengerWalletTotal, '0');
|
||||
}
|
||||
// getPassengerWallet();
|
||||
final localAuth = LocalAuthentication();
|
||||
super.onInit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,45 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sefer_driver/constant/box_name.dart';
|
||||
import 'package:sefer_driver/main.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../local/local_controller.dart';
|
||||
|
||||
class SettingController extends GetxController {
|
||||
bool isGoogleMapsEnabled = false;
|
||||
bool isMapDarkMode = false;
|
||||
bool isDarkMode = false;
|
||||
|
||||
void onChangMapApp() {
|
||||
if (!isGoogleMapsEnabled) {
|
||||
isGoogleMapsEnabled = true;
|
||||
box.write(BoxName.googlaMapApp, true);
|
||||
update();
|
||||
} else {
|
||||
isGoogleMapsEnabled = false;
|
||||
box.write(BoxName.googlaMapApp, false);
|
||||
update();
|
||||
isGoogleMapsEnabled = !isGoogleMapsEnabled;
|
||||
box.write(BoxName.googlaMapApp, isGoogleMapsEnabled);
|
||||
update();
|
||||
}
|
||||
|
||||
void toggleMapTheme() {
|
||||
isMapDarkMode = !isMapDarkMode;
|
||||
box.write('isMapDarkMode', isMapDarkMode);
|
||||
update();
|
||||
}
|
||||
|
||||
void toggleAppTheme() {
|
||||
isDarkMode = !isDarkMode;
|
||||
box.write('isDarkMode', isDarkMode);
|
||||
|
||||
// Update the theme using the LocaleController to ensure correct fonts are used
|
||||
if (Get.isRegistered<LocaleController>()) {
|
||||
Get.find<LocaleController>().refreshTheme();
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
if (box.read(BoxName.googlaMapApp) != null) {
|
||||
isGoogleMapsEnabled = box.read(BoxName.googlaMapApp);
|
||||
}
|
||||
update();
|
||||
isGoogleMapsEnabled = box.read(BoxName.googlaMapApp) ?? false;
|
||||
isMapDarkMode = box.read('isMapDarkMode') ?? false;
|
||||
isDarkMode = box.read('isDarkMode') ?? false;
|
||||
|
||||
super.onInit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,146 +2,89 @@ import 'package:flutter/material.dart';
|
||||
import 'package:sefer_driver/constant/style.dart';
|
||||
import '../../constant/colors.dart';
|
||||
|
||||
ThemeData lightThemeEnglish = ThemeData(
|
||||
ThemeData _createTheme({
|
||||
required Brightness brightness,
|
||||
required String fontFamily,
|
||||
required Color scaffoldBackgroundColor,
|
||||
required Color cardColor,
|
||||
required Color dividerColor,
|
||||
}) {
|
||||
return ThemeData(
|
||||
brightness: brightness,
|
||||
fontFamily: fontFamily,
|
||||
scaffoldBackgroundColor: scaffoldBackgroundColor,
|
||||
cardColor: cardColor,
|
||||
dividerColor: dividerColor,
|
||||
primaryColor: AppColor.primaryColor,
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: AppColor.primaryColor,
|
||||
brightness: brightness,
|
||||
primary: AppColor.primaryColor,
|
||||
surface: cardColor,
|
||||
background: scaffoldBackgroundColor,
|
||||
error: AppColor.redColor,
|
||||
),
|
||||
textTheme: TextTheme(
|
||||
displaySmall: AppStyle.title.copyWith(color: AppColor.writeColor),
|
||||
displayLarge: AppStyle.headTitle.copyWith(color: AppColor.writeColor),
|
||||
displayMedium: AppStyle.headTitle2.copyWith(color: AppColor.writeColor),
|
||||
bodyLarge: AppStyle.title.copyWith(color: AppColor.writeColor),
|
||||
bodyMedium: AppStyle.subtitle.copyWith(color: AppColor.writeColor),
|
||||
titleLarge: AppStyle.headTitle2.copyWith(color: AppColor.writeColor),
|
||||
),
|
||||
appBarTheme: AppBarTheme(
|
||||
elevation: 0,
|
||||
backgroundColor: scaffoldBackgroundColor,
|
||||
centerTitle: true,
|
||||
iconTheme: const IconThemeData(color: AppColor.primaryColor),
|
||||
titleTextStyle: AppStyle.headTitle2.copyWith(color: AppColor.writeColor),
|
||||
),
|
||||
dialogTheme: DialogThemeData(
|
||||
backgroundColor: cardColor,
|
||||
contentTextStyle: AppStyle.title.copyWith(color: AppColor.writeColor),
|
||||
titleTextStyle: AppStyle.headTitle2.copyWith(color: AppColor.writeColor),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
),
|
||||
cardTheme: CardThemeData(
|
||||
color: cardColor,
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
dividerTheme: DividerThemeData(
|
||||
color: dividerColor,
|
||||
thickness: 1,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
ThemeData lightThemeEnglish = _createTheme(
|
||||
brightness: Brightness.light,
|
||||
fontFamily: "SFPro",
|
||||
textTheme: TextTheme(
|
||||
displaySmall: AppStyle.title,
|
||||
displayLarge: AppStyle.headTitle,
|
||||
displayMedium: AppStyle.headTitle2,
|
||||
bodyLarge: AppStyle.title,
|
||||
bodyMedium: AppStyle.subtitle,
|
||||
),
|
||||
primarySwatch: Colors.blue,
|
||||
dialogTheme: DialogThemeData(
|
||||
backgroundColor: AppColor.secondaryColor,
|
||||
contentTextStyle: AppStyle.title,
|
||||
titleTextStyle: AppStyle.headTitle2,
|
||||
),
|
||||
appBarTheme: AppBarTheme(
|
||||
elevation: 0,
|
||||
color: AppColor.secondaryColor,
|
||||
centerTitle: true,
|
||||
iconTheme: const IconThemeData(
|
||||
color: AppColor.primaryColor,
|
||||
),
|
||||
toolbarTextStyle: TextTheme(
|
||||
titleSmall: AppStyle.subtitle,
|
||||
headlineSmall: AppStyle.title,
|
||||
titleLarge: AppStyle.headTitle2,
|
||||
).bodyMedium,
|
||||
titleTextStyle: TextTheme(
|
||||
titleSmall: AppStyle.subtitle,
|
||||
headlineSmall: AppStyle.title,
|
||||
titleLarge: AppStyle.headTitle2,
|
||||
).titleLarge,
|
||||
),
|
||||
scaffoldBackgroundColor: Colors.white,
|
||||
cardColor: Colors.white,
|
||||
dividerColor: Colors.black12,
|
||||
);
|
||||
|
||||
ThemeData darkThemeEnglish = ThemeData(
|
||||
ThemeData darkThemeEnglish = _createTheme(
|
||||
brightness: Brightness.dark,
|
||||
fontFamily: "SFPro",
|
||||
textTheme: TextTheme(
|
||||
displaySmall: AppStyle.title,
|
||||
displayLarge: AppStyle.headTitle,
|
||||
displayMedium: AppStyle.headTitle2,
|
||||
bodyLarge: AppStyle.title,
|
||||
bodyMedium: AppStyle.subtitle,
|
||||
),
|
||||
primarySwatch: Colors.blue,
|
||||
dialogTheme: DialogThemeData(
|
||||
backgroundColor: AppColor.secondaryColor,
|
||||
contentTextStyle: AppStyle.title,
|
||||
titleTextStyle: AppStyle.headTitle2,
|
||||
),
|
||||
appBarTheme: AppBarTheme(
|
||||
elevation: 0,
|
||||
color: AppColor.secondaryColor,
|
||||
centerTitle: true,
|
||||
iconTheme: const IconThemeData(
|
||||
color: AppColor.primaryColor,
|
||||
),
|
||||
toolbarTextStyle: TextTheme(
|
||||
titleSmall: AppStyle.subtitle,
|
||||
headlineSmall: AppStyle.title,
|
||||
titleLarge: AppStyle.headTitle2,
|
||||
).bodyMedium,
|
||||
titleTextStyle: TextTheme(
|
||||
titleSmall: AppStyle.subtitle,
|
||||
headlineSmall: AppStyle.title,
|
||||
titleLarge: AppStyle.headTitle2,
|
||||
).titleLarge,
|
||||
),
|
||||
scaffoldBackgroundColor: const Color(0xFF121212),
|
||||
cardColor: const Color(0xFF1E1E1E),
|
||||
dividerColor: Colors.white10,
|
||||
);
|
||||
|
||||
ThemeData lightThemeArabic = ThemeData(
|
||||
ThemeData lightThemeArabic = _createTheme(
|
||||
brightness: Brightness.light,
|
||||
fontFamily: 'SFArabic',
|
||||
textTheme: TextTheme(
|
||||
displaySmall: AppStyle.title,
|
||||
displayLarge: AppStyle.headTitle,
|
||||
displayMedium: AppStyle.headTitle2,
|
||||
bodyLarge: AppStyle.title,
|
||||
bodyMedium: AppStyle.subtitle,
|
||||
),
|
||||
primarySwatch: Colors.blue,
|
||||
dialogTheme: DialogThemeData(
|
||||
backgroundColor: AppColor.secondaryColor,
|
||||
contentTextStyle: AppStyle.title,
|
||||
titleTextStyle: AppStyle.headTitle2,
|
||||
),
|
||||
appBarTheme: AppBarTheme(
|
||||
elevation: 0,
|
||||
color: AppColor.secondaryColor,
|
||||
centerTitle: true,
|
||||
iconTheme: const IconThemeData(
|
||||
color: AppColor.primaryColor,
|
||||
),
|
||||
toolbarTextStyle: TextTheme(
|
||||
titleSmall: AppStyle.subtitle,
|
||||
headlineSmall: AppStyle.title,
|
||||
titleLarge: AppStyle.headTitle2,
|
||||
).bodyMedium,
|
||||
titleTextStyle: TextTheme(
|
||||
titleSmall: AppStyle.subtitle,
|
||||
headlineSmall: AppStyle.title,
|
||||
titleLarge: AppStyle.headTitle2,
|
||||
).titleLarge,
|
||||
),
|
||||
scaffoldBackgroundColor: Colors.white,
|
||||
cardColor: Colors.white,
|
||||
dividerColor: Colors.black12,
|
||||
);
|
||||
|
||||
ThemeData darkThemeArabic = ThemeData(
|
||||
ThemeData darkThemeArabic = _createTheme(
|
||||
brightness: Brightness.dark,
|
||||
fontFamily: 'SFArabic',
|
||||
textTheme: TextTheme(
|
||||
displaySmall: AppStyle.title,
|
||||
displayLarge: AppStyle.headTitle,
|
||||
displayMedium: AppStyle.headTitle2,
|
||||
bodyLarge: AppStyle.title,
|
||||
bodyMedium: AppStyle.subtitle,
|
||||
),
|
||||
primarySwatch: Colors.blue,
|
||||
dialogTheme: DialogThemeData(
|
||||
backgroundColor: AppColor.secondaryColor,
|
||||
contentTextStyle: AppStyle.title,
|
||||
titleTextStyle: AppStyle.headTitle2,
|
||||
),
|
||||
appBarTheme: AppBarTheme(
|
||||
elevation: 0,
|
||||
color: AppColor.secondaryColor,
|
||||
centerTitle: true,
|
||||
iconTheme: const IconThemeData(
|
||||
color: AppColor.primaryColor,
|
||||
),
|
||||
toolbarTextStyle: TextTheme(
|
||||
titleSmall: AppStyle.subtitle,
|
||||
headlineSmall: AppStyle.title,
|
||||
titleLarge: AppStyle.headTitle2,
|
||||
).bodyMedium,
|
||||
titleTextStyle: TextTheme(
|
||||
titleSmall: AppStyle.subtitle,
|
||||
headlineSmall: AppStyle.title,
|
||||
titleLarge: AppStyle.headTitle2,
|
||||
).titleLarge,
|
||||
),
|
||||
scaffoldBackgroundColor: const Color(0xFF121212),
|
||||
cardColor: const Color(0xFF1E1E1E),
|
||||
dividerColor: Colors.white10,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user