feat: refactor financial wallet UI components and add offline map service support

This commit is contained in:
Hamza-Ayed
2026-04-21 00:35:30 +03:00
parent 4293d20561
commit b92db3bb39
99 changed files with 22888 additions and 27387 deletions

View File

@@ -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;
}
}