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

2
.env
View File

@@ -113,4 +113,4 @@ W=T
X=D X=D
Y=S Y=S
Z=M Z=M
mapSaasKey=zP9vL5mK2nQ8xR7jT4wS1yB6hG3fV0cX mapSaasKey=in_9478b32836d19cff73db3063

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:math';
import 'package:Intaleq/constant/api_key.dart'; import 'package:Intaleq/constant/api_key.dart';
import 'package:Intaleq/controller/firebase/firbase_messge.dart'; import 'package:Intaleq/controller/firebase/firbase_messge.dart';
import 'package:Intaleq/views/auth/otp_page.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/constant/info.dart';
import 'package:Intaleq/controller/functions/add_error.dart'; import 'package:Intaleq/controller/functions/add_error.dart';
import 'package:Intaleq/views/auth/login_page.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:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:Intaleq/constant/box_name.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/controller/functions/crud.dart';
import 'package:Intaleq/main.dart'; import 'package:Intaleq/main.dart';
import 'package:Intaleq/views/home/map_page_passenger.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 '../../print.dart';
import '../../views/auth/otp_token_page.dart'; import '../../views/auth/otp_token_page.dart';
import '../functions/encrypt_decrypt.dart'; import '../functions/encrypt_decrypt.dart';
import '../functions/package_info.dart'; import '../functions/package_info.dart';
import '../functions/secure_storage.dart';
import '../functions/securty_check.dart';
class LoginController extends GetxController { class LoginController extends GetxController {
final formKey = GlobalKey<FormState>(); final formKey = GlobalKey<FormState>();
@@ -88,20 +87,7 @@ class LoginController extends GetxController {
update(); 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 // LoginController — دوال إدارة الـ JWT
// ─────────────────────────────────────────────────────────────── // ───────────────────────────────────────────────────────────────
@@ -141,8 +127,8 @@ class LoginController extends GetxController {
); );
Log.print('AppLink.loginFirstTime: ${AppLink.loginFirstTime}'); Log.print('AppLink.loginFirstTime: ${AppLink.loginFirstTime}');
Log.print('payload: ${payload}'); Log.print('payload: $payload');
Log.print('response: ${response}'); Log.print('response: $response');
if (response.statusCode == 200) { if (response.statusCode == 200) {
final decoded = jsonDecode(response.body); final decoded = jsonDecode(response.body);
@@ -169,7 +155,7 @@ class LoginController extends GetxController {
); );
Log.print('AppLink.loginJwtRider: ${AppLink.loginJwtRider}'); Log.print('AppLink.loginJwtRider: ${AppLink.loginJwtRider}');
Log.print('payload: ${payload}'); Log.print('payload: $payload');
Log.print('response: ${response.body}'); Log.print('response: ${response.body}');
if (response.statusCode == 200) { if (response.statusCode == 200) {
final decoded = jsonDecode(response.body); final decoded = jsonDecode(response.body);
@@ -361,7 +347,7 @@ class LoginController extends GetxController {
} }
Get.offAll(() => const MapPagePassenger()); Get.offAll(() => const MapPagePassenger());
} catch (e, st) { } catch (e) {
addError('$e', 'loginUsingCredentials'); addError('$e', 'loginUsingCredentials');
Get.snackbar('Error', e.toString(), backgroundColor: Colors.redAccent); Get.snackbar('Error', e.toString(), backgroundColor: Colors.redAccent);
} finally { } finally {
@@ -433,7 +419,7 @@ class LoginController extends GetxController {
} }
} }
goToMapPage() { void goToMapPage() {
if (box.read(BoxName.email) != null) { if (box.read(BoxName.email) != null) {
Get.offAll(() => const MapPagePassenger()); Get.offAll(() => const MapPagePassenger());
} }

View File

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

View File

@@ -10,6 +10,8 @@ class MyTranslation extends Translations {
"Email Support": "الدعم عبر البريد الإلكتروني", "Email Support": "الدعم عبر البريد الإلكتروني",
"For official inquiries": "للاستفسارات الرسمية", "For official inquiries": "للاستفسارات الرسمية",
"Intaleq Support": "دعم انطلق", "Intaleq Support": "دعم انطلق",
'Change Home location ?': 'تغيير موقع المنزل؟',
'Change Work location ?': 'تغيير موقع العمل؟',
"Reach out to us via": "تواصل معنا عبر", "Reach out to us via": "تواصل معنا عبر",
"Support is Away": "الدعم غير متاح حالياً", "Support is Away": "الدعم غير متاح حالياً",
"Support is currently Online": "الدعم متاح حالياً", "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 'dart:io';
import 'package:get/get.dart'; 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 'package:get_storage/get_storage.dart';
import 'dart:math' as math; import 'dart:math' as math;
import '../../main.dart'; import '../../main.dart';

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.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/colors.dart';
import '../../../constant/style.dart'; import '../../../constant/style.dart';

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:Intaleq/constant/table_names.dart'; import 'package:Intaleq/constant/table_names.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import '../../../constant/colors.dart'; import '../../../constant/colors.dart';
import '../../../constant/style.dart'; import '../../../constant/style.dart';
@@ -16,8 +17,7 @@ GetBuilder<MapPassengerController> formSearchPlaces(int index) {
Padding( Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Container( child: Container(
decoration: decoration: BoxDecoration(color: AppColor.secondaryColor),
BoxDecoration(color: AppColor.secondaryColor),
child: TextField( child: TextField(
decoration: InputDecoration( decoration: InputDecoration(
border: const OutlineInputBorder( border: const OutlineInputBorder(
@@ -82,7 +82,15 @@ GetBuilder<MapPassengerController> formSearchPlaces(int index) {
var res = controller.placeListResponseAll[index][i]; var res = controller.placeListResponseAll[index][i];
return InkWell( return InkWell(
onTap: () async { 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(); controller.changeHeightPlaces();
// ── Update start/end based on context ──
if (controller.currentLocationToFormPlacesAll[index] == if (controller.currentLocationToFormPlacesAll[index] ==
true) { true) {
controller.newStartPointLocation = controller.newStartPointLocation =
@@ -92,7 +100,20 @@ GetBuilder<MapPassengerController> formSearchPlaces(int index) {
controller.newStartPointLocation; 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); 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( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10), padding: const EdgeInsets.symmetric(horizontal: 10),

View File

@@ -1,13 +1,11 @@
import 'package:Intaleq/print.dart'; import 'package:Intaleq/print.dart';
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:Intaleq/env/env.dart';
import 'package:get/get.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/controller/home/points_for_rider_controller.dart';
import 'package:Intaleq/services/offline_map_service.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 '../../../controller/home/map_passenger_controller.dart';
import '../../widgets/mycircular.dart'; import '../../widgets/mycircular.dart';
import '../../widgets/mydialoug.dart'; import '../../widgets/mydialoug.dart';
@@ -27,82 +25,53 @@ class GoogleMapPassengerWidget extends StatelessWidget {
top: 0, top: 0,
left: 0, left: 0,
right: 0, right: 0,
child: MapLibreMap( child: IntaleqMap(
attributionButtonPosition: AttributionButtonPosition.bottomLeft, apiKey: Env.mapSaasKey,
attributionButtonMargins: null, styleUrl: Get.isDarkMode
? 'assets/style_dark.json'
: 'assets/style.json',
onMapCreated: controller.onMapCreated, onMapCreated: controller.onMapCreated,
onStyleLoadedCallback: () => controller.onStyleLoaded(), onStyleLoaded: controller.onStyleLoaded,
styleString: Get.isDarkMode ? "assets/style_dark.json" : "assets/style.json", onCameraMove: controller.onCameraMoveThrottled,
// ✅ 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
onCameraIdle: () { onCameraIdle: () {
if (controller.mapController != null) { if (controller.mapController != null) {
final position = controller.mapController!.cameraPosition; final position = controller.mapController!.cameraPosition;
if (position != null) { if (position != null) {
Log.print('✅ onCameraIdle targeted: ${position.target}'); Log.print('✅ onCameraIdle targeted: ${position.target}');
// 1. Always update current view target (for pickers)
controller controller
.updateCurrentLocationFromCamera(position.target); .updateCurrentLocationFromCamera(position.target);
OfflineMapService.instance
// 2. Cache explicitly when panning around .downloadRegion(position.target, radiusKm: 1.0);
// Optional: Limit this to only cache smaller regions (1km) so it doesn't overload on fast panning } else {
OfflineMapService.instance.downloadRegion(position.target, radiusKm: 1.0); Log.print('⚠️ onCameraIdle: cameraPosition is NULL');
} }
} else {
Log.print('⚠️ onCameraIdle: mapController is NULL');
} }
}, },
markers: controller.markers,
onMapLongClick: (point, latlng) { 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, '', MyDialog().getDialog('Are you want to go to this site'.tr, '',
() async { () async {
controller.clearPolyline(); controller.clearPolyline();
if (controller.carsLocationByPassenger.isNotEmpty) { controller.getDirectionMap(
await controller.getDirectionMap( '${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}',
'${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}', '${latlng.latitude},${latlng.longitude}',
'${latlng.latitude},${latlng.longitude}', );
); controller.showBottomSheet1();
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),
);
}
}); });
}, },
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/material.dart';
import 'package:flutter_font_icons/flutter_font_icons.dart'; import 'package:flutter_font_icons/flutter_font_icons.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:maplibre_gl/maplibre_gl.dart'; import 'package:intaleq_maps/intaleq_maps.dart';
import 'dart:ui'; // مهم لإضافة تأثير الضبابية import 'dart:ui'; // مهم لإضافة تأثير الضبابية
import '../../../constant/colors.dart'; 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/main.dart';
import 'package:Intaleq/views/home/map_widget.dart/form_search_places_destenation.dart'; import 'package:Intaleq/views/home/map_widget.dart/form_search_places_destenation.dart';
import 'package:Intaleq/views/widgets/elevated_btn.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/colors.dart';
import '../../../constant/table_names.dart'; import '../../../constant/table_names.dart';
import '../../widgets/error_snakbar.dart'; import '../../widgets/error_snakbar.dart';
@@ -607,26 +607,32 @@ class _MapPickerOverlay extends StatelessWidget {
const _MapPickerOverlay({required this.controller}); const _MapPickerOverlay({required this.controller});
String _getModeTitle(BuildContext context) { String _getModeTitle(BuildContext context) {
if (controller.isPickingWaypoint) if (controller.isPickingWaypoint) {
return 'Move map to set stop'.tr + return 'Move map to set stop'.tr +
' ${controller.pickingWaypointIndex + 1}'.tr; ' ${controller.pickingWaypointIndex + 1}'.tr;
if (controller.passengerStartLocationFromMap) }
if (controller.passengerStartLocationFromMap) {
return controller.isAnotherOreder return controller.isAnotherOreder
? 'Now set the pickup point for the other person'.tr ? 'Now set the pickup point for the other person'.tr
: 'Move map to your pickup point'.tr; : 'Move map to your pickup point'.tr;
if (controller.startLocationFromMap) }
if (controller.startLocationFromMap) {
return 'Move map to set start location'.tr; return 'Move map to set start location'.tr;
if (controller.workLocationFromMap) }
if (controller.workLocationFromMap) {
return 'Move map to your work location'.tr; 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 your home location'.tr;
}
return 'Move map to select destination'.tr; return 'Move map to select destination'.tr;
} }
String _getConfirmLabel(BuildContext context) { String _getConfirmLabel(BuildContext context) {
if (controller.isPickingWaypoint) return 'Set as Stop'.tr; if (controller.isPickingWaypoint) return 'Set as Stop'.tr;
if (controller.passengerStartLocationFromMap) if (controller.passengerStartLocationFromMap) {
return 'Confirm Pickup Location'.tr; return 'Confirm Pickup Location'.tr;
}
if (controller.workLocationFromMap) return 'Set as Work'.tr; if (controller.workLocationFromMap) return 'Set as Work'.tr;
if (controller.homeLocationFromMap) return 'Set as Home'.tr; if (controller.homeLocationFromMap) return 'Set as Home'.tr;
return 'Set Destination'.tr; return 'Set Destination'.tr;
@@ -634,8 +640,9 @@ class _MapPickerOverlay extends StatelessWidget {
IconData _getModeIcon() { IconData _getModeIcon() {
if (controller.isPickingWaypoint) return Icons.add_location_alt_rounded; if (controller.isPickingWaypoint) return Icons.add_location_alt_rounded;
if (controller.passengerStartLocationFromMap) if (controller.passengerStartLocationFromMap) {
return Icons.person_pin_circle_rounded; return Icons.person_pin_circle_rounded;
}
if (controller.workLocationFromMap) return Icons.work_rounded; if (controller.workLocationFromMap) return Icons.work_rounded;
if (controller.homeLocationFromMap) return Icons.home_rounded; if (controller.homeLocationFromMap) return Icons.home_rounded;
return Icons.location_on_rounded; return Icons.location_on_rounded;
@@ -760,6 +767,8 @@ class _MapPickerOverlay extends StatelessWidget {
Future<void> _onConfirmTap( Future<void> _onConfirmTap(
MapPassengerController controller, BuildContext context) async { MapPassengerController controller, BuildContext context) async {
Log.print(
'🔘 _onConfirmTap: isPickingWaypoint=${controller.isPickingWaypoint}, newMyLocation=${controller.newMyLocation}');
await Future.delayed(const Duration(milliseconds: 280)); await Future.delayed(const Duration(milliseconds: 280));
final LatLng currentCameraPosition = LatLng( final LatLng currentCameraPosition = LatLng(
controller.newMyLocation.latitude, controller.newMyLocation.longitude); controller.newMyLocation.latitude, controller.newMyLocation.longitude);
@@ -905,7 +914,6 @@ class _RecentPlaceChip extends StatelessWidget {
'Are you want to go this site'.tr, 'Are you want to go this site'.tr,
' ', ' ',
() async { () async {
Get.back();
await controller.getLocation(); await controller.getLocation();
await controller.getDirectionMap( await controller.getDirectionMap(
'${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}', '${controller.passengerLocation.latitude},${controller.passengerLocation.longitude}',

View File

@@ -7,7 +7,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:geolocator/geolocator.dart'; import 'package:geolocator/geolocator.dart';
import 'package:get/get.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 'package:http/http.dart' as http;
import '../../../constant/box_name.dart'; import '../../../constant/box_name.dart';
import '../../../constant/links.dart'; import '../../../constant/links.dart';
@@ -47,7 +47,7 @@ class NavigationController extends GetxController
static const int _offRouteTriggerSeconds = 6; static const int _offRouteTriggerSeconds = 6;
bool isLoading = false; bool isLoading = false;
MaplibreMapController? mapController; IntaleqMapController? mapController;
bool isStyleLoaded = false; bool isStyleLoaded = false;
final TextEditingController placeDestinationController = final TextEditingController placeDestinationController =
TextEditingController(); TextEditingController();
@@ -65,11 +65,10 @@ class NavigationController extends GetxController
double currentSpeed = 0.0; double currentSpeed = 0.0;
double totalDistance = 0.0; double totalDistance = 0.0;
Symbol? carSymbol; Set<Marker> markers = {};
Symbol? originSymbol; Set<Polyline> polylines = {};
Symbol? destinationSymbol; Set<Circle> circles = {};
Line? remainingRouteLine; Set<Polygon> polygons = {};
Line? traveledRouteLine;
Timer? _locationUpdateTimer; Timer? _locationUpdateTimer;
LatLng? _lastProcessedLocation; LatLng? _lastProcessedLocation;
@@ -102,6 +101,61 @@ class NavigationController extends GetxController
bool _cameraLockedToUser = true; bool _cameraLockedToUser = true;
bool _mapReady = false; 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; DateTime? _offRouteStartTime;
bool _autoRecalcInProgress = false; bool _autoRecalcInProgress = false;
@@ -114,7 +168,7 @@ class NavigationController extends GetxController
List<RouteData> routes = []; List<RouteData> routes = [];
int selectedRouteIndex = 0; int selectedRouteIndex = 0;
List<Line> alternativeRouteLines = [];
List<Map<String, dynamic>> recentLocations = []; List<Map<String, dynamic>> recentLocations = [];
double get _targetZoom { double get _targetZoom {
@@ -131,6 +185,85 @@ class NavigationController extends GetxController
return 55.0; 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 = static final String _routeApiBaseUrl =
"${AppLink.routesOsm}/route/v1/driving"; "${AppLink.routesOsm}/route/v1/driving";
@@ -179,11 +312,11 @@ class NavigationController extends GetxController
if (isStyleLoaded) { if (isStyleLoaded) {
_updateCarMarker(); _updateCarMarker();
if (_fullRouteCoordinates.isNotEmpty && _cameraLockedToUser) { if (_cameraLockedToUser) {
animateCameraToPosition(myLocation!, animateCameraToPosition(myLocation!,
bearing: _smoothedHeading, bearing: _smoothedHeading,
zoom: _targetZoom, zoom: isNavigating ? _targetZoom : 17.0,
tilt: _targetTilt); tilt: isNavigating ? _targetTilt : 0.0);
} }
} }
} }
@@ -208,7 +341,7 @@ class NavigationController extends GetxController
} else { } else {
parsed = []; parsed = [];
} }
recentLocations = parsed recentLocations = parsed
.map((e) => Map<String, dynamic>.from(e)) .map((e) => Map<String, dynamic>.from(e))
.toList() .toList()
@@ -238,23 +371,29 @@ class NavigationController extends GetxController
super.onClose(); super.onClose();
} }
void onMapCreated(MaplibreMapController controller) { void onMapCreated(IntaleqMapController controller) async {
Log.print("DEBUG: NavigationController.onMapCreated called");
mapController = controller; mapController = controller;
await onStyleLoaded();
} }
Future<void> onStyleLoaded() async { Future<void> onStyleLoaded() async {
Log.print("DEBUG: NavigationController.onStyleLoaded called");
isStyleLoaded = true; isStyleLoaded = true;
await _loadCustomIcons(); await _loadCustomIcons();
WidgetsBinding.instance.addPostFrameCallback((_) async { WidgetsBinding.instance.addPostFrameCallback((_) async {
await Future.delayed(const Duration(milliseconds: 300)); await Future.delayed(const Duration(milliseconds: 300));
if (!_mapReady) { if (!_mapReady) {
Log.print("DEBUG: NavigationController setting _mapReady = true");
_mapReady = true; _mapReady = true;
if (myLocation != null) { if (myLocation != null) {
Log.print("DEBUG: Animating camera to initial location: $myLocation");
animateCameraToPosition(myLocation!); animateCameraToPosition(myLocation!);
_updateCarMarker(); _updateCarMarker();
} }
if (_fullRouteCoordinates.isNotEmpty) { if (_fullRouteCoordinates.isNotEmpty) {
Log.print("DEBUG: Updating initial polylines");
_updatePolylinesSets([], _fullRouteCoordinates); _updatePolylinesSets([], _fullRouteCoordinates);
} }
} }
@@ -319,9 +458,11 @@ class NavigationController extends GetxController
Future<void> _getCurrentLocationAndStartUpdates() async { Future<void> _getCurrentLocationAndStartUpdates() async {
try { try {
Log.print("DEBUG: Getting initial location...");
final position = await Geolocator.getCurrentPosition( final position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high); desiredAccuracy: LocationAccuracy.high);
myLocation = LatLng(position.latitude, position.longitude); myLocation = LatLng(position.latitude, position.longitude);
Log.print("DEBUG: Initial location acquired: $myLocation");
_targetHeading = position.heading; _targetHeading = position.heading;
_oldHeading = position.heading; _oldHeading = position.heading;
_smoothedHeading = position.heading; _smoothedHeading = position.heading;
@@ -330,7 +471,7 @@ class NavigationController extends GetxController
_startLocationTimer(); _startLocationTimer();
_startBatchTimers(); _startBatchTimers();
} catch (e) { } 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()); Timer.periodic(const Duration(seconds: 4), (_) => _tick());
} }
bool _isTicking = false;
Future<void> _tick() async { Future<void> _tick() async {
if (_isTicking) return;
_isTicking = true;
try { try {
final position = await Geolocator.getCurrentPosition( final position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high); desiredAccuracy: LocationAccuracy.high);
@@ -356,6 +500,8 @@ class NavigationController extends GetxController
); );
if (d < _minMoveToProcess) return; if (d < _minMoveToProcess) return;
} }
Log.print(
"DEBUG: Location tick - Speed: ${currentSpeed.toStringAsFixed(1)} km/h, Loc: $newLoc");
if (_lastDistanceLocation != null) { if (_lastDistanceLocation != null) {
final d = Geolocator.distanceBetween( final d = Geolocator.distanceBetween(
@@ -396,7 +542,9 @@ class NavigationController extends GetxController
} }
update(); update();
} catch (e) { } 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 { Future<void> _updateCarMarker() async {
if (myLocation == null || mapController == null || !isStyleLoaded) return; // Car marker is now handled natively by myLocationEnabled: true.
// 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,
));
}
} }
void animateCameraToPosition(LatLng position, void animateCameraToPosition(LatLng position,
@@ -618,49 +741,41 @@ class NavigationController extends GetxController
Future<void> _updatePolylinesSets( Future<void> _updatePolylinesSets(
List<LatLng> traveled, List<LatLng> remaining) async { List<LatLng> traveled, List<LatLng> remaining) async {
if (mapController == null || !isStyleLoaded) return; Log.print(
"DEBUG: Updating polylines. Traveled: ${traveled.length}, Remaining: ${remaining.length}");
// Clear old alternative lines Set<Polyline> newPolylines = {};
for (var line in alternativeRouteLines) {
await mapController!.removeLine(line);
}
alternativeRouteLines.clear();
if (remainingRouteLine != null) { // Render Alternative Routes first
await mapController!.removeLine(remainingRouteLine!);
}
if (traveledRouteLine != null) {
await mapController!.removeLine(traveledRouteLine!);
}
// Render Alternative Routes first (so they are below)
for (int i = 0; i < routes.length; i++) { for (int i = 0; i < routes.length; i++) {
if (i == selectedRouteIndex) continue; if (i == selectedRouteIndex) continue;
final altLine = await mapController!.addLine(LineOptions( newPolylines.add(Polyline(
geometry: routes[i].coordinates, polylineId: PolylineId('alt_$i'),
lineColor: '#B0BEC5', // Soft gray for alternatives points: routes[i].coordinates,
lineWidth: 6.0, color: const Color(0xFFB0BEC5).withOpacity(0.8),
lineJoin: 'round', width: 6,
lineOpacity: 0.8,
)); ));
alternativeRouteLines.add(altLine);
} }
if (remaining.isNotEmpty) { if (remaining.isNotEmpty) {
remainingRouteLine = await mapController!.addLine(LineOptions( newPolylines.add(Polyline(
geometry: remaining, polylineId: const PolylineId('remaining'),
lineColor: '#00e5ff', // Cyan/Blue for selected points: remaining,
lineWidth: 8.0, color: const Color(0xFF00E5FF),
lineJoin: 'round')); width: 8,
));
} }
if (traveled.isNotEmpty) { if (traveled.isNotEmpty) {
traveledRouteLine = await mapController!.addLine(LineOptions( newPolylines.add(Polyline(
geometry: traveled, polylineId: const PolylineId('traveled'),
lineColor: '#BDBDBD', points: traveled,
lineWidth: 5.0, color: const Color(0xFFBDBDBD).withOpacity(0.6),
lineJoin: 'round', width: 5,
lineOpacity: 0.6)); ));
} }
polylines = newPolylines;
update();
} }
void selectRoute(int index) { void selectRoute(int index) {
@@ -671,7 +786,7 @@ class NavigationController extends GetxController
routeSteps = r.steps; routeSteps = r.steps;
_routeTotalDistanceM = r.distanceM; _routeTotalDistanceM = r.distanceM;
_routeTotalDurationS = r.durationS; _routeTotalDurationS = r.durationS;
_lastTraveledIndexInFullRoute = 0; _lastTraveledIndexInFullRoute = 0;
_recomputeETA(); _recomputeETA();
_updatePolylinesSets([], _fullRouteCoordinates); _updatePolylinesSets([], _fullRouteCoordinates);
@@ -763,7 +878,8 @@ class NavigationController extends GetxController
routes.clear(); routes.clear();
final primaryPts = data['points']?.toString() ?? ""; final primaryPts = data['points']?.toString() ?? "";
if (primaryPts.isNotEmpty) { if (primaryPts.isNotEmpty) {
final coords = await compute<String, List<LatLng>>(decodePolylineIsolate, primaryPts); final coords = await compute<String, List<LatLng>>(
decodePolylineIsolate, primaryPts);
routes.add(RouteData( routes.add(RouteData(
coordinates: coords, coordinates: coords,
steps: List<Map<String, dynamic>>.from(data['instructions'] ?? []), steps: List<Map<String, dynamic>>.from(data['instructions'] ?? []),
@@ -778,7 +894,8 @@ class NavigationController extends GetxController
for (var alt in data['alternatives']) { for (var alt in data['alternatives']) {
final altPts = alt['points']?.toString() ?? ""; final altPts = alt['points']?.toString() ?? "";
if (altPts.isEmpty) continue; 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( routes.add(RouteData(
coordinates: altCoords, coordinates: altCoords,
steps: List<Map<String, dynamic>>.from(alt['instructions'] ?? []), steps: List<Map<String, dynamic>>.from(alt['instructions'] ?? []),
@@ -899,19 +1016,27 @@ class NavigationController extends GetxController
try { try {
_finalDestination = destination; _finalDestination = destination;
await clearRoute(isNewRoute: true); await clearRoute(isNewRoute: true);
if (isStyleLoaded && mapController != null) {
destinationSymbol = await mapController!.addSymbol(SymbolOptions( // Preserve car marker if it exists
geometry: destination, markers = markers.where((m) => m.markerId.value == 'car').toSet();
iconImage: 'dest_icon',
iconSize: 1.0, markers.add(Marker(
textField: infoWindowTitle, markerId: const MarkerId('destination'),
textOffset: const Offset(0, 2))); position: destination,
if (myLocation != null) { icon: InlqBitmap.fromStyleImage('dest_icon'),
originSymbol = await mapController!.addSymbol(SymbolOptions( infoWindow: infoWindowTitle.isNotEmpty
geometry: myLocation, iconImage: 'start_icon', iconSize: 1.0)); ? 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 { } finally {
isLoading = false; isLoading = false;
update(); update();
@@ -928,32 +1053,27 @@ class NavigationController extends GetxController
update(); update();
} }
Future<void> clearEverything() async {
placeDestinationController.clear();
placesDestination = [];
await clearRoute();
}
Future<void> clearRoute({bool isNewRoute = false}) async { Future<void> clearRoute({bool isNewRoute = false}) async {
_offRouteStartTime = null; _offRouteStartTime = null;
_autoRecalcInProgress = false; _autoRecalcInProgress = false;
if (!isNewRoute) { if (!isNewRoute) {
if (destinationSymbol != null && mapController != null) { markers = {};
await mapController!.removeSymbol(destinationSymbol!); polylines = {};
destinationSymbol = null; circles = {};
} polygons = {};
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;
}
_finalDestination = null; _finalDestination = null;
isNavigating = false; isNavigating = false;
routes = [];
await _flushBufferToServer(); await _flushBufferToServer();
} }
routeSteps.clear(); routeSteps = [];
_fullRouteCoordinates.clear(); _fullRouteCoordinates = [];
_lastTraveledIndexInFullRoute = 0; _lastTraveledIndexInFullRoute = 0;
currentInstruction = ""; currentInstruction = "";
nextInstruction = ""; nextInstruction = "";
@@ -964,6 +1084,10 @@ class NavigationController extends GetxController
arrivalTime = "--:--"; arrivalTime = "--:--";
_routeTotalDistanceM = 0; _routeTotalDistanceM = 0;
_routeTotalDurationS = 0; _routeTotalDurationS = 0;
if (!isNewRoute) {
await _updateCarMarker();
}
update(); update();
} }

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -319,90 +319,100 @@ class MyDialog extends GetxController {
HapticFeedback.mediumImpact(); HapticFeedback.mediumImpact();
Get.dialog( Get.dialog(
_DialogShell( Builder(builder: (dialogContext) {
child: _GlassCard( return _DialogShell(
child: Column( child: _GlassCard(
mainAxisSize: MainAxisSize.min, child: Column(
children: [ mainAxisSize: MainAxisSize.min,
// ── Body ────────────────────────────────────────────── children: [
Padding( // ── Body ──────────────────────────────────────────────
padding: const EdgeInsets.fromLTRB(24, 28, 24, 20), Padding(
child: Column( padding: const EdgeInsets.fromLTRB(24, 28, 24, 20),
children: [ child: Column(
// Icon badge children: [
Container( // Icon badge
width: 56, Container(
height: 56, width: 56,
decoration: BoxDecoration( height: 56,
shape: BoxShape.circle, decoration: BoxDecoration(
color: (isDestructive shape: BoxShape.circle,
? AppColor.redColor
: AppColor.primaryColor)
.withOpacity(0.1),
border: Border.all(
color: (isDestructive color: (isDestructive
? AppColor.redColor ? AppColor.redColor
: AppColor.primaryColor) : AppColor.primaryColor)
.withOpacity(0.2), .withOpacity(0.1),
border: Border.all(
color: (isDestructive
? AppColor.redColor
: AppColor.primaryColor)
.withOpacity(0.2),
),
), ),
), child: Icon(
child: Icon( icon ??
icon ?? (isDestructive
(isDestructive ? Icons.warning_amber_rounded
? Icons.warning_amber_rounded : Icons.info_outline_rounded),
: Icons.info_outline_rounded), color: isDestructive
color: isDestructive ? AppColor.redColor
? AppColor.redColor : AppColor.primaryColor,
: AppColor.primaryColor, size: 26,
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],
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// TTS button // Title
_SpeakButton( Text(
texts: [title, if (midTitle.isNotEmpty) midTitle]), title,
], textAlign: TextAlign.center,
], style: AppStyle.title.copyWith(
), fontSize: 18,
), fontWeight: FontWeight.w700,
letterSpacing: -0.4,
color: AppColor.writeColor,
),
),
// ── Actions ─────────────────────────────────────────── if (midTitle != null && midTitle.isNotEmpty) ...[
_ActionRow( const SizedBox(height: 10),
onCancel: () => Get.back(), Text(
onConfirm: onPressed, midTitle,
confirmLabel: 'OK'.tr, textAlign: TextAlign.center,
isDestructive: isDestructive, 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, barrierDismissible: true,
barrierColor: _DC.barrierColor, barrierColor: _DC.barrierColor,
); );
@@ -493,7 +503,12 @@ class MyDialogContent extends GetxController {
// ── Actions ─────────────────────────────────────────── // ── Actions ───────────────────────────────────────────
_ActionRow( _ActionRow(
onCancel: () => Get.back(), onCancel: () => Get.back(),
onConfirm: onPressed, onConfirm: () {
Get.back(); // Dismiss dialog first
WidgetsBinding.instance.addPostFrameCallback((_) {
onPressed();
});
},
confirmLabel: confirmLabel.tr, confirmLabel: confirmLabel.tr,
isDestructive: isDestructive, isDestructive: isDestructive,
), ),

View File

@@ -125,10 +125,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: build_config name: build_config
sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187" sha256: "4070d2a59f8eec34c97c86ceb44403834899075f66e8a9d59706f8e7834f6f71"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" version: "1.3.0"
build_daemon: build_daemon:
dependency: transitive dependency: transitive
description: description:
@@ -141,10 +141,10 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_runner name: build_runner
sha256: "39ad4ca8a2876779737c60e4228b4bcd35d4352ef7e14e47514093edc012c734" sha256: "521daf8d189deb79ba474e43a696b41c49fb3987818dbacf3308f1e03673a75e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.11.1" version: "2.13.1"
built_collection: built_collection:
dependency: transitive dependency: transitive
description: description:
@@ -380,18 +380,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: envied name: envied
sha256: "2ca9842c7f513ab527e4f35a58331d6f9a7f90f270b5ba501d73ff2d9fa449ff" sha256: cac8bf0df6c53bd3c3511a6ee295ba6b86e6547df25c8c8648fc479128d2317c
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.3" version: "1.3.4"
envied_generator: envied_generator:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: envied_generator name: envied_generator
sha256: "4ed57d61dccb8e546a811b6a3dc5ebcef24bdfe60febbbd6fef31342ab15f9e5" sha256: ac7c2d0871a25a917f145a42ca6b4596b81b97d71017a511e0b3ffa60067cf75
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.3" version: "1.3.4"
equatable: equatable:
dependency: transitive dependency: transitive
description: description:
@@ -1037,7 +1037,7 @@ packages:
source: hosted source: hosted
version: "4.1.2" version: "4.1.2"
image: image:
dependency: transitive dependency: "direct main"
description: description:
name: image name: image
sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce
@@ -1132,6 +1132,13 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.2.2" version: "0.2.2"
intaleq_maps:
dependency: "direct main"
description:
path: "/Users/hamzaaleghwairyeen/development/App/map-saas/packages/flutter-sdk/"
relative: false
source: path
version: "2.1.3"
internet_connection_checker: internet_connection_checker:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1212,14 +1219,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "2.0.1"
latlong2:
dependency: "direct main"
description:
name: latlong2
sha256: "98227922caf49e6056f91b6c56945ea1c7b166f28ffcd5fb8e72fc0b453cc8fe"
url: "https://pub.dev"
source: hosted
version: "0.9.1"
leak_tracker: leak_tracker:
dependency: transitive dependency: transitive
description: description:
@@ -1333,7 +1332,7 @@ packages:
source: hosted source: hosted
version: "1.3.0" version: "1.3.0"
maplibre_gl: maplibre_gl:
dependency: "direct main" dependency: transitive
description: description:
name: maplibre_gl name: maplibre_gl
sha256: d9773555ae4ebab94bbc3ae2176b077cfda486ec729eefe01e1613f164cb8410 sha256: d9773555ae4ebab94bbc3ae2176b077cfda486ec729eefe01e1613f164cb8410

View File

@@ -41,9 +41,9 @@ dependencies:
# camera: ^0.10.5+5 #to be remove # camera: ^0.10.5+5 #to be remove
flutter_widget_from_html: ^0.17.1 flutter_widget_from_html: ^0.17.1
local_auth: ^3.0.1 local_auth: ^3.0.1
# image: ^4.1.3 #to be remove image: ^4.1.3
image_cropper: ^11.0.0 image_cropper: ^11.0.0
envied: ^1.3.3 envied: ^1.3.4
# cached_network_image: ^3.3.0 # cached_network_image: ^3.3.0
calendar_builder: calendar_builder:
path: ./packages/calendar_builder path: ./packages/calendar_builder
@@ -81,8 +81,8 @@ dependencies:
internet_connection_checker: ^3.0.1 internet_connection_checker: ^3.0.1
connectivity_plus: ^6.1.5 connectivity_plus: ^6.1.5
app_links: ^7.0.0 app_links: ^7.0.0
latlong2: ^0.9.1 intaleq_maps:
maplibre_gl: ^0.25.0 path: /Users/hamzaaleghwairyeen/development/App/map-saas/packages/flutter-sdk/
socket_io_client: 1.0.2 socket_io_client: 1.0.2
# home_widget: ^0.7.0+1 # home_widget: ^0.7.0+1
@@ -90,8 +90,8 @@ dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
flutter_lints: ^6.0.0 flutter_lints: ^6.0.0
envied_generator: ^1.3.3 envied_generator: ^1.3.4
build_runner: ^2.11.1 build_runner: ^2.13.1
flutter_launcher_icons: flutter_launcher_icons:
android: "launcher_icon" android: "launcher_icon"
@@ -131,6 +131,7 @@ flutter:
- asset: assets/fonts/digit.ttf - asset: assets/fonts/digit.ttf
dependency_overrides: dependency_overrides:
# record_platform_interface: "1.2.0" # record_platform_interface: "1.2.0"
get: get:
path: ./packages/get path: ./packages/get
@@ -140,3 +141,4 @@ dependency_overrides:
path: ./packages/calendar_builder path: ./packages/calendar_builder
flutter_paypal: flutter_paypal:
path: ./packages/flutter_paypal path: ./packages/flutter_paypal