fix: stabilize passenger mapping interactions and finalize localization

This commit is contained in:
Hamza-Ayed
2026-04-18 19:16:01 +03:00
parent a54a7a4189
commit 61343111a2
28 changed files with 14917 additions and 14716 deletions

View File

@@ -1,6 +1,6 @@
// في ملف: constant/country_polygons.dart
import 'package:maplibre_gl/maplibre_gl.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
class CountryPolygons {
// ==========================================================

View File

@@ -17,6 +17,8 @@ class AppLink {
'https://map-saas.intaleqapp.com/api/geocoding/reverse';
static String searchGeocoding =
'https://map-saas.intaleqapp.com/api/geocoding/search';
static String mapSaasPlaces =
'https://map-saas.intaleqapp.com/api/geocoding/places';
///https://location.intaleq.xyz/intaleq/ride/location
///locationServerSide هو السيرفر الجانبي الخاص بموقع السائقين، حيث يتم إرسال تحديثات الموقع من التطبيق إلى هذا السيرفر، ومن ثم يقوم هذا السيرفر بتوزيع هذه التحديثات إلى الركاب المتصلين الذين يتابعون السائق في الوقت الحقيقي.

View File

@@ -1,4 +1,4 @@
import 'package:maplibre_gl/maplibre_gl.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
class UniversitiesPolygons {
// AUC polygon points

View File

@@ -1,6 +1,6 @@
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:Intaleq/constant/api_key.dart';
import 'package:Intaleq/controller/firebase/firbase_messge.dart';
import 'package:Intaleq/views/auth/otp_page.dart';
@@ -10,7 +10,7 @@ import 'package:http/http.dart' as http;
import 'package:Intaleq/constant/info.dart';
import 'package:Intaleq/controller/functions/add_error.dart';
import 'package:Intaleq/views/auth/login_page.dart';
import 'package:Intaleq/views/auth/sms_verfy_page.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:Intaleq/constant/box_name.dart';
@@ -18,17 +18,16 @@ import 'package:Intaleq/constant/links.dart';
import 'package:Intaleq/controller/functions/crud.dart';
import 'package:Intaleq/main.dart';
import 'package:Intaleq/views/home/map_page_passenger.dart';
import 'package:jwt_decoder/jwt_decoder.dart';
import 'package:location/location.dart';
import 'package:secure_string_operations/secure_string_operations.dart';
import '../../constant/char_map.dart';
import 'package:location/location.dart';
import '../../print.dart';
import '../../views/auth/otp_token_page.dart';
import '../functions/encrypt_decrypt.dart';
import '../functions/package_info.dart';
import '../functions/secure_storage.dart';
import '../functions/securty_check.dart';
class LoginController extends GetxController {
final formKey = GlobalKey<FormState>();
@@ -88,20 +87,7 @@ class LoginController extends GetxController {
update();
}
void _showJwtErrorDialog(String message) {
if (Get.context == null) return;
Get.defaultDialog(
title: "خطأ في الاتصال",
middleText: message,
textConfirm: "إعادة المحاولة",
confirmTextColor: Colors.white,
onConfirm: () {
Get.back();
getJwtWallet();
},
);
}
// ═══════════════════════════════════════════════════════════════
// LoginController — دوال إدارة الـ JWT
// ───────────────────────────────────────────────────────────────
@@ -141,8 +127,8 @@ class LoginController extends GetxController {
);
Log.print('AppLink.loginFirstTime: ${AppLink.loginFirstTime}');
Log.print('payload: ${payload}');
Log.print('response: ${response}');
Log.print('payload: $payload');
Log.print('response: $response');
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body);
@@ -169,7 +155,7 @@ class LoginController extends GetxController {
);
Log.print('AppLink.loginJwtRider: ${AppLink.loginJwtRider}');
Log.print('payload: ${payload}');
Log.print('payload: $payload');
Log.print('response: ${response.body}');
if (response.statusCode == 200) {
final decoded = jsonDecode(response.body);
@@ -361,7 +347,7 @@ class LoginController extends GetxController {
}
Get.offAll(() => const MapPagePassenger());
} catch (e, st) {
} catch (e) {
addError('$e', 'loginUsingCredentials');
Get.snackbar('Error', e.toString(), backgroundColor: Colors.redAccent);
} finally {
@@ -433,7 +419,7 @@ class LoginController extends GetxController {
}
}
goToMapPage() {
void goToMapPage() {
if (box.read(BoxName.email) != null) {
Get.offAll(() => const MapPagePassenger());
}

View File

@@ -675,4 +675,32 @@ class CRUD {
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;
}
}
}

View File

@@ -1,4 +1,4 @@
import 'package:maplibre_gl/maplibre_gl.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
List<LatLng> decodePolylineIsolate(String encoded) {
List<LatLng> points = [];

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:Intaleq/constant/style.dart';
import 'package:Intaleq/controller/home/map_passenger_controller.dart';

View File

@@ -6,14 +6,14 @@ import 'package:Intaleq/controller/functions/crud.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
class TripMonitorController extends GetxController {
bool isLoading = false;
Map tripData = {};
late String rideId;
late String driverId;
MapLibreMapController? mapController;
IntaleqMapController? mapController;
List myListString = [];
late Timer timer;
late LatLng parentLocation;
@@ -23,6 +23,8 @@ class TripMonitorController extends GetxController {
double rotation = 0;
double speed = 0;
bool isStyleLoaded = false;
Set<Marker> markers = {};
getLocationParent() async {
var res = await CRUD().get(
@@ -34,76 +36,56 @@ class TripMonitorController extends GetxController {
double.parse(tripData['message'][0]['longitude'].toString()));
rotation = double.parse(tripData['message'][0]['heading'].toString());
speed = double.parse(tripData['message'][0]['speed'].toString());
_updateMarker();
update();
}
}
void onMapCreated(MapLibreMapController controller) async {
void onMapCreated(IntaleqMapController controller) async {
mapController = controller;
update();
}
void onStyleLoaded() async {
isStyleLoaded = true;
await _loadMapIcons();
mapController?.animateCamera(
CameraUpdate.newLatLng(parentLocation),
);
refreshMapElements();
_updateMarker();
// Set up a timer or interval to trigger the marker update every 10 seconds.
timer = Timer.periodic(const Duration(seconds: 10), (_) async {
await getLocationParent();
mapController?.animateCamera(CameraUpdate.newLatLng(parentLocation));
refreshMapElements();
update();
});
}
Future<void> _loadMapIcons() async {
if (mapController == null) return;
final icons = {
'car': 'assets/images/car.png',
'moto': 'assets/images/moto1.png',
'lady': 'assets/images/lady1.png',
};
for (var entry in icons.entries) {
final bytes = await rootBundle.load(entry.value);
await mapController!.addImage(entry.key, bytes.buffer.asUint8List());
}
}
void refreshMapElements() async {
if (!isStyleLoaded || mapController == null) return;
await mapController!.clearSymbols();
String iconToUse = carIcon;
void _updateMarker() {
String iconPath = 'assets/images/car.png';
if (tripData['message'] != null && tripData['message'].isNotEmpty) {
final model = tripData['message'][0]['model'].toString();
final gender = tripData['message'][0]['gender'].toString();
if (model.contains('دراجة')) {
iconToUse = motoIcon;
iconPath = 'assets/images/moto1.png';
} else if (gender == 'Female') {
iconToUse = ladyIcon;
iconPath = 'assets/images/lady1.png';
}
}
await mapController!.addSymbol(SymbolOptions(
geometry: parentLocation,
iconImage: iconToUse,
iconRotate: rotation,
textField: 'driver',
textOpacity: 0,
));
markers = {
Marker(
markerId: const MarkerId('driver'),
position: parentLocation,
icon: InlqBitmap.fromAsset(iconPath),
rotation: rotation,
anchor: const Offset(0.5, 0.5),
),
};
update();
}
// init() async {
// final arguments = Get.arguments;
// driverId = arguments['driverId'];
// rideId = arguments['rideId'];
// await getLocationParent();
// }
Future<void> init({String? rideId, String? driverId}) async {
this.driverId = driverId!;
this.rideId = rideId!;
@@ -120,7 +102,6 @@ class TripMonitorController extends GetxController {
void onClose() {
timer.cancel();
mapController = null;
super.onClose();
}
}

View File

@@ -10,6 +10,8 @@ class MyTranslation extends Translations {
"Email Support": "الدعم عبر البريد الإلكتروني",
"For official inquiries": "للاستفسارات الرسمية",
"Intaleq Support": "دعم انطلق",
'Change Home location ?': 'تغيير موقع المنزل؟',
'Change Work location ?': 'تغيير موقع العمل؟',
"Reach out to us via": "تواصل معنا عبر",
"Support is Away": "الدعم غير متاح حالياً",
"Support is currently Online": "الدعم متاح حالياً",

26306
lib/env/env.g.dart vendored

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
import 'dart:io';
import 'package:get/get.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:get_storage/get_storage.dart';
import 'dart:math' as math;
import '../../main.dart';

View File

@@ -1,10 +1,11 @@
import 'dart:io';
import 'package:Intaleq/controller/home/trip_monitor_controller.dart';
import 'package:Intaleq/env/env.dart';
import 'package:Intaleq/views/widgets/my_scafold.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:vibration/vibration.dart';
import '../../../../constant/colors.dart';
@@ -23,15 +24,16 @@ class TripMonitor extends StatelessWidget {
return MyScafolld(
title: 'Trip Monitor'.tr,
body: [
MapLibreMap(
IntaleqMap(
apiKey: Env.mapSaasKey,
onMapCreated: tripMonitorController.onMapCreated,
onStyleLoadedCallback: tripMonitorController.onStyleLoaded,
onStyleLoaded: tripMonitorController.onStyleLoaded,
initialCameraPosition: CameraPosition(
target: tripMonitorController.parentLocation,
zoom: 16,
tilt: 40,
),
styleString: "assets/style.json",
markers: tripMonitorController.markers,
),
speedCircle()
],

View File

@@ -1,11 +1,12 @@
import 'dart:io';
import 'package:Intaleq/controller/home/trip_monitor_controller.dart';
import 'package:Intaleq/env/env.dart';
import 'package:Intaleq/views/widgets/my_scafold.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:vibration/vibration.dart';
import '../../../../constant/colors.dart';
@@ -30,15 +31,16 @@ class TripMonitor extends StatelessWidget {
return MyScafolld(
title: 'Trip Monitor'.tr,
body: [
MapLibreMap(
IntaleqMap(
apiKey: Env.mapSaasKey,
onMapCreated: tripMonitorController.onMapCreated,
onStyleLoadedCallback: tripMonitorController.onStyleLoaded,
onStyleLoaded: controller.onStyleLoaded,
initialCameraPosition: CameraPosition(
target: tripMonitorController.parentLocation,
zoom: 16,
tilt: 40,
),
styleString: "assets/style.json",
markers: tripMonitorController.markers,
),
speedCircle()
],

View File

@@ -1,9 +1,10 @@
import 'package:Intaleq/print.dart';
import 'package:Intaleq/views/widgets/mydialoug.dart';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:Intaleq/constant/box_name.dart';
import 'package:Intaleq/constant/table_names.dart';
import 'package:Intaleq/views/widgets/elevated_btn.dart';
@@ -131,8 +132,7 @@ class _SearchFieldState extends State<_SearchField> {
decoration: InputDecoration(
hintText: widget.controller.hintTextDestinationPoint,
hintStyle: AppStyle.subtitle.copyWith(color: Colors.grey[600]),
prefixIcon:
Icon(Icons.search, color: AppColor.primaryColor),
prefixIcon: Icon(Icons.search, color: AppColor.primaryColor),
// --- [إصلاح] تم استبدال Obx بشرط بسيط لأن `setState` يعيد بناء الواجهة الآن ---
suffixIcon: widget
.controller.placeDestinationController.text.isNotEmpty
@@ -365,6 +365,11 @@ class _SearchResults extends StatelessWidget {
controller.changeMainBottomMenuMap();
controller.passengerStartLocationFromMap = true;
controller.isPickerShown = true;
// ✅ FIX: Draw the route after setting destination (matching the "Another Order" flow)
controller.getDirectionMap(
'${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}',
'${controller.myDestination.latitude},${controller.myDestination.longitude}');
}
}
@@ -407,22 +412,18 @@ Widget _buildQuickActionButton({
void _showChangeLocationDialog(
MapPassengerController controller, String locationType) {
Get.defaultDialog(
title: 'Change $locationType location?'.tr,
middleText: '',
confirm: MyElevatedButton(
title: 'Yes'.tr,
onPressed: () {
Get.back();
if (locationType == 'Work') {
controller.workLocationFromMap = true;
} else {
controller.homeLocationFromMap = true;
}
controller.changeMainBottomMenuMap();
controller.changePickerShown();
},
),
MyDialog().getDialog(
locationType == 'Work' ? 'Change Work location ?'.tr : 'Change Home location ?'.tr,
'',
() {
if (locationType == 'Work') {
controller.workLocationFromMap = true;
} else {
controller.homeLocationFromMap = true;
}
controller.changeMainBottomMenuMap();
controller.changePickerShown();
},
);
}
@@ -455,4 +456,4 @@ void _handleQuickAction(
Log.print("Error handling quick action: $e");
Toast.show(Get.context!, "Failed to get location".tr, AppColor.redColor);
}
}
}

View File

@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import '../../../constant/colors.dart';
import '../../../constant/style.dart';

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:Intaleq/constant/table_names.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
@@ -16,8 +17,7 @@ GetBuilder<MapPassengerController> formSearchPlaces(int index) {
Padding(
padding: const EdgeInsets.all(16),
child: Container(
decoration:
BoxDecoration(color: AppColor.secondaryColor),
decoration: BoxDecoration(color: AppColor.secondaryColor),
child: TextField(
decoration: InputDecoration(
border: const OutlineInputBorder(
@@ -82,7 +82,15 @@ GetBuilder<MapPassengerController> formSearchPlaces(int index) {
var res = controller.placeListResponseAll[index][i];
return InkWell(
onTap: () async {
// ── Extract selected location ──
final double lat = res['geometry']['location']['lat'];
final double lng = res['geometry']['location']['lng'];
final String placeName = res['name'].toString();
final selectedLatLng = LatLng(lat, lng);
controller.changeHeightPlaces();
// ── Update start/end based on context ──
if (controller.currentLocationToFormPlacesAll[index] ==
true) {
controller.newStartPointLocation =
@@ -92,7 +100,20 @@ GetBuilder<MapPassengerController> formSearchPlaces(int index) {
controller.newStartPointLocation;
}
// ✅ FIX: Set the waypoint to the selected location
controller.menuWaypoints[index] = selectedLatLng;
controller.menuWaypointNames[index] = placeName;
// ✅ FIX: Update hint text and coordinates
controller.convertHintTextPlaces(index, res);
// ✅ FIX: Draw the route with the updated waypoint
final String start =
'${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}';
final String dest =
'${controller.myDestination.latitude},${controller.myDestination.longitude}';
await controller.getDirectionMap(start, dest);
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),

View File

@@ -1,13 +1,11 @@
import 'package:Intaleq/print.dart';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:Intaleq/env/env.dart';
import 'package:get/get.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:Intaleq/controller/home/points_for_rider_controller.dart';
import 'package:Intaleq/services/offline_map_service.dart';
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
import '../../../controller/home/map_passenger_controller.dart';
import '../../widgets/mycircular.dart';
import '../../widgets/mydialoug.dart';
@@ -27,82 +25,53 @@ class GoogleMapPassengerWidget extends StatelessWidget {
top: 0,
left: 0,
right: 0,
child: MapLibreMap(
attributionButtonPosition: AttributionButtonPosition.bottomLeft,
attributionButtonMargins: null,
child: IntaleqMap(
apiKey: Env.mapSaasKey,
styleUrl: Get.isDarkMode
? 'assets/style_dark.json'
: 'assets/style.json',
onMapCreated: controller.onMapCreated,
onStyleLoadedCallback: () => controller.onStyleLoaded(),
styleString: Get.isDarkMode ? "assets/style_dark.json" : "assets/style.json",
// ✅ Performance: Smoother zoom limits for low-end devices
minMaxZoomPreference: controller.lowPerf
? const MinMaxZoomPreference(6, 17)
: const MinMaxZoomPreference(6, 18),
initialCameraPosition: CameraPosition(
target: controller.passengerLocation,
zoom: controller.lowPerf ? 14.5 : 15,
),
// ✅ Map Settings
myLocationEnabled: true,
trackCameraPosition: true,
// ✅ Camera Movement Logic
onStyleLoaded: controller.onStyleLoaded,
onCameraMove: controller.onCameraMoveThrottled,
onCameraIdle: () {
if (controller.mapController != null) {
final position = controller.mapController!.cameraPosition;
if (position != null) {
Log.print('✅ onCameraIdle targeted: ${position.target}');
// 1. Always update current view target (for pickers)
controller
.updateCurrentLocationFromCamera(position.target);
// 2. Cache explicitly when panning around
// Optional: Limit this to only cache smaller regions (1km) so it doesn't overload on fast panning
OfflineMapService.instance.downloadRegion(position.target, radiusKm: 1.0);
OfflineMapService.instance
.downloadRegion(position.target, radiusKm: 1.0);
} else {
Log.print('⚠️ onCameraIdle: cameraPosition is NULL');
}
} else {
Log.print('⚠️ onCameraIdle: mapController is NULL');
}
},
onMapLongClick: (point, latlng) {
markers: controller.markers,
polylines: controller.polyLines,
polygons: controller.polygons,
circles: controller.circles,
initialCameraPosition: CameraPosition(
target: controller.passengerLocation,
zoom: controller.lowPerf ? 14.5 : 15,
),
myLocationEnabled: true,
onTap: (latlng) => controller.hidePlaces(),
onLongPress: (latlng) {
MyDialog().getDialog('Are you want to go to this site'.tr, '',
() async {
controller.clearPolyline();
if (controller.carsLocationByPassenger.isNotEmpty) {
await controller.getDirectionMap(
'${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}',
'${latlng.latitude},${latlng.longitude}',
);
Get.back(); // Close Dialog
await controller.bottomSheet();
controller.showBottomSheet1();
} else {
Get.back();
Get.snackbar(
'We Are Sorry That we dont have cars in your Location!'
.tr,
'',
colorText: AppColor.redColor,
duration: const Duration(seconds: 5),
backgroundColor: AppColor.secondaryColor,
icon: Icon(Icons.error, color: AppColor.redColor),
titleText: Text('Error'.tr,
style: TextStyle(color: AppColor.redColor)),
messageText: Text(
'We Are Sorry That we dont have cars in your Location!'
.tr,
style: AppStyle.title),
);
}
controller.getDirectionMap(
'${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}',
'${latlng.latitude},${latlng.longitude}',
);
controller.showBottomSheet1();
});
},
onMapClick: (point, latlng) {
controller.hidePlaces();
},
),
),
);
}
}
}

View File

@@ -6,7 +6,7 @@ import 'package:Intaleq/views/widgets/mycircular.dart';
import 'package:flutter/material.dart';
import 'package:flutter_font_icons/flutter_font_icons.dart';
import 'package:get/get.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'dart:ui'; // مهم لإضافة تأثير الضبابية
import '../../../constant/colors.dart';

View File

@@ -9,7 +9,7 @@ import 'package:Intaleq/controller/home/map_passenger_controller.dart';
import 'package:Intaleq/main.dart';
import 'package:Intaleq/views/home/map_widget.dart/form_search_places_destenation.dart';
import 'package:Intaleq/views/widgets/elevated_btn.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import '../../../constant/colors.dart';
import '../../../constant/table_names.dart';
import '../../widgets/error_snakbar.dart';
@@ -607,26 +607,32 @@ class _MapPickerOverlay extends StatelessWidget {
const _MapPickerOverlay({required this.controller});
String _getModeTitle(BuildContext context) {
if (controller.isPickingWaypoint)
if (controller.isPickingWaypoint) {
return 'Move map to set stop'.tr +
' ${controller.pickingWaypointIndex + 1}'.tr;
if (controller.passengerStartLocationFromMap)
}
if (controller.passengerStartLocationFromMap) {
return controller.isAnotherOreder
? 'Now set the pickup point for the other person'.tr
: 'Move map to your pickup point'.tr;
if (controller.startLocationFromMap)
}
if (controller.startLocationFromMap) {
return 'Move map to set start location'.tr;
if (controller.workLocationFromMap)
}
if (controller.workLocationFromMap) {
return 'Move map to your work location'.tr;
if (controller.homeLocationFromMap)
}
if (controller.homeLocationFromMap) {
return 'Move map to your home location'.tr;
}
return 'Move map to select destination'.tr;
}
String _getConfirmLabel(BuildContext context) {
if (controller.isPickingWaypoint) return 'Set as Stop'.tr;
if (controller.passengerStartLocationFromMap)
if (controller.passengerStartLocationFromMap) {
return 'Confirm Pickup Location'.tr;
}
if (controller.workLocationFromMap) return 'Set as Work'.tr;
if (controller.homeLocationFromMap) return 'Set as Home'.tr;
return 'Set Destination'.tr;
@@ -634,8 +640,9 @@ class _MapPickerOverlay extends StatelessWidget {
IconData _getModeIcon() {
if (controller.isPickingWaypoint) return Icons.add_location_alt_rounded;
if (controller.passengerStartLocationFromMap)
if (controller.passengerStartLocationFromMap) {
return Icons.person_pin_circle_rounded;
}
if (controller.workLocationFromMap) return Icons.work_rounded;
if (controller.homeLocationFromMap) return Icons.home_rounded;
return Icons.location_on_rounded;
@@ -760,6 +767,8 @@ class _MapPickerOverlay extends StatelessWidget {
Future<void> _onConfirmTap(
MapPassengerController controller, BuildContext context) async {
Log.print(
'🔘 _onConfirmTap: isPickingWaypoint=${controller.isPickingWaypoint}, newMyLocation=${controller.newMyLocation}');
await Future.delayed(const Duration(milliseconds: 280));
final LatLng currentCameraPosition = LatLng(
controller.newMyLocation.latitude, controller.newMyLocation.longitude);
@@ -905,7 +914,6 @@ class _RecentPlaceChip extends StatelessWidget {
'Are you want to go this site'.tr,
' ',
() async {
Get.back();
await controller.getLocation();
await controller.getDirectionMap(
'${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}',

View File

@@ -7,7 +7,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:http/http.dart' as http;
import '../../../constant/box_name.dart';
import '../../../constant/links.dart';
@@ -47,7 +47,7 @@ class NavigationController extends GetxController
static const int _offRouteTriggerSeconds = 6;
bool isLoading = false;
MaplibreMapController? mapController;
IntaleqMapController? mapController;
bool isStyleLoaded = false;
final TextEditingController placeDestinationController =
TextEditingController();
@@ -65,11 +65,10 @@ class NavigationController extends GetxController
double currentSpeed = 0.0;
double totalDistance = 0.0;
Symbol? carSymbol;
Symbol? originSymbol;
Symbol? destinationSymbol;
Line? remainingRouteLine;
Line? traveledRouteLine;
Set<Marker> markers = {};
Set<Polyline> polylines = {};
Set<Circle> circles = {};
Set<Polygon> polygons = {};
Timer? _locationUpdateTimer;
LatLng? _lastProcessedLocation;
@@ -102,6 +101,61 @@ class NavigationController extends GetxController
bool _cameraLockedToUser = true;
bool _mapReady = false;
bool isSelectingPlaceLocation = false;
void togglePlaceSelectionMode() {
isSelectingPlaceLocation = !isSelectingPlaceLocation;
update();
}
Future<void> submitNewPlace(String name, String category) async {
if (mapController == null || name.isEmpty || category.isEmpty) return;
// Get current center of the map as the picked location
final LatLng pickedPos = mapController!.cameraPosition!.target;
isLoading = true;
update();
final String country =
box.read(BoxName.countryCode) == 'SY' ? 'syria' : 'jordan';
final Map<String, dynamic> payload = {
'name': name,
'category': category,
'lat': pickedPos.latitude,
'lng': pickedPos.longitude,
'country': country,
};
try {
final response = await CRUD().postMapSaas(
link: AppLink.mapSaasPlaces,
payload: payload,
);
isLoading = false;
if (response != null) {
HapticFeedback.lightImpact();
mySnackbarSuccess(box.read(BoxName.lang) == 'ar'
? 'تمت إضافة المكان بنجاح! شكراً لمساهمتك.'
: 'Place added successfully! Thanks for your contribution.');
isSelectingPlaceLocation = false;
} else {
mySnackbarWarning(box.read(BoxName.lang) == 'ar'
? 'تعذر إضافة المكان. يرجى المحاولة لاحقاً.'
: 'Failed to add place. Please try again later.');
}
update();
} catch (e) {
isLoading = false;
mySnackbarWarning(box.read(BoxName.lang) == 'ar'
? 'حدث خطأ أثناء الاتصال بالخادم.'
: 'An error occurred while connecting to the server.');
update();
}
}
DateTime? _offRouteStartTime;
bool _autoRecalcInProgress = false;
@@ -114,7 +168,7 @@ class NavigationController extends GetxController
List<RouteData> routes = [];
int selectedRouteIndex = 0;
List<Line> alternativeRouteLines = [];
List<Map<String, dynamic>> recentLocations = [];
double get _targetZoom {
@@ -131,6 +185,85 @@ class NavigationController extends GetxController
return 55.0;
}
// Categories list for the picker
static final List<Map<String, String>> placeCategories = [
{
'id': 'restaurant',
'en': 'Restaurant',
'ar': 'مطعم',
'icon': 'restaurant'
},
{'id': 'cafe', 'en': 'Cafe', 'ar': 'مقهى', 'icon': 'coffee'},
{
'id': 'supermarket',
'en': 'Supermarket',
'ar': 'سوبر ماركت',
'icon': 'shopping_basket'
},
{
'id': 'pharmacy',
'en': 'Pharmacy',
'ar': 'صيدلية',
'icon': 'local_pharmacy'
},
{
'id': 'gas_station',
'en': 'Gas Station',
'ar': 'محطة وقود',
'icon': 'local_gas_station'
},
{'id': 'atm', 'en': 'ATM', 'ar': 'صراف آلي', 'icon': 'atm'},
{'id': 'bank', 'en': 'Bank', 'ar': 'بنك', 'icon': 'account_balance'},
{'id': 'mosque', 'en': 'Mosque', 'ar': 'مسجد', 'icon': 'mosque'},
{
'id': 'hospital',
'en': 'Hospital',
'ar': 'مستشفى',
'icon': 'local_hospital'
},
{'id': 'school', 'en': 'School', 'ar': 'مدرسة', 'icon': 'school'},
{
'id': 'university',
'en': 'University',
'ar': 'جامعة',
'icon': 'account_balance'
},
{'id': 'park', 'en': 'Park', 'ar': 'منتزه', 'icon': 'park'},
{'id': 'hotel', 'en': 'Hotel', 'ar': 'فندق', 'icon': 'hotel'},
{
'id': 'mall',
'en': 'Shopping Mall',
'ar': 'مركز تسوق',
'icon': 'shopping_mall'
},
{'id': 'gym', 'en': 'Gym', 'ar': 'نادي رياضي', 'icon': 'fitness_center'},
{
'id': 'salon',
'en': 'Beauty Salon',
'ar': 'صالون تجميل',
'icon': 'content_cut'
},
{'id': 'bakery', 'en': 'Bakery', 'ar': 'مخبز', 'icon': 'bakery_dining'},
{
'id': 'laundry',
'ar': 'مصبغة',
'en': 'Laundry',
'icon': 'local_laundry_service'
},
{
'id': 'car_repair',
'en': 'Car Repair',
'ar': 'تصليح سيارات',
'icon': 'build'
},
{
'id': 'government',
'en': 'Government Office',
'ar': 'دائرة حكومية',
'icon': 'gavel'
},
];
static final String _routeApiBaseUrl =
"${AppLink.routesOsm}/route/v1/driving";
@@ -179,11 +312,11 @@ class NavigationController extends GetxController
if (isStyleLoaded) {
_updateCarMarker();
if (_fullRouteCoordinates.isNotEmpty && _cameraLockedToUser) {
if (_cameraLockedToUser) {
animateCameraToPosition(myLocation!,
bearing: _smoothedHeading,
zoom: _targetZoom,
tilt: _targetTilt);
zoom: isNavigating ? _targetZoom : 17.0,
tilt: isNavigating ? _targetTilt : 0.0);
}
}
}
@@ -208,7 +341,7 @@ class NavigationController extends GetxController
} else {
parsed = [];
}
recentLocations = parsed
.map((e) => Map<String, dynamic>.from(e))
.toList()
@@ -238,23 +371,29 @@ class NavigationController extends GetxController
super.onClose();
}
void onMapCreated(MaplibreMapController controller) {
void onMapCreated(IntaleqMapController controller) async {
Log.print("DEBUG: NavigationController.onMapCreated called");
mapController = controller;
await onStyleLoaded();
}
Future<void> onStyleLoaded() async {
Log.print("DEBUG: NavigationController.onStyleLoaded called");
isStyleLoaded = true;
await _loadCustomIcons();
WidgetsBinding.instance.addPostFrameCallback((_) async {
await Future.delayed(const Duration(milliseconds: 300));
if (!_mapReady) {
Log.print("DEBUG: NavigationController setting _mapReady = true");
_mapReady = true;
if (myLocation != null) {
Log.print("DEBUG: Animating camera to initial location: $myLocation");
animateCameraToPosition(myLocation!);
_updateCarMarker();
}
if (_fullRouteCoordinates.isNotEmpty) {
Log.print("DEBUG: Updating initial polylines");
_updatePolylinesSets([], _fullRouteCoordinates);
}
}
@@ -319,9 +458,11 @@ class NavigationController extends GetxController
Future<void> _getCurrentLocationAndStartUpdates() async {
try {
Log.print("DEBUG: Getting initial location...");
final position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high);
myLocation = LatLng(position.latitude, position.longitude);
Log.print("DEBUG: Initial location acquired: $myLocation");
_targetHeading = position.heading;
_oldHeading = position.heading;
_smoothedHeading = position.heading;
@@ -330,7 +471,7 @@ class NavigationController extends GetxController
_startLocationTimer();
_startBatchTimers();
} catch (e) {
Log.print("Error getting initial location: $e");
Log.print("DEBUG: Error getting initial location: $e");
}
}
@@ -340,7 +481,10 @@ class NavigationController extends GetxController
Timer.periodic(const Duration(seconds: 4), (_) => _tick());
}
bool _isTicking = false;
Future<void> _tick() async {
if (_isTicking) return;
_isTicking = true;
try {
final position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high);
@@ -356,6 +500,8 @@ class NavigationController extends GetxController
);
if (d < _minMoveToProcess) return;
}
Log.print(
"DEBUG: Location tick - Speed: ${currentSpeed.toStringAsFixed(1)} km/h, Loc: $newLoc");
if (_lastDistanceLocation != null) {
final d = Geolocator.distanceBetween(
@@ -396,7 +542,9 @@ class NavigationController extends GetxController
}
update();
} catch (e) {
Log.print("Error occurred: $e");
Log.print("DEBUG: Error in _tick: $e");
} finally {
_isTicking = false;
}
}
@@ -501,32 +649,7 @@ class NavigationController extends GetxController
}
Future<void> _updateCarMarker() async {
if (myLocation == null || mapController == null || !isStyleLoaded) return;
// Check if symbol still exists in map controller's internal list
bool exists =
carSymbol != null && mapController!.symbols.contains(carSymbol);
if (!exists) {
if (carSymbol != null) {
try {
await mapController!.removeSymbol(carSymbol!);
} catch (_) {}
}
carSymbol = await mapController!.addSymbol(SymbolOptions(
geometry: myLocation,
iconImage: 'car_icon',
iconSize: 1.6,
iconRotate: _smoothedHeading,
));
} else {
mapController!.updateSymbol(
carSymbol!,
SymbolOptions(
geometry: myLocation,
iconRotate: _smoothedHeading,
));
}
// Car marker is now handled natively by myLocationEnabled: true.
}
void animateCameraToPosition(LatLng position,
@@ -618,49 +741,41 @@ class NavigationController extends GetxController
Future<void> _updatePolylinesSets(
List<LatLng> traveled, List<LatLng> remaining) async {
if (mapController == null || !isStyleLoaded) return;
// Clear old alternative lines
for (var line in alternativeRouteLines) {
await mapController!.removeLine(line);
}
alternativeRouteLines.clear();
Log.print(
"DEBUG: Updating polylines. Traveled: ${traveled.length}, Remaining: ${remaining.length}");
Set<Polyline> newPolylines = {};
if (remainingRouteLine != null) {
await mapController!.removeLine(remainingRouteLine!);
}
if (traveledRouteLine != null) {
await mapController!.removeLine(traveledRouteLine!);
}
// Render Alternative Routes first (so they are below)
// Render Alternative Routes first
for (int i = 0; i < routes.length; i++) {
if (i == selectedRouteIndex) continue;
final altLine = await mapController!.addLine(LineOptions(
geometry: routes[i].coordinates,
lineColor: '#B0BEC5', // Soft gray for alternatives
lineWidth: 6.0,
lineJoin: 'round',
lineOpacity: 0.8,
newPolylines.add(Polyline(
polylineId: PolylineId('alt_$i'),
points: routes[i].coordinates,
color: const Color(0xFFB0BEC5).withOpacity(0.8),
width: 6,
));
alternativeRouteLines.add(altLine);
}
if (remaining.isNotEmpty) {
remainingRouteLine = await mapController!.addLine(LineOptions(
geometry: remaining,
lineColor: '#00e5ff', // Cyan/Blue for selected
lineWidth: 8.0,
lineJoin: 'round'));
newPolylines.add(Polyline(
polylineId: const PolylineId('remaining'),
points: remaining,
color: const Color(0xFF00E5FF),
width: 8,
));
}
if (traveled.isNotEmpty) {
traveledRouteLine = await mapController!.addLine(LineOptions(
geometry: traveled,
lineColor: '#BDBDBD',
lineWidth: 5.0,
lineJoin: 'round',
lineOpacity: 0.6));
newPolylines.add(Polyline(
polylineId: const PolylineId('traveled'),
points: traveled,
color: const Color(0xFFBDBDBD).withOpacity(0.6),
width: 5,
));
}
polylines = newPolylines;
update();
}
void selectRoute(int index) {
@@ -671,7 +786,7 @@ class NavigationController extends GetxController
routeSteps = r.steps;
_routeTotalDistanceM = r.distanceM;
_routeTotalDurationS = r.durationS;
_lastTraveledIndexInFullRoute = 0;
_recomputeETA();
_updatePolylinesSets([], _fullRouteCoordinates);
@@ -763,7 +878,8 @@ class NavigationController extends GetxController
routes.clear();
final primaryPts = data['points']?.toString() ?? "";
if (primaryPts.isNotEmpty) {
final coords = await compute<String, List<LatLng>>(decodePolylineIsolate, primaryPts);
final coords = await compute<String, List<LatLng>>(
decodePolylineIsolate, primaryPts);
routes.add(RouteData(
coordinates: coords,
steps: List<Map<String, dynamic>>.from(data['instructions'] ?? []),
@@ -778,7 +894,8 @@ class NavigationController extends GetxController
for (var alt in data['alternatives']) {
final altPts = alt['points']?.toString() ?? "";
if (altPts.isEmpty) continue;
final altCoords = await compute<String, List<LatLng>>(decodePolylineIsolate, altPts);
final altCoords = await compute<String, List<LatLng>>(
decodePolylineIsolate, altPts);
routes.add(RouteData(
coordinates: altCoords,
steps: List<Map<String, dynamic>>.from(alt['instructions'] ?? []),
@@ -899,19 +1016,27 @@ class NavigationController extends GetxController
try {
_finalDestination = destination;
await clearRoute(isNewRoute: true);
if (isStyleLoaded && mapController != null) {
destinationSymbol = await mapController!.addSymbol(SymbolOptions(
geometry: destination,
iconImage: 'dest_icon',
iconSize: 1.0,
textField: infoWindowTitle,
textOffset: const Offset(0, 2)));
if (myLocation != null) {
originSymbol = await mapController!.addSymbol(SymbolOptions(
geometry: myLocation, iconImage: 'start_icon', iconSize: 1.0));
}
// Preserve car marker if it exists
markers = markers.where((m) => m.markerId.value == 'car').toSet();
markers.add(Marker(
markerId: const MarkerId('destination'),
position: destination,
icon: InlqBitmap.fromStyleImage('dest_icon'),
infoWindow: infoWindowTitle.isNotEmpty
? InfoWindow(title: infoWindowTitle)
: InfoWindow.noText,
));
if (myLocation != null) {
markers.add(Marker(
markerId: const MarkerId('origin'),
position: myLocation!,
icon: InlqBitmap.fromStyleImage('start_icon'),
));
await getRoute(myLocation!, destination);
}
if (myLocation != null) await getRoute(myLocation!, destination);
} finally {
isLoading = false;
update();
@@ -928,32 +1053,27 @@ class NavigationController extends GetxController
update();
}
Future<void> clearEverything() async {
placeDestinationController.clear();
placesDestination = [];
await clearRoute();
}
Future<void> clearRoute({bool isNewRoute = false}) async {
_offRouteStartTime = null;
_autoRecalcInProgress = false;
if (!isNewRoute) {
if (destinationSymbol != null && mapController != null) {
await mapController!.removeSymbol(destinationSymbol!);
destinationSymbol = null;
}
if (originSymbol != null && mapController != null) {
await mapController!.removeSymbol(originSymbol!);
originSymbol = null;
}
if (remainingRouteLine != null && mapController != null) {
await mapController!.removeLine(remainingRouteLine!);
remainingRouteLine = null;
}
if (traveledRouteLine != null && mapController != null) {
await mapController!.removeLine(traveledRouteLine!);
traveledRouteLine = null;
}
markers = {};
polylines = {};
circles = {};
polygons = {};
_finalDestination = null;
isNavigating = false;
routes = [];
await _flushBufferToServer();
}
routeSteps.clear();
_fullRouteCoordinates.clear();
routeSteps = [];
_fullRouteCoordinates = [];
_lastTraveledIndexInFullRoute = 0;
currentInstruction = "";
nextInstruction = "";
@@ -964,6 +1084,10 @@ class NavigationController extends GetxController
arrivalTime = "--:--";
_routeTotalDistanceM = 0;
_routeTotalDurationS = 0;
if (!isNewRoute) {
await _updateCarMarker();
}
update();
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,11 @@
import 'dart:math' as math;
import 'dart:typed_data';
import 'package:Intaleq/env/env.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import '../../../constant/colors.dart';
import '../../../constant/style.dart';
@@ -413,7 +414,9 @@ class _RideDetailSheet extends StatefulWidget {
}
class _RideDetailSheetState extends State<_RideDetailSheet> {
MapLibreMapController? _mc;
IntaleqMapController? _mc;
Set<Marker> _markers = {};
Set<Polyline> _polylines = {};
LatLngBounds? get _bounds {
final latDiff = (widget.start.latitude - widget.end.latitude).abs();
@@ -431,7 +434,7 @@ class _RideDetailSheetState extends State<_RideDetailSheet> {
);
}
void _onMapCreated(MapLibreMapController c) => _mc = c;
void _onMapCreated(IntaleqMapController c) => _mc = c;
Future<void> _onStyleLoaded() async {
WidgetsBinding.instance.addPostFrameCallback((_) async {
@@ -442,48 +445,45 @@ class _RideDetailSheetState extends State<_RideDetailSheet> {
}
Future<void> _draw() async {
final mc = _mc;
if (mc == null || !mounted) return;
if (!mounted) return;
try {
final aData = await rootBundle.load('assets/images/A.png');
await mc.addImage('det_start', aData.buffer.asUint8List());
final bData = await rootBundle.load('assets/images/b.png');
await mc.addImage('det_end', bData.buffer.asUint8List());
} catch (_) {}
setState(() {
_polylines = {
Polyline(
polylineId: const PolylineId('route'),
points: [widget.start, widget.end],
color: AppColor.primaryColor,
width: 4,
),
};
await mc.addLine(LineOptions(
geometry: [widget.start, widget.end],
lineColor:
'#${AppColor.primaryColor.value.toRadixString(16).substring(2)}',
lineWidth: 4.0,
lineOpacity: 1.0,
));
await mc.addSymbol(SymbolOptions(
geometry: widget.start,
iconImage: 'det_start',
iconSize: 0.8,
iconAnchor: 'bottom',
));
await mc.addSymbol(SymbolOptions(
geometry: widget.end,
iconImage: 'det_end',
iconSize: 0.8,
iconAnchor: 'bottom',
));
_markers = {
Marker(
markerId: const MarkerId('start'),
position: widget.start,
icon: InlqBitmap.fromAsset('assets/images/A.png'),
anchor: const Offset(0.5, 1.0),
),
Marker(
markerId: const MarkerId('end'),
position: widget.end,
icon: InlqBitmap.fromAsset('assets/images/b.png'),
anchor: const Offset(0.5, 1.0),
),
};
});
final b = _bounds;
if (b != null) {
await mc.animateCamera(CameraUpdate.newLatLngBounds(b,
await _mc?.animateCamera(CameraUpdate.newLatLngBounds(b,
left: 60, top: 60, right: 60, bottom: 60));
} else {
await mc.animateCamera(CameraUpdate.newLatLngZoom(widget.start, 14));
await _mc?.animateCamera(CameraUpdate.newLatLngZoom(widget.start, 14));
}
}
@override
void dispose() {
_mc?.dispose();
super.dispose();
}
@@ -524,14 +524,20 @@ class _RideDetailSheetState extends State<_RideDetailSheet> {
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
child: MapLibreMap(
styleString: 'assets/style.json',
child: IntaleqMap(
apiKey: Env.mapSaasKey,
styleUrl: Get.isDarkMode
? 'assets/style_dark.json'
: 'assets/style.json',
initialCameraPosition:
CameraPosition(target: center, zoom: 12),
onMapCreated: _onMapCreated,
onStyleLoadedCallback: _onStyleLoaded,
onMapCreated: (c) {
_mc = c;
_onStyleLoaded();
},
myLocationEnabled: false,
trackCameraPosition: false,
markers: _markers,
polylines: _polylines,
),
),
),

View File

@@ -319,90 +319,100 @@ class MyDialog extends GetxController {
HapticFeedback.mediumImpact();
Get.dialog(
_DialogShell(
child: _GlassCard(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// ── Body ──────────────────────────────────────────────
Padding(
padding: const EdgeInsets.fromLTRB(24, 28, 24, 20),
child: Column(
children: [
// Icon badge
Container(
width: 56,
height: 56,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: (isDestructive
? AppColor.redColor
: AppColor.primaryColor)
.withOpacity(0.1),
border: Border.all(
Builder(builder: (dialogContext) {
return _DialogShell(
child: _GlassCard(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// ── Body ──────────────────────────────────────────────
Padding(
padding: const EdgeInsets.fromLTRB(24, 28, 24, 20),
child: Column(
children: [
// Icon badge
Container(
width: 56,
height: 56,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: (isDestructive
? AppColor.redColor
: AppColor.primaryColor)
.withOpacity(0.2),
.withOpacity(0.1),
border: Border.all(
color: (isDestructive
? AppColor.redColor
: AppColor.primaryColor)
.withOpacity(0.2),
),
),
),
child: Icon(
icon ??
(isDestructive
? Icons.warning_amber_rounded
: Icons.info_outline_rounded),
color: isDestructive
? AppColor.redColor
: AppColor.primaryColor,
size: 26,
),
),
const SizedBox(height: 16),
// Title
Text(
title,
textAlign: TextAlign.center,
style: AppStyle.title.copyWith(
fontSize: 18,
fontWeight: FontWeight.w700,
letterSpacing: -0.4,
color: AppColor.writeColor,
),
),
if (midTitle != null && midTitle.isNotEmpty) ...[
const SizedBox(height: 10),
Text(
midTitle,
textAlign: TextAlign.center,
style: AppStyle.subtitle.copyWith(
fontSize: 14.5,
height: 1.5,
color: Colors.grey[600],
child: Icon(
icon ??
(isDestructive
? Icons.warning_amber_rounded
: Icons.info_outline_rounded),
color: isDestructive
? AppColor.redColor
: AppColor.primaryColor,
size: 26,
),
),
const SizedBox(height: 16),
// TTS button
_SpeakButton(
texts: [title, if (midTitle.isNotEmpty) midTitle]),
],
],
),
),
// Title
Text(
title,
textAlign: TextAlign.center,
style: AppStyle.title.copyWith(
fontSize: 18,
fontWeight: FontWeight.w700,
letterSpacing: -0.4,
color: AppColor.writeColor,
),
),
// ── Actions ───────────────────────────────────────────
_ActionRow(
onCancel: () => Get.back(),
onConfirm: onPressed,
confirmLabel: 'OK'.tr,
isDestructive: isDestructive,
),
],
if (midTitle != null && midTitle.isNotEmpty) ...[
const SizedBox(height: 10),
Text(
midTitle,
textAlign: TextAlign.center,
style: AppStyle.subtitle.copyWith(
fontSize: 14.5,
height: 1.5,
color: Colors.grey[600],
),
),
const SizedBox(height: 16),
// TTS button
_SpeakButton(
texts: [title, if (midTitle.isNotEmpty) midTitle]),
],
],
),
),
// ── Actions ───────────────────────────────────────────
_ActionRow(
onCancel: () =>
Navigator.of(dialogContext, rootNavigator: true).pop(),
onConfirm: () {
// إغلاق الديالوج مباشرة باستخدام Navigator.pop
Navigator.of(dialogContext, rootNavigator: true).pop();
// تنفيذ الأمر بعد إغلاق الديالوج
Future.delayed(const Duration(milliseconds: 100), () {
onPressed();
});
},
confirmLabel: 'OK'.tr,
isDestructive: isDestructive,
),
],
),
),
),
),
);
}),
barrierDismissible: true,
barrierColor: _DC.barrierColor,
);
@@ -493,7 +503,12 @@ class MyDialogContent extends GetxController {
// ── Actions ───────────────────────────────────────────
_ActionRow(
onCancel: () => Get.back(),
onConfirm: onPressed,
onConfirm: () {
Get.back(); // Dismiss dialog first
WidgetsBinding.instance.addPostFrameCallback((_) {
onPressed();
});
},
confirmLabel: confirmLabel.tr,
isDestructive: isDestructive,
),