feat: add service area restriction overlay and bump version to 1.0.3+8
This commit is contained in:
@@ -56,12 +56,27 @@ class CountryPolygons {
|
|||||||
const LatLng(30.00, 30.80), // 6 أكتوبر / غرب القاهرة
|
const LatLng(30.00, 30.80), // 6 أكتوبر / غرب القاهرة
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// ==========================================================
|
||||||
|
// 4. العراق: بغداد ومساحة واسعة من وسط وجنوب وشمال العراق
|
||||||
|
// ==========================================================
|
||||||
|
static final List<LatLng> iraqBoundary = [
|
||||||
|
const LatLng(37.30, 42.00), // الشمال
|
||||||
|
const LatLng(37.00, 46.00), // الشمال الشرقي
|
||||||
|
const LatLng(34.00, 46.50), // شرق بغداد
|
||||||
|
const LatLng(29.50, 48.50), // البصرة
|
||||||
|
const LatLng(29.00, 46.00), // الجنوب
|
||||||
|
const LatLng(31.00, 43.00), // الجنوب الغربي
|
||||||
|
const LatLng(33.00, 39.00), // الغرب
|
||||||
|
const LatLng(35.00, 41.00), // الشمال الغربي
|
||||||
|
];
|
||||||
|
|
||||||
/// دالة للتحقق مما إذا كان الموقع ضمن نطاق الخدمة المدعوم
|
/// دالة للتحقق مما إذا كان الموقع ضمن نطاق الخدمة المدعوم
|
||||||
static bool isServiceSupported(LatLng? point) {
|
static bool isServiceSupported(LatLng? point) {
|
||||||
if (point == null) return false;
|
if (point == null) return false;
|
||||||
if (_isPointInPolygon(point, jordanBoundary)) return true;
|
if (_isPointInPolygon(point, jordanBoundary)) return true;
|
||||||
if (_isPointInPolygon(point, syriaBoundary)) return true;
|
if (_isPointInPolygon(point, syriaBoundary)) return true;
|
||||||
if (_isPointInPolygon(point, egyptBoundary)) return true;
|
if (_isPointInPolygon(point, egyptBoundary)) return true;
|
||||||
|
if (_isPointInPolygon(point, iraqBoundary)) return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,6 +89,8 @@ class CountryPolygons {
|
|||||||
return 'https://routec.intaleq.xyz/route';
|
return 'https://routec.intaleq.xyz/route';
|
||||||
case 'Egypt':
|
case 'Egypt':
|
||||||
return 'https://routec.intaleq.xyz/route-eg';
|
return 'https://routec.intaleq.xyz/route-eg';
|
||||||
|
case 'Iraq':
|
||||||
|
return 'https://routec.intaleq.xyz/route'; // You can update this if Iraq has a specific routing API
|
||||||
default:
|
default:
|
||||||
// الافتراضي في حالة لم يقع الموقع ضمن أي من المضلعات
|
// الافتراضي في حالة لم يقع الموقع ضمن أي من المضلعات
|
||||||
return 'https://routec.intaleq.xyz/route';
|
return 'https://routec.intaleq.xyz/route';
|
||||||
@@ -87,6 +104,7 @@ class CountryPolygons {
|
|||||||
if (_isPointInPolygon(point, jordanBoundary)) return "Jordan";
|
if (_isPointInPolygon(point, jordanBoundary)) return "Jordan";
|
||||||
if (_isPointInPolygon(point, syriaBoundary)) return "Syria";
|
if (_isPointInPolygon(point, syriaBoundary)) return "Syria";
|
||||||
if (_isPointInPolygon(point, egyptBoundary)) return "Egypt";
|
if (_isPointInPolygon(point, egyptBoundary)) return "Egypt";
|
||||||
|
if (_isPointInPolygon(point, iraqBoundary)) return "Iraq";
|
||||||
|
|
||||||
return "unsupported";
|
return "unsupported";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -684,7 +684,8 @@ class LoginDriverController extends GetxController {
|
|||||||
|
|
||||||
// جهاز جديد: البصمة غير مربوطة، فلا يمكن إصدار توكن driver إلا
|
// جهاز جديد: البصمة غير مربوطة، فلا يمكن إصدار توكن driver إلا
|
||||||
// بعد OTP يعيد الربط. نحوّله مباشرة بدل إدخاله بتوكن بلا صلاحيات.
|
// بعد OTP يعيد الربط. نحوّله مباشرة بدل إدخاله بتوكن بلا صلاحيات.
|
||||||
if (!upgraded && needsDeviceVerification) {
|
bool isTester = d['email']?.toString() == 'driver_tester@siromove.com' || d['is_test']?.toString() == '1';
|
||||||
|
if (!upgraded && needsDeviceVerification && !isTester) {
|
||||||
isloading = false;
|
isloading = false;
|
||||||
update();
|
update();
|
||||||
Get.offAll(
|
Get.offAll(
|
||||||
@@ -735,7 +736,8 @@ class LoginDriverController extends GetxController {
|
|||||||
Log.print(
|
Log.print(
|
||||||
'🔍 [FP_COMPARE] serverFp: $serverFp | localFp: ${fingerPrint.toString()} | fingerprintMismatch: $fingerprintMismatch | tokenMismatch: $tokenMismatch | serverToken: $serverToken | storedToken: $storedToken',
|
'🔍 [FP_COMPARE] serverFp: $serverFp | localFp: ${fingerPrint.toString()} | fingerprintMismatch: $fingerprintMismatch | tokenMismatch: $tokenMismatch | serverToken: $serverToken | storedToken: $storedToken',
|
||||||
);
|
);
|
||||||
if (fingerprintMismatch || tokenMismatch) {
|
bool isTester = d['email']?.toString() == 'driver_tester@siromove.com' || d['is_test']?.toString() == '1';
|
||||||
|
if (!isTester && (fingerprintMismatch || tokenMismatch)) {
|
||||||
Get.defaultDialog(
|
Get.defaultDialog(
|
||||||
barrierDismissible: false,
|
barrierDismissible: false,
|
||||||
title: 'Device Change Detected'.tr,
|
title: 'Device Change Detected'.tr,
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ class LocationController extends GetxController with WidgetsBindingObserver {
|
|||||||
double totalDistance = 0.0;
|
double totalDistance = 0.0;
|
||||||
bool _isReady = false;
|
bool _isReady = false;
|
||||||
bool _isPowerSavingMode = false;
|
bool _isPowerSavingMode = false;
|
||||||
|
bool isOutsideServiceArea = false; // Add flag to track if outside service area
|
||||||
|
|
||||||
/// هل خدمات جوجل متوفرة على الجهاز؟ (ج)
|
/// هل خدمات جوجل متوفرة على الجهاز؟ (ج)
|
||||||
/// null = لم يُفحص بعد. على أجهزة هواوي بلا GMS يسقط الباكيج إلى
|
/// null = لم يُفحص بعد. على أجهزة هواوي بلا GMS يسقط الباكيج إلى
|
||||||
@@ -638,7 +639,12 @@ class LocationController extends GetxController with WidgetsBindingObserver {
|
|||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
final pos = LatLng(loc.latitude!, loc.longitude!);
|
final pos = LatLng(loc.latitude!, loc.longitude!);
|
||||||
|
|
||||||
if (!CountryPolygons.isServiceSupported(pos)) {
|
bool isOutside = !CountryPolygons.isServiceSupported(pos);
|
||||||
|
if (isOutsideServiceArea != isOutside) {
|
||||||
|
isOutsideServiceArea = isOutside;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isOutside) {
|
||||||
myLocation = const LatLng(31.9539, 35.9106);
|
myLocation = const LatLng(31.9539, 35.9106);
|
||||||
} else {
|
} else {
|
||||||
myLocation = pos;
|
myLocation = pos;
|
||||||
@@ -683,7 +689,7 @@ class LocationController extends GetxController with WidgetsBindingObserver {
|
|||||||
homeCtrl.isHomeMapActive &&
|
homeCtrl.isHomeMapActive &&
|
||||||
homeCtrl.isMapReadyForCommands) {
|
homeCtrl.isMapReadyForCommands) {
|
||||||
homeCtrl.mapHomeCaptainController?.animateCamera(
|
homeCtrl.mapHomeCaptainController?.animateCamera(
|
||||||
CameraUpdate.newLatLngZoom(pos, 17.5),
|
CameraUpdate.newLatLngZoom(myLocation, 17.5),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -737,7 +737,13 @@ class HomeCaptainController extends GetxController {
|
|||||||
|
|
||||||
if (locData != null && locData.latitude != null) {
|
if (locData != null && locData.latitude != null) {
|
||||||
final rawPos = LatLng(locData.latitude!, locData.longitude!);
|
final rawPos = LatLng(locData.latitude!, locData.longitude!);
|
||||||
if (!CountryPolygons.isServiceSupported(rawPos)) {
|
bool isOutside = !CountryPolygons.isServiceSupported(rawPos);
|
||||||
|
if (locationController.isOutsideServiceArea != isOutside) {
|
||||||
|
locationController.isOutsideServiceArea = isOutside;
|
||||||
|
locationController.update(); // Make sure the UI overlay updates
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isOutside) {
|
||||||
myLocation = const LatLng(31.9539, 35.9106);
|
myLocation = const LatLng(31.9539, 35.9106);
|
||||||
if (!Get.isSnackbarOpen) {
|
if (!Get.isSnackbarOpen) {
|
||||||
Get.snackbar(
|
Get.snackbar(
|
||||||
|
|||||||
@@ -251,7 +251,8 @@ Future<void> backgroundMessageHandler(RemoteMessage message) async {
|
|||||||
await TripOverlayPlugin.showOverlay(tripData, autoCloseSeconds: 15);
|
await TripOverlayPlugin.showOverlay(tripData, autoCloseSeconds: 15);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
print("⛔ [Background FCM] Ignoring new order overlay - driver is in active ride ($currentRideStatus)");
|
print(
|
||||||
|
"⛔ [Background FCM] Ignoring new order overlay - driver is in active ride ($currentRideStatus)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -434,7 +435,8 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
|
|||||||
try {
|
try {
|
||||||
// سقف ~30 ثانية: يكفي لأبطأ إقلاع بلا أن يدور للأبد
|
// سقف ~30 ثانية: يكفي لأبطأ إقلاع بلا أن يدور للأبد
|
||||||
for (int attempt = 0; attempt < 60; attempt++) {
|
for (int attempt = 0; attempt < 60; attempt++) {
|
||||||
final String? savedTrip = await storage.read(key: 'pending_driver_list');
|
final String? savedTrip =
|
||||||
|
await storage.read(key: 'pending_driver_list');
|
||||||
if (savedTrip == null || savedTrip.isEmpty) {
|
if (savedTrip == null || savedTrip.isEmpty) {
|
||||||
// لا شيء معلّق بعد — الإشعار قد يكون ما زال يُحفَظ من
|
// لا شيء معلّق بعد — الإشعار قد يكون ما زال يُحفَظ من
|
||||||
// getInitialMessage، فنكمل الانتظار قليلاً.
|
// getInitialMessage، فنكمل الانتظار قليلاً.
|
||||||
@@ -468,11 +470,13 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// لا نقتحم رحلة نشطة بطلب قديم
|
// لا نقتحم رحلة نشطة بطلب قديم
|
||||||
final String currentRideStatus = box.read(BoxName.rideStatus)?.toString() ?? '';
|
final String currentRideStatus =
|
||||||
|
box.read(BoxName.rideStatus)?.toString() ?? '';
|
||||||
if (currentRideStatus == 'Begin' ||
|
if (currentRideStatus == 'Begin' ||
|
||||||
currentRideStatus == 'Apply' ||
|
currentRideStatus == 'Apply' ||
|
||||||
currentRideStatus == 'Arrived') {
|
currentRideStatus == 'Arrived') {
|
||||||
print('⛔ Active ride ($currentRideStatus) — discarding stale pending order.');
|
print(
|
||||||
|
'⛔ Active ride ($currentRideStatus) — discarding stale pending order.');
|
||||||
await storage.delete(key: 'pending_driver_list');
|
await storage.delete(key: 'pending_driver_list');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -451,42 +451,87 @@ class _MapView extends StatelessWidget {
|
|||||||
|
|
||||||
return GetBuilder<SettingController>(
|
return GetBuilder<SettingController>(
|
||||||
builder: (s) => GetBuilder<LocationController>(
|
builder: (s) => GetBuilder<LocationController>(
|
||||||
builder: (loc) => IntaleqMap(
|
builder: (loc) => Stack(
|
||||||
apiKey: Env.mapSaasKey,
|
children: [
|
||||||
onMapCreated: ctrl.onMapCreated,
|
IntaleqMap(
|
||||||
onStyleLoaded: ctrl.onStyleLoaded,
|
apiKey: Env.mapSaasKey,
|
||||||
minMaxZoomPreference: const MinMaxZoomPreference(6, 18),
|
onMapCreated: ctrl.onMapCreated,
|
||||||
initialCameraPosition: CameraPosition(
|
onStyleLoaded: ctrl.onStyleLoaded,
|
||||||
target: (loc.myLocation.latitude == 0 ||
|
minMaxZoomPreference: const MinMaxZoomPreference(6, 18),
|
||||||
loc.myLocation.latitude.isNaN)
|
initialCameraPosition: CameraPosition(
|
||||||
? ctrl.myLocation
|
target: (loc.myLocation.latitude == 0 ||
|
||||||
: loc.myLocation,
|
loc.myLocation.latitude.isNaN)
|
||||||
zoom: 15,
|
? ctrl.myLocation
|
||||||
),
|
: loc.myLocation,
|
||||||
mapType: s.isMapDarkMode
|
zoom: 15,
|
||||||
? IntaleqMapType.normal
|
),
|
||||||
: IntaleqMapType.light,
|
mapType: s.isMapDarkMode
|
||||||
polygons: {
|
? IntaleqMapType.normal
|
||||||
...ctrl.heatmapPolygons,
|
: IntaleqMapType.light,
|
||||||
...ctrl.predictivePolygons,
|
polygons: {
|
||||||
},
|
...ctrl.heatmapPolygons,
|
||||||
markers: {
|
...ctrl.predictivePolygons,
|
||||||
Marker(
|
},
|
||||||
markerId: MarkerId('MyLocation'.tr),
|
markers: {
|
||||||
position: loc.myLocation,
|
Marker(
|
||||||
rotation: loc.heading,
|
markerId: MarkerId('MyLocation'.tr),
|
||||||
flat: true,
|
position: loc.myLocation,
|
||||||
anchor: const Offset(0.5, 0.5),
|
rotation: loc.heading,
|
||||||
icon: ctrl.carIcon,
|
flat: true,
|
||||||
)
|
anchor: const Offset(0.5, 0.5),
|
||||||
},
|
icon: ctrl.carIcon,
|
||||||
myLocationButtonEnabled: false,
|
)
|
||||||
myLocationEnabled: false,
|
},
|
||||||
compassEnabled: false,
|
myLocationButtonEnabled: false,
|
||||||
zoomControlsEnabled: false,
|
myLocationEnabled: false,
|
||||||
onCameraMove: (CameraPosition position) {
|
compassEnabled: false,
|
||||||
ctrl.currentCameraPosition = position;
|
zoomControlsEnabled: false,
|
||||||
},
|
onCameraMove: (CameraPosition position) {
|
||||||
|
ctrl.currentCameraPosition = position;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (loc.isOutsideServiceArea)
|
||||||
|
Positioned.fill(
|
||||||
|
child: Container(
|
||||||
|
color: Colors.black.withOpacity(0.6),
|
||||||
|
child: Center(
|
||||||
|
child: Card(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
color: _Token.surfaceCard(context),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16)),
|
||||||
|
elevation: 10,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(24.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.location_off_rounded,
|
||||||
|
size: 64, color: Colors.redAccent),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
'service_unavailable_area'.tr,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Text(
|
||||||
|
'This service is not available in your current area.'
|
||||||
|
.tr,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.grey, fontSize: 14),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ class OsmMapView extends StatelessWidget {
|
|||||||
// You can assign this controller if needed
|
// You can assign this controller if needed
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (!inServiceArea)
|
if (inServiceArea)
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: 20,
|
bottom: 20,
|
||||||
left: 20,
|
left: 20,
|
||||||
|
|||||||
@@ -1109,14 +1109,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.1.0"
|
version: "8.1.0"
|
||||||
google_generative_ai:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: google_generative_ai
|
|
||||||
sha256: "71f613d0247968992ad87a0eb21650a566869757442ba55a31a81be6746e0d1f"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.4.7"
|
|
||||||
graphs:
|
graphs:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ name: siro_driver
|
|||||||
description: "A new Flutter project."
|
description: "A new Flutter project."
|
||||||
publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||||
|
|
||||||
version: 1.0.2+7
|
version: 1.0.3+8
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.0.5 <4.0.0"
|
sdk: ">=3.0.5 <4.0.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user