feat: refactor financial wallet UI components and add offline map service support
This commit is contained in:
@@ -5,14 +5,16 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_overlay_window/flutter_overlay_window.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'package:intaleq_maps/intaleq_maps.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:just_audio/just_audio.dart';
|
||||
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import '../../../constant/box_name.dart';
|
||||
import '../../../constant/links.dart';
|
||||
import '../../../env/env.dart';
|
||||
import '../../../main.dart';
|
||||
import '../../../print.dart';
|
||||
import '../../../views/home/Captin/driver_map_page.dart';
|
||||
@@ -22,8 +24,6 @@ import '../../firebase/local_notification.dart';
|
||||
import '../../functions/crud.dart';
|
||||
import '../../functions/location_controller.dart';
|
||||
import '../../home/captin/home_captain_controller.dart';
|
||||
import '../../firebase/notification_service.dart';
|
||||
import '../navigation/decode_polyline_isolate.dart';
|
||||
|
||||
class OrderRequestController extends GetxController
|
||||
with WidgetsBindingObserver {
|
||||
@@ -40,7 +40,7 @@ class OrderRequestController extends GetxController
|
||||
bool _isRideTakenHandled = false;
|
||||
|
||||
// --- الأيقونات والماركرز ---
|
||||
BitmapDescriptor? driverIcon;
|
||||
InlqBitmap? driverIcon;
|
||||
Map<MarkerId, Marker> markersMap = {};
|
||||
Set<Marker> get markers => markersMap.values.toSet();
|
||||
|
||||
@@ -49,7 +49,7 @@ class OrderRequestController extends GetxController
|
||||
List<dynamic>? myList;
|
||||
Map<dynamic, dynamic>? myMapData;
|
||||
|
||||
GoogleMapController? mapController;
|
||||
IntaleqMapController? mapController;
|
||||
|
||||
// الإحداثيات (أزلنا late لتجنب الأخطاء القاتلة)
|
||||
double latPassenger = 0.0;
|
||||
@@ -64,7 +64,7 @@ class OrderRequestController extends GetxController
|
||||
String totalTripDuration = "--";
|
||||
String tripPrice = "--";
|
||||
|
||||
String timeToPassenger = "جاري الحساب...";
|
||||
String timeToPassenger = "Calculating...".tr;
|
||||
String distanceToPassenger = "--";
|
||||
|
||||
// --- الخريطة ---
|
||||
@@ -193,9 +193,25 @@ class OrderRequestController extends GetxController
|
||||
void _parseExtraData() {
|
||||
passengerRating = _safeGet(33).isEmpty ? "5.0" : _safeGet(33);
|
||||
tripType = _safeGet(31);
|
||||
totalTripDistance = _safeGet(5);
|
||||
totalTripDuration = _safeGet(19);
|
||||
tripPrice = _safeGet(2);
|
||||
|
||||
// Format numbers to avoid many decimal places
|
||||
String rawDist = _safeGet(5);
|
||||
if (rawDist.isNotEmpty) {
|
||||
double? d = double.tryParse(rawDist);
|
||||
totalTripDistance = d != null ? "${d.toStringAsFixed(1)} km" : rawDist;
|
||||
}
|
||||
|
||||
String rawDur = _safeGet(19);
|
||||
if (rawDur.isNotEmpty) {
|
||||
double? d = double.tryParse(rawDur);
|
||||
totalTripDuration = d != null ? "${d.toStringAsFixed(0)} min" : rawDur;
|
||||
}
|
||||
|
||||
String rawPrice = _safeGet(2);
|
||||
if (rawPrice.isNotEmpty) {
|
||||
double? p = double.tryParse(rawPrice);
|
||||
tripPrice = p != null ? p.toStringAsFixed(0) : rawPrice;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
@@ -203,6 +219,8 @@ class OrderRequestController extends GetxController
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
Future<void> _calculateFullJourney() async {
|
||||
if (mapController == null) return; // Wait for controller to draw
|
||||
|
||||
try {
|
||||
Position driverPos = await Geolocator.getCurrentPosition(
|
||||
desiredAccuracy: LocationAccuracy.high);
|
||||
@@ -210,6 +228,9 @@ class OrderRequestController extends GetxController
|
||||
|
||||
updateDriverLocation(driverLatLng, driverPos.heading);
|
||||
|
||||
// Clear old polylines to avoid "ghost lines"
|
||||
polylines.clear();
|
||||
|
||||
var pickupFuture = _fetchRouteData(
|
||||
start: driverLatLng,
|
||||
end: LatLng(latPassenger, lngPassenger),
|
||||
@@ -246,7 +267,8 @@ class OrderRequestController extends GetxController
|
||||
destTime: totalTripDuration,
|
||||
destDist: totalTripDistance);
|
||||
|
||||
zoomToFitRide(driverLatLng);
|
||||
// Now zoom to fit all polylines and markers
|
||||
zoomToFitRide();
|
||||
|
||||
update();
|
||||
} catch (e) {
|
||||
@@ -254,6 +276,22 @@ class OrderRequestController extends GetxController
|
||||
}
|
||||
}
|
||||
|
||||
String _formatDistance(dynamic rawDist) {
|
||||
if (rawDist == null || rawDist.toString().isEmpty) return "--";
|
||||
double dist = double.tryParse(rawDist.toString()) ?? 0.0;
|
||||
if (dist <= 0) return "--";
|
||||
if (dist < 1000) return "${dist.toStringAsFixed(0)} m";
|
||||
return "${(dist / 1000).toStringAsFixed(1)} km";
|
||||
}
|
||||
|
||||
String _formatDuration(dynamic rawDur) {
|
||||
if (rawDur == null || rawDur.toString().isEmpty) return "--";
|
||||
double dur = double.tryParse(rawDur.toString()) ?? 0.0;
|
||||
if (dur <= 0) return "1 min"; // Minimum 1 min for UI
|
||||
if (dur < 60) return "${dur.toStringAsFixed(0)} sec";
|
||||
return "${(dur / 60).toStringAsFixed(0)} min";
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>?> _fetchRouteData(
|
||||
{required LatLng start,
|
||||
required LatLng end,
|
||||
@@ -261,46 +299,56 @@ class OrderRequestController extends GetxController
|
||||
required String id,
|
||||
bool isDashed = false}) async {
|
||||
try {
|
||||
// حماية من الإحداثيات الصفرية
|
||||
if (start.latitude == 0 || end.latitude == 0) return null;
|
||||
if (mapController == null) return null;
|
||||
|
||||
String apiUrl = "https://routesy.intaleq.xyz/route/v1/driving";
|
||||
String coords =
|
||||
"${start.longitude},${start.latitude};${end.longitude},${end.latitude}";
|
||||
String url = "$apiUrl/$coords?steps=false&overview=full";
|
||||
final saasUrl = Uri.parse(AppLink.mapSaasRoute).replace(queryParameters: {
|
||||
'fromLat': start.latitude.toString(),
|
||||
'fromLng': start.longitude.toString(),
|
||||
'toLat': end.latitude.toString(),
|
||||
'toLng': end.longitude.toString(),
|
||||
'steps': 'false',
|
||||
'alternatives': 'false',
|
||||
});
|
||||
|
||||
var response = await http.get(Uri.parse(url));
|
||||
if (response.statusCode == 200) {
|
||||
var json = jsonDecode(response.body);
|
||||
if (json['code'] == 'Ok' && json['routes'].isNotEmpty) {
|
||||
var route = json['routes'][0];
|
||||
final response = await http.get(saasUrl, headers: {
|
||||
'x-api-key': Env.mapSaasKey,
|
||||
'Content-Type': 'application/json',
|
||||
});
|
||||
|
||||
double distM = double.parse(route['distance'].toString());
|
||||
double durS = double.parse(route['duration'].toString());
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception("Routing request failed: ${response.statusCode}");
|
||||
}
|
||||
|
||||
String distText = "${(distM / 1000).toStringAsFixed(1)} كم";
|
||||
String durText = "${(durS / 60).toStringAsFixed(0)} دقيقة";
|
||||
final data = jsonDecode(response.body);
|
||||
print("🛣️ Route API Response [$id]: ${data}");
|
||||
|
||||
String geometry = route['geometry'];
|
||||
List<LatLng> points = await compute(decodePolylineIsolate, geometry);
|
||||
// The map-saas API returns the route data directly at the root,
|
||||
// with 'points' being an encoded polyline string.
|
||||
final String? encodedPoints = data['points']?.toString();
|
||||
|
||||
Polyline polyline = Polyline(
|
||||
polylineId: PolylineId(id),
|
||||
color: color,
|
||||
width: 5,
|
||||
points: points,
|
||||
patterns:
|
||||
isDashed ? [PatternItem.dash(10), PatternItem.gap(5)] : [],
|
||||
startCap: Cap.roundCap,
|
||||
endCap: Cap.roundCap,
|
||||
);
|
||||
if (encodedPoints != null && encodedPoints.isNotEmpty) {
|
||||
List<LatLng> path = controllerDecodePolyline(encodedPoints);
|
||||
print("📍 Path for [$id] has ${path.length} points.");
|
||||
|
||||
return {
|
||||
'distance_text': distText,
|
||||
'duration_text': durText,
|
||||
'polyline': polyline
|
||||
};
|
||||
}
|
||||
final num? rawDist = data['distance'] is num ? data['distance'] : null;
|
||||
final num? rawDur = data['duration'] is num ? data['duration'] : null;
|
||||
|
||||
final distanceText = data['distance_text'] ?? _formatDistance(rawDist);
|
||||
final durationText = data['duration_text'] ?? _formatDuration(rawDur);
|
||||
|
||||
Polyline polyline = Polyline(
|
||||
polylineId: PolylineId(id),
|
||||
color: color,
|
||||
width: 5,
|
||||
points: path,
|
||||
);
|
||||
|
||||
return {
|
||||
'distance_text': distanceText,
|
||||
'duration_text': durationText,
|
||||
'polyline': polyline
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
print("Route Fetch Error: $e");
|
||||
@@ -308,36 +356,47 @@ class OrderRequestController extends GetxController
|
||||
return null;
|
||||
}
|
||||
|
||||
void zoomToFitRide(LatLng driverPos) {
|
||||
void zoomToFitRide() {
|
||||
if (mapController == null) return;
|
||||
|
||||
// حماية من النقاط الصفرية
|
||||
if (latPassenger == 0 || latDestination == 0) return;
|
||||
List<LatLng> allPoints = [];
|
||||
|
||||
List<LatLng> points = [
|
||||
driverPos,
|
||||
LatLng(latPassenger, lngPassenger),
|
||||
LatLng(latDestination, lngDestination),
|
||||
];
|
||||
// Add all polyline points to the bounds calculation
|
||||
for (var polyline in polylines) {
|
||||
allPoints.addAll(polyline.points);
|
||||
}
|
||||
|
||||
double minLat = points.first.latitude;
|
||||
double maxLat = points.first.latitude;
|
||||
double minLng = points.first.longitude;
|
||||
double maxLng = points.first.longitude;
|
||||
// Fallback to basic markers if polylines are empty
|
||||
if (allPoints.isEmpty) {
|
||||
allPoints.addAll([
|
||||
LatLng(latPassenger, lngPassenger),
|
||||
LatLng(latDestination, lngDestination),
|
||||
]);
|
||||
}
|
||||
|
||||
for (var p in points) {
|
||||
if (allPoints.isEmpty) return;
|
||||
|
||||
double minLat = allPoints.first.latitude;
|
||||
double maxLat = allPoints.first.latitude;
|
||||
double minLng = allPoints.first.longitude;
|
||||
double maxLng = allPoints.first.longitude;
|
||||
|
||||
for (var p in allPoints) {
|
||||
if (p.latitude < minLat) minLat = p.latitude;
|
||||
if (p.latitude > maxLat) maxLat = p.latitude;
|
||||
if (p.longitude < minLng) minLng = p.longitude;
|
||||
if (p.longitude > maxLng) maxLng = p.longitude;
|
||||
}
|
||||
|
||||
// Add some padding to the bounds
|
||||
double latPad = (maxLat - minLat) * 0.25;
|
||||
double lngPad = (maxLng - minLng) * 0.2;
|
||||
|
||||
mapController!.animateCamera(CameraUpdate.newLatLngBounds(
|
||||
LatLngBounds(
|
||||
southwest: LatLng(minLat, minLng),
|
||||
northeast: LatLng(maxLat, maxLng),
|
||||
southwest: LatLng(minLat - latPad, minLng - lngPad),
|
||||
northeast: LatLng(maxLat + latPad, maxLng + lngPad),
|
||||
),
|
||||
100.0,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -357,15 +416,15 @@ class OrderRequestController extends GetxController
|
||||
// حماية إذا لم يتم جلب الإحداثيات
|
||||
if (latPassenger == 0 || latDestination == 0) return;
|
||||
|
||||
final BitmapDescriptor pickupIcon =
|
||||
final InlqBitmap pickupIcon =
|
||||
await MarkerGenerator.createCustomMarkerBitmap(
|
||||
title: paxTime,
|
||||
subtitle: paxDist,
|
||||
color: Colors.green.shade700,
|
||||
color: Colors.amber.shade900, // Matching the amber pickup line
|
||||
iconData: Icons.person_pin_circle,
|
||||
);
|
||||
|
||||
final BitmapDescriptor dropoffIcon =
|
||||
final InlqBitmap dropoffIcon =
|
||||
await MarkerGenerator.createCustomMarkerBitmap(
|
||||
title: destTime ?? totalTripDuration,
|
||||
subtitle: destDist ?? totalTripDistance,
|
||||
@@ -411,10 +470,9 @@ class OrderRequestController extends GetxController
|
||||
points: [driverLatLng, LatLng(latPassenger, lngPassenger)],
|
||||
color: Colors.grey,
|
||||
width: 2,
|
||||
patterns: [PatternItem.dash(10), PatternItem.gap(10)],
|
||||
));
|
||||
|
||||
zoomToFitRide(driverLatLng);
|
||||
zoomToFitRide();
|
||||
}
|
||||
|
||||
update();
|
||||
@@ -435,8 +493,9 @@ class OrderRequestController extends GetxController
|
||||
}
|
||||
}
|
||||
|
||||
void onMapCreated(GoogleMapController controller) {
|
||||
void onMapCreated(IntaleqMapController controller) {
|
||||
mapController = controller;
|
||||
_calculateFullJourney();
|
||||
}
|
||||
|
||||
// --- قبول الطلب وإدارة التايمر ---
|
||||
@@ -488,10 +547,13 @@ class OrderRequestController extends GetxController
|
||||
// 1. حذف الإشعار من شريط التنبيهات فوراً
|
||||
NotificationController().cancelOrderNotification();
|
||||
if (Get.isSnackbarOpen) Get.closeCurrentSnackbar();
|
||||
if (Get.isDialogOpen ?? false) Get.back();
|
||||
|
||||
// إغلاق أي ديالوج مفتوح قسرياً
|
||||
if (Get.isDialogOpen ?? false) {
|
||||
navigatorKey.currentState?.pop();
|
||||
}
|
||||
Get.back();
|
||||
Get.snackbar("تنبيه", "تم قبول الطلب من قبل سائق آخر",
|
||||
backgroundColor: Colors.orange, colorText: Colors.white);
|
||||
mySnackbarInfo("The order has been accepted by another driver.".tr);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -546,9 +608,10 @@ class OrderRequestController extends GetxController
|
||||
|
||||
if (isFailure) {
|
||||
// ⛔ حالة الفشل: الطلب مأخوذ
|
||||
MyDialog().getDialog("عذراً، الطلب أخذه سائق آخر.", '', () {
|
||||
Get.back(); // إغلاق الديالوج
|
||||
Get.back(); // العودة للصفحة الرئيسية (إغلاق صفحة الطلب)
|
||||
MyDialog().getDialog(
|
||||
"Sorry, the order was taken by another driver.".tr, '', () {
|
||||
// بما أن MyDialog يغلق نفسه الآن، نحتاج Get.back() واحدة فقط لإغلاق صفحة الطلب
|
||||
Get.back();
|
||||
});
|
||||
} else {
|
||||
// ✅ حالة النجاح
|
||||
@@ -611,7 +674,37 @@ class OrderRequestController extends GetxController
|
||||
audioPlayer.dispose();
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
_timer?.cancel();
|
||||
mapController?.dispose();
|
||||
// mapController?.dispose();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
List<LatLng> controllerDecodePolyline(String encoded) {
|
||||
List<LatLng> points = [];
|
||||
int index = 0, len = encoded.length;
|
||||
int lat = 0, lng = 0;
|
||||
|
||||
while (index < len) {
|
||||
int b, shift = 0, result = 0;
|
||||
do {
|
||||
b = encoded.codeUnitAt(index++) - 63;
|
||||
result |= (b & 0x1f) << shift;
|
||||
shift += 5;
|
||||
} while (b >= 0x20);
|
||||
int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
|
||||
lat += dlat;
|
||||
|
||||
shift = 0;
|
||||
result = 0;
|
||||
do {
|
||||
b = encoded.codeUnitAt(index++) - 63;
|
||||
result |= (b & 0x1f) << shift;
|
||||
shift += 5;
|
||||
} while (b >= 0x20);
|
||||
int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
|
||||
lng += dlng;
|
||||
|
||||
points.add(LatLng(lat / 1E5, lng / 1E5));
|
||||
}
|
||||
return points;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user