fix: stabilize passenger mapping interactions and finalize localization
This commit is contained in:
@@ -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()
|
||||
],
|
||||
|
||||
@@ -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()
|
||||
],
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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}',
|
||||
|
||||
@@ -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
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user