feat: refactor financial wallet UI components and add offline map service support
This commit is contained in:
@@ -3,15 +3,9 @@ import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'dart:ui' as ui; // للألوان
|
||||
// import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
// import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'package:intaleq_maps/intaleq_maps.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
// import 'package:latlong2/latlong.dart'
|
||||
// as latlng; // هذا مهم جداً للتعامل مع إحداثيات OSM
|
||||
import 'package:sefer_driver/constant/box_name.dart';
|
||||
import 'package:sefer_driver/controller/home/captin/map_driver_controller.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import '../../../constant/links.dart';
|
||||
@@ -35,7 +29,8 @@ class HomeCaptainController extends GetxController {
|
||||
Timer? activeTimer;
|
||||
Map data = {};
|
||||
bool isHomeMapActive = true;
|
||||
BitmapDescriptor carIcon = BitmapDescriptor.defaultMarker;
|
||||
InlqBitmap carIcon = InlqBitmap.defaultMarker;
|
||||
bool isMapReadyForCommands = false;
|
||||
bool isLoading = true;
|
||||
late double kazan = 0;
|
||||
double latePrice = 0;
|
||||
@@ -55,7 +50,8 @@ class HomeCaptainController extends GetxController {
|
||||
String totalMoneyInSEFER = '0';
|
||||
String totalDurationToday = '0';
|
||||
Timer? timer;
|
||||
late LatLng myLocation = const LatLng(33.5138, 36.2765);
|
||||
Timer? _cameraFollowTimer;
|
||||
LatLng myLocation = const LatLng(33.5138, 36.2765);
|
||||
String totalPoints = '0';
|
||||
String countRefuse = '0';
|
||||
bool mapType = false;
|
||||
@@ -81,10 +77,13 @@ class HomeCaptainController extends GetxController {
|
||||
// دالة لتغيير حالة الهيت ماب (عرض/إخفاء)
|
||||
void toggleHeatmap() async {
|
||||
isHeatmapVisible = !isHeatmapVisible;
|
||||
print("🔥 [Heatmap] Visibility toggled to: $isHeatmapVisible");
|
||||
if (isHeatmapVisible) {
|
||||
await fetchAndDrawHeatmap();
|
||||
startHeatmapCycle();
|
||||
} else {
|
||||
_heatmapTimer?.cancel();
|
||||
heatmapPolygons.clear();
|
||||
print("🧹 [Heatmap] Polygons cleared.");
|
||||
}
|
||||
update(); // تحديث الواجهة
|
||||
}
|
||||
@@ -96,6 +95,7 @@ class HomeCaptainController extends GetxController {
|
||||
|
||||
// دالة جلب البيانات ورسم الخريطة
|
||||
Future<void> fetchAndDrawHeatmap() async {
|
||||
print("🚀 [Heatmap] Fetching live data...");
|
||||
// استخدم الرابط المباشر لملف JSON لسرعة قصوى
|
||||
final String jsonUrl =
|
||||
"https://api.intaleq.xyz/intaleq/ride/rides/heatmap_live.json";
|
||||
@@ -107,20 +107,26 @@ class HomeCaptainController extends GetxController {
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final List<dynamic> data = json.decode(response.body);
|
||||
print("✅ [Heatmap] Data received. Points count: ${data.length}");
|
||||
_generatePolygons(data);
|
||||
} else {
|
||||
print("⚠️ [Heatmap] Server error: ${response.statusCode}");
|
||||
}
|
||||
} catch (e) {
|
||||
print("Heatmap Error: $e");
|
||||
print("❌ [Heatmap] Error: $e");
|
||||
}
|
||||
}
|
||||
|
||||
void _generatePolygons(List<dynamic> data) {
|
||||
print("🎨 [Heatmap] Processing polygons...");
|
||||
Set<Polygon> tempPolygons = {};
|
||||
|
||||
// الأوفست لرسم المربع (نصف حجم الشبكة)
|
||||
// الشبكة دقتها 0.01 درجة، لذا نصفها 0.005
|
||||
double offset = 0.005;
|
||||
|
||||
int highCount = 0, medCount = 0, lowCount = 0;
|
||||
|
||||
for (var point in data) {
|
||||
double lat = double.parse(point['lat'].toString());
|
||||
double lng = double.parse(point['lng'].toString());
|
||||
@@ -133,24 +139,27 @@ class HomeCaptainController extends GetxController {
|
||||
|
||||
// 🧠 منطق الألوان: ندمج الذكاء (Intensity) مع العدد (Count)
|
||||
if (intensity == 'high' || count >= 5) {
|
||||
highCount++;
|
||||
// منطقة مشتعلة (أحمر)
|
||||
// إما فيها طلبات ضائعة (Timeout) أو فيها عدد كبير من الطلبات
|
||||
color = Colors.red.withOpacity(0.35);
|
||||
strokeColor = Colors.red.withOpacity(0.8);
|
||||
color = Colors.red.withValues(alpha: 0.35);
|
||||
strokeColor = Colors.red.withValues(alpha: 0.8);
|
||||
} else if (intensity == 'medium' || count >= 3) {
|
||||
medCount++;
|
||||
// منطقة متوسطة (برتقالي)
|
||||
color = Colors.orange.withOpacity(0.35);
|
||||
strokeColor = Colors.orange.withOpacity(0.8);
|
||||
color = Colors.orange.withValues(alpha: 0.35);
|
||||
strokeColor = Colors.orange.withValues(alpha: 0.8);
|
||||
} else {
|
||||
lowCount++;
|
||||
// منطقة خفيفة (أصفر)
|
||||
color = Colors.yellow.withOpacity(0.3);
|
||||
strokeColor = Colors.yellow.withOpacity(0.6);
|
||||
color = Colors.yellow.withValues(alpha: 0.3);
|
||||
strokeColor = Colors.yellow.withValues(alpha: 0.6);
|
||||
}
|
||||
|
||||
// رسم المربع
|
||||
tempPolygons.add(Polygon(
|
||||
polygonId: PolygonId("$lat-$lng"),
|
||||
consumeTapEvents: true, // للسماح بالضغط عليه مستقبلاً
|
||||
// consumeTapEvents: true, // للسماح بالضغط عليه مستقبلاً
|
||||
points: [
|
||||
LatLng(lat - offset, lng - offset),
|
||||
LatLng(lat + offset, lng - offset),
|
||||
@@ -164,13 +173,27 @@ class HomeCaptainController extends GetxController {
|
||||
}
|
||||
|
||||
heatmapPolygons = tempPolygons;
|
||||
print(
|
||||
"✨ [Heatmap] Rendering Done. (🔥 High: $highCount, 🟠 Med: $medCount, 🟡 Low: $lowCount)");
|
||||
print("📍 [Heatmap] Total Polygons on Map: ${heatmapPolygons.length}");
|
||||
update(); // تحديث الخريطة
|
||||
}
|
||||
|
||||
// دالة لتشغيل الخريطة الحرارية كل فترة (مثلاً عند فتح الصفحة)
|
||||
Timer? _heatmapTimer;
|
||||
|
||||
// دالة لتشغيل الخريطة الحرارية كل فترة (كل 5 دقائق) لضمان نشاط البيانات
|
||||
void startHeatmapCycle() {
|
||||
_heatmapTimer?.cancel();
|
||||
fetchAndDrawHeatmap();
|
||||
// يمكن تفعيل Timer هنا لو أردت تحديثها تلقائياً كل 5 دقائق
|
||||
|
||||
_heatmapTimer = Timer.periodic(const Duration(minutes: 5), (timer) {
|
||||
if (isHeatmapVisible) {
|
||||
print("🔄 [Heatmap] Periodic refresh started...");
|
||||
fetchAndDrawHeatmap();
|
||||
} else {
|
||||
timer.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void goToWalletFromConnect() {
|
||||
@@ -185,16 +208,8 @@ class HomeCaptainController extends GetxController {
|
||||
}
|
||||
|
||||
void addCustomCarIcon() {
|
||||
ImageConfiguration config = ImageConfiguration(
|
||||
size: const Size(30, 35), devicePixelRatio: Get.pixelRatio);
|
||||
BitmapDescriptor.asset(
|
||||
config,
|
||||
'assets/images/car.png',
|
||||
// mipmaps: false,
|
||||
).then((value) {
|
||||
carIcon = value;
|
||||
update();
|
||||
});
|
||||
carIcon = InlqBitmap.fromAsset('assets/images/car.png');
|
||||
update();
|
||||
}
|
||||
|
||||
String stringActiveDuration = '';
|
||||
@@ -259,7 +274,7 @@ class HomeCaptainController extends GetxController {
|
||||
|
||||
// 3. إظهار الديالوج المانع
|
||||
Get.defaultDialog(
|
||||
title: "حسابك مقيد مؤقتاً ⛔",
|
||||
title: "Your account is temporarily restricted ⛔".tr,
|
||||
titleStyle:
|
||||
const TextStyle(color: Colors.red, fontWeight: FontWeight.bold),
|
||||
barrierDismissible: false, // 🚫 ممنوع الإغلاق بالضغط خارجاً
|
||||
@@ -269,8 +284,9 @@ class HomeCaptainController extends GetxController {
|
||||
const Icon(Icons.timer_off_outlined,
|
||||
size: 50, color: Colors.orange),
|
||||
const SizedBox(height: 15),
|
||||
const Text(
|
||||
"لقد تجاوزت حد الإلغاء المسموح به (3 مرات).\nلا يمكنك العمل حتى انتهاء العقوبة.",
|
||||
Text(
|
||||
"You have exceeded the allowed cancellation limit (3 times).\nYou cannot work until the penalty expires."
|
||||
.tr,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
@@ -296,11 +312,11 @@ class HomeCaptainController extends GetxController {
|
||||
? () {
|
||||
Get.back(); // إغلاق الديالوج
|
||||
box.remove(BoxName.blockUntilDate); // إزالة الحظر
|
||||
Get.snackbar("أهلاً بك", "يمكنك الآن استقبال الطلبات",
|
||||
Get.snackbar("Welcome".tr, "You can now receive orders".tr,
|
||||
backgroundColor: Colors.green);
|
||||
}
|
||||
: null, // زر معطل
|
||||
child: Text(isFinished ? "Go Online" : "انتظر انتهاء الوقت"),
|
||||
child: Text(isFinished ? "Go Online".tr : "Wait for timer".tr),
|
||||
);
|
||||
}),
|
||||
);
|
||||
@@ -334,7 +350,13 @@ class HomeCaptainController extends GetxController {
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
print("🔥 [HomeCaptain] onClose called. Tearing down map resources...");
|
||||
_blockTimer?.cancel();
|
||||
activeTimer?.cancel();
|
||||
_cameraFollowTimer?.cancel();
|
||||
_heatmapTimer?.cancel();
|
||||
stopTimer();
|
||||
mapHomeCaptainController = null;
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
@@ -377,7 +399,8 @@ class HomeCaptainController extends GetxController {
|
||||
confirm: MyElevatedButton(
|
||||
title: 'Ok , See you Tomorrow'.tr,
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
// إغلاق الديالوج والعودة قسرياً
|
||||
navigatorKey.currentState?.pop();
|
||||
Get.back();
|
||||
}));
|
||||
}
|
||||
@@ -395,26 +418,40 @@ class HomeCaptainController extends GetxController {
|
||||
update();
|
||||
}
|
||||
|
||||
// late GoogleMapController mapHomeCaptainController;
|
||||
GoogleMapController? mapHomeCaptainController;
|
||||
// final locationController = Get.find<LocationController>();
|
||||
// late IntaleqMapController mapHomeCaptainController;
|
||||
IntaleqMapController? mapHomeCaptainController;
|
||||
|
||||
// --- FIX 2: Smart Map Creation ---
|
||||
void onMapCreated(GoogleMapController controller) {
|
||||
void onMapCreated(IntaleqMapController controller) {
|
||||
print("🔥 [HomeCaptain] onMapCreated started");
|
||||
mapHomeCaptainController = controller;
|
||||
|
||||
// Check actual location before moving camera
|
||||
var currentLoc = locationController.myLocation;
|
||||
if (currentLoc.latitude != 0 && currentLoc.longitude != 0) {
|
||||
controller.animateCamera(
|
||||
CameraUpdate.newLatLng(currentLoc),
|
||||
);
|
||||
} else {
|
||||
// Optional: Move to default city view instead of ocean
|
||||
controller.animateCamera(
|
||||
CameraUpdate.newLatLngZoom(myLocation, 10),
|
||||
);
|
||||
}
|
||||
// We delay the first move to ensure the native side is fully ready
|
||||
Future.delayed(const Duration(milliseconds: 800), () {
|
||||
if (isClosed || mapHomeCaptainController == null) return;
|
||||
|
||||
try {
|
||||
var currentLoc = locationController.myLocation;
|
||||
if (currentLoc.latitude != 0 &&
|
||||
currentLoc.latitude != null &&
|
||||
!currentLoc.latitude.isNaN) {
|
||||
print(
|
||||
"🔥 [HomeCaptain] Safely moving camera to: ${currentLoc.latitude}");
|
||||
mapHomeCaptainController!.moveCamera(
|
||||
CameraUpdate.newLatLngZoom(currentLoc, 15),
|
||||
);
|
||||
} else {
|
||||
print("🔥 [HomeCaptain] Safely moving to default Damascus");
|
||||
mapHomeCaptainController!.moveCamera(
|
||||
CameraUpdate.newLatLngZoom(myLocation, 12),
|
||||
);
|
||||
}
|
||||
// Mark as ready for regular listener updates
|
||||
isMapReadyForCommands = true;
|
||||
} catch (e) {
|
||||
print("❌ [HomeCaptain] Map move failed: $e");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void savePeriod(Duration period) {
|
||||
@@ -441,7 +478,7 @@ class HomeCaptainController extends GetxController {
|
||||
await getCaptainDurationOnToday();
|
||||
String? initialDurationStr = totalDurationToday;
|
||||
|
||||
if (initialDurationStr != null) {
|
||||
if (initialDurationStr != '0') {
|
||||
// تحويل النص (01:20:30) إلى كائن Duration
|
||||
List<String> parts = initialDurationStr.split(':');
|
||||
_currentDuration = Duration(
|
||||
@@ -490,16 +527,27 @@ class HomeCaptainController extends GetxController {
|
||||
getlocation() async {
|
||||
isLoading = true;
|
||||
update();
|
||||
// This ensures we try to get a fix, but map doesn't crash if it fails
|
||||
await locationController.getLocation();
|
||||
try {
|
||||
// ننتظر جلب الموقع مع مهلة 10 ثوانٍ لتجنب التعليق
|
||||
var locData = await locationController.getLocation().timeout(
|
||||
const Duration(seconds: 10),
|
||||
onTimeout: () => null,
|
||||
);
|
||||
|
||||
var loc = locationController.myLocation;
|
||||
if (loc.latitude != 0) {
|
||||
myLocation = loc;
|
||||
if (locData != null && locData.latitude != null) {
|
||||
myLocation = LatLng(locData.latitude!, locData.longitude!);
|
||||
print(
|
||||
"📍 [HomeCaptain] Location updated: ${myLocation.latitude}, ${myLocation.longitude}");
|
||||
} else {
|
||||
print(
|
||||
"⚠️ [HomeCaptain] Could not get current location, using default.");
|
||||
}
|
||||
} catch (e) {
|
||||
print("❌ Error in getlocation: $e");
|
||||
} finally {
|
||||
isLoading = false;
|
||||
update();
|
||||
}
|
||||
|
||||
isLoading = false;
|
||||
update();
|
||||
}
|
||||
|
||||
Map walletDriverPointsDate = {};
|
||||
@@ -535,27 +583,18 @@ class HomeCaptainController extends GetxController {
|
||||
// 4. دالة نستدعيها عند العودة للصفحة الرئيسية
|
||||
void resumeHomeMapUpdates() {
|
||||
isHomeMapActive = true;
|
||||
// إنعاش الخريطة عند العودة
|
||||
if (mapHomeCaptainController != null) {
|
||||
onMapCreated(mapHomeCaptainController!);
|
||||
}
|
||||
// تم حذف استدعاء onMapCreated المتكرر لمنع قفز الخريطة عند العودة
|
||||
update();
|
||||
}
|
||||
|
||||
@override
|
||||
void onInit() async {
|
||||
// ✅ طلب الإذونات أولاً
|
||||
bool permissionsGranted = await PermissionsHelper.requestAllPermissions();
|
||||
// ✅ تم إرجاعه كتعليق لمنع الديالوج عند التشغيل (كما كان في الكود الأصلي)
|
||||
// bool permissionsGranted = await PermissionsHelper.requestAllPermissions();
|
||||
// if (permissionsGranted) {
|
||||
// await BackgroundServiceHelper.startService();
|
||||
// }
|
||||
|
||||
if (permissionsGranted) {
|
||||
// ✅ بدء الخدمة بعد الحصول على الإذونات
|
||||
await BackgroundServiceHelper.startService();
|
||||
print('✅ Background service started successfully');
|
||||
} else {
|
||||
print('❌ لم يتم منح الإذونات - الخدمة لن تعمل');
|
||||
// اعرض رسالة للمستخدم
|
||||
}
|
||||
// await locationBackController.requestLocationPermission();
|
||||
Get.put(FirebaseMessagesController());
|
||||
addToken();
|
||||
await getlocation();
|
||||
@@ -575,29 +614,25 @@ class HomeCaptainController extends GetxController {
|
||||
checkAndShowBlockDialog();
|
||||
box.write(BoxName.statusDriverLocation, 'off');
|
||||
// 2. عدل الليسنر ليصبح مشروطاً
|
||||
locationController.addListener(() {
|
||||
// الشرط الذهبي: إذا كانت الصفحة غير نشطة أو الخريطة غير موجودة، لا تفعل شيئاً
|
||||
if (!isHomeMapActive || mapHomeCaptainController == null || isClosed)
|
||||
return;
|
||||
// 2. مؤقت التتبع التلقائي (كل 5 ثوانٍ كما في الكود السابق)
|
||||
_cameraFollowTimer = Timer.periodic(const Duration(seconds: 5), (timer) {
|
||||
if (isClosed ||
|
||||
!isHomeMapActive ||
|
||||
mapHomeCaptainController == null ||
|
||||
!isActive) return;
|
||||
|
||||
if (isActive) {
|
||||
// isActive الخاصة بالزر "متصل/غير متصل"
|
||||
var loc = locationController.myLocation;
|
||||
if (loc.latitude != 0 && loc.longitude != 0) {
|
||||
try {
|
||||
mapHomeCaptainController!.animateCamera(
|
||||
CameraUpdate.newCameraPosition(
|
||||
CameraPosition(
|
||||
target: loc,
|
||||
zoom: 17.5,
|
||||
tilt: 50.0,
|
||||
bearing: locationController.heading,
|
||||
),
|
||||
),
|
||||
var loc = locationController.myLocation;
|
||||
if (loc.latitude != 0 && loc.latitude != null && !loc.latitude.isNaN) {
|
||||
try {
|
||||
// 🔥 Safety double-check before animating
|
||||
if (mapHomeCaptainController != null) {
|
||||
print("🔥 [HomeCaptain] Safely moving camera to: ${loc.latitude}");
|
||||
mapHomeCaptainController?.animateCamera(
|
||||
CameraUpdate.newLatLngZoom(loc, 17.5),
|
||||
);
|
||||
} catch (e) {
|
||||
// التقاط الخطأ بصمت إذا حدث أثناء الانتقال
|
||||
}
|
||||
} catch (e) {
|
||||
print("❌ [HomeCaptain] Camera movement failed: $e");
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -724,11 +759,4 @@ class HomeCaptainController extends GetxController {
|
||||
update();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
activeTimer?.cancel();
|
||||
stopTimer();
|
||||
mapHomeCaptainController?.dispose(); // Dispose controller
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user