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

3
.env
View File

@@ -115,4 +115,5 @@ V=P
W=T W=T
X=D X=D
Y=S Y=S
Z=M Z=M
mapSaasKey=in_9478b32836d19cff73db3063

View File

@@ -34,6 +34,20 @@ subprojects {
defaultConfig { defaultConfig {
targetSdk 36 targetSdk 36
} }
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
if (project.hasProperty('kotlinOptions')) {
kotlinOptions {
jvmTarget = '17'
}
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
kotlinOptions {
jvmTarget = "17"
}
}
} }
} }
} }

View File

@@ -27,6 +27,7 @@ class AK {
X.r(X.r(X.r(Env.accountSIDTwillo, cn), cC), cs); X.r(X.r(X.r(Env.accountSIDTwillo, cn), cC), cs);
static final String serverAPI = X.r(X.r(X.r(Env.serverAPI, cn), cC), cs); static final String serverAPI = X.r(X.r(X.r(Env.serverAPI, cn), cC), cs);
static final String mapAPIKEY = Env.mapAPIKEY; static final String mapAPIKEY = Env.mapAPIKEY;
static final String mapSaasKey = Env.mapSaasKey;
static final String twilloRecoveryCode = static final String twilloRecoveryCode =
X.r(X.r(X.r(Env.twilloRecoveryCode, cn), cC), cs); X.r(X.r(X.r(Env.twilloRecoveryCode, cn), cC), cs);
static final String authTokenTwillo = static final String authTokenTwillo =

View File

@@ -1,33 +1,69 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart';
class AppColor { class AppColor {
// --- Core Brand Colors ---
/// **Primary Color:** The brand's signature blue.
static const Color primaryColor = Color(0xFF1DA1F2); static const Color primaryColor = Color(0xFF1DA1F2);
static const Color writeColor = Color(0xff222359);
/// **Text/Write Color:** Dynamic based on theme.
static Color get writeColor =>
Get.isDarkMode ? Colors.white : const Color(0xFF1A1A1A);
/// **Secondary Color:** Main background color, dynamic based on theme.
static Color get secondaryColor =>
Get.isDarkMode ? const Color(0xFF121212) : Colors.white;
/// **Surface Color:** For cards and elevated elements.
static Color get surfaceColor =>
Get.isDarkMode ? const Color(0xFF1E1E1E) : Colors.white;
/// **Card Color:** Specifically for card backgrounds.
static Color get cardColor =>
Get.isDarkMode ? const Color(0xFF1E1E1E) : Colors.white;
/// **Border Color:** Subtle borders for both modes.
static Color get borderColor =>
Get.isDarkMode ? Colors.white10 : Colors.black12;
/// **Accent Color:** Greyish accent.
static const Color accentColor = Color.fromARGB(255, 148, 140, 141);
// --- Neutral & Status Colors ---
/// **Grey Color:** Dynamic based on theme.
static Color get grayColor =>
Get.isDarkMode ? Colors.grey[400]! : const Color(0xFF8E8E93);
/// **Red Color (Error):** Clear red for alerts.
static const Color redColor = Color(0xFFD32F2F);
/// **Green Color (Success):** Positive green.
static const Color greenColor = Color(0xFF388E3C);
/// **Blue Color (Info):** Info text or success green variant.
static const Color blueColor = Color(0xFF1DA1F2);
/// **Yellow Color (Warning):** Warm yellow.
static const Color yellowColor = Color(0xFFFFA000);
// --- Tier & Social Colors ---
static const Color gold = Color(0xFFFFD700);
static const Color bronze = Color(0xFFCD7F32); static const Color bronze = Color(0xFFCD7F32);
static const Color goldenBronze = Color(0xFFB87333); // Golden bronze color static const Color goldenBronze = Color(0xFFB87333);
static const Color gold = Color(0xFFD4AF37); static const Color twitterColor = Color(0xFF1DA1F2);
static const Color secondaryColor = Colors.white;
static const Color accentColor = Colors.grey;
static const Color greyColor = Colors.grey;
static const Color twitterColor = Color(0xFF1DA1F2); // Twitter blue
static const Color redColor = Color(0xFFEA4335); // Google Red // --- Utility Colors ---
static const Color greenColor = Color(0xFF34A853); // Google Green
static const Color blueColor = Color(0xFF1DA1F2); // Google Blue
static const Color yellowColor = Color(0xFFFBBC05); // Google Yellow
static Color deepPurpleAccent =
const Color.fromARGB(255, 123, 76, 254).withOpacity(0.3);
// For dynamic elements like gradients static Color get greyColor => grayColor;
static List<Color> gradientStartEnd = [
Color.fromARGB(255, 40, 158, 232), // Start with primary color
Color.fromARGB(
255, 44, 63, 75), // End with a slightly darker shade of Twitter blue
];
static List<Color> secondaryGradientStartEnd = [ static Color get cyanBlue => const Color(0xFF1DA1F2);
const Color(0xFF1DA1F2), // Start with Twitter blue static Color get cyanAccent => const Color(0xFF1DA1F2).withOpacity(0.12);
const Color(0xFF0C7ABF), // End with a slightly darker shade of Twitter blue static Color get deepPurpleAccent => const Color(0xFFCE1126).withOpacity(0.1);
];
// --- Theme Helpers ---
static Brightness get brightness => Get.isDarkMode ? Brightness.dark : Brightness.light;
} }

View File

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

View File

@@ -0,0 +1,53 @@
import 'package:flutter/material.dart';
class FinanceDesignSystem {
// --- Colors ---
static const Color primaryDark = Color(0xFF0A0E21);
static const Color accentBlue = Color(0xFF3D5AFE);
static const Color successGreen = Color(0xFF00C853);
static const Color dangerRed = Color(0xFFD50000);
static const Color backgroundColor = Color(0xFFF6F8FA);
static const Color cardColor = Color(0xFFFFFFFF);
static const Color textSecondary = Color(0xFF757575);
static const Color textMuted = Color(0xFFBDBDBD);
// --- Gradients ---
static const LinearGradient balanceGradient = LinearGradient(
colors: [Color(0xFF0A0E21), Color(0xFF1A237E)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
static const LinearGradient dangerGradient = LinearGradient(
colors: [Color(0xFFD50000), Color(0xFFFF5252)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
// --- Radius ---
static const double mainRadius = 16.0;
static const double cardRadius = 20.0;
static const double buttonRadius = 12.0;
// --- Spacing ---
static const double horizontalPadding = 16.0;
static const double verticalSectionPadding = 24.0;
// --- Text Styles ---
static const TextStyle balanceStyle = TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Colors.white,
);
static const TextStyle headingStyle = TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: primaryDark,
);
static const TextStyle subHeadingStyle = TextStyle(
fontSize: 14,
color: textSecondary,
);
}

View File

@@ -10,6 +10,11 @@ class AppLink {
static String locationServer = static String locationServer =
'https://location.intaleq.xyz/intaleq/ride/location'; 'https://location.intaleq.xyz/intaleq/ride/location';
static String locationServerSide =
'https://location.intaleq.xyz/intaleq/ride/location';
static String mapSaasRoute = 'https://map-saas.intaleqapp.com/api/maps/route';
static String mapSaasPlaces =
'https://map-saas.intaleqapp.com/api/geocoding/places';
static const String routeApiBaseUrl = static const String routeApiBaseUrl =
"https://routesjo.intaleq.xyz/route/v1/driving"; "https://routesjo.intaleq.xyz/route/v1/driving";
static final String endPoint = 'https://api.intaleq.xyz/intaleq_v1'; static final String endPoint = 'https://api.intaleq.xyz/intaleq_v1';

View File

@@ -5,7 +5,7 @@ import 'package:google_fonts/google_fonts.dart';
import 'colors.dart'; import 'colors.dart';
class AppStyle { class AppStyle {
static TextStyle headTitle = TextStyle( static TextStyle get headTitle => TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 36, fontSize: 36,
color: AppColor.accentColor, color: AppColor.accentColor,
@@ -13,57 +13,61 @@ class AppStyle {
// ?GoogleFonts.markaziText().fontFamily // ?GoogleFonts.markaziText().fontFamily
? GoogleFonts.markaziText().fontFamily ? GoogleFonts.markaziText().fontFamily
: GoogleFonts.inter().fontFamily); : GoogleFonts.inter().fontFamily);
static TextStyle headTitle2 = TextStyle( static TextStyle get headTitle2 => TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 24, fontSize: 24,
color: AppColor.writeColor, color: AppColor.writeColor,
fontFamily: box.read(BoxName.lang) == 'ar' fontFamily: box.read(BoxName.lang) == 'ar'
? GoogleFonts.markaziText().fontFamily ? GoogleFonts.markaziText().fontFamily
: GoogleFonts.inter().fontFamily); : GoogleFonts.inter().fontFamily);
static TextStyle title = TextStyle( static TextStyle get title => TextStyle(
fontWeight: FontWeight.normal, fontWeight: FontWeight.normal,
fontSize: 16, fontSize: 16,
color: AppColor.writeColor, color: AppColor.writeColor,
fontFamily: box.read(BoxName.lang) == 'ar' fontFamily: box.read(BoxName.lang) == 'ar'
? GoogleFonts.markaziText().fontFamily ? GoogleFonts.markaziText().fontFamily
: GoogleFonts.inter().fontFamily); : GoogleFonts.inter().fontFamily);
static TextStyle subtitle = TextStyle( static TextStyle get subtitle => TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 12, fontSize: 12,
color: AppColor.writeColor, color: AppColor.writeColor,
fontFamily: box.read(BoxName.lang) == 'ar' fontFamily: box.read(BoxName.lang) == 'ar'
? GoogleFonts.markaziText().fontFamily ? GoogleFonts.markaziText().fontFamily
: GoogleFonts.inter().fontFamily); : GoogleFonts.inter().fontFamily);
static TextStyle number = const TextStyle( static TextStyle get number => TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 14, fontSize: 14,
color: AppColor.writeColor, color: AppColor.writeColor,
fontFamily: 'digit'); fontFamily: 'digit');
static BoxDecoration boxDecoration = const BoxDecoration( static BoxDecoration get boxDecoration => BoxDecoration(
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: AppColor.accentColor, blurRadius: 5, offset: Offset(2, 4)), color: AppColor.accentColor.withValues(alpha: 0.3),
blurRadius: 5,
offset: const Offset(2, 4)),
BoxShadow( BoxShadow(
color: AppColor.accentColor, blurRadius: 5, offset: Offset(-2, -2)) color: AppColor.accentColor.withValues(alpha: 0.1),
blurRadius: 5,
offset: const Offset(-2, -2))
], ],
color: AppColor.secondaryColor, color: AppColor.secondaryColor,
borderRadius: BorderRadius.all( borderRadius: const BorderRadius.all(
Radius.elliptical(15, 30), Radius.elliptical(15, 30),
)); ));
static BoxDecoration boxDecoration1 = const BoxDecoration( static BoxDecoration get boxDecoration1 => BoxDecoration(
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Color.fromARGB(255, 237, 230, 230), color: Colors.black.withValues(alpha: 0.05),
blurRadius: 5, blurRadius: 10,
offset: Offset(2, 4)), offset: const Offset(0, 4)),
BoxShadow( BoxShadow(
color: Color.fromARGB(255, 242, 237, 237), color: AppColor.primaryColor.withValues(alpha: 0.02),
blurRadius: 5, blurRadius: 5,
offset: Offset(-2, -2)) offset: const Offset(-2, -2))
], ],
color: AppColor.secondaryColor, color: AppColor.secondaryColor,
borderRadius: BorderRadius.all( borderRadius: const BorderRadius.all(
Radius.elliptical(15, 30), Radius.elliptical(15, 30),
), ),
); );

View File

@@ -8,7 +8,6 @@ import '../../../constant/links.dart';
import '../../../main.dart'; import '../../../main.dart';
import '../../../views/home/Captin/history/history_details_page.dart'; import '../../../views/home/Captin/history/history_details_page.dart';
import '../../functions/crud.dart'; import '../../functions/crud.dart';
import '../../functions/encrypt_decrypt.dart';
class HistoryCaptainController extends GetxController { class HistoryCaptainController extends GetxController {
bool isloading = false; bool isloading = false;

View File

@@ -379,16 +379,20 @@ class LoginDriverController extends GetxController {
box.read(BoxName.tokenDriver).toString() || box.read(BoxName.tokenDriver).toString() ||
serverData['data'][0]['fingerPrint'].toString() != serverData['data'][0]['fingerPrint'].toString() !=
fingerPrint.toString()) { fingerPrint.toString()) {
await Get.defaultDialog( Get.defaultDialog(
barrierDismissible: false, barrierDismissible: false,
title: 'Device Change Detected'.tr, title: 'Device Change Detected'.tr,
middleText: 'Please verify your identity'.tr, middleText: 'Please verify your identity'.tr,
textConfirm: 'Verify'.tr, textConfirm: 'Verify'.tr,
confirmTextColor: Colors.white, confirmTextColor: Colors.white,
onConfirm: () { onConfirm: () {
Get.back(); // نغلق الـ Dialog أولاً بشكل صريح
// انتقل لصفحة OTP الجديدة if (Get.isDialogOpen ?? false) {
Get.to( Get.back();
}
// ثم ننتقل لصفحة OTP
Get.offAll(
() => OtpVerificationPage( () => OtpVerificationPage(
phone: d['phone'].toString(), phone: d['phone'].toString(),
deviceToken: fingerPrint.toString(), deviceToken: fingerPrint.toString(),
@@ -399,6 +403,9 @@ class LoginDriverController extends GetxController {
); );
}, },
); );
isloading = false;
update();
return true; // نخرج من الدالة هنا لنسمح لـ OTP بالتعامل مع الأمر
} }
// } // }
} }

View File

@@ -1,15 +1,17 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'dart:ui'; import 'dart:ui';
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_background_service/flutter_background_service.dart'; import 'package:flutter_background_service/flutter_background_service.dart';
import 'package:flutter_background_service_android/flutter_background_service_android.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_overlay_window/flutter_overlay_window.dart'; import 'package:flutter_overlay_window/flutter_overlay_window.dart';
import 'package:socket_io_client/socket_io_client.dart' as IO; import 'package:socket_io_client/socket_io_client.dart' as IO;
import 'package:flutter_overlay_window/flutter_overlay_window.dart' as Overlay; import 'package:flutter_overlay_window/flutter_overlay_window.dart' as Overlay;
import 'package:get_storage/get_storage.dart'; import 'package:get_storage/get_storage.dart';
import 'package:geolocator/geolocator.dart' as geo;
import '../../constant/box_name.dart'; import '../../constant/box_name.dart';
import '../firebase/local_notification.dart';
const String notificationChannelId = 'driver_service_channel'; const String notificationChannelId = 'driver_service_channel';
const int notificationId = 888; const int notificationId = 888;
@@ -35,14 +37,18 @@ Future<bool> onStart(ServiceInstance service) async {
IO.OptionBuilder() IO.OptionBuilder()
.setTransports(['websocket']) .setTransports(['websocket'])
.disableAutoConnect() .disableAutoConnect()
.setQuery({'driver_id': driverId, 'token': token}) .setQuery({
'driver_id': driverId,
'token': token,
'EIO': '3', // توافقية مع Workerman
})
.setReconnectionAttempts(double.infinity) .setReconnectionAttempts(double.infinity)
.build()); .build());
socket.connect(); socket.connect();
socket.onConnect((_) { socket.onConnect((_) {
print("✅ Background Service: Socket Connected!"); print("✅ Background Service: Socket Connected! ID: ${socket?.id}");
if (service is AndroidServiceInstance) { if (service is AndroidServiceInstance) {
flutterLocalNotificationsPlugin.show( flutterLocalNotificationsPlugin.show(
id: notificationId, id: notificationId,
@@ -70,39 +76,94 @@ Future<bool> onStart(ServiceInstance service) async {
final box = GetStorage(); final box = GetStorage();
bool isAppInForeground = box.read(BoxName.isAppInForeground) ?? false; bool isAppInForeground = box.read(BoxName.isAppInForeground) ?? false;
// 🔥 Check إضافي: هل الـ Overlay مفتوح بالفعل؟ // 🔥 Check إضافي: هل الـ Overlay مفتوح بالفعل؟ (للأندرويد فقط)
bool overlayActive = await Overlay.FlutterOverlayWindow.isActive(); bool overlayActive = false;
if (Platform.isAndroid) {
overlayActive = await Overlay.FlutterOverlayWindow.isActive();
}
if (isAppInForeground || overlayActive) { if (isAppInForeground || overlayActive) {
print("🛑 App is FOREGROUND or Overlay already shown. Skipping."); print("🛑 App is FOREGROUND or Overlay already shown. Skipping.");
return; return;
} }
// عرض الـ Overlay // عرض الـ Overlay (للأندرويد فقط)
print("🚀 App is BACKGROUND. Showing Overlay..."); if (Platform.isAndroid) {
try { print("🚀 App is BACKGROUND. Showing Overlay...");
await Overlay.FlutterOverlayWindow.showOverlay( try {
enableDrag: true, await Overlay.FlutterOverlayWindow.showOverlay(
overlayTitle: "طلب جديد", enableDrag: true,
overlayContent: "لديك طلب جديد وصل للتو!", overlayTitle: "طلب جديد",
flag: OverlayFlag.focusPointer, overlayContent: "لديك طلب جديد وصل للتو!",
positionGravity: PositionGravity.auto, flag: OverlayFlag.focusPointer,
height: WindowSize.matchParent, positionGravity: PositionGravity.auto,
width: WindowSize.matchParent, height: WindowSize.matchParent,
startPosition: const OverlayPosition(0, -30), width: WindowSize.matchParent,
); startPosition: const OverlayPosition(0, -30),
await Overlay.FlutterOverlayWindow.shareData(data); );
} catch (e) { await Overlay.FlutterOverlayWindow.shareData(data);
print("Overlay Error: $e"); } catch (e) {
print("Overlay Error: $e");
}
} else if (Platform.isIOS) {
// على iOS، نظهر إشعاراً عادياً لأن الـ Overlay غير موجود
flutterLocalNotificationsPlugin.show(
id: 1002,
title: "طلب رحلة جديد 🚖",
body: "لديك طلب رحلة جديد، افتح التطبيق للموافقة عليه",
notificationDetails: const NotificationDetails(
iOS: DarwinNotificationDetails(
presentAlert: true,
presentBadge: true,
presentSound: true,
),
),
payload: jsonEncode(data));
} }
}); });
} }
service.on('stopService').listen((event) { service.on('stopService').listen((event) {
socket?.disconnect(); socket?.clearListeners();
socket?.dispose();
service.stopSelf(); service.stopSelf();
}); });
// 🔥 Location management in background isolate (Using Geolocator)
geo.Position? latestPos;
// Listen to location changes continuously in the background
geo.Geolocator.getPositionStream(
locationSettings: geo.AndroidSettings(
accuracy: geo.LocationAccuracy.high,
distanceFilter: 10,
intervalDuration: const Duration(seconds: 10),
),
).listen((pos) {
latestPos = pos;
});
// 🔥 MERCY HEARTBEAT: Send location every 2 minutes to keep driver active in 'raids'
Timer.periodic(const Duration(minutes: 2), (timer) async {
if (socket != null && socket.connected && latestPos != null) {
try {
socket.emit('update_location', {
'driver_id': driverId,
'lat': latestPos!.latitude,
'lng': latestPos!.longitude,
'heading': latestPos!.heading,
'speed': latestPos!.speed * 3.6,
'status': box.read(BoxName.statusDriverLocation) ?? 'on',
'source': 'background_heartbeat'
});
print(
"💓 Background Mercy Heartbeat Sent: ${latestPos!.latitude}, ${latestPos!.longitude}");
} catch (e) {
print("❌ Background Heartbeat Error: $e");
}
}
});
Timer.periodic(const Duration(seconds: 30), (timer) async { Timer.periodic(const Duration(seconds: 30), (timer) async {
if (service is AndroidServiceInstance) { if (service is AndroidServiceInstance) {
if (await service.isForegroundService()) { if (await service.isForegroundService()) {

View File

@@ -1,3 +1,4 @@
/*
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
@@ -234,3 +235,4 @@ class CameraClassController extends GetxController {
super.onClose(); super.onClose();
} }
} }
*/

View File

@@ -604,6 +604,59 @@ class CRUD {
); );
return json.decode(response.body); return json.decode(response.body);
} }
Future<dynamic> getMapSaas({
required String link,
}) async {
var url = Uri.parse(link);
try {
var response = await http.get(
url,
headers: {
'Content-Type': 'application/json',
'x-api-key': Env.mapSaasKey,
},
);
Log.print('link -MapSaas: $link');
Log.print('response -MapSaas: ${response.body}');
if (response.statusCode == 200) {
return jsonDecode(response.body);
}
Log.print('MapSaas Error: ${response.statusCode} - ${response.body}');
return null;
} catch (e) {
Log.print('MapSaas Exception: $e');
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;
}
}
} }
class NoInternetException implements Exception { class NoInternetException implements Exception {

View File

@@ -1,7 +1,11 @@
import 'dart:io'; import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart'; import 'package:device_info_plus/device_info_plus.dart';
import 'package:get/get_core/src/get_main.dart';
import 'package:get/get_navigation/src/extension_navigation.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
import 'package:sefer_driver/views/widgets/mydialoug.dart';
import 'background_service.dart'; import 'background_service.dart';
@@ -28,18 +32,19 @@ class PermissionsHelper {
if (Platform.isAndroid) { if (Platform.isAndroid) {
final androidInfo = await DeviceInfoPlugin().androidInfo; final androidInfo = await DeviceInfoPlugin().androidInfo;
// Android 13+ (API 33+) يحتاج إذن POST_NOTIFICATIONS
if (androidInfo.version.sdkInt >= 33) { if (androidInfo.version.sdkInt >= 33) {
final status = await Permission.notification.request(); final status = await Permission.notification.request();
if (status.isDenied) { if (status.isDenied) {
print('⚠️ إذن الإشعارات مرفوض'); print('⚠️ إذن الإشعارات مرفوض');
mySnackbarWarning(
"يرجى منح صلاحية الإشعارات لضمان وصول الطلبات إليك");
return false; return false;
} }
if (status.isPermanentlyDenied) { if (status.isPermanentlyDenied) {
print('⚠️ إذن الإشعارات مرفوض بشكل دائم - افتح الإعدادات'); print('⚠️ إذن الإشعارات مرفوض بشكل دائم - افتح الإعدادات');
await openAppSettings(); mySnackbarWarning('يرجى فتح الإعدادات وتفعيل صلاحية الإشعارات');
return false; return false;
} }
} }
@@ -50,25 +55,31 @@ class PermissionsHelper {
/// طلب جميع الإذونات المطلوبة /// طلب جميع الإذونات المطلوبة
static Future<bool> requestAllPermissions() async { static Future<bool> requestAllPermissions() async {
// إذن الإشعارات أولاً // إذن الإشعارات (اختياري)
bool notificationGranted = await requestNotificationPermission(); await requestNotificationPermission();
if (!notificationGranted) return false;
// إذن الموقع // 1. طلب إذن الموقع الأساسي فقط إذا كان مرفوضاً
final locationStatus = await Permission.location.request(); var status = await Permission.location.status;
if (!locationStatus.isGranted) { if (status.isDenied) {
print('⚠️ إذن الموقع مرفوض'); status = await Permission.location.request();
}
if (status.isPermanentlyDenied) {
_showSettingsDialog('الموقع');
return false; return false;
} }
// إذن الموقع في الخلفية return status.isGranted || status.isLimited;
if (Platform.isAndroid) { }
final bgLocationStatus = await Permission.locationAlways.request();
if (!bgLocationStatus.isGranted) {
print('⚠️ إذن الموقع في الخلفية مرفوض');
}
}
return true; static void _showSettingsDialog(String permissionName) {
MyDialog().getDialog(
'صلاحية $permissionName مطلوبة',
'لقد قمت برفض صلاحية $permissionName سابقاً. يرجى تفعيلها من الإعدادات لتمكين التطبيق من العمل.',
() async {
await openAppSettings();
Get.back();
},
);
} }
} }

View File

@@ -1,15 +1,13 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:geolocator/geolocator.dart' as geo; import 'package:geolocator/geolocator.dart' as geo;
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:location/location.dart'; import 'package:location/location.dart';
import 'package:battery_plus/battery_plus.dart'; import 'package:battery_plus/battery_plus.dart';
import 'package:permission_handler/permission_handler.dart' as ph; import 'package:permission_handler/permission_handler.dart' as ph;
import 'package:sefer_driver/views/home/Captin/orderCaptin/order_request_page.dart';
import 'package:socket_io_client/socket_io_client.dart' as IO; import 'package:socket_io_client/socket_io_client.dart' as IO;
import 'package:sefer_driver/constant/table_names.dart'; import 'package:sefer_driver/constant/table_names.dart';
import 'package:trip_overlay_plugin/trip_overlay_plugin.dart'; import 'package:trip_overlay_plugin/trip_overlay_plugin.dart';
@@ -17,6 +15,7 @@ import '../../constant/box_name.dart';
import '../../constant/links.dart'; import '../../constant/links.dart';
import '../../main.dart'; import '../../main.dart';
import '../../print.dart'; import '../../print.dart';
import '../firebase/local_notification.dart';
import '../home/captin/home_captain_controller.dart'; import '../home/captin/home_captain_controller.dart';
import '../home/captin/map_driver_controller.dart'; import '../home/captin/map_driver_controller.dart';
import '../home/payment/captain_wallet_controller.dart'; import '../home/payment/captain_wallet_controller.dart';
@@ -63,6 +62,7 @@ class LocationController extends GetxController with WidgetsBindingObserver {
bool _isPowerSavingMode = false; bool _isPowerSavingMode = false;
final List<Map<String, dynamic>> _trackBuffer = []; final List<Map<String, dynamic>> _trackBuffer = [];
final List<Map<String, dynamic>> _behaviorBuffer = [];
LatLng? _lastPosForDistance; LatLng? _lastPosForDistance;
LatLng? _lastRecordedRealLoc; LatLng? _lastRecordedRealLoc;
@@ -137,26 +137,22 @@ class LocationController extends GetxController with WidgetsBindingObserver {
Log.print("📱 Lifecycle: App is in FOREGROUND"); Log.print("📱 Lifecycle: App is in FOREGROUND");
box.write(BoxName.isAppInForeground, true); box.write(BoxName.isAppInForeground, true);
// إيقاف خدمة الخلفية لأننا في الواجهة الآن // إيقاف خدمة الخلفية
BackgroundServiceHelper.stopService(); BackgroundServiceHelper.stopService();
// التأكد من أن السوكيت متصل، وإذا لا، نعيد الاتصال فوراً if (socket == null || (!socket!.connected && !_isInitializingSocket)) {
if (socket == null || !socket!.connected) { Log.print("🔄 Initializing Socket on resume...");
Log.print("🔄 Socket disconnected in background. Reconnecting now...");
initSocket(); initSocket();
} else {
// إذا كان متصلاً، ننعش المستمعين فقط للتأكد
_setupSocketListeners();
} }
} else if (state == AppLifecycleState.paused || } else if (state == AppLifecycleState.paused ||
state == AppLifecycleState.detached) { state == AppLifecycleState.detached) {
Log.print("📱 Lifecycle: App is in BACKGROUND"); Log.print("📱 Lifecycle: App is in BACKGROUND");
box.write(BoxName.isAppInForeground, false); box.write(BoxName.isAppInForeground, false);
// تشغيل خدمة الخلفية لضمان بقاء التطبيق حياً // تشغيل خدمة الخلفية للأندرويد لضمان بقاء التطبيق حياً
BackgroundServiceHelper.startService(); if (!Platform.isIOS) {
BackgroundServiceHelper.startService();
// ملاحظة: لا نقطع السوكيت هنا، نتركه يعمل قدر الإمكان }
} }
} }
@@ -179,69 +175,107 @@ class LocationController extends GetxController with WidgetsBindingObserver {
// ====== Socket Logic (Improved) ====== // ====== Socket Logic (Improved) ======
// =================================================================== // ===================================================================
bool _isInitializingSocket = false;
void initSocket() { void initSocket() {
// منع الاستدعاءات المتداخلة التي تسبب قتل الاتصال قبل اكتماله
if (_isInitializingSocket) {
Log.print("⏳ Socket is already initializing. Skipping redundant call.");
return;
}
if (socket != null && socket!.connected) {
Log.print("✅ Socket is already connected. No need to re-init.");
return;
}
String driverId = box.read(BoxName.driverID).toString(); String driverId = box.read(BoxName.driverID).toString();
String token = box.read(BoxName.tokenDriver).toString(); String token = box.read(BoxName.tokenDriver).toString();
String platform = Platform.isIOS ? 'ios' : 'android'; String platform = Platform.isIOS ? 'ios' : 'android';
// 1. إذا كان السوكيت موجوداً، فقط تأكد من اتصاله _isInitializingSocket = true;
// تنظيف السوكيت القديم فقط إذا كان موجوداً وغير متصل
if (socket != null) { if (socket != null) {
if (!socket!.connected) { Log.print("🧹 Cleaning up old socket instance...");
Log.print("🟡 Socket exists but disconnected. Reconnecting..."); socket!.clearListeners();
socket!.connect(); socket!.dispose();
} socket = null;
_setupSocketListeners(); // تحديث المستمعين
return;
} }
Log.print( Log.print(
"🟡 [LocationController] Creating NEW Socket for Driver: $driverId"); "🟡 [LocationController] Initializing NEW Socket for Driver: $driverId");
// 2. إنشاء الاتصال try {
socket = IO.io( // العودة للـ Websocket حصراً لأنه الوحيد الذي ينجح في فتح القناة
'https://location.intaleq.xyz', socket = IO.io(
IO.OptionBuilder() 'https://location.intaleq.xyz',
.setTransports(['websocket']) IO.OptionBuilder()
.enableAutoConnect() // تفعيل إعادة الاتصال التلقائي .setTransports(['websocket'])
.setQuery( .setQuery({'driver_id': driverId, 'token': token, 'EIO': '3'})
{'driver_id': driverId, 'token': token, 'platform': platform}) .enableForceNew()
.setReconnectionAttempts(double.infinity) .build());
.setReconnectionDelay(2000)
.build());
socket!.connect(); _setupSocketListeners();
_setupSocketListeners(); socket!.connect();
} catch (e) {
_isInitializingSocket = false;
Log.print("❌ Socket Initialization Exception: $e");
}
} }
// دالة منفصلة لضمان عدم تكرار المستمعين
void _setupSocketListeners() { void _setupSocketListeners() {
if (socket == null) return; if (socket == null) return;
// تنظيف القديم أولاً
socket!.off('connect'); socket!.off('connect');
socket!.off('disconnect'); socket!.off('disconnect');
socket!.off('new_ride_request'); socket!.off('connect_error');
socket!.off('ride_cancelled'); socket!.off('error');
socket!.onConnect((_) { socket!.onConnect((_) {
Log.print('✅ Socket Connected! ID: ${socket?.id}'); _isInitializingSocket = false;
isSocketConnected = true;
_startHeartbeat(); // ننتظر قليلاً للتأكد من تعبئة الـ IDs
Future.delayed(const Duration(milliseconds: 1000), () {
String? sid = socket?.id;
String? eid = socket?.io.engine?.id;
Log.print(
'✅ Socket Connected! ID: ${sid ?? eid ?? 'N/A'} (SID: $sid, EID: $eid)');
if (sid != null || eid != null) {
isSocketConnected = true;
_startHeartbeat();
}
});
}); });
socket!.onDisconnect((_) { socket!.onDisconnect((data) {
Log.print('❌ Socket Disconnected'); _isInitializingSocket = false;
Log.print('❌ Socket Disconnected: $data');
isSocketConnected = false; isSocketConnected = false;
_stopHeartbeat(); _stopHeartbeat();
}); });
socket!.onConnectError((err) { socket!.onConnectError((err) {
_isInitializingSocket = false;
Log.print('❌ Socket Connect Error: $err'); Log.print('❌ Socket Connect Error: $err');
}); });
socket!.onConnectTimeout((data) {
_isInitializingSocket = false;
Log.print('❌ Socket Connect Timeout: $data');
});
socket!.onError((err) { socket!.onError((err) {
_isInitializingSocket = false;
Log.print('❌ Socket General Error: $err'); Log.print('❌ Socket General Error: $err');
}); });
socket!.on('reconnect_attempt', (attempt) {
Log.print('🔄 Socket Reconnecting... Attempt: $attempt');
});
// 🔥 الاستماع للطلبات الجديدة // 🔥 الاستماع للطلبات الجديدة
socket!.on('new_ride_request', (data) { socket!.on('new_ride_request', (data) {
Log.print("🔔 Socket: New Ride Request Arrived!"); Log.print("🔔 Socket: New Ride Request Arrived!");
@@ -296,8 +330,17 @@ class LocationController extends GetxController with WidgetsBindingObserver {
if (!isAppInForeground) { if (!isAppInForeground) {
Log.print( Log.print(
"🛑 التطبيق في الخلفية. السوكيت سيتجاهل التوجيه ويترك المهمة للـ Overlay."); "📱 [LocationController] Order received in background (iOS/Android). Source: $source");
return; // 👈 هذا السطر يمنع السوكيت من إكمال العمل وفتح الصفحة
if (Platform.isIOS) {
// على iOS، نقوم بإظهار إشعار محلي لأن الـ Overlay غير مدعوم
NotificationController().showNotification(
"طلب رحلة جديد 🚖",
"لديك طلب رحلة جديد، افتح التطبيق للموافقة عليه",
jsonEncode(rideData),
'ding.wav');
}
return;
} }
try { try {
@@ -309,14 +352,23 @@ class LocationController extends GetxController with WidgetsBindingObserver {
// 3. تجهيز البيانات (DriverList) // 3. تجهيز البيانات (DriverList)
List<dynamic> driverList = []; List<dynamic> driverList = [];
if (rideData.length > 0) { if (rideData.isNotEmpty) {
var sortedKeys = rideData.keys.map((e) => int.tryParse(e) ?? 0).toList() var sortedKeys = rideData.keys
..sort(); .where((e) => int.tryParse(e) != null)
.map((e) => int.parse(e))
.toList()..sort();
for (var key in sortedKeys) { for (var key in sortedKeys) {
driverList.add(rideData[key.toString()]); driverList.add(rideData[key.toString()]);
} }
} }
// الحماية ضد البنية غير المكتملة
if (driverList.length <= 16) {
Log.print("❌ Socket Error: Parsed driver list is incomplete.");
return;
}
// 4. إغلاق النافذة (إن وجدت بالخطأ) والتنقل // 4. إغلاق النافذة (إن وجدت بالخطأ) والتنقل
try { try {
if (await TripOverlayPlugin.isOverlayActive()) { if (await TripOverlayPlugin.isOverlayActive()) {
@@ -459,21 +511,16 @@ class LocationController extends GetxController with WidgetsBindingObserver {
_uploadBatchTimer?.cancel(); _uploadBatchTimer?.cancel();
_socketHeartbeat?.cancel(); _socketHeartbeat?.cancel();
if (socket != null && socket!.connected) { if (socket != null) {
String driverId = box.read(BoxName.driverID).toString(); socket!.clearListeners();
socket!.emit('update_location', { socket!
'driver_id': driverId, .dispose(); // استخدام dispose بدلاً من disconnect لضمان تحرير الموارد على iOS
'lat': myLocation.latitude, }
'lng': myLocation.longitude,
'heading': heading, if (!Platform.isIOS) {
'speed': speed * 3.6, await BackgroundServiceHelper.stopService();
'status': 'close', // Changed to off
'distance': totalDistance
});
socket!.disconnect();
} }
await BackgroundServiceHelper.stopService();
socket = null; socket = null;
isSocketConnected = false; isSocketConnected = false;
_isReady = false; _isReady = false;
@@ -526,16 +573,31 @@ class LocationController extends GetxController with WidgetsBindingObserver {
Future<void> _flushBufferToServer() async { Future<void> _flushBufferToServer() async {
if (_trackBuffer.isEmpty) return; if (_trackBuffer.isEmpty) return;
List<Map<String, dynamic>> batch = List.from(_trackBuffer);
_trackBuffer.clear(); int itemsToTake = _trackBuffer.length > 100 ? 100 : _trackBuffer.length;
List<Map<String, dynamic>> batch = _trackBuffer.sublist(0, itemsToTake);
final String driverId = (box.read(BoxName.driverID) ?? '').toString(); final String driverId = (box.read(BoxName.driverID) ?? '').toString();
try { try {
await CRUD().post( var res = await CRUD().post(
link: '${AppLink.locationServer}/add_batch.php', link: '${AppLink.locationServer}/add_batch.php',
payload: {'driver_id': driverId, 'batch_data': jsonEncode(batch)}, payload: {'driver_id': driverId, 'batch_data': jsonEncode(batch)},
); );
if (res != 'failure') {
_trackBuffer.removeRange(0, itemsToTake);
} else {
_enforceBufferLimit();
}
} catch (e) { } catch (e) {
Log.print('❌ Failed to upload batch: $e'); Log.print('❌ Failed to upload batch: $e');
_enforceBufferLimit();
}
}
void _enforceBufferLimit() {
if (_trackBuffer.length > 500) {
_trackBuffer.removeRange(0, _trackBuffer.length - 500);
Log.print("⚠️ Buffer limit enforced. Removed oldest entries.");
} }
} }
@@ -547,32 +609,65 @@ class LocationController extends GetxController with WidgetsBindingObserver {
if (level >= powerSaveExitLevel) _isPowerSavingMode = false; if (level >= powerSaveExitLevel) _isPowerSavingMode = false;
if (previousMode != _isPowerSavingMode) { if (previousMode != _isPowerSavingMode) {
_startBatchTimers(); _startBatchTimers();
startLocationUpdates(); _updateLocationSettings();
} }
}); });
} }
Future<void> _updateLocationSettings() async {
if (_locSub == null) return;
int interval = _isPowerSavingMode ? 10000 : 5000;
try {
await location.changeSettings(
accuracy: LocationAccuracy.navigation,
interval: interval,
distanceFilter: _isPowerSavingMode ? 20 : 10,
);
Log.print("🔋 Location settings updated. Power Save: $_isPowerSavingMode");
} catch (e) {
Log.print("❌ Failed to update location settings: $e");
}
}
Future<void> _saveBehaviorIfMoved(LatLng pos, DateTime now, Future<void> _saveBehaviorIfMoved(LatLng pos, DateTime now,
{required double currentSpeed}) async { {required double currentSpeed}) async {
final dist = final dist =
(_lastSqlLoc == null) ? 999.0 : _calculateDistance(_lastSqlLoc!, pos); (_lastSqlLoc == null) ? 999.0 : _calculateDistance(_lastSqlLoc!, pos);
if (dist < 15.0) return; if (dist < 15.0) return;
final accel = _calcAcceleration(currentSpeed, now) ?? 0.0; final accel = _calcAcceleration(currentSpeed, now) ?? 0.0;
try { _lastSqlLoc = pos;
await sql.insertData({
'driver_id': (box.read(BoxName.driverID) ?? '').toString(), _behaviorBuffer.add({
'latitude': pos.latitude, 'driver_id': (box.read(BoxName.driverID) ?? '').toString(),
'longitude': pos.longitude, 'latitude': pos.latitude,
'acceleration': accel, 'longitude': pos.longitude,
'created_at': now.toIso8601String(), 'acceleration': accel,
'updated_at': now.toIso8601String(), 'created_at': now.toIso8601String(),
}, TableName.behavior); 'updated_at': now.toIso8601String(),
_lastSqlLoc = pos; });
} catch (e) {
Log.print('SQLite Error: $e'); if (_behaviorBuffer.length >= 10) {
_flushBehaviorBuffer();
} }
} }
void _flushBehaviorBuffer() {
if (_behaviorBuffer.isEmpty) return;
List<Map<String, dynamic>> batch = List.from(_behaviorBuffer);
_behaviorBuffer.clear();
Future.microtask(() async {
try {
for (var data in batch) {
await sql.insertData(data, TableName.behavior);
}
} catch (e) {
Log.print('SQLite Batch Insert Error: $e');
}
});
}
// استبدال دالة Haversine اليدوية بـ Geolocator في باقي الكود أيضاً // استبدال دالة Haversine اليدوية بـ Geolocator في باقي الكود أيضاً
// لأنها تعتمد على C++ في الأندرويد و Obj-C في الآيفون (Native Speed) // لأنها تعتمد على C++ في الأندرويد و Obj-C في الآيفون (Native Speed)
double _calculateDistance(LatLng a, LatLng b) { double _calculateDistance(LatLng a, LatLng b) {

View File

@@ -217,8 +217,12 @@ class SecurityHelper {
isNotTrust = await JailbreakRootDetection.instance.isNotTrust; isNotTrust = await JailbreakRootDetection.instance.isNotTrust;
isJailBroken = await JailbreakRootDetection.instance.isJailBroken; isJailBroken = await JailbreakRootDetection.instance.isJailBroken;
isRealDevice = await JailbreakRootDetection.instance.isRealDevice; isRealDevice = await JailbreakRootDetection.instance.isRealDevice;
isOnExternalStorage =
await JailbreakRootDetection.instance.isOnExternalStorage; // This method is only relevant/implemented for Android
if (Platform.isAndroid) {
isOnExternalStorage =
await JailbreakRootDetection.instance.isOnExternalStorage;
}
List<JailbreakIssue> issues = List<JailbreakIssue> issues =
await JailbreakRootDetection.instance.checkForIssues; await JailbreakRootDetection.instance.checkForIssues;
@@ -230,7 +234,6 @@ class SecurityHelper {
PackageInfo packageInfo = await PackageInfo.fromPlatform(); PackageInfo packageInfo = await PackageInfo.fromPlatform();
bundleId = packageInfo.packageName; bundleId = packageInfo.packageName;
if (bundleId.isNotEmpty) { if (bundleId.isNotEmpty) {
// Pass the CORRECT bundle ID to isTampered
isTampered = await JailbreakRootDetection.instance.isTampered(bundleId); isTampered = await JailbreakRootDetection.instance.isTampered(bundleId);
} }
} catch (e) { } catch (e) {

View File

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

View File

@@ -5,28 +5,21 @@ import 'dart:math';
import 'dart:math' as math; import 'dart:math' as math;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:intl/intl.dart';
import 'package:sefer_driver/controller/firebase/local_notification.dart';
import 'package:sefer_driver/controller/home/captin/behavior_controller.dart';
import 'package:sefer_driver/controller/home/captin/home_captain_controller.dart'; import 'package:sefer_driver/controller/home/captin/home_captain_controller.dart';
import 'package:sefer_driver/controller/home/navigation/decode_polyline_isolate.dart';
import 'package:sefer_driver/views/widgets/error_snakbar.dart'; import 'package:sefer_driver/views/widgets/error_snakbar.dart';
import 'package:sefer_driver/views/widgets/mydialoug.dart'; import 'package:sefer_driver/views/widgets/mydialoug.dart';
import 'package:bubble_head/bubble.dart'; import 'package:bubble_head/bubble.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart'; import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:google_polyline_algorithm/google_polyline_algorithm.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import '../../../constant/api_key.dart';
import '../../../constant/box_name.dart'; import '../../../constant/box_name.dart';
import '../../../constant/colors.dart'; import '../../../constant/colors.dart';
import '../../../constant/country_polygons.dart'; import '../../../constant/country_polygons.dart';
import '../../../constant/links.dart'; import '../../../constant/links.dart';
import '../../../constant/table_names.dart'; import '../../../constant/table_names.dart';
import '../../../env/env.dart';
import '../../../main.dart'; import '../../../main.dart';
import '../../../print.dart'; import '../../../print.dart';
import '../../../views/Rate/rate_passenger.dart'; import '../../../views/Rate/rate_passenger.dart';
@@ -36,6 +29,7 @@ import '../../firebase/notification_service.dart';
import '../../functions/crud.dart'; import '../../functions/crud.dart';
import '../../functions/location_controller.dart'; import '../../functions/location_controller.dart';
import '../../functions/tts.dart'; import '../../functions/tts.dart';
import 'behavior_controller.dart';
class MapDriverController extends GetxController { class MapDriverController extends GetxController {
bool isLoading = true; bool isLoading = true;
@@ -48,10 +42,10 @@ class MapDriverController extends GetxController {
List data = []; List data = [];
List dataDestination = []; List dataDestination = [];
LatLngBounds? boundsData; LatLngBounds? boundsData;
BitmapDescriptor carIcon = BitmapDescriptor.defaultMarker; InlqBitmap carIcon = InlqBitmap.defaultMarker;
BitmapDescriptor passengerIcon = BitmapDescriptor.defaultMarker; InlqBitmap passengerIcon = InlqBitmap.defaultMarker;
BitmapDescriptor startIcon = BitmapDescriptor.defaultMarker; InlqBitmap startIcon = InlqBitmap.defaultMarker;
BitmapDescriptor endIcon = BitmapDescriptor.defaultMarker; InlqBitmap endIcon = InlqBitmap.defaultMarker;
final List<LatLng> polylineCoordinates = []; final List<LatLng> polylineCoordinates = [];
final List<LatLng> polylineCoordinatesDestination = []; final List<LatLng> polylineCoordinatesDestination = [];
List<Polyline> polyLines = []; List<Polyline> polyLines = [];
@@ -104,7 +98,7 @@ class MapDriverController extends GetxController {
int remainingTimeToPassenger = 60; int remainingTimeToPassenger = 60;
int remainingTimeInPassengerLocatioWait = 60; int remainingTimeInPassengerLocatioWait = 60;
bool isDriverNearPassengerStart = false; bool isDriverNearPassengerStart = false;
GoogleMapController? mapController; IntaleqMapController? mapController;
late LatLng myLocation; late LatLng myLocation;
int remainingTimeTimerRideBegin = 60; int remainingTimeTimerRideBegin = 60;
String stringRemainingTimeRideBegin = ''; String stringRemainingTimeRideBegin = '';
@@ -158,16 +152,32 @@ class MapDriverController extends GetxController {
_posSub?.cancel(); _posSub?.cancel();
_posSub = null; _posSub = null;
mapController?.dispose(); // mapController?.dispose();
super.onClose(); super.onClose();
} }
void onMapCreated(GoogleMapController controller) { void onMapCreated(IntaleqMapController controller) {
mapController = controller; mapController = controller;
if (Get.isRegistered<LocationController>()) { if (Get.isRegistered<LocationController>()) {
myLocation = Get.find<LocationController>().myLocation; myLocation = Get.find<LocationController>().myLocation;
controller.animateCamera(CameraUpdate.newLatLngZoom(myLocation, 16)); controller.animateCamera(CameraUpdate.newLatLngZoom(myLocation, 16));
} }
// 🔥 رسم المسار فور جاهزية الخريطة
if (isRideStarted) {
// إذا كانت الرحلة بدأت، ارسم للمكان النهائي
getRoute(
origin: myLocation,
destination: latLngPassengerDestination,
routeColor: Colors.blue);
} else {
// إذا كان السائق ذاهب للراكب
getRoute(
origin: myLocation,
destination: latLngPassengerLocation,
routeColor: Colors.yellow);
}
// بدء الاستماع للموقع للملاحة وتحديث الماركر // بدء الاستماع للموقع للملاحة وتحديث الماركر
startListeningStepNavigation(); startListeningStepNavigation();
} }
@@ -239,7 +249,7 @@ class MapDriverController extends GetxController {
} }
takeSnapMap() { takeSnapMap() {
mapController!.takeSnapshot(); // mapController!.takeSnapshot();
} }
@override @override
@@ -254,7 +264,7 @@ class MapDriverController extends GetxController {
_passengerTimer?.cancel(); _passengerTimer?.cancel();
_waitingTimer?.cancel(); _waitingTimer?.cancel();
_posSub?.cancel(); _posSub?.cancel();
mapController?.dispose(); // mapController?.dispose();
} }
Future openGoogleMapFromDriverToPassenger() async { Future openGoogleMapFromDriverToPassenger() async {
@@ -307,7 +317,9 @@ class MapDriverController extends GetxController {
box.remove(BoxName.rideArguments); box.remove(BoxName.rideArguments);
// 3. عرض رسالة للسائق // 3. عرض رسالة للسائق
if (Get.isDialogOpen == true) Get.back(); // إغلاق أي ديالوج مفتوح if (Get.isDialogOpen == true) {
navigatorKey.currentState?.pop();
}
Get.defaultDialog( Get.defaultDialog(
title: "تم إلغاء الرحلة".tr, title: "تم إلغاء الرحلة".tr,
@@ -325,7 +337,7 @@ class MapDriverController extends GetxController {
), ),
confirm: ElevatedButton( confirm: ElevatedButton(
onPressed: () { onPressed: () {
Get.back(); // إغلاق الديالوج navigatorKey.currentState?.pop(); // إغلاق الديالوج
Get.offAll(() => HomeCaptain()); // العودة للرئيسية Get.offAll(() => HomeCaptain()); // العودة للرئيسية
}, },
child: Text("OK".tr), child: Text("OK".tr),
@@ -367,8 +379,8 @@ class MapDriverController extends GetxController {
box.write(BoxName.statusDriverLocation, 'blocked'); box.write(BoxName.statusDriverLocation, 'blocked');
// عرض رسالة العقوبة // عرض رسالة العقوبة
Get.snackbar("تم تقييد حسابك مؤقتاً ⛔", Get.snackbar("Your account is temporarily restricted ⛔".tr,
"بسبب كثرة الإلغاءات (3 مرات)، تم إيقاف استقبال الطلبات لمدة 4 ساعات.", "Due to excessive cancellations (3 times), receiving orders has been suspended for 4 hours.".tr,
duration: Duration(seconds: 8), duration: Duration(seconds: 8),
backgroundColor: Colors.red, backgroundColor: Colors.red,
colorText: Colors.white, colorText: Colors.white,
@@ -402,15 +414,21 @@ class MapDriverController extends GetxController {
Get.put(HomeCaptainController()).getRefusedOrderByCaptain(); Get.put(HomeCaptainController()).getRefusedOrderByCaptain();
} }
if (Get.isDialogOpen == true) Get.back(); if (Get.isDialogOpen == true) {
navigatorKey.currentState?.pop();
}
Get.offAll( Get.offAll(
() => HomeCaptain()); // العودة للرئيسية ليتم تطبيق الحظر هناك () => HomeCaptain()); // العودة للرئيسية ليتم تطبيق الحظر هناك
} else { } else {
if (Get.isDialogOpen == true) Get.back(); if (Get.isDialogOpen == true) {
navigatorKey.currentState?.pop();
}
Get.snackbar("Error", "Failed to cancel ride"); Get.snackbar("Error", "Failed to cancel ride");
} }
} catch (e) { } catch (e) {
if (Get.isDialogOpen == true) Get.back(); if (Get.isDialogOpen == true) {
navigatorKey.currentState?.pop();
}
Log.print("Error: $e"); Log.print("Error: $e");
} }
} }
@@ -707,7 +725,9 @@ class MapDriverController extends GetxController {
await calculateDistanceBetweenDriverAndPassengerLocation(); await calculateDistanceBetweenDriverAndPassengerLocation();
// إغلاق مؤشر التحميل لأننا حصلنا على النتيجة // إغلاق مؤشر التحميل لأننا حصلنا على النتيجة
if (Get.isDialogOpen == true) Get.back(); if (Get.isDialogOpen == true) {
navigatorKey.currentState?.pop();
}
if (distanceToPassenger < 100) { if (distanceToPassenger < 100) {
// زدت المسافة قليلاً لمرونة أكبر (150م) // زدت المسافة قليلاً لمرونة أكبر (150م)
@@ -752,15 +772,17 @@ class MapDriverController extends GetxController {
}); });
} else { } else {
// --- حالة الرفض (بعيد جداً) --- // --- حالة الرفض (بعيد جداً) ---
MyDialog().getDialog( MyDialog().getDialog('You are far from passenger location'.tr,
'You are far from passenger location'.tr,
'Please go closer to the passenger location (less than 150m)'.tr, 'Please go closer to the passenger location (less than 150m)'.tr,
() => Get.back() // إغلاق الديالوج فقط () {
); // الديالوج يغلق نفسه الآن تلقائياً
});
} }
} catch (e) { } catch (e) {
// تنظيف اللودينج في حال حدوث خطأ غير متوقع // تنظيف اللودينج في حال حدوث خطأ غير متوقع
if (Get.isDialogOpen == true) Get.back(); if (Get.isDialogOpen == true) {
navigatorKey.currentState?.pop();
}
Log.print("Error starting ride: $e"); Log.print("Error starting ride: $e");
Get.snackbar("Error", "Could not start ride. Please check internet."); Get.snackbar("Error", "Could not start ride. Please check internet.");
} }
@@ -1448,56 +1470,23 @@ class MapDriverController extends GetxController {
} }
void addCustomCarIcon() { void addCustomCarIcon() {
ImageConfiguration config = ImageConfiguration( carIcon = InlqBitmap.fromAsset('assets/images/car.png');
size: const Size(30, 35), devicePixelRatio: Get.pixelRatio); update();
BitmapDescriptor.asset(
config,
'assets/images/car.png',
// mipmaps: false,
).then((value) {
carIcon = value;
update();
});
} }
void addCustomStartIcon() async { void addCustomStartIcon() async {
// Create the marker with the resized image startIcon = InlqBitmap.fromAsset('assets/images/A.png');
update();
ImageConfiguration config = ImageConfiguration(
size: const Size(30, 30), devicePixelRatio: Get.pixelRatio);
BitmapDescriptor.asset(
config,
'assets/images/A.png',
).then((value) {
startIcon = value;
update();
});
} }
void addCustomEndIcon() { void addCustomEndIcon() {
ImageConfiguration config = ImageConfiguration( endIcon = InlqBitmap.fromAsset('assets/images/b.png');
size: const Size(25, 25), devicePixelRatio: Get.pixelRatio); update();
BitmapDescriptor.asset(
config,
'assets/images/b.png',
).then((value) {
endIcon = value;
update();
});
} }
void addCustomPassengerIcon() { void addCustomPassengerIcon() {
ImageConfiguration config = ImageConfiguration( passengerIcon = InlqBitmap.fromAsset('assets/images/picker.png');
size: const Size(30, 30), devicePixelRatio: Get.pixelRatio update();
// scale: 1.0,
);
BitmapDescriptor.asset(
config,
'assets/images/picker.png',
).then((value) {
passengerIcon = value;
update();
});
} }
var activeRouteSteps = <Map<String, dynamic>>[]; var activeRouteSteps = <Map<String, dynamic>>[];
@@ -1596,82 +1585,94 @@ class MapDriverController extends GetxController {
required LatLng destination, required LatLng destination,
required Color routeColor, required Color routeColor,
}) async { }) async {
// 1. استخدام الرابط الجديد والإعدادات الصحيحة if (mapController == null) return;
String coordinates =
'${origin.longitude},${origin.latitude};${destination.longitude},${destination.latitude}';
// استخدام الرابط من الكلاس المرجعي لأنه أحدث
var url =
"${AppLink.mapOSM}/route/v1/driving/$coordinates?steps=true&overview=full";
try { try {
var response = await http.get(Uri.parse(url)); // 1. طلب المسار من الباكيج
final response =
await mapController!.getDirections(origin, destination, steps: true);
if (response.statusCode == 200) { // 2. التعامل مع الـ JSON المباشر (الذي أرسله المستخدم)
var decoded = jsonDecode(response.body); // إذا كان الـ response يحتوي على الحقول مباشرة في الجذر
final String? encodedPoints = response['points'];
if (decoded['code'] != 'Ok' || (decoded['routes'] as List).isEmpty) { if (encodedPoints == null) {
mySnackeBarError("لم يتم العثور على مسار"); mySnackeBarError("No route points found".tr);
return; return;
} }
var route = decoded['routes'][0]; // 🔥 فك التشفير باستخدام compute لضمان أداء ممتاز
List<LatLng> fullRoute =
await compute(PolylineUtils.decode, encodedPoints);
// أ) تشغيل الـ Isolate لفك التشفير (ممتاز، ابقِ عليه) if (fullRoute.isEmpty) {
final String pointsString = route["geometry"]; mySnackeBarError("Failed to process route points".tr);
List<LatLng> fullRoute = return;
await compute(decodePolylineIsolate, pointsString); }
// ب) تهيئة المتغيرات // تحديث المسافة والوقت من الـ JSON الجديد
upcomingPathPoints.assignAll(fullRoute); distance = (response['distance'] ?? 0).toString();
traveledPathPoints.clear(); duration = (response['duration'] ?? 0).toString();
_lastTraveledIndex = 0;
// ج) رسم المسار الأولي // ب) تهيئة المتغيرات
polyLines.clear(); upcomingPathPoints.assignAll(fullRoute);
polyLines.add(Polyline( traveledPathPoints.clear();
polylineId: const PolylineId("upcoming_route"), _lastTraveledIndex = 0;
points: fullRoute,
width: 8,
color: routeColor,
startCap: Cap.roundCap,
endCap: Cap.roundCap,
));
// د) معالجة الخطوات (Legs & Steps) // ج) رسم المسار الأولي
List<dynamic> legs = route['legs']; polyLines.clear();
if (legs.isNotEmpty) { polyLines.add(Polyline(
final stepsList = List<Map<String, dynamic>>.from(legs[0]['steps']); polylineId: const PolylineId("upcoming_route"),
points: fullRoute,
width: 8,
color: routeColor,
));
// 🔥 استخدام دالة الترجمة المحسنة من الكلاس المرجعي // د) معالجة الخطوات (Legs & Steps) إذا توفرت في الـ JSON
for (var step in stepsList) { List<dynamic> legs = response['legs'] ?? [];
step['html_instructions'] = if (legs.isNotEmpty) {
_createInstructionFromManeuverSmart(step); final stepsList =
// تصحيح مواقع المناورات List<Map<String, dynamic>>.from(legs[0]['steps'] ?? []);
if (step['maneuver'] != null &&
step['maneuver']['location'] != null) { for (var step in stepsList) {
var loc = step['maneuver']['location']; step['html_instructions'] = _createInstructionFromManeuverSmart(step);
if (step['maneuver'] != null &&
step['maneuver']['location'] != null) {
var loc = step['maneuver']['location'];
// التعامل مع تنسيق OSRM [lng, lat]
if (loc is List && loc.length >= 2) {
step['end_location'] = {'lat': loc[1], 'lng': loc[0]}; step['end_location'] = {'lat': loc[1], 'lng': loc[0]};
} else if (loc is Map) {
step['end_location'] = loc;
} }
} }
}
routeSteps = stepsList; routeSteps = stepsList;
currentStepIndex = 0; currentStepIndex = 0;
// نطق أول تعليمة // نطق أول تعليمة
if (routeSteps.isNotEmpty) { if (routeSteps.isNotEmpty) {
currentInstruction = routeSteps[0]['html_instructions']; currentInstruction = routeSteps[0]['html_instructions'];
if (Get.isRegistered<TextToSpeechController>()) {
Get.find<TextToSpeechController>().speakText(currentInstruction); Get.find<TextToSpeechController>().speakText(currentInstruction);
} }
} }
} else {
// هـ) تحريك الكاميرا لتشمل المسار // في حال عدم وجود steps، نقوم بتصفيرها
if (fullRoute.isNotEmpty) { routeSteps = [];
final bounds = _boundsFromLatLngList(fullRoute); currentInstruction = "";
safeAnimateCamera(CameraUpdate.newLatLngBounds(bounds, 80));
}
update();
} }
// هـ) تحريك الكاميرا لتشمل المسار
if (fullRoute.isNotEmpty) {
final bounds = _boundsFromLatLngList(fullRoute);
safeAnimateCamera(CameraUpdate.newLatLngBounds(bounds,
left: 80, top: 80, right: 80, bottom: 80));
}
update();
} catch (e) { } catch (e) {
Log.print("Route Error: $e"); Log.print("Route Error: $e");
} }
@@ -1679,7 +1680,7 @@ class MapDriverController extends GetxController {
// 🔥 دالة الترجمة المحسنة (من NavigationController) // 🔥 دالة الترجمة المحسنة (من NavigationController)
String _createInstructionFromManeuverSmart(Map<String, dynamic> step) { String _createInstructionFromManeuverSmart(Map<String, dynamic> step) {
if (step['maneuver'] == null) return "تابع المسير"; if (step['maneuver'] == null) return "Continue straight".tr;
final maneuver = step['maneuver']; final maneuver = step['maneuver'];
final type = maneuver['type'] ?? 'continue'; final type = maneuver['type'] ?? 'continue';
@@ -1690,10 +1691,10 @@ class MapDriverController extends GetxController {
switch (type) { switch (type) {
case 'depart': case 'depart':
instruction = "انطلق"; instruction = "Go".tr;
break; break;
case 'arrive': case 'arrive':
return "لقد وصلت إلى وجهتك، $name"; return "You have arrived at your destination, @name".trParams({'name': name});
case 'turn': case 'turn':
case 'fork': case 'fork':
case 'roundabout': case 'roundabout':
@@ -1705,14 +1706,14 @@ class MapDriverController extends GetxController {
_getTurnInstruction(modifier); // استخدم نفس دالتك المساعدة هنا _getTurnInstruction(modifier); // استخدم نفس دالتك المساعدة هنا
break; break;
default: default:
instruction = "تابع المسير"; instruction = "Continue straight".tr;
} }
if (name.isNotEmpty) { if (name.isNotEmpty) {
if (type == 'continue') { if (type == 'continue') {
instruction += " على $name"; instruction += " ${"on".tr} $name";
} else { } else {
instruction += " نحو $name"; instruction += " ${"towards".tr} $name";
} }
} }
return instruction; return instruction;
@@ -1728,10 +1729,10 @@ class MapDriverController extends GetxController {
switch (type) { switch (type) {
case 'depart': case 'depart':
instruction = "انطلق"; instruction = "Go".tr;
break; break;
case 'arrive': case 'arrive':
instruction = "لقد وصلت إلى وجهتك"; instruction = "You have arrived at your destination".tr;
if (name.isNotEmpty) instruction += "، $name"; if (name.isNotEmpty) instruction += "، $name";
return instruction; return instruction;
case 'turn': case 'turn':
@@ -1742,20 +1743,20 @@ class MapDriverController extends GetxController {
instruction = _getTurnInstruction(modifier); instruction = _getTurnInstruction(modifier);
break; break;
case 'continue': case 'continue':
instruction = "استمر"; instruction = "Continue".tr;
break; break;
default: default:
instruction = "اتجه"; instruction = "Head".tr;
} }
if (name.isNotEmpty) { if (name.isNotEmpty) {
if (instruction == "استمر") { if (instruction == "استمر") {
instruction += " على $name"; instruction += " ${"on".tr} $name";
} else { } else {
instruction += " إلى $name"; instruction += " ${"to".tr} $name";
} }
} else if (type == 'continue' && modifier == 'straight') { } else if (type == 'continue' && modifier == 'straight') {
instruction = "استمر بشكل مستقيم"; instruction = "Continue straight".tr;
} }
return instruction; return instruction;
@@ -1767,23 +1768,23 @@ class MapDriverController extends GetxController {
String _getTurnInstruction(String modifier) { String _getTurnInstruction(String modifier) {
switch (modifier) { switch (modifier) {
case 'uturn': case 'uturn':
return "قم بالاستدارة والعودة"; return "Make a U-turn".tr;
case 'sharp right': case 'sharp right':
return "انعطف يمينًا بحدة"; return "Turn sharp right".tr;
case 'right': case 'right':
return "انعطف يمينًا"; return "Turn right".tr;
case 'slight right': case 'slight right':
return "انعطف يمينًا قليلاً"; return "Turn slight right".tr;
case 'straight': case 'straight':
return "استمر بشكل مستقيم"; return "Continue straight".tr;
case 'slight left': case 'slight left':
return "انعطف يسارًا قليلاً"; return "Turn slight left".tr;
case 'left': case 'left':
return "انعطف يسارًا"; return "Turn left".tr;
case 'sharp left': case 'sharp left':
return "انعطف يسارًا بحدة"; return "Turn sharp left".tr;
default: default:
return "اتجه"; return "Head".tr;
} }
} }
@@ -1850,7 +1851,7 @@ class MapDriverController extends GetxController {
void _advanceStep() { void _advanceStep() {
if (currentStepIndex >= _stepBounds.length - 1) { if (currentStepIndex >= _stepBounds.length - 1) {
// وصل للنهاية // وصل للنهاية
currentInstruction = "لقد وصلت إلى وجهتك"; currentInstruction = "You have arrived at your destination".tr;
return; return;
} }
@@ -1909,11 +1910,11 @@ class MapDriverController extends GetxController {
routeColor: Colors.blue); routeColor: Colors.blue);
} else { } else {
if (Get.isDialogOpen == true) Get.back(); if (Get.isDialogOpen == true) Get.back();
mySnackeBarError("يجب أن تكون أقرب من 100 متر للوصول"); mySnackeBarError("You must be closer than 100 meters to arrive".tr);
} }
} catch (e) { } catch (e) {
if (Get.isDialogOpen == true) Get.back(); if (Get.isDialogOpen == true) Get.back();
mySnackeBarError("حدث خطأ في الاتصال"); mySnackeBarError("A connection error occurred".tr);
} }
} }
@@ -1941,9 +1942,7 @@ class MapDriverController extends GetxController {
// 2. فك تشفير البوليلاين الخاص بالخطوة وتحويله إلى LatLng // 2. فك تشفير البوليلاين الخاص بالخطوة وتحويله إلى LatLng
// -->> هنا تم التصحيح <<-- // -->> هنا تم التصحيح <<--
List<LatLng> pts = decodePolyline(s['polyline']['points']) List<LatLng> pts = PolylineUtils.decode(s['polyline']['points']);
.map((point) => LatLng(point[0].toDouble(), point[1].toDouble()))
.toList();
// أضف نقاط البداية والنهاية إذا لم تكن موجودة في البوليلاين لضمان دقة الحدود // أضف نقاط البداية والنهاية إذا لم تكن موجودة في البوليلاين لضمان دقة الحدود
if (pts.isNotEmpty) { if (pts.isNotEmpty) {
@@ -1962,14 +1961,9 @@ class MapDriverController extends GetxController {
// A helper function to decode and convert the polyline string // A helper function to decode and convert the polyline string
List<LatLng> decodePolylineToLatLng(String polylineString) { List<LatLng> decodePolylineToLatLng(String polylineString) {
// 1. Decode the string into a list of number lists (e.g., [[lat, lng], ...]) // 1. Decode the string into a list of number lists (e.g., [[lat, lng], ...])
List<List<num>> decodedPoints = decodePolyline(polylineString); List<LatLng> decodedPoints = PolylineUtils.decode(polylineString);
// 2. Map each [lat, lng] pair to a LatLng object, ensuring conversion to double return decodedPoints;
List<LatLng> latLngPoints = decodedPoints
.map((point) => LatLng(point[0].toDouble(), point[1].toDouble()))
.toList();
return latLngPoints;
} }
// ================================================================= // =================================================================
@@ -1986,11 +1980,11 @@ class MapDriverController extends GetxController {
void _suggestOptimization() { void _suggestOptimization() {
Get.snackbar( Get.snackbar(
"تحسين أداء التطبيق", "Improve app performance".tr,
"لضمان أفضل تجربة، نقترح تعديل الإعدادات لتناسب جهازك. هل تود المتابعة؟", "To ensure the best experience, we suggest adjusting the settings to suit your device. Would you like to proceed?".tr,
duration: const Duration(seconds: 15), duration: const Duration(seconds: 15),
mainButton: TextButton( mainButton: TextButton(
child: const Text("نعم، قم بالتحسين"), child: Text("Yes, optimize".tr),
onPressed: () { onPressed: () {
updateInterval.value = 8; // غير الفترة إلى 8 ثوانٍ updateInterval.value = 8; // غير الفترة إلى 8 ثوانٍ
// save setting to shared_preferences // save setting to shared_preferences
@@ -2017,7 +2011,8 @@ class MapDriverController extends GetxController {
Future<void> _fitToBounds(LatLngBounds b, {double padding = 60}) async { Future<void> _fitToBounds(LatLngBounds b, {double padding = 60}) async {
// نستخدم الدالة الآمنة التي أنشأناها // نستخدم الدالة الآمنة التي أنشأناها
await safeAnimateCamera(CameraUpdate.newLatLngBounds(b, padding)); await safeAnimateCamera(CameraUpdate.newLatLngBounds(b,
left: padding, top: padding, right: padding, bottom: padding));
} }
double distanceBetweenDriverAndPassengerWhenConfirm = 0; double distanceBetweenDriverAndPassengerWhenConfirm = 0;
@@ -2154,7 +2149,8 @@ class MapDriverController extends GetxController {
LatLngBounds(northeast: northeast, southwest: southwest); LatLngBounds(northeast: northeast, southwest: southwest);
// Fit the camera to the bounds // Fit the camera to the bounds
var cameraUpdate = CameraUpdate.newLatLngBounds(boundsData, 140); var cameraUpdate = CameraUpdate.newLatLngBounds(boundsData,
left: 140, top: 140, right: 140, bottom: 140);
safeAnimateCamera(cameraUpdate); safeAnimateCamera(cameraUpdate);
} }
@@ -2395,22 +2391,19 @@ class MapDriverController extends GetxController {
polyLines.removeWhere((p) => p.polylineId.value == 'upcoming_route'); polyLines.removeWhere((p) => p.polylineId.value == 'upcoming_route');
polyLines.removeWhere((p) => p.polylineId.value == 'traveled_route'); polyLines.removeWhere((p) => p.polylineId.value == 'traveled_route');
// المسار المتبقي (أزرق) // المسار المتبقي
polyLines.add(Polyline( polyLines.add(Polyline(
polylineId: const PolylineId('upcoming_route'), polylineId: const PolylineId("upcoming_route"),
points: remaining, points: remaining,
color: Colors.blue,
width: 8, width: 8,
zIndex: 2, color: isRideStarted ? Colors.blue : Colors.yellow,
startCap: Cap.roundCap,
endCap: Cap.roundCap,
)); ));
// المسار المقطوع (رمادي) // المسار المقطوع (رمادي)
polyLines.add(Polyline( polyLines.add(Polyline(
polylineId: const PolylineId('traveled_route'), polylineId: const PolylineId('traveled_route'),
points: traveled, points: traveled,
color: Colors.grey.withOpacity(0.8), color: Colors.grey.withValues(alpha: 0.8),
width: 7, width: 7,
zIndex: 1, zIndex: 1,
)); ));

View File

@@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:intaleq_maps/intaleq_maps.dart';
class NavigationStep { class NavigationStep {
final String instruction; final String instruction;

View File

@@ -1,7 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:google_polyline_algorithm/google_polyline_algorithm.dart';
import 'package:sefer_driver/constant/api_key.dart'; import 'package:sefer_driver/constant/api_key.dart';
import 'package:sefer_driver/constant/box_name.dart'; import 'package:sefer_driver/constant/box_name.dart';
import 'package:sefer_driver/constant/links.dart'; import 'package:sefer_driver/constant/links.dart';
@@ -19,10 +18,10 @@ class NavigationService extends GetxService {
final RxSet<Polyline> polylines = <Polyline>{}.obs; final RxSet<Polyline> polylines = <Polyline>{}.obs;
final RxString currentInstruction = "".obs; final RxString currentInstruction = "".obs;
BitmapDescriptor carIcon = BitmapDescriptor.defaultMarker; InlqBitmap carIcon = InlqBitmap.defaultMarker;
BitmapDescriptor passengerIcon = BitmapDescriptor.defaultMarker; InlqBitmap passengerIcon = InlqBitmap.defaultMarker;
BitmapDescriptor startIcon = BitmapDescriptor.defaultMarker; InlqBitmap startIcon = InlqBitmap.defaultMarker;
BitmapDescriptor endIcon = BitmapDescriptor.defaultMarker; InlqBitmap endIcon = InlqBitmap.defaultMarker;
@override @override
void onInit() { void onInit() {
@@ -30,19 +29,11 @@ class NavigationService extends GetxService {
_loadCustomIcons(); _loadCustomIcons();
} }
void _loadCustomIcons() async { void _loadCustomIcons() {
carIcon = await _createBitmapDescriptor('assets/images/car.png'); carIcon = InlqBitmap.fromAsset('assets/images/car.png');
passengerIcon = await _createBitmapDescriptor('assets/images/picker.png'); passengerIcon = InlqBitmap.fromAsset('assets/images/picker.png');
startIcon = await _createBitmapDescriptor('assets/images/A.png'); startIcon = InlqBitmap.fromAsset('assets/images/A.png');
endIcon = await _createBitmapDescriptor('assets/images/b.png'); endIcon = InlqBitmap.fromAsset('assets/images/b.png');
}
Future<BitmapDescriptor> _createBitmapDescriptor(String assetName) {
return BitmapDescriptor.fromAssetImage(
ImageConfiguration(
size: const Size(30, 35), devicePixelRatio: Get.pixelRatio),
assetName,
);
} }
Future<Map<String, dynamic>?> getRoute({ Future<Map<String, dynamic>?> getRoute({
@@ -62,9 +53,7 @@ class NavigationService extends GetxService {
void drawRoute(Map<String, dynamic> routeData, {Color color = Colors.blue}) { void drawRoute(Map<String, dynamic> routeData, {Color color = Colors.blue}) {
final pointsString = routeData["overview_polyline"]["points"]; final pointsString = routeData["overview_polyline"]["points"];
final points = decodePolyline(pointsString) final points = PolylineUtils.decode(pointsString);
.map((p) => LatLng(p[0].toDouble(), p[1].toDouble()))
.toList();
final polyline = Polyline( final polyline = Polyline(
polylineId: PolylineId(routeData["summary"] ?? DateTime.now().toString()), polylineId: PolylineId(routeData["summary"] ?? DateTime.now().toString()),

View File

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

View File

@@ -1,16 +1,6 @@
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:intaleq_maps/intaleq_maps.dart';
// تم تعديل الدالة لتقبل وسيط من نوع `dynamic` لحل مشكلة عدم تطابق الأنواع مع دالة `compute`.
// هذه الدالة لا تزال تعمل كدالة من المستوى الأعلى (Top-level function)
// وهو شرط أساسي لاستخدامها مع دالة compute.
List<LatLng> decodePolylineIsolate(dynamic encodedMessage) {
// التأكد من أن الرسالة المستقبلة هي من نوع String
if (encodedMessage is! String) {
// إرجاع قائمة فارغة أو إظهار خطأ إذا كان النوع غير صحيح
return [];
}
final String encoded = encodedMessage;
List<LatLng> decodePolylineIsolate(String encoded) {
List<LatLng> points = []; List<LatLng> points = [];
int index = 0, len = encoded.length; int index = 0, len = encoded.length;
int lat = 0, lng = 0; int lat = 0, lng = 0;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:isolate'; import 'dart:isolate';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:intaleq_maps/intaleq_maps.dart';
import 'dart:math'; import 'dart:math';
/// Worker entrypoint (spawnUri/spawn). /// Worker entrypoint (spawnUri/spawn).

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:google_maps_flutter/google_maps_flutter.dart'; import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:sefer_driver/constant/style.dart'; import 'package:sefer_driver/constant/style.dart';
import '../../constant/api_key.dart'; import '../../constant/api_key.dart';

View File

@@ -4,109 +4,57 @@ import 'package:get/get.dart';
import '../../constant/box_name.dart'; import '../../constant/box_name.dart';
import '../../main.dart'; import '../../main.dart';
import '../themes/themes.dart'; import '../themes/themes.dart';
import '../profile/setting_controller.dart';
class LocaleController extends GetxController { class LocaleController extends GetxController {
Locale? language; Locale? language;
String countryCode = ''; String countryCode = '';
ThemeData appTheme = lightThemeEnglish; ThemeData get appTheme {
String lang = box.read(BoxName.lang) ?? 'en';
bool isDarkMode = box.read('isDarkMode') ?? false;
return _getThemeFor(lang, isDarkMode);
}
ThemeData get lightTheme {
String lang = box.read(BoxName.lang) ?? 'en';
return _getThemeFor(lang, false);
}
ThemeData get darkTheme {
String lang = box.read(BoxName.lang) ?? 'en';
return _getThemeFor(lang, true);
}
ThemeData _getThemeFor(String lang, bool isDarkMode) {
if (lang.startsWith('ar')) {
return isDarkMode ? darkThemeArabic : lightThemeArabic;
} else {
return isDarkMode ? darkThemeEnglish : lightThemeEnglish;
}
}
void refreshTheme() {
update();
}
void changeLang(String langcode) { void changeLang(String langcode) {
Locale locale; Locale locale = Locale(langcode);
switch (langcode) {
case "ar":
locale = const Locale("ar");
appTheme = lightThemeArabic;
box.write(BoxName.lang, 'ar');
break;
case "en":
locale = const Locale("en");
appTheme = lightThemeEnglish;
box.write(BoxName.lang, 'en');
break;
case "tr":
locale = const Locale("tr");
appTheme = lightThemeEnglish;
box.write(BoxName.lang, 'tr');
break;
case "fr":
locale = const Locale("fr");
appTheme = lightThemeEnglish;
box.write(BoxName.lang, 'fr');
break;
case "it":
locale = const Locale("it");
appTheme = lightThemeEnglish;
box.write(BoxName.lang, 'it');
break;
case "de":
locale = const Locale("de");
appTheme = lightThemeEnglish;
box.write(BoxName.lang, 'de');
break;
case "el":
locale = const Locale("el");
appTheme = lightThemeEnglish;
box.write(BoxName.lang, 'el');
break;
case "es":
locale = const Locale("es");
appTheme = lightThemeEnglish;
box.write(BoxName.lang, 'es');
break;
case "fa":
locale = const Locale("fa");
appTheme = lightThemeEnglish;
box.write(BoxName.lang, 'fa');
break;
case "zh":
locale = const Locale("zh");
appTheme = lightThemeEnglish;
box.write(BoxName.lang, 'zh');
break;
case "ru":
locale = const Locale("ru");
appTheme = lightThemeEnglish;
box.write(BoxName.lang, 'ru');
break;
case "hi":
locale = const Locale("hi");
appTheme = lightThemeEnglish;
box.write(BoxName.lang, 'hi');
break;
case "ar-ma":
locale = const Locale("ar-ma");
appTheme = lightThemeArabic;
box.write(BoxName.lang, 'ar-ma');
break;
case "ar-gulf":
locale = const Locale("ar-gulf");
appTheme = lightThemeArabic;
box.write(BoxName.lang, 'ar-gulf');
break;
default:
locale = Locale(Get.deviceLocale!.languageCode);
box.write(BoxName.lang, Get.deviceLocale!.languageCode);
appTheme = lightThemeEnglish;
break;
}
box.write(BoxName.lang, langcode); box.write(BoxName.lang, langcode);
Get.changeTheme(appTheme);
Get.updateLocale(locale); Get.updateLocale(locale);
update(); update();
} }
@override @override
void onInit() { void onInit() {
String? storedLang = box.read(BoxName.lang); String? storedLang = box.read(BoxName.lang);
if (storedLang == null) { if (storedLang == null) {
// Use device language if no language is stored
storedLang = Get.deviceLocale!.languageCode; storedLang = Get.deviceLocale!.languageCode;
box.write(BoxName.lang, storedLang); box.write(BoxName.lang, storedLang);
} }
language = Locale(storedLang);
changeLang(storedLang);
super.onInit(); super.onInit();
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -18,9 +18,7 @@ import '../../constant/colors.dart';
import '../../constant/info.dart'; import '../../constant/info.dart';
import '../../constant/links.dart'; import '../../constant/links.dart';
import '../../main.dart'; import '../../main.dart';
import '../../print.dart';
import '../functions/crud.dart'; import '../functions/crud.dart';
import '../functions/encrypt_decrypt.dart';
import '../functions/toast.dart'; import '../functions/toast.dart';
import 'paymob/paymob_wallet.dart'; import 'paymob/paymob_wallet.dart';
@@ -170,8 +168,8 @@ class PaymentController extends GetxController {
)), )),
allowsDelayedPaymentMethods: true, allowsDelayedPaymentMethods: true,
customerEphemeralKeySecret: Stripe.merchantIdentifier, customerEphemeralKeySecret: Stripe.merchantIdentifier,
appearance: const PaymentSheetAppearance( appearance: PaymentSheetAppearance(
shapes: PaymentSheetShape(borderRadius: 12), shapes: const PaymentSheetShape(borderRadius: 12),
colors: PaymentSheetAppearanceColors( colors: PaymentSheetAppearanceColors(
background: AppColor.secondaryColor, background: AppColor.secondaryColor,
), ),
@@ -725,7 +723,6 @@ class PaymentController extends GetxController {
box.write(BoxName.passengerWalletTotal, '0'); box.write(BoxName.passengerWalletTotal, '0');
} }
// getPassengerWallet(); // getPassengerWallet();
final localAuth = LocalAuthentication();
super.onInit(); super.onInit();
} }
} }

View File

@@ -1,28 +1,45 @@
import 'package:flutter/material.dart';
import 'package:sefer_driver/constant/box_name.dart'; import 'package:sefer_driver/constant/box_name.dart';
import 'package:sefer_driver/main.dart'; import 'package:sefer_driver/main.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../local/local_controller.dart';
class SettingController extends GetxController { class SettingController extends GetxController {
bool isGoogleMapsEnabled = false; bool isGoogleMapsEnabled = false;
bool isMapDarkMode = false;
bool isDarkMode = false;
void onChangMapApp() { void onChangMapApp() {
if (!isGoogleMapsEnabled) { isGoogleMapsEnabled = !isGoogleMapsEnabled;
isGoogleMapsEnabled = true; box.write(BoxName.googlaMapApp, isGoogleMapsEnabled);
box.write(BoxName.googlaMapApp, true); update();
update(); }
} else {
isGoogleMapsEnabled = false; void toggleMapTheme() {
box.write(BoxName.googlaMapApp, false); isMapDarkMode = !isMapDarkMode;
update(); box.write('isMapDarkMode', isMapDarkMode);
update();
}
void toggleAppTheme() {
isDarkMode = !isDarkMode;
box.write('isDarkMode', isDarkMode);
// Update the theme using the LocaleController to ensure correct fonts are used
if (Get.isRegistered<LocaleController>()) {
Get.find<LocaleController>().refreshTheme();
} }
update();
} }
@override @override
void onInit() { void onInit() {
if (box.read(BoxName.googlaMapApp) != null) { isGoogleMapsEnabled = box.read(BoxName.googlaMapApp) ?? false;
isGoogleMapsEnabled = box.read(BoxName.googlaMapApp); isMapDarkMode = box.read('isMapDarkMode') ?? false;
} isDarkMode = box.read('isDarkMode') ?? false;
update();
super.onInit(); super.onInit();
} }
} }

View File

@@ -2,146 +2,89 @@ import 'package:flutter/material.dart';
import 'package:sefer_driver/constant/style.dart'; import 'package:sefer_driver/constant/style.dart';
import '../../constant/colors.dart'; import '../../constant/colors.dart';
ThemeData lightThemeEnglish = ThemeData( ThemeData _createTheme({
required Brightness brightness,
required String fontFamily,
required Color scaffoldBackgroundColor,
required Color cardColor,
required Color dividerColor,
}) {
return ThemeData(
brightness: brightness,
fontFamily: fontFamily,
scaffoldBackgroundColor: scaffoldBackgroundColor,
cardColor: cardColor,
dividerColor: dividerColor,
primaryColor: AppColor.primaryColor,
colorScheme: ColorScheme.fromSeed(
seedColor: AppColor.primaryColor,
brightness: brightness,
primary: AppColor.primaryColor,
surface: cardColor,
background: scaffoldBackgroundColor,
error: AppColor.redColor,
),
textTheme: TextTheme(
displaySmall: AppStyle.title.copyWith(color: AppColor.writeColor),
displayLarge: AppStyle.headTitle.copyWith(color: AppColor.writeColor),
displayMedium: AppStyle.headTitle2.copyWith(color: AppColor.writeColor),
bodyLarge: AppStyle.title.copyWith(color: AppColor.writeColor),
bodyMedium: AppStyle.subtitle.copyWith(color: AppColor.writeColor),
titleLarge: AppStyle.headTitle2.copyWith(color: AppColor.writeColor),
),
appBarTheme: AppBarTheme(
elevation: 0,
backgroundColor: scaffoldBackgroundColor,
centerTitle: true,
iconTheme: const IconThemeData(color: AppColor.primaryColor),
titleTextStyle: AppStyle.headTitle2.copyWith(color: AppColor.writeColor),
),
dialogTheme: DialogThemeData(
backgroundColor: cardColor,
contentTextStyle: AppStyle.title.copyWith(color: AppColor.writeColor),
titleTextStyle: AppStyle.headTitle2.copyWith(color: AppColor.writeColor),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
),
cardTheme: CardThemeData(
color: cardColor,
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
dividerTheme: DividerThemeData(
color: dividerColor,
thickness: 1,
),
);
}
ThemeData lightThemeEnglish = _createTheme(
brightness: Brightness.light, brightness: Brightness.light,
fontFamily: "SFPro", fontFamily: "SFPro",
textTheme: TextTheme( scaffoldBackgroundColor: Colors.white,
displaySmall: AppStyle.title, cardColor: Colors.white,
displayLarge: AppStyle.headTitle, dividerColor: Colors.black12,
displayMedium: AppStyle.headTitle2,
bodyLarge: AppStyle.title,
bodyMedium: AppStyle.subtitle,
),
primarySwatch: Colors.blue,
dialogTheme: DialogThemeData(
backgroundColor: AppColor.secondaryColor,
contentTextStyle: AppStyle.title,
titleTextStyle: AppStyle.headTitle2,
),
appBarTheme: AppBarTheme(
elevation: 0,
color: AppColor.secondaryColor,
centerTitle: true,
iconTheme: const IconThemeData(
color: AppColor.primaryColor,
),
toolbarTextStyle: TextTheme(
titleSmall: AppStyle.subtitle,
headlineSmall: AppStyle.title,
titleLarge: AppStyle.headTitle2,
).bodyMedium,
titleTextStyle: TextTheme(
titleSmall: AppStyle.subtitle,
headlineSmall: AppStyle.title,
titleLarge: AppStyle.headTitle2,
).titleLarge,
),
); );
ThemeData darkThemeEnglish = ThemeData( ThemeData darkThemeEnglish = _createTheme(
brightness: Brightness.dark, brightness: Brightness.dark,
fontFamily: "SFPro", fontFamily: "SFPro",
textTheme: TextTheme( scaffoldBackgroundColor: const Color(0xFF121212),
displaySmall: AppStyle.title, cardColor: const Color(0xFF1E1E1E),
displayLarge: AppStyle.headTitle, dividerColor: Colors.white10,
displayMedium: AppStyle.headTitle2,
bodyLarge: AppStyle.title,
bodyMedium: AppStyle.subtitle,
),
primarySwatch: Colors.blue,
dialogTheme: DialogThemeData(
backgroundColor: AppColor.secondaryColor,
contentTextStyle: AppStyle.title,
titleTextStyle: AppStyle.headTitle2,
),
appBarTheme: AppBarTheme(
elevation: 0,
color: AppColor.secondaryColor,
centerTitle: true,
iconTheme: const IconThemeData(
color: AppColor.primaryColor,
),
toolbarTextStyle: TextTheme(
titleSmall: AppStyle.subtitle,
headlineSmall: AppStyle.title,
titleLarge: AppStyle.headTitle2,
).bodyMedium,
titleTextStyle: TextTheme(
titleSmall: AppStyle.subtitle,
headlineSmall: AppStyle.title,
titleLarge: AppStyle.headTitle2,
).titleLarge,
),
); );
ThemeData lightThemeArabic = ThemeData( ThemeData lightThemeArabic = _createTheme(
brightness: Brightness.light, brightness: Brightness.light,
fontFamily: 'SFArabic', fontFamily: 'SFArabic',
textTheme: TextTheme( scaffoldBackgroundColor: Colors.white,
displaySmall: AppStyle.title, cardColor: Colors.white,
displayLarge: AppStyle.headTitle, dividerColor: Colors.black12,
displayMedium: AppStyle.headTitle2,
bodyLarge: AppStyle.title,
bodyMedium: AppStyle.subtitle,
),
primarySwatch: Colors.blue,
dialogTheme: DialogThemeData(
backgroundColor: AppColor.secondaryColor,
contentTextStyle: AppStyle.title,
titleTextStyle: AppStyle.headTitle2,
),
appBarTheme: AppBarTheme(
elevation: 0,
color: AppColor.secondaryColor,
centerTitle: true,
iconTheme: const IconThemeData(
color: AppColor.primaryColor,
),
toolbarTextStyle: TextTheme(
titleSmall: AppStyle.subtitle,
headlineSmall: AppStyle.title,
titleLarge: AppStyle.headTitle2,
).bodyMedium,
titleTextStyle: TextTheme(
titleSmall: AppStyle.subtitle,
headlineSmall: AppStyle.title,
titleLarge: AppStyle.headTitle2,
).titleLarge,
),
); );
ThemeData darkThemeArabic = ThemeData( ThemeData darkThemeArabic = _createTheme(
brightness: Brightness.dark, brightness: Brightness.dark,
fontFamily: 'SFArabic', fontFamily: 'SFArabic',
textTheme: TextTheme( scaffoldBackgroundColor: const Color(0xFF121212),
displaySmall: AppStyle.title, cardColor: const Color(0xFF1E1E1E),
displayLarge: AppStyle.headTitle, dividerColor: Colors.white10,
displayMedium: AppStyle.headTitle2,
bodyLarge: AppStyle.title,
bodyMedium: AppStyle.subtitle,
),
primarySwatch: Colors.blue,
dialogTheme: DialogThemeData(
backgroundColor: AppColor.secondaryColor,
contentTextStyle: AppStyle.title,
titleTextStyle: AppStyle.headTitle2,
),
appBarTheme: AppBarTheme(
elevation: 0,
color: AppColor.secondaryColor,
centerTitle: true,
iconTheme: const IconThemeData(
color: AppColor.primaryColor,
),
toolbarTextStyle: TextTheme(
titleSmall: AppStyle.subtitle,
headlineSmall: AppStyle.title,
titleLarge: AppStyle.headTitle2,
).bodyMedium,
titleTextStyle: TextTheme(
titleSmall: AppStyle.subtitle,
headlineSmall: AppStyle.title,
titleLarge: AppStyle.headTitle2,
).titleLarge,
),
); );

View File

@@ -67,7 +67,7 @@ class CompatibilityDetailCard extends StatelessWidget {
color: Colors.grey.shade800, color: Colors.grey.shade800,
fontWeight: FontWeight.w600)), fontWeight: FontWeight.w600)),
), ),
Text("$achievedScore/$maxScore نقطة", Text("$achievedScore/$maxScore ${"points".tr}",
style: TextStyle( style: TextStyle(
color: color, fontWeight: FontWeight.bold, fontSize: 14)), color: color, fontWeight: FontWeight.bold, fontSize: 14)),
], ],
@@ -130,10 +130,10 @@ class _DeviceCompatibilityPageState extends State<DeviceCompatibilityPage> {
} }
String _getScoreMessage(int value) { String _getScoreMessage(int value) {
if (value >= 80) return "جهازك يقدم أداءً ممتازاً"; if (value >= 80) return "Your device provides excellent performance".tr;
if (value >= 60) return "جهازك جيد ومناسب جداً"; if (value >= 60) return "Your device is good and very suitable".tr;
if (value >= 40) return "متوافق، قد تلاحظ بعض البطء"; if (value >= 40) return "Compatible, you may notice some slowness".tr;
return "قد لا يعمل التطبيق بالشكل الأمثل"; return "The app may not work optimally".tr;
} }
@override @override
@@ -143,8 +143,8 @@ class _DeviceCompatibilityPageState extends State<DeviceCompatibilityPage> {
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF7F8FC), backgroundColor: const Color(0xFFF7F8FC),
appBar: AppBar( appBar: AppBar(
title: const Text("توافق الجهاز", title: Text("Device Compatibility".tr,
style: TextStyle( style: const TextStyle(
color: Colors.black87, fontWeight: FontWeight.bold)), color: Colors.black87, fontWeight: FontWeight.bold)),
centerTitle: true, centerTitle: true,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
@@ -159,12 +159,12 @@ class _DeviceCompatibilityPageState extends State<DeviceCompatibilityPage> {
children: [ children: [
const Icon(Icons.phone_iphone, size: 56, color: Colors.grey), const Icon(Icons.phone_iphone, size: 56, color: Colors.grey),
const SizedBox(height: 12), const SizedBox(height: 12),
const Text("هذه الصفحة متاحة لأجهزة أندرويد فقط", Text("This page is only available for Android devices".tr,
style: TextStyle(fontSize: 16)), style: TextStyle(fontSize: 16)),
const SizedBox(height: 8), const SizedBox(height: 8),
ElevatedButton( ElevatedButton(
onPressed: () => Get.back(), onPressed: () => Get.back(),
child: const Text("رجوع"), child: Text("Back".tr),
), ),
], ],
), ),
@@ -176,9 +176,9 @@ class _DeviceCompatibilityPageState extends State<DeviceCompatibilityPage> {
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF7F8FC), backgroundColor: const Color(0xFFF7F8FC),
appBar: AppBar( appBar: AppBar(
title: const Text("توافق الجهاز", title: Text("Device Compatibility".tr,
style: style:
TextStyle(color: Colors.black87, fontWeight: FontWeight.bold)), const TextStyle(color: Colors.black87, fontWeight: FontWeight.bold)),
centerTitle: true, centerTitle: true,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
elevation: 0, elevation: 0,
@@ -208,7 +208,7 @@ class _DeviceCompatibilityPageState extends State<DeviceCompatibilityPage> {
elevation: 0, elevation: 0,
), ),
onPressed: () => Get.back(), onPressed: () => Get.back(),
child: const Text("المتابعة إلى التطبيق", child: Text("Continue to App".tr,
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,

3
lib/env/env.dart vendored
View File

@@ -12,6 +12,9 @@ abstract class Env {
@EnviedField(varName: 'mapAPIKEYIOS', obfuscate: true) @EnviedField(varName: 'mapAPIKEYIOS', obfuscate: true)
static final String mapAPIKEYIOS = _Env.mapAPIKEYIOS; static final String mapAPIKEYIOS = _Env.mapAPIKEYIOS;
@EnviedField(varName: 'mapSaasKey', obfuscate: true)
static final String mapSaasKey = _Env.mapSaasKey;
@EnviedField(varName: 'email', obfuscate: true) @EnviedField(varName: 'email', obfuscate: true)
static final String email = _Env.email; static final String email = _Env.email;

24722
lib/env/env.g.dart vendored

File diff suppressed because it is too large Load Diff

View File

@@ -35,6 +35,7 @@ import 'print.dart';
import 'splash_screen_page.dart'; import 'splash_screen_page.dart';
import 'views/home/Captin/orderCaptin/order_request_page.dart'; import 'views/home/Captin/orderCaptin/order_request_page.dart';
import 'views/home/Captin/driver_map_page.dart'; import 'views/home/Captin/driver_map_page.dart';
import 'controller/profile/setting_controller.dart';
final box = GetStorage(); final box = GetStorage();
const storage = FlutterSecureStorage(); const storage = FlutterSecureStorage();
@@ -315,10 +316,15 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
Future<void> _initApp() async { Future<void> _initApp() async {
try { try {
if (!Get.isRegistered<NotificationController>()) if (!Get.isRegistered<NotificationController>()) {
Get.put(NotificationController()); Get.put(NotificationController());
if (!Get.isRegistered<FirebaseMessagesController>()) }
if (!Get.isRegistered<FirebaseMessagesController>()) {
Get.put(FirebaseMessagesController()).getToken(); Get.put(FirebaseMessagesController()).getToken();
}
if (!Get.isRegistered<SettingController>()) {
Get.put(SettingController());
}
await FirebaseMessaging.instance.requestPermission(); await FirebaseMessaging.instance.requestPermission();
await NotificationController().initNotifications(); await NotificationController().initNotifications();
@@ -433,12 +439,15 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
print("📥 Server Response: $res"); print("📥 Server Response: $res");
if (Get.isDialogOpen == true) Get.back(); // إغلاق أي ديالوج مفتوح باستخدام المفتاح العالمي لضمان الموثوقية
if (Get.isDialogOpen == true) {
navigatorKey.currentState?.pop();
}
bool isFailure = false; bool isFailure = false;
if (res is Map && res['status'] == 'failure') if (res is Map && res['status'] == 'failure') {
isFailure = true; isFailure = true;
else if (res == 'failure') isFailure = true; } else if (res == 'failure') isFailure = true;
if (isFailure) { if (isFailure) {
Get.defaultDialog( Get.defaultDialog(
@@ -465,7 +474,9 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
Get.offAll(() => PassengerLocationMapPage(), arguments: rideArgs); Get.offAll(() => PassengerLocationMapPage(), arguments: rideArgs);
} catch (e) { } catch (e) {
if (Get.isDialogOpen == true) Get.back(); if (Get.isDialogOpen == true) {
navigatorKey.currentState?.pop();
}
print("❌ Error in accept process: $e"); print("❌ Error in accept process: $e");
Get.snackbar("خطأ", "حدث خطأ غير متوقع"); Get.snackbar("خطأ", "حدث خطأ غير متوقع");
} }
@@ -511,13 +522,18 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final LocaleController localController = Get.put(LocaleController()); final LocaleController localController = Get.put(LocaleController());
final SettingController settingController = Get.put(SettingController());
return GetMaterialApp( return GetMaterialApp(
navigatorKey: navigatorKey, navigatorKey: navigatorKey,
title: AppInformation.appName, title: AppInformation.appName,
translations: MyTranslation(), translations: MyTranslation(),
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
locale: localController.language, locale: localController.language,
theme: localController.appTheme, theme: localController.lightTheme,
darkTheme: localController.darkTheme,
themeMode: settingController.isDarkMode ? ThemeMode.dark : ThemeMode.light,
initialRoute: '/', initialRoute: '/',
getPages: [ getPages: [
GetPage(name: '/', page: () => SplashScreen()), GetPage(name: '/', page: () => SplashScreen()),
@@ -529,3 +545,4 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
); );
} }
} }

View File

@@ -1,5 +1,7 @@
// lib/models/model/order_data.dart // lib/models/model/order_data.dart
import 'package:get/get.dart';
class OrderData { class OrderData {
final String customerName; final String customerName;
final String customerToken; final String customerToken;
@@ -122,17 +124,17 @@ class OrderData {
static String _getRideType(String type) { static String _getRideType(String type) {
switch (type) { switch (type) {
case 'Comfort': case 'Comfort':
return '‏كمفورت ❄️'; return 'Comfort ❄️'.tr;
case 'Lady': case 'Lady':
return '‏ليدي 👩'; return 'Lady 👩'.tr;
case 'Speed': case 'Speed':
return '‏‏‏سبيد 🔻'; return 'Speed 🔻'.tr;
case 'Mashwari': case 'Mashwari':
return '‏مشواري'; return 'Mashwari'.tr;
case 'Rayeh Gai': case 'Rayeh Gai':
return 'رايح جاي'; return 'Rayeh Gai'.tr;
default: default:
return type; return type.tr;
} }
} }

View File

@@ -4,7 +4,7 @@ class Log {
Log._(); Log._();
static void print(String value, {StackTrace? stackTrace}) { static void print(String value, {StackTrace? stackTrace}) {
developer.log(value, name: 'LOG', stackTrace: stackTrace); // developer.log(value, name: 'LOG', stackTrace: stackTrace);
} }
static Object? inspect(Object? object) { static Object? inspect(Object? object) {

View File

@@ -0,0 +1,119 @@
import 'dart:io';
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'dart:math' as math;
import '../main.dart';
import '../print.dart';
class OfflineMapService {
static final OfflineMapService instance = OfflineMapService._();
OfflineMapService._();
final _offlineRegionName = "UserRegion";
bool _isDownloading = false;
LatLng? _lastDownloadedCenter;
/// Calculate bounding box for a given center and radius in km
LatLngBounds _calculateBounds(LatLng center, double radiusKm) {
const double earthRadius = 6371.0;
// Latitude degrees per km
double latDelta = (radiusKm / earthRadius) * (180 / math.pi);
// Longitude degrees per km at given latitude
double lngDelta = (radiusKm / earthRadius) *
(180 / math.pi) /
math.cos(center.latitude * math.pi / 180);
return LatLngBounds(
southwest:
LatLng(center.latitude - latDelta, center.longitude - lngDelta),
northeast:
LatLng(center.latitude + latDelta, center.longitude + lngDelta),
);
}
/// Downloads a specified radius around a coordinate
Future<void> downloadRegion(LatLng center,
{double radiusKm = 10.0,
double minZoom = 6.0,
double maxZoom = 15.0}) async {
if (_isDownloading) return;
// Avoid re-downloading if the user hasn't moved significantly (e.g. > 5km)
if (_lastDownloadedCenter != null) {
double distance = _calculateDistance(center, _lastDownloadedCenter!);
if (distance < 5.0) return; // skip if close to previously downloaded
}
_isDownloading = true;
try {
final bounds = _calculateBounds(center, radiusKm);
// Select style based on current theme
final String styleStr =
Get.isDarkMode ? "assets/style_dark.json" : "assets/style.json";
// iOS native crash guard: MLNTilePyramidOfflineRegion does not support relative asset URLs.
// We skip native offline registration on iOS if using local assets to ensure stability.
if (Platform.isIOS && !styleStr.startsWith('http')) {
Log.print(
" Skipping native offline registration on iOS for asset-based style to prevent crash.");
return;
}
final regionDefinition = OfflineRegionDefinition(
bounds: bounds,
mapStyleUrl: styleStr,
minZoom: minZoom,
maxZoom: maxZoom,
);
// We'll update the last downloaded center immediately
_lastDownloadedCenter = center;
// MapLibre standard API for offline downloads
await downloadOfflineRegion(regionDefinition, metadata: {
'name': '$_offlineRegionName-${center.latitude}-${center.longitude}',
'downloadDate': DateTime.now().toIso8601String(),
});
// Reassurance log for the user
Log.print("📍 Map Ready: Service is utilizing local tile cache.");
Log.print(
"✅ Offline Map Cached for Region: $center (radius: ${radiusKm}km, style: $styleStr)");
} catch (e) {
Log.print("⚠️ Offline Map Download Failed: $e");
} finally {
_isDownloading = false;
}
}
/// Helper to calculate distance in km
double _calculateDistance(LatLng p1, LatLng p2) {
var p = 0.017453292519943295;
var c = math.cos;
var a = 0.5 -
c((p2.latitude - p1.latitude) * p) / 2 +
c(p1.latitude * p) *
c(p2.latitude * p) *
(1 - c((p2.longitude - p1.longitude) * p)) /
2;
return 12742 * math.asin(math.sqrt(a));
}
/// Clears all offline map regions and tiles from local storage
Future<void> clearCache() async {
try {
Log.print("♻️ Purging MapLibre Offline Cache...");
final List<OfflineRegion> regions = await getListOfRegions();
for (var region in regions) {
await deleteOfflineRegion(region.id);
}
Log.print("✅ Map cache cleared successfully. ${regions.length} regions removed.");
} catch (e) {
Log.print("⚠️ Failed to clear map cache: $e");
}
}
}

View File

@@ -749,5 +749,58 @@
"My Profile": "", "My Profile": "",
"Female": "", "Female": "",
"Update Available": "", "Update Available": "",
"Recorded Trips for Safety": "" "Recorded Trips for Safety": "",
"Available Balance": "الرصيد المتاح",
"Available Balance Ar": "Available Balance",
"Last updated:": "آخر تحديث:",
"Just now": "الآن",
"Add Balance": "شحن الرصيد",
"Withdraw": "سحب",
"Transfer": "تحويل",
"History": "السجل",
"Earnings Summary": "ملخص الأرباح",
"Wallet Details": "تفاصيل المحفظة",
"Progress:": "التقدم:",
"Claim Reward": "الحصول على المكافأة",
"Today": "اليوم",
"Yesterday": "أمس",
"This Week": "هذا الأسبوع",
"Trip Payment": "دفع رحلة",
"Total Earnings": "إجمالي الأرباح",
"Total Trips": "إجمالي الرحلات",
"Commission": "العمولة",
"Net Profit": "صافي الربح",
"Cash Earnings": "أرباح نقداً",
"Card Earnings": "أرباح البطاقة",
"Pending": "قيد الانتظار",
"View All": "عرض الكل",
"Last updated: before 2 min": "آخر تحديث: قبل دقيقتين",
"PTS": "نقطة",
"E-Cash payment gateway": "بوابة دفع E-Cash",
"Syriatel Cash": "سيريتل كاش",
"Pay using Syriatel mobile wallet": "الدفع عبر محفظة سيريتل",
"Sham Cash": "شام كاش",
"Pay using Sham Cash wallet": "الدفع عبر محفظة شام كاش",
"Confirm payment with biometrics": "تأكيد الدفع بالبصمة",
"Wallet Phone Number": "رقم هاتف المحفظة",
"Confirm": "تأكيد",
"Buy Points Packages": "باقات شحن الرصيد",
"Select Payment Method": "اختر طريقة الدفع",
"You are buying": "أنت تقوم بشراء",
"Points": "ل.س",
"for": "بـ",
"Use Touch ID or Face ID to confirm payment": "استخدم بصمة الإصبع أو الوجه لتأكيد الدفع",
"Your Budget less than needed": "رصيدك أقل من المطلوب",
"Pay from my budget": "الدفع من الرصيد المتاح",
"Select how you want to charge your account": "اختر طريقة شحن حسابك",
"Pay": "دفع",
"Cancel": "إلغاء",
"from 7:00am to 10:00am": "من 7:00 صباحاً حتى 10:00 صباحاً",
"from 3:00pm to 6:00 pm": "من 3:00 مساءً حتى 6:00 مساءً",
"Morning Promo": "عرض الصباح",
"Afternoon Promo": "عرض بعد الظهر",
"Recharge Balance": "شحن الرصيد",
"Recharge Balance Packages": "باقات شحن الرصيد",
"Price:": "السعر:",
"Amount to charge:": "المبلغ المطلوب شحنه:"
} }

View File

@@ -749,5 +749,58 @@
"My Profile": "", "My Profile": "",
"Female": "", "Female": "",
"Update Available": "", "Update Available": "",
"Recorded Trips for Safety": "" "Recorded Trips for Safety": "",
"Available Balance": "Available Balance",
"Available Balance Ar": "الرصيد المتاح",
"Last updated:": "Last updated:",
"Just now": "Just now",
"Add Balance": "Add Balance",
"Withdraw": "Withdraw",
"Transfer": "Transfer",
"History": "History",
"Earnings Summary": "Earnings Summary",
"Wallet Details": "Wallet Details",
"Progress:": "Progress:",
"Claim Reward": "Claim Reward",
"Today": "Today",
"Yesterday": "Yesterday",
"This Week": "This Week",
"Trip Payment": "Trip Payment",
"Total Earnings": "Total Earnings",
"Total Trips": "Total Trips",
"Commission": "Commission",
"Net Profit": "Net Profit",
"Cash Earnings": "Cash Earnings",
"Card Earnings": "Card Earnings",
"Pending": "Pending",
"View All": "View All",
"Last updated: before 2 min": "Last updated: before 2 min",
"PTS": "PTS",
"E-Cash payment gateway": "E-Cash payment gateway",
"Syriatel Cash": "Syriatel Cash",
"Pay using Syriatel mobile wallet": "Pay using Syriatel mobile wallet",
"Sham Cash": "Sham Cash",
"Pay using Sham Cash wallet": "Pay using Sham Cash wallet",
"Confirm payment with biometrics": "Confirm payment with biometrics",
"Wallet Phone Number": "Wallet Phone Number",
"Confirm": "Confirm",
"Buy Points Packages": "Recharge Balance Packages",
"Select Payment Method": "Select Payment Method",
"You are buying": "You are buying",
"Points": "SYP",
"for": "for",
"Use Touch ID or Face ID to confirm payment": "Use Touch ID or Face ID to confirm payment",
"Your Budget less than needed": "Your Budget less than needed",
"Pay from my budget": "Pay from my budget",
"Select how you want to charge your account": "Select how you want to charge your account",
"Pay": "Pay",
"Cancel": "Cancel",
"from 7:00am to 10:00am": "from 7:00am to 10:00am",
"from 3:00pm to 6:00 pm": "from 3:00pm to 6:00 pm",
"Morning Promo": "Morning Promo",
"Afternoon Promo": "Afternoon Promo",
"Recharge Balance": "Recharge Balance",
"Recharge Balance Packages": "Recharge Balance Packages",
"Price:": "Price:",
"Amount to charge:": "Amount to charge:"
} }

View File

@@ -21,13 +21,11 @@ class RatePassenger extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: Colors.grey[50], backgroundColor: Theme.of(context).scaffoldBackgroundColor,
appBar: AppBar( appBar: AppBar(
title: Text('Trip Completed'.tr, title: Text('Trip Completed'.tr),
style: const TextStyle(color: Colors.black)),
centerTitle: true, centerTitle: true,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
backgroundColor: Colors.white,
elevation: 0, elevation: 0,
), ),
body: GetBuilder<RateController>( body: GetBuilder<RateController>(
@@ -156,23 +154,23 @@ class RatePassenger extends StatelessWidget {
Widget _buildWalletSection(BuildContext context, RateController controller) { Widget _buildWalletSection(BuildContext context, RateController controller) {
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.grey.shade200), border: Border.all(color: Theme.of(context).dividerColor),
), ),
child: Padding( child: Padding(
padding: const EdgeInsets.all(20.0), padding: const EdgeInsets.all(20.0),
child: AnimatedSwitcher( child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
child: controller.ispassengerWantWalletFromDriver child: controller.ispassengerWantWalletFromDriver
? _buildAmountInput(controller) ? _buildAmountInput(context, controller)
: _buildWalletQuery(controller), : _buildWalletQuery(context, controller),
), ),
), ),
); );
} }
Widget _buildWalletQuery(RateController controller) { Widget _buildWalletQuery(BuildContext context, RateController controller) {
return Column( return Column(
key: const ValueKey('walletQuery'), key: const ValueKey('walletQuery'),
children: [ children: [
@@ -181,11 +179,11 @@ class RatePassenger extends StatelessWidget {
Container( Container(
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.orange.withOpacity(0.1), color: Colors.amber.withOpacity(0.1),
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: const Icon(Icons.account_balance_wallet, child:
color: Colors.orange), const Icon(Icons.account_balance_wallet, color: Colors.amber),
), ),
const SizedBox(width: 15), const SizedBox(width: 15),
Expanded( Expanded(
@@ -206,8 +204,8 @@ class RatePassenger extends StatelessWidget {
child: OutlinedButton( child: OutlinedButton(
onPressed: () {}, // Optional logic onPressed: () {}, // Optional logic
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
foregroundColor: Colors.grey, foregroundColor: Theme.of(context).hintColor,
side: BorderSide(color: Colors.grey.shade300), side: BorderSide(color: Theme.of(context).dividerColor),
padding: const EdgeInsets.symmetric(vertical: 12), padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)), borderRadius: BorderRadius.circular(10)),
@@ -235,14 +233,17 @@ class RatePassenger extends StatelessWidget {
); );
} }
Widget _buildAmountInput(RateController controller) { Widget _buildAmountInput(BuildContext context, RateController controller) {
return Column( return Column(
key: const ValueKey('amountInput'), key: const ValueKey('amountInput'),
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"Enter Amount Paid".tr, "Enter Amount Paid".tr,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), style: Theme.of(context)
.textTheme
.titleMedium
?.copyWith(fontWeight: FontWeight.bold),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
Form( Form(
@@ -279,11 +280,12 @@ class RatePassenger extends StatelessWidget {
Text( Text(
'Rate Passenger'.tr, 'Rate Passenger'.tr,
style: TextStyle( style: TextStyle(
color: Colors.grey[600], color: Theme.of(context).hintColor,
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
RatingBar.builder( RatingBar.builder(
initialRating: 0, initialRating: 0,
@@ -306,19 +308,20 @@ class RatePassenger extends StatelessWidget {
TextField( TextField(
controller: controller.comment, controller: controller.comment,
maxLines: 2, maxLines: 2,
style: Theme.of(context).textTheme.bodyLarge,
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Any comments about the passenger?'.tr, hintText: 'Any comments about the passenger?'.tr,
filled: true, filled: true,
fillColor: Colors.white, fillColor: Theme.of(context).cardColor,
contentPadding: contentPadding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 12), const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none, borderSide: BorderSide(color: Theme.of(context).dividerColor),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.grey.shade200), borderSide: BorderSide(color: Theme.of(context).dividerColor),
), ),
), ),
), ),

View File

@@ -1,83 +1,83 @@
import 'package:camera/camera.dart'; // import 'package:camera/camera.dart';
import 'package:flutter/material.dart'; // import 'package:flutter/material.dart';
import 'package:get/get.dart'; // import 'package:get/get.dart';
import 'package:sefer_driver/views/widgets/elevated_btn.dart'; // import 'package:sefer_driver/views/widgets/elevated_btn.dart';
import '../../../../constant/colors.dart'; // import '../../../../constant/colors.dart';
import '../../../../constant/style.dart'; // import '../../../../constant/style.dart';
import '../../../../controller/functions/camer_controller.dart'; // import '../../../../controller/functions/camer_controller.dart';
import '../../../../controller/functions/ocr_controller.dart'; // import '../../../../controller/functions/ocr_controller.dart';
import '../../../widgets/my_scafold.dart'; // import '../../../widgets/my_scafold.dart';
class CameraLisencePage extends StatelessWidget { // class CameraLisencePage extends StatelessWidget {
CameraLisencePage.CameraLicensePage({super.key}); // CameraLisencePage.CameraLicensePage({super.key});
final CameraClassController cameraClassController = // final CameraClassController cameraClassController =
Get.put(CameraClassController()); // Get.put(CameraClassController());
@override // @override
Widget build(BuildContext context) { // Widget build(BuildContext context) {
return MyScafolld( // return MyScafolld(
title: 'Scan Driver License'.tr, // title: 'Scan Driver License'.tr,
body: [ // body: [
Column(children: [ // Column(children: [
Text( // Text(
'Please put your licence in these border'.tr, // 'Please put your licence in these border'.tr,
style: AppStyle.title.copyWith(color: AppColor.greenColor), // style: AppStyle.title.copyWith(color: AppColor.greenColor),
), // ),
Padding( // Padding(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), // padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
child: GetBuilder<CameraClassController>( // child: GetBuilder<CameraClassController>(
builder: (cameraClassController) => // builder: (cameraClassController) =>
cameraClassController.isCameraInitialized // cameraClassController.isCameraInitialized
? Stack( // ? Stack(
children: [ // children: [
Container( // Container(
decoration: AppStyle.boxDecoration, // decoration: AppStyle.boxDecoration,
child: CameraPreview( // child: CameraPreview(
cameraClassController.cameraController, // cameraClassController.cameraController,
), // ),
), // ),
Positioned( // Positioned(
top: Get.height * .1, // top: Get.height * .1,
right: 5, // right: 5,
left: 5, // left: 5,
child: Container( // child: Container(
height: Get.width * 3 / 4, // height: Get.width * 3 / 4,
width: Get.width * .9, // width: Get.width * .9,
decoration: BoxDecoration( // decoration: BoxDecoration(
// color: AppColor.blueColor, // // color: AppColor.blueColor,
border: Border.all( // border: Border.all(
color: AppColor.yellowColor, width: 2), // color: AppColor.yellowColor, width: 2),
), // ),
), // ),
), // ),
], // ],
) // )
: Container( // : Container(
decoration: AppStyle.boxDecoration, // decoration: AppStyle.boxDecoration,
height: Get.width * 3 / 4, // height: Get.width * 3 / 4,
width: Get.width, // width: Get.width,
child: Center( // child: Center(
child: Text( // child: Text(
'Camera not initialized yet', // 'Camera not initialized yet',
style: AppStyle.title, // style: AppStyle.title,
), // ),
), // ),
), // ),
), // ),
), // ),
const SizedBox( // const SizedBox(
height: 20, // height: 20,
), // ),
MyElevatedButton( // MyElevatedButton(
title: 'Take Image'.tr, // title: 'Take Image'.tr,
onPressed: () { // onPressed: () {
ScanDocumentsByApi().scanDocumentsByApi(); // ScanDocumentsByApi().scanDocumentsByApi();
}, // },
) // )
]), // ]),
], // ],
isleading: true, // isleading: true,
); // );
} // }
} // }

View File

@@ -61,7 +61,7 @@ class _LoginCaptinState extends State<LoginCaptin> with WidgetsBindingObserver {
return GetBuilder<LoginDriverController>( return GetBuilder<LoginDriverController>(
builder: (controller) { builder: (controller) {
return Scaffold( return Scaffold(
backgroundColor: AppColor.secondaryColor, backgroundColor: Theme.of(context).scaffoldBackgroundColor,
body: SafeArea( body: SafeArea(
child: Center( child: Center(
child: _buildBodyContent(context, controller), child: _buildBodyContent(context, controller),
@@ -72,6 +72,7 @@ class _LoginCaptinState extends State<LoginCaptin> with WidgetsBindingObserver {
); );
} }
Widget _buildBodyContent( Widget _buildBodyContent(
BuildContext context, LoginDriverController controller) { BuildContext context, LoginDriverController controller) {
// 1. صفحة الموافقة على الشروط // 1. صفحة الموافقة على الشروط
@@ -130,17 +131,22 @@ class _LoginCaptinState extends State<LoginCaptin> with WidgetsBindingObserver {
Expanded( Expanded(
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300), border: Border.all(color: Theme.of(context).dividerColor),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
color: Theme.of(context).cardColor.withOpacity(0.5),
), ),
child: SingleChildScrollView( child: SingleChildScrollView(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
child: HtmlWidget(box.read(BoxName.lang).toString() == 'ar' child: HtmlWidget(
? AppInformation.privacyPolicyArabic box.read(BoxName.lang).toString() == 'ar'
: AppInformation.privacyPolicy), ? AppInformation.privacyPolicyArabic
: AppInformation.privacyPolicy,
textStyle: TextStyle(color: Theme.of(context).textTheme.bodyLarge?.color),
),
), ),
), ),
), ),
CheckboxListTile( CheckboxListTile(
title: Text('I Agree'.tr, style: AppStyle.title), title: Text('I Agree'.tr, style: AppStyle.title),
value: controller.isAgreeTerms, value: controller.isAgreeTerms,
@@ -324,14 +330,15 @@ class _LoginCaptinState extends State<LoginCaptin> with WidgetsBindingObserver {
const SizedBox(height: 24), const SizedBox(height: 24),
Row( Row(
children: [ children: [
const Expanded(child: Divider()), Expanded(child: Divider(color: Theme.of(context).dividerColor)),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0), padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Text('Or'.tr, style: AppStyle.subtitle), child: Text('Or'.tr, style: AppStyle.subtitle),
), ),
const Expanded(child: Divider()), Expanded(child: Divider(color: Theme.of(context).dividerColor)),
], ],
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
MyElevatedButton( MyElevatedButton(
title: 'Create Account with Email'.tr, title: 'Create Account with Email'.tr,
@@ -457,19 +464,29 @@ class _LoginCaptinState extends State<LoginCaptin> with WidgetsBindingObserver {
validator: validator, validator: validator,
obscureText: obscureText, obscureText: obscureText,
keyboardType: keyboardType, keyboardType: keyboardType,
style: Theme.of(context).textTheme.bodyLarge,
decoration: InputDecoration( decoration: InputDecoration(
labelText: labelText, labelText: labelText,
labelStyle: TextStyle(color: Theme.of(context).hintColor),
hintText: hintText, hintText: hintText,
hintStyle: TextStyle(color: Theme.of(context).hintColor.withOpacity(0.5)),
prefixIcon: Icon(prefixIcon, color: AppColor.primaryColor), prefixIcon: Icon(prefixIcon, color: AppColor.primaryColor),
suffixIcon: suffixIcon, suffixIcon: suffixIcon,
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12.0)), border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12.0),
borderSide: BorderSide(color: Theme.of(context).dividerColor),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12.0),
borderSide: BorderSide(color: Theme.of(context).dividerColor),
),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12.0), borderRadius: BorderRadius.circular(12.0),
borderSide: borderSide: const BorderSide(color: AppColor.primaryColor, width: 2.0),
const BorderSide(color: AppColor.primaryColor, width: 2.0),
), ),
), ),
); );
} }
Widget _buildSocialButton({ Widget _buildSocialButton({

View File

@@ -23,10 +23,10 @@ class RegisterPage extends StatelessWidget {
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: SingleChildScrollView( child: SingleChildScrollView(
child: Container( child: Container(
decoration: const BoxDecoration( decoration: BoxDecoration(
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
offset: Offset(3, 3), offset: const Offset(3, 3),
color: AppColor.accentColor, color: AppColor.accentColor,
blurRadius: 3) blurRadius: 3)
], ],
@@ -45,7 +45,7 @@ class RegisterPage extends StatelessWidget {
controller: controller.firstNameController, controller: controller.firstNameController,
decoration: InputDecoration( decoration: InputDecoration(
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColor.primaryColor, color: AppColor.primaryColor,
width: 2.0, width: 2.0,
), ),
@@ -54,7 +54,7 @@ class RegisterPage extends StatelessWidget {
fillColor: AppColor.accentColor, fillColor: AppColor.accentColor,
hoverColor: AppColor.accentColor, hoverColor: AppColor.accentColor,
focusColor: AppColor.accentColor, focusColor: AppColor.accentColor,
border: const OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.all( borderRadius: BorderRadius.all(
Radius.circular(12))), Radius.circular(12))),
labelText: 'First name'.tr, labelText: 'First name'.tr,
@@ -75,7 +75,7 @@ class RegisterPage extends StatelessWidget {
controller: controller.lastNameController, controller: controller.lastNameController,
decoration: InputDecoration( decoration: InputDecoration(
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderSide: const BorderSide( borderSide: BorderSide(
color: AppColor.primaryColor, color: AppColor.primaryColor,
width: 2.0, width: 2.0,
), ),
@@ -83,7 +83,7 @@ class RegisterPage extends StatelessWidget {
), ),
focusColor: AppColor.accentColor, focusColor: AppColor.accentColor,
fillColor: AppColor.accentColor, fillColor: AppColor.accentColor,
border: const OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.all( borderRadius: BorderRadius.all(
Radius.circular(12))), Radius.circular(12))),
labelText: 'Last name'.tr, labelText: 'Last name'.tr,
@@ -116,7 +116,7 @@ class RegisterPage extends StatelessWidget {
fillColor: AppColor.accentColor, fillColor: AppColor.accentColor,
hoverColor: AppColor.accentColor, hoverColor: AppColor.accentColor,
focusColor: AppColor.accentColor, focusColor: AppColor.accentColor,
border: const OutlineInputBorder( border: OutlineInputBorder(
borderRadius: borderRadius:
BorderRadius.all(Radius.circular(12))), BorderRadius.all(Radius.circular(12))),
labelText: 'Email'.tr, labelText: 'Email'.tr,
@@ -149,7 +149,7 @@ class RegisterPage extends StatelessWidget {
fillColor: AppColor.accentColor, fillColor: AppColor.accentColor,
hoverColor: AppColor.accentColor, hoverColor: AppColor.accentColor,
focusColor: AppColor.accentColor, focusColor: AppColor.accentColor,
border: const OutlineInputBorder( border: OutlineInputBorder(
borderRadius: borderRadius:
BorderRadius.all(Radius.circular(12))), BorderRadius.all(Radius.circular(12))),
labelText: 'Password'.tr, labelText: 'Password'.tr,
@@ -188,7 +188,7 @@ class RegisterPage extends StatelessWidget {
), ),
focusColor: AppColor.accentColor, focusColor: AppColor.accentColor,
fillColor: AppColor.accentColor, fillColor: AppColor.accentColor,
border: const OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.all( borderRadius: BorderRadius.all(
Radius.circular(12))), Radius.circular(12))),
labelText: 'Phone'.tr, labelText: 'Phone'.tr,
@@ -217,7 +217,7 @@ class RegisterPage extends StatelessWidget {
), ),
focusColor: AppColor.accentColor, focusColor: AppColor.accentColor,
fillColor: AppColor.accentColor, fillColor: AppColor.accentColor,
border: const OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.all( borderRadius: BorderRadius.all(
Radius.circular(12))), Radius.circular(12))),
labelText: 'City'.tr, labelText: 'City'.tr,

View File

@@ -59,6 +59,14 @@ class SettingsCaptain extends StatelessWidget {
_buildSectionHeader('App Preferences'.tr, context), _buildSectionHeader('App Preferences'.tr, context),
_buildSettingsCard( _buildSettingsCard(
children: [ children: [
_buildSwitchTile(
icon: Icons.dark_mode_outlined,
title: 'App Dark Mode'.tr,
subtitle: 'Switch between light and dark themes'.tr,
controller: settingsController,
valueGetter: (ctrl) => (ctrl).isDarkMode,
onChanged: (ctrl) => (ctrl).toggleAppTheme(),
),
_buildSwitchTile( _buildSwitchTile(
icon: Icons.map_outlined, icon: Icons.map_outlined,
color: AppColor.redColor, color: AppColor.redColor,
@@ -68,6 +76,15 @@ class SettingsCaptain extends StatelessWidget {
valueGetter: (ctrl) => (ctrl).isGoogleMapsEnabled, valueGetter: (ctrl) => (ctrl).isGoogleMapsEnabled,
onChanged: (ctrl) => (ctrl).onChangMapApp(), onChanged: (ctrl) => (ctrl).onChangMapApp(),
), ),
_buildSwitchTile(
icon: Icons.map_outlined,
title: 'Map Dark Mode'.tr,
subtitle: 'Switch between light and dark map styles'.tr,
controller: settingsController,
valueGetter: (ctrl) => (ctrl).isMapDarkMode,
onChanged: (ctrl) => (ctrl).toggleMapTheme(),
),
_buildSwitchTile( _buildSwitchTile(
icon: Icons.vibration, icon: Icons.vibration,
title: 'Vibration'.tr, title: 'Vibration'.tr,

View File

@@ -1,6 +1,3 @@
import 'package:sefer_driver/constant/box_name.dart';
import 'package:sefer_driver/main.dart';
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -9,208 +6,171 @@ import '../../../controller/home/captin/help/assurance_controller.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import '../../widgets/my_scafold.dart';
class AssuranceHealthPage extends StatelessWidget { class AssuranceHealthPage extends StatelessWidget {
AssuranceHealthPage({super.key}); AssuranceHealthPage({super.key});
AssuranceHealthController assuranceHealthController = final AssuranceHealthController assuranceHealthController =
Get.put(AssuranceHealthController()); Get.put(AssuranceHealthController());
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return CupertinoPageScaffold( final theme = Theme.of(context);
navigationBar: CupertinoNavigationBar( return MyScafolld(
middle: Text("Health Insurance".tr), title: "Health Insurance".tr,
), isleading: true,
child: SafeArea( body: [
child: Padding( GetBuilder<AssuranceHealthController>(
padding: const EdgeInsets.all(8.0), builder: (controller) {
child: GetBuilder<AssuranceHealthController>( return SingleChildScrollView(
builder: (assuranceHealthController) { padding: const EdgeInsets.all(20.0),
return Column( child: Column(
children: [ crossAxisAlignment: CrossAxisAlignment.stretch,
Text( children: [
"When you complete 500 trips, you will be eligible for exclusive health insurance offers." Text(
.tr, "When you complete 500 trips, you will be eligible for exclusive health insurance offers."
textAlign: TextAlign.center, .tr,
style: CupertinoTheme.of(context).textTheme.navTitleTextStyle, textAlign: TextAlign.center,
), style: theme.textTheme.titleMedium?.copyWith(
const SizedBox(height: 10), color:
Row( theme.textTheme.bodyMedium?.color?.withOpacity(0.8),
mainAxisAlignment: MainAxisAlignment.spaceAround, height: 1.5,
children: [
CupertinoButton.filled(
child: Text("Show My Trip Count".tr),
onPressed: () async {
assuranceHealthController.getTripCountByCaptain();
},
), ),
_buildTripCountAvatar(
assuranceHealthController.tripCount['count'] == null
? '0'
: assuranceHealthController.tripCount['count']
.toString(),
),
],
),
const SizedBox(height: 10),
Container(
decoration: BoxDecoration(
color: CupertinoColors.systemGrey6,
borderRadius: BorderRadius.circular(10),
), ),
child: Padding( const SizedBox(height: 24),
padding: const EdgeInsets.all(14), Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () =>
controller.getTripCountByCaptain(),
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.primaryColor,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
padding:
const EdgeInsets.symmetric(vertical: 12),
),
child: Text("Show My Trip Count".tr),
),
),
const SizedBox(width: 16),
_buildTripCountAvatar(
controller.tripCount['count'] == null
? '0'
: controller.tripCount['count'].toString(),
),
],
),
),
),
const SizedBox(height: 24),
Container(
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
color: theme.colorScheme.surfaceVariant.withOpacity(0.5),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: theme.dividerColor),
),
child: Text( child: Text(
"We have partnered with health insurance providers to offer you special health coverage. Complete 500 trips and receive a 20% discount on health insurance premiums." "We have partnered with health insurance providers to offer you special health coverage. Complete 500 trips and receive a 20% discount on health insurance premiums."
.tr, .tr,
style: CupertinoTheme.of(context) style: theme.textTheme.bodyLarge
.textTheme ?.copyWith(fontWeight: FontWeight.w500),
.textStyle
.copyWith(fontWeight: FontWeight.w600),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
), ),
), const SizedBox(height: 32),
const SizedBox(height: 10), ElevatedButton(
CupertinoButton.filled( onPressed: () => _showInsuranceDialog(context, controller),
disabledColor: AppColor.blueColor, style: ElevatedButton.styleFrom(
padding: backgroundColor: AppColor.primaryColor,
const EdgeInsets.symmetric(horizontal: 20, vertical: 15), foregroundColor: Colors.white,
borderRadius: BorderRadius.circular(12), padding: const EdgeInsets.symmetric(vertical: 16),
child: Text( shape: RoundedRectangleBorder(
"Would you like to proceed with health insurance?".tr, borderRadius: BorderRadius.circular(12)),
style: const TextStyle( ),
fontSize: 16, fontWeight: FontWeight.bold), child: Text(
"Would you like to proceed with health insurance?".tr,
style: const TextStyle(fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
), ),
onPressed: () async { ],
// Show confirmation dialog before proceeding ),
showCupertinoDialog(
context: context,
builder: (BuildContext context) {
// Variable to store the health insurance provider chosen by the driver
TextEditingController providerController =
TextEditingController();
return CupertinoAlertDialog(
title: Text(
"Confirmation".tr,
style: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 18),
),
content: Column(
children: [
Text(
"Would you like to proceed with health insurance?"
.tr,
style: const TextStyle(fontSize: 14),
),
const SizedBox(height: 20),
CupertinoTextField(
controller: providerController,
placeholder:
"Do you have a disease for a long time?".tr,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
border: Border.all(
color: CupertinoColors.systemGrey,
width: 1),
borderRadius: BorderRadius.circular(8),
),
),
],
),
actions: <Widget>[
CupertinoDialogAction(
isDefaultAction: true,
child: Text(
"Yes".tr,
style: const TextStyle(
color: CupertinoColors.activeBlue),
),
onPressed: () async {
// Ensure the provider name is not empty
if (providerController.text.isNotEmpty) {
// Call the function to insert data into the database
await assuranceHealthController
.addDriverHealthAssurance(
healthInsuranceProvider:
providerController.text,
);
// Close the dialog and navigate to a success screen or show a snackbar
Navigator.of(context).pop();
} else {
// Show an alert if the provider name is empty
showCupertinoDialog(
context: context,
builder: (_) => CupertinoAlertDialog(
title: Text("Error".tr),
content: Text(
"Do you have a disease for a long time?"
.tr),
actions: [
CupertinoDialogAction(
child: Text("OK".tr),
onPressed: () =>
Navigator.of(context).pop(),
),
],
),
);
}
},
),
CupertinoDialogAction(
child: Text(
"No".tr,
style: const TextStyle(
color: CupertinoColors.destructiveRed),
),
onPressed: () {
Navigator.of(context)
.pop(); // Just close the dialog
// Optionally show feedback if the driver opts out
// Get.snackbar(
// "Opted out".tr,
// "You have chosen not to proceed with health insurance."
// .tr,
// backgroundColor:
// CupertinoColors.systemGrey);
mySnackeBarError(
"You have chosen not to proceed with health insurance."
.tr);
},
),
],
);
},
);
},
),
],
); );
}), },
), ),
],
);
}
void _showInsuranceDialog(
BuildContext context, AssuranceHealthController controller) {
final TextEditingController providerController = TextEditingController();
Get.dialog(
AlertDialog(
title: Text("Confirmation".tr),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text("Would you like to proceed with health insurance?".tr),
const SizedBox(height: 16),
TextField(
controller: providerController,
decoration: InputDecoration(
hintText: "Do you have a disease for a long time?".tr,
border:
OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
),
),
],
),
actions: [
TextButton(
onPressed: () => Get.back(),
child: Text("No".tr, style: const TextStyle(color: Colors.red)),
),
ElevatedButton(
onPressed: () async {
if (providerController.text.isNotEmpty) {
await controller.addDriverHealthAssurance(
healthInsuranceProvider: providerController.text,
);
Get.back();
} else {
Get.snackbar("Error".tr,
"Please provide details about any long-term diseases.".tr);
}
},
child: Text("Yes".tr),
),
],
), ),
); );
} }
Widget _buildTripCountAvatar(String count) { Widget _buildTripCountAvatar(String count) {
return Container( return Container(
width: 80, width: 70,
height: 80, height: 70,
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
gradient: const RadialGradient( gradient: const LinearGradient(
colors: [ colors: [Color(0xFF42A5F5), Color(0xFF1976D2)],
Color(0xFF42A5F5), begin: Alignment.topLeft,
Color(0xFF1976D2), end: Alignment.bottomRight,
], // Health theme colors
center: Alignment.center,
radius: 0.8,
), ),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: CupertinoColors.black.withOpacity(0.2), color: Colors.black.withOpacity(0.1),
blurRadius: 8, blurRadius: 8,
offset: const Offset(0, 4), offset: const Offset(0, 4),
), ),
@@ -220,9 +180,9 @@ class AssuranceHealthPage extends StatelessWidget {
child: Text( child: Text(
count, count,
style: const TextStyle( style: const TextStyle(
fontSize: 22, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: CupertinoColors.white, color: Colors.white,
), ),
), ),
), ),

View File

@@ -31,40 +31,32 @@ class HomeScreen extends StatelessWidget {
], ],
)), )),
bottomNavigationBar: Obx(() => BottomNavigationBar( bottomNavigationBar: Obx(() => BottomNavigationBar(
backgroundColor: Colors.greenAccent, backgroundColor: Theme.of(context).cardColor,
currentIndex: controller.currentIndex.value, currentIndex: controller.currentIndex.value,
onTap: controller.changePage, onTap: controller.changePage,
selectedItemColor: AppColor.primaryColor,
unselectedItemColor: Theme.of(context).hintColor,
type: BottomNavigationBarType.fixed,
items: [ items: [
BottomNavigationBarItem( BottomNavigationBarItem(
icon: const Icon( icon: const Icon(Icons.home),
Icons.home,
color: AppColor.primaryColor,
),
label: 'Home'.tr, label: 'Home'.tr,
), ),
BottomNavigationBarItem( BottomNavigationBarItem(
icon: const Icon( icon: const Icon(Icons.person),
Icons.person,
color: AppColor.primaryColor,
),
label: 'Profile'.tr, label: 'Profile'.tr,
), ),
BottomNavigationBarItem( BottomNavigationBarItem(
icon: const Icon( icon: const Icon(Icons.bar_chart),
Icons.bar_chart,
color: AppColor.primaryColor,
),
label: 'Statistics'.tr, label: 'Statistics'.tr,
), ),
BottomNavigationBarItem( BottomNavigationBarItem(
icon: const Icon( icon: const Icon(Icons.account_balance_wallet),
Icons.account_balance_wallet,
color: AppColor.primaryColor,
),
label: 'Wallet'.tr, label: 'Wallet'.tr,
), ),
], ],
)), )),
); );
} }
} }

View File

@@ -1,303 +1,302 @@
import 'package:camera/camera.dart'; // import 'package:flutter/material.dart';
import 'package:flutter/material.dart'; // import 'package:get/get.dart';
import 'package:get/get.dart'; // import 'package:sefer_driver/constant/colors.dart';
import 'package:sefer_driver/constant/colors.dart'; // import 'package:sefer_driver/constant/style.dart';
import 'package:sefer_driver/constant/style.dart'; // import 'package:sefer_driver/controller/functions/camer_controller.dart';
import 'package:sefer_driver/controller/functions/camer_controller.dart'; // import 'package:sefer_driver/views/widgets/elevated_btn.dart';
import 'package:sefer_driver/views/widgets/elevated_btn.dart'; // import 'package:sefer_driver/views/widgets/my_scafold.dart';
import 'package:sefer_driver/views/widgets/my_scafold.dart';
class CameraWidgetCardId extends StatelessWidget { // class CameraWidgetCardId extends StatelessWidget {
final CameraClassController cameraClassController = // final CameraClassController cameraClassController =
Get.put(CameraClassController()); // Get.put(CameraClassController());
CameraWidgetCardId({super.key}); // CameraWidgetCardId({super.key});
@override // @override
Widget build(BuildContext context) { // Widget build(BuildContext context) {
return MyScafolld( // return MyScafolld(
title: 'Scan Id'.tr, // title: 'Scan Id'.tr,
body: [ // body: [
Column( // Column(
children: [ // children: [
Padding( // Padding(
padding: // padding:
const EdgeInsets.symmetric(vertical: 8, horizontal: 12), // const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
child: GetBuilder<CameraClassController>( // child: GetBuilder<CameraClassController>(
builder: (cameraClassController) => // builder: (cameraClassController) =>
cameraClassController.isCameraInitialized // cameraClassController.isCameraInitialized
? Stack( // ? Stack(
children: [ // children: [
Container( // Container(
decoration: AppStyle.boxDecoration, // decoration: AppStyle.boxDecoration,
child: FittedBox( // child: FittedBox(
fit: BoxFit.fitWidth, // fit: BoxFit.fitWidth,
child: SizedBox( // child: SizedBox(
width: Get.width * .9, // width: Get.width * .9,
height: Get.width * // height: Get.width *
.9, // Set the desired aspect ratio here // .9, // Set the desired aspect ratio here
child: CameraPreview( // child: CameraPreview(
cameraClassController.cameraController, // cameraClassController.cameraController,
), // ),
), // ),
), // ),
), // ),
Positioned( // Positioned(
top: 72, // top: 72,
child: Container( // child: Container(
width: 230, // width: 230,
height: 25, // height: 25,
decoration: BoxDecoration( // decoration: BoxDecoration(
// color: AppColor.blueColor, // // color: AppColor.blueColor,
border: Border.all( // border: Border.all(
color: AppColor.yellowColor, // color: AppColor.yellowColor,
width: 2), // width: 2),
), // ),
), // ),
), // ),
Positioned( // Positioned(
top: 60, // top: 60,
right: 5, // right: 5,
child: Container( // child: Container(
width: 230, // width: 230,
height: 25, // height: 25,
decoration: BoxDecoration( // decoration: BoxDecoration(
// color: AppColor.blueColor, // // color: AppColor.blueColor,
border: Border.all( // border: Border.all(
color: AppColor.blueColor, width: 2), // color: AppColor.blueColor, width: 2),
), // ),
), // ),
), // ),
Positioned( // Positioned(
top: 156, // top: 156,
right: 5, // right: 5,
child: Container( // child: Container(
width: 140, // width: 140,
height: 20, // height: 20,
decoration: BoxDecoration( // decoration: BoxDecoration(
// color: AppColor.blueColor, // // color: AppColor.blueColor,
border: Border.all( // border: Border.all(
color: AppColor.blueColor, width: 2), // color: AppColor.blueColor, width: 2),
), // ),
), // ),
), // ),
Positioned( // Positioned(
top: 175, // top: 175,
right: 5, // right: 5,
child: Container( // child: Container(
width: 140, // width: 140,
height: 15, // height: 15,
decoration: BoxDecoration( // decoration: BoxDecoration(
// color: AppColor.blueColor, // // color: AppColor.blueColor,
border: Border.all( // border: Border.all(
color: AppColor.blueColor, width: 2), // color: AppColor.blueColor, width: 2),
), // ),
), // ),
), // ),
Positioned( // Positioned(
top: 191, // top: 191,
right: 5, // right: 5,
child: Container( // child: Container(
width: 140, // width: 140,
height: 15, // height: 15,
decoration: BoxDecoration( // decoration: BoxDecoration(
// color: AppColor.blueColor, // // color: AppColor.blueColor,
border: Border.all( // border: Border.all(
color: AppColor.blueColor, width: 2), // color: AppColor.blueColor, width: 2),
), // ),
), // ),
), // ),
Positioned( // Positioned(
top: 207, // top: 207,
right: 5, // right: 5,
child: Container( // child: Container(
width: 140, // width: 140,
height: 15, // height: 15,
decoration: BoxDecoration( // decoration: BoxDecoration(
// color: AppColor.blueColor, // // color: AppColor.blueColor,
border: Border.all( // border: Border.all(
color: AppColor.blueColor, width: 2), // color: AppColor.blueColor, width: 2),
), // ),
), // ),
), // ),
Positioned( // Positioned(
top: 225, // top: 225,
right: 5, // right: 5,
child: Container( // child: Container(
width: 140, // width: 140,
height: 15, // height: 15,
decoration: BoxDecoration( // decoration: BoxDecoration(
// color: AppColor.blueColor, // // color: AppColor.blueColor,
border: Border.all( // border: Border.all(
color: AppColor.blueColor, width: 2), // color: AppColor.blueColor, width: 2),
), // ),
), // ),
), // ),
Positioned( // Positioned(
top: 115, // top: 115,
left: 25, // left: 25,
child: Container( // child: Container(
width: 120, // width: 120,
height: 110, // height: 110,
decoration: BoxDecoration( // decoration: BoxDecoration(
// color: AppColor.blueColor, // // color: AppColor.blueColor,
border: Border.all( // border: Border.all(
color: AppColor.blueColor, width: 2), // color: AppColor.blueColor, width: 2),
), // ),
), // ),
), // ),
], // ],
) // )
: Container( // : Container(
decoration: AppStyle.boxDecoration, // decoration: AppStyle.boxDecoration,
height: Get.width * 3 / 4, // height: Get.width * 3 / 4,
width: Get.width, // width: Get.width,
child: Center( // child: Center(
child: Text( // child: Text(
'Camera not initilaized yet'.tr, // 'Camera not initilaized yet'.tr,
style: AppStyle.title, // style: AppStyle.title,
), // ),
), // ),
), // ),
), // ),
), // ),
Row( // Row(
mainAxisAlignment: MainAxisAlignment.spaceAround, // mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [ // children: [
MyElevatedButton( // MyElevatedButton(
title: 'Scan ID MklGoogle'.tr, onPressed: () {}), // title: 'Scan ID MklGoogle'.tr, onPressed: () {}),
// cameraClassController.takePictureAndMLGoogleScan()), // // cameraClassController.takePictureAndMLGoogleScan()),
MyElevatedButton( // MyElevatedButton(
title: 'Scan ID Tesseract'.tr, onPressed: () {}), // title: 'Scan ID Tesseract'.tr, onPressed: () {}),
], // ],
), // ),
MyElevatedButton( // MyElevatedButton(
title: 'Scan ID Api'.tr, // title: 'Scan ID Api'.tr,
onPressed: () => cameraClassController.extractCardId()), // onPressed: () => cameraClassController.extractCardId()),
GetBuilder<CameraClassController>( // GetBuilder<CameraClassController>(
builder: (cameraClassController) => Expanded( // builder: (cameraClassController) => Expanded(
child: // child:
Text(cameraClassController.scannedText.toString()))) // Text(cameraClassController.scannedText.toString())))
], // ],
) // )
], // ],
isleading: true); // isleading: true);
} // }
} // }
class CameraWidgetPassPort extends StatelessWidget { // class CameraWidgetPassPort extends StatelessWidget {
final CameraClassController cameraClassController = // final CameraClassController cameraClassController =
Get.put(CameraClassController()); // Get.put(CameraClassController());
CameraWidgetPassPort({super.key}); // CameraWidgetPassPort({super.key});
@override // @override
Widget build(BuildContext context) { // Widget build(BuildContext context) {
return MyScafolld( // return MyScafolld(
title: 'Scan Id'.tr, // title: 'Scan Id'.tr,
body: [ // body: [
Column( // Column(
children: [ // children: [
Padding( // Padding(
padding: // padding:
const EdgeInsets.symmetric(vertical: 8, horizontal: 12), // const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
child: GetBuilder<CameraClassController>( // child: GetBuilder<CameraClassController>(
builder: (cameraClassController) => // builder: (cameraClassController) =>
cameraClassController.isCameraInitialized // cameraClassController.isCameraInitialized
? Stack( // ? Stack(
children: [ // children: [
Container( // Container(
decoration: AppStyle.boxDecoration, // decoration: AppStyle.boxDecoration,
child: FittedBox( // child: FittedBox(
fit: BoxFit.fitWidth, // fit: BoxFit.fitWidth,
child: SizedBox( // child: SizedBox(
width: Get.width * .9, // width: Get.width * .9,
height: Get.width * // height: Get.width *
.9, // Set the desired aspect ratio here // .9, // Set the desired aspect ratio here
child: CameraPreview( // child: CameraPreview(
cameraClassController.cameraController, // cameraClassController.cameraController,
), // ),
), // ),
), // ),
), // ),
Positioned( // Positioned(
top: 35, // top: 35,
left: 35, // left: 35,
width: Get.width * .77, // width: Get.width * .77,
height: // height:
17, //left":95.0,"top":134.0,"width":2909.0,"height":175.0 // 17, //left":95.0,"top":134.0,"width":2909.0,"height":175.0
child: Container( // child: Container(
// width: 230, // // width: 230,
// height: 25, // // height: 25,
decoration: BoxDecoration( // decoration: BoxDecoration(
// color: AppColor.blueColor, // // color: AppColor.blueColor,
border: Border.all( // border: Border.all(
color: AppColor.yellowColor, // color: AppColor.yellowColor,
width: 2), // width: 2),
), // ),
), // ),
), // ),
Positioned( // Positioned(
top: 60, // top: 60,
right: 25, // right: 25,
width: 90, // width: 90,
height: 25, // height: 25,
child: Container( // child: Container(
decoration: BoxDecoration( // decoration: BoxDecoration(
// color: AppColor.blueColor, // // color: AppColor.blueColor,
border: Border.all( // border: Border.all(
color: AppColor.blueColor, width: 2), // color: AppColor.blueColor, width: 2),
), // ),
), // ),
), // ),
Positioned( // Positioned(
top: 110, // top: 110,
right: 90, // right: 90,
child: Container( // child: Container(
width: 140, // width: 140,
height: 20, // height: 20,
decoration: BoxDecoration( // decoration: BoxDecoration(
// color: AppColor.blueColor, // // color: AppColor.blueColor,
border: Border.all( // border: Border.all(
color: AppColor.blueColor, width: 2), // color: AppColor.blueColor, width: 2),
), // ),
), // ),
), // ),
], // ],
) // )
: Container( // : Container(
decoration: AppStyle.boxDecoration, // decoration: AppStyle.boxDecoration,
height: Get.width * 3 / 4, // height: Get.width * 3 / 4,
width: Get.width, // width: Get.width,
child: Center( // child: Center(
child: Text( // child: Text(
'Camera not initilaized yet', // 'Camera not initilaized yet',
style: AppStyle.title, // style: AppStyle.title,
), // ),
), // ),
), // ),
), // ),
), // ),
Row( // Row(
mainAxisAlignment: MainAxisAlignment.spaceAround, // mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [ // children: [
MyElevatedButton( // MyElevatedButton(
title: 'Scan ID MklGoogle'.tr, onPressed: () => {}), // title: 'Scan ID MklGoogle'.tr, onPressed: () => {}),
// cameraClassController.takePictureAndMLGoogleScan()), // // cameraClassController.takePictureAndMLGoogleScan()),
MyElevatedButton( // MyElevatedButton(
title: 'Scan ID Tesseract'.tr, onPressed: () {}), // title: 'Scan ID Tesseract'.tr, onPressed: () {}),
], // ],
), // ),
MyElevatedButton( // MyElevatedButton(
title: 'Scan ID Api'.tr, // title: 'Scan ID Api'.tr,
onPressed: () => cameraClassController.extractCardId()), // onPressed: () => cameraClassController.extractCardId()),
GetBuilder<CameraClassController>( // GetBuilder<CameraClassController>(
builder: (cameraClassController) => Expanded( // builder: (cameraClassController) => Expanded(
child: // child:
Text(cameraClassController.scannedText.toString()))) // Text(cameraClassController.scannedText.toString())))
], // ],
) // )
], // ],
isleading: true); // isleading: true);
} // }
} // }

View File

@@ -152,9 +152,11 @@ class InstructionsOfRoads extends StatelessWidget {
if (controller.currentInstruction.isEmpty) return const SizedBox(); if (controller.currentInstruction.isEmpty) return const SizedBox();
return TweenAnimationBuilder<double>( return TweenAnimationBuilder<double>(
tween: Tween(begin: 0.0, end: 1.0), tween: Tween(begin: 0.0, end: 1.0),
duration: const Duration(milliseconds: 500), duration: const Duration(milliseconds: 500),
builder: (context, value, child) { builder: (context, value, child) {
final theme = Theme.of(context);
return Transform.translate( return Transform.translate(
offset: Offset(0, 50 * (1 - value)), // حركة انزلاق offset: Offset(0, 50 * (1 - value)), // حركة انزلاق
child: Opacity( child: Opacity(
@@ -163,16 +165,15 @@ class InstructionsOfRoads extends StatelessWidget {
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 12), horizontal: 16, vertical: 12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFF1F1F1F) color: theme.cardColor.withOpacity(0.95), // Adaptive background
.withOpacity(0.95), // خلفية داكنة
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.4), color: Colors.black.withOpacity(0.2),
blurRadius: 15, blurRadius: 15,
offset: const Offset(0, 5)), offset: const Offset(0, 5)),
], ],
border: Border.all(color: Colors.white.withOpacity(0.1)), border: Border.all(color: theme.dividerColor.withOpacity(0.1)),
), ),
child: Row( child: Row(
children: [ children: [
@@ -187,7 +188,6 @@ class InstructionsOfRoads extends StatelessWidget {
color: Colors.white, size: 24), color: Colors.white, size: 24),
), ),
const SizedBox(width: 14), const SizedBox(width: 14),
// نص التعليمات // نص التعليمات
Expanded( Expanded(
child: Column( child: Column(
@@ -195,18 +195,15 @@ class InstructionsOfRoads extends StatelessWidget {
children: [ children: [
Text( Text(
"NEXT STEP".tr, "NEXT STEP".tr,
style: TextStyle( style: theme.textTheme.labelSmall?.copyWith(
color: Colors.grey.shade500, color: theme.hintColor,
fontSize: 10,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
letterSpacing: 1.2), letterSpacing: 1.2),
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
Text( Text(
controller.currentInstruction, controller.currentInstruction,
style: const TextStyle( style: theme.textTheme.titleMedium?.copyWith(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
height: 1.2), height: 1.2),
maxLines: 2, maxLines: 2,
@@ -216,6 +213,7 @@ class InstructionsOfRoads extends StatelessWidget {
), ),
), ),
// فاصل عمودي // فاصل عمودي
Container( Container(
width: 1, width: 1,
@@ -278,12 +276,13 @@ class CancelWidget extends StatelessWidget {
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white.withOpacity(0.9), color: Theme.of(context).cardColor.withOpacity(0.9),
borderRadius: BorderRadius.circular(30), borderRadius: BorderRadius.circular(30),
boxShadow: [ boxShadow: [
BoxShadow(color: Colors.black.withOpacity(0.1), blurRadius: 8) BoxShadow(color: Theme.of(context).shadowColor.withOpacity(0.1), blurRadius: 8)
], ],
), ),
child: Material( child: Material(
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
@@ -379,16 +378,17 @@ class PricesWindow extends StatelessWidget {
width: Get.width * 0.85, width: Get.width * 0.85,
padding: const EdgeInsets.all(30), padding: const EdgeInsets.all(30),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.2), color: Theme.of(context).shadowColor.withOpacity(0.2),
blurRadius: 20, blurRadius: 20,
spreadRadius: 5, spreadRadius: 5,
), ),
], ],
), ),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@@ -411,11 +411,12 @@ class PricesWindow extends StatelessWidget {
Text( Text(
'${controller.totalCost} ${'\$'.tr}', '${controller.totalCost} ${'\$'.tr}',
style: AppStyle.headTitle2.copyWith( style: AppStyle.headTitle2.copyWith(
color: Colors.black87, color: Theme.of(context).textTheme.bodyLarge?.color,
fontSize: 42, fontSize: 42,
fontWeight: FontWeight.w900, fontWeight: FontWeight.w900,
), ),
), ),
const SizedBox(height: 30), const SizedBox(height: 30),
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,

View File

@@ -1,7 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import '../../../../constant/finance_design_system.dart';
import '../../../../controller/auth/captin/history_captain.dart'; import '../../../../controller/auth/captin/history_captain.dart';
import 'package:sefer_driver/views/widgets/mydialoug.dart'; import 'package:sefer_driver/views/widgets/mydialoug.dart';
@@ -13,74 +14,159 @@ class HistoryCaptain extends StatelessWidget {
Get.put(HistoryCaptainController()); Get.put(HistoryCaptainController());
return Scaffold( return Scaffold(
backgroundColor: Colors.grey[100], // A softer background color backgroundColor: Colors.white,
appBar: AppBar(
title: Text('Ride History'.tr),
backgroundColor: Colors.white,
foregroundColor: Colors.black,
elevation: 1,
),
body: GetBuilder<HistoryCaptainController>( body: GetBuilder<HistoryCaptainController>(
builder: (controller) { builder: (controller) {
if (controller.isloading) { if (controller.isloading) {
return const Center(child: CircularProgressIndicator()); return const Center(
} child: CircularProgressIndicator(
color: FinanceDesignSystem.primaryDark),
if (controller.historyData['message'].isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.history_toggle_off,
size: 80, color: Colors.grey[400]),
const SizedBox(height: 16),
Text(
'No Rides Yet'.tr,
style: Theme.of(context)
.textTheme
.headlineSmall
?.copyWith(color: Colors.grey[600]),
),
],
),
); );
} }
// 动画: Wrap ListView with AnimationLimiter for staggered animations final List trips = controller.historyData['message'] ?? [];
return AnimationLimiter(
child: ListView.builder(
padding: const EdgeInsets.all(16.0),
itemCount: controller.historyData['message'].length,
itemBuilder: (BuildContext context, int index) {
var trip = controller.historyData['message'][index];
// 动画: Apply animation to each list item return CustomScrollView(
return AnimationConfiguration.staggeredList( physics: const BouncingScrollPhysics(),
position: index, slivers: [
duration: const Duration(milliseconds: 375), // 1. Custom App Bar
child: SlideAnimation( SliverAppBar(
verticalOffset: 50.0, expandedHeight: 180,
child: FadeInAnimation( pinned: true,
child: _AnimatedHistoryCard( stretch: true,
trip: trip, backgroundColor: FinanceDesignSystem.primaryDark,
onTap: () { flexibleSpace: FlexibleSpaceBar(
// Your original logic is preserved here centerTitle: true,
if (trip['status'] != 'Cancel') { title: Text(
controller.getHistoryDetails(trip['order_id']); 'Ride History'.tr,
} else { style: const TextStyle(
MyDialog().getDialog( color: Colors.white,
'This Trip Was Cancelled'.tr, fontWeight: FontWeight.bold,
'This Trip Was Cancelled'.tr, fontSize: 18,
() => Get.back(),
);
}
},
),
), ),
), ),
); background: Stack(
}, fit: StackFit.expand,
), children: [
Container(
decoration: const BoxDecoration(
gradient: FinanceDesignSystem.balanceGradient,
),
),
Positioned(
right: -50,
top: -20,
child: Icon(
Icons.history_rounded,
size: 200,
color: Colors.white.withOpacity(0.05),
),
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(height: 40),
Text(
trips.length.toString(),
style: const TextStyle(
color: Colors.white,
fontSize: 40,
fontWeight: FontWeight.w900,
),
),
Text(
'Total Rides'.tr,
style: TextStyle(
color: Colors.white.withOpacity(0.7),
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
],
),
],
),
),
),
// 2. Trips List
if (trips.isEmpty)
SliverFillRemaining(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(32),
decoration: BoxDecoration(
color: Colors.grey.shade50,
shape: BoxShape.circle,
),
child: Icon(
Icons.history_toggle_off_rounded,
size: 64,
color: Colors.grey.shade300,
),
),
const SizedBox(height: 24),
Text(
'No Rides Yet'.tr,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.grey.shade600,
),
),
const SizedBox(height: 8),
Text(
'Your completed trips will appear here'.tr,
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade400,
),
),
],
),
),
)
else
SliverPadding(
padding: const EdgeInsets.fromLTRB(16, 24, 16, 40),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
final trip = trips[index];
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 375),
child: SlideAnimation(
verticalOffset: 50.0,
child: FadeInAnimation(
child: _TripHistoryCard(
trip: trip,
onTap: () {
if (trip['status'] != 'Cancel' &&
trip['status'] != 'CancelByPassenger') {
controller
.getHistoryDetails(trip['order_id']);
} else {
MyDialog().getDialog(
'This Trip Was Cancelled'.tr,
'This Trip Was Cancelled'.tr,
() => Get.back(),
);
}
},
),
),
),
);
},
childCount: trips.length,
),
),
),
],
); );
}, },
), ),
@@ -88,97 +174,159 @@ class HistoryCaptain extends StatelessWidget {
} }
} }
// 动画: A new stateful widget to handle the tap animation class _TripHistoryCard extends StatelessWidget {
class _AnimatedHistoryCard extends StatefulWidget { final Map trip;
final Map<String, dynamic> trip;
final VoidCallback onTap; final VoidCallback onTap;
const _AnimatedHistoryCard({required this.trip, required this.onTap}); const _TripHistoryCard({required this.trip, required this.onTap});
@override String _formatDateToSyria(String? dateStr) {
__AnimatedHistoryCardState createState() => __AnimatedHistoryCardState(); if (dateStr == null) return '';
} try {
// Parse GMT date
class __AnimatedHistoryCardState extends State<_AnimatedHistoryCard> DateTime date = DateTime.parse(dateStr);
with SingleTickerProviderStateMixin { // Add 3 hours for Syria (GMT+3)
late AnimationController _controller; DateTime syriaDate = date.add(const Duration(hours: 3));
late Animation<double> _scaleAnimation; // Format to readable string
return DateFormat('yyyy-MM-dd HH:mm').format(syriaDate);
@override } catch (e) {
void initState() { return dateStr;
super.initState(); }
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 150),
);
_scaleAnimation = Tween<double>(begin: 1.0, end: 0.95).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _onTapDown(TapDownDetails details) {
_controller.forward();
}
void _onTapUp(TapUpDetails details) {
_controller.reverse();
widget.onTap();
}
void _onTapCancel() {
_controller.reverse();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return Container(
onTapDown: _onTapDown, margin: const EdgeInsets.only(bottom: 20),
onTapUp: _onTapUp, decoration: BoxDecoration(
onTapCancel: _onTapCancel, color: Colors.white,
child: ScaleTransition( borderRadius: BorderRadius.circular(FinanceDesignSystem.cardRadius),
scale: _scaleAnimation, boxShadow: [
child: Card( BoxShadow(
elevation: 4, color: Colors.black.withOpacity(0.04),
shadowColor: Colors.black.withOpacity(0.1), blurRadius: 15,
margin: const EdgeInsets.only(bottom: 16.0), offset: const Offset(0, 8),
shape: ),
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ],
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(FinanceDesignSystem.cardRadius),
child: Padding( child: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(20),
child: Row( child: Column(
children: [ children: [
Icon(Icons.receipt_long, Row(
color: Theme.of(context).primaryColor, size: 40), mainAxisAlignment: MainAxisAlignment.spaceBetween,
const SizedBox(width: 16), children: [
Expanded( Row(
child: Column( children: [
crossAxisAlignment: CrossAxisAlignment.start, Container(
children: [ padding: const EdgeInsets.all(10),
Text( decoration: BoxDecoration(
'${'OrderId'.tr}: ${widget.trip['order_id']}', color: FinanceDesignSystem.primaryDark
style: .withOpacity(0.05),
Theme.of(context).textTheme.titleMedium?.copyWith( borderRadius: BorderRadius.circular(12),
fontWeight: FontWeight.bold, ),
), child: const Icon(
), Icons.receipt_long_rounded,
const SizedBox(height: 4), color: FinanceDesignSystem.primaryDark,
Text( size: 20,
widget.trip['created_at'], ),
style: Theme.of(context).textTheme.bodySmall?.copyWith( ),
color: Colors.grey[600], const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${'OrderId'.tr} #${trip['order_id']}',
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15,
color: FinanceDesignSystem.primaryDark,
),
), ),
), Text(
], _formatDateToSyria(trip['created_at']),
), style: TextStyle(
fontSize: 11,
color: Colors.grey.shade500,
),
),
],
),
],
),
_buildStatusChip(trip['status']),
],
),
const SizedBox(height: 20),
const Divider(height: 1),
const SizedBox(height: 20),
Row(
children: [
Column(
children: [
const Icon(Icons.circle,
size: 12, color: FinanceDesignSystem.accentBlue),
Container(
width: 2,
height: 20,
color: Colors.grey.shade200,
),
const Icon(Icons.location_on_rounded,
size: 14, color: FinanceDesignSystem.dangerRed),
],
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
trip['start_name'] ?? 'Pickup Location'.tr,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 13,
color: Colors.grey.shade700,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 12),
Text(
trip['end_name'] ?? 'Destination Location'.tr,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 13,
color: Colors.grey.shade700,
fontWeight: FontWeight.w500,
),
),
],
),
),
if (trip['price'] != null)
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'${trip['price']} ${'SYP'.tr}',
style: const TextStyle(
fontWeight: FontWeight.w900,
fontSize: 16,
color: FinanceDesignSystem.primaryDark,
),
),
const Icon(Icons.arrow_forward_ios_rounded,
size: 12, color: Colors.grey),
],
),
],
), ),
const SizedBox(width: 16),
_buildStatusChip(widget.trip['status']),
], ],
), ),
), ),
@@ -186,46 +334,78 @@ class __AnimatedHistoryCardState extends State<_AnimatedHistoryCard>
), ),
); );
} }
}
// 🎨 A separate function for the status chip, slightly restyled for Material Widget _buildStatusChip(String? status) {
Widget _buildStatusChip(String status) { Color color;
Color chipColor; IconData icon;
Color textColor; String label = status ?? 'Unknown';
String statusText = status; List<Color> gradientColors;
IconData iconData;
switch (status) { switch (status) {
case 'Apply': case 'Apply':
chipColor = Colors.green.shade50; color = FinanceDesignSystem.successGreen;
textColor = Colors.green.shade800; icon = Icons.check_circle_rounded;
iconData = Icons.check_circle; label = 'Completed'.tr;
break; gradientColors = [color.withOpacity(0.2), color.withOpacity(0.05)];
case 'Refused': break;
chipColor = Colors.red.shade50; case 'Refused':
textColor = Colors.red.shade800; color = FinanceDesignSystem.dangerRed;
iconData = Icons.cancel; icon = Icons.cancel_rounded;
break; label = 'Refused'.tr;
case 'Cancel': gradientColors = [color.withOpacity(0.2), color.withOpacity(0.05)];
chipColor = Colors.orange.shade50; break;
textColor = Colors.orange.shade800; case 'Cancel':
iconData = Icons.info; color = Colors.orange;
statusText = 'Cancelled'; icon = Icons.info_rounded;
break; label = 'Cancelled'.tr;
default: gradientColors = [color.withOpacity(0.2), color.withOpacity(0.05)];
chipColor = Colors.grey.shade200; break;
textColor = Colors.grey.shade800; case 'CancelByPassenger':
iconData = Icons.hourglass_empty; color = const Color(0xFF6B4EFF);
icon = Icons.person_off_rounded;
label = 'Cancelled by Passenger'.tr;
gradientColors = [color.withOpacity(0.2), color.withOpacity(0.05)];
break;
default:
color = Colors.grey;
icon = Icons.help_rounded;
gradientColors = [color.withOpacity(0.2), color.withOpacity(0.05)];
}
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: gradientColors,
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(20),
border: Border.all(color: color.withOpacity(0.3), width: 1),
boxShadow: [
BoxShadow(
color: color.withOpacity(0.1),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 14, color: color),
const SizedBox(width: 6),
Text(
label,
style: TextStyle(
color: color,
fontSize: 11,
fontWeight: FontWeight.w800,
letterSpacing: 0.3,
),
),
],
),
);
} }
return Chip(
avatar: Icon(iconData, color: textColor, size: 16),
label: Text(
statusText.tr,
style: TextStyle(color: textColor, fontWeight: FontWeight.w600),
),
backgroundColor: chipColor,
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
side: BorderSide.none,
);
} }

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:google_maps_flutter/google_maps_flutter.dart'; import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:sefer_driver/constant/api_key.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:sefer_driver/controller/auth/captin/history_captain.dart'; import 'package:sefer_driver/controller/auth/captin/history_captain.dart';
@@ -168,7 +169,7 @@ class _HistoryDetailsPageState extends State<HistoryDetailsPage> {
return Card( return Card(
elevation: 4, elevation: 4,
shadowColor: Colors.black.withOpacity(0.1), shadowColor: Colors.black.withValues(alpha: 0.1),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
clipBehavior: clipBehavior:
Clip.antiAlias, // Ensures the map respects the border radius Clip.antiAlias, // Ensures the map respects the border radius
@@ -176,7 +177,8 @@ class _HistoryDetailsPageState extends State<HistoryDetailsPage> {
children: [ children: [
SizedBox( SizedBox(
height: 250, height: 250,
child: GoogleMap( child: IntaleqMap(
apiKey: AK.mapAPIKEY,
initialCameraPosition: initialCameraPosition, initialCameraPosition: initialCameraPosition,
markers: markers, markers: markers,
polylines: { polylines: {
@@ -188,7 +190,7 @@ class _HistoryDetailsPageState extends State<HistoryDetailsPage> {
width: 5, width: 5,
), ),
}, },
onMapCreated: (GoogleMapController mapController) { onMapCreated: (IntaleqMapController mapController) {
// Animate camera to fit the route // Animate camera to fit the route
if (startLocation != null && endLocation != null) { if (startLocation != null && endLocation != null) {
LatLngBounds bounds = LatLngBounds( LatLngBounds bounds = LatLngBounds(
@@ -210,7 +212,7 @@ class _HistoryDetailsPageState extends State<HistoryDetailsPage> {
), ),
); );
mapController.animateCamera( mapController.animateCamera(
CameraUpdate.newLatLngBounds(bounds, 60.0)); CameraUpdate.newLatLngBounds(bounds, left: 60, top: 60, right: 60, bottom: 60));
} }
}, },
), ),
@@ -252,7 +254,7 @@ class _DetailCard extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Card( return Card(
elevation: 2, elevation: 2,
shadowColor: Colors.black.withOpacity(0.05), shadowColor: Colors.black.withValues(alpha: 0.05),
margin: const EdgeInsets.only(bottom: 16.0), margin: const EdgeInsets.only(bottom: 16.0),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Padding( child: Padding(

View File

@@ -5,7 +5,9 @@ import 'package:sefer_driver/views/home/Captin/home_captain/help_details_replay_
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import '../../../../constant/colors.dart';
import '../../../../controller/functions/encrypt_decrypt.dart'; import '../../../../controller/functions/encrypt_decrypt.dart';
import '../../../widgets/my_scafold.dart';
class HelpCaptain extends StatelessWidget { class HelpCaptain extends StatelessWidget {
HelpCaptain({super.key}); HelpCaptain({super.key});
@@ -13,162 +15,158 @@ class HelpCaptain extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Get.put(HelpController()); Get.put(HelpController());
return CupertinoPageScaffold( final theme = Theme.of(context);
navigationBar: CupertinoNavigationBar(
middle: Text( return MyScafolld(
'Helping Page'.tr, title: 'Helping Page'.tr,
style: const TextStyle(fontWeight: FontWeight.bold), isleading: true,
), body: [
leading: CupertinoButton( SingleChildScrollView(
padding: EdgeInsets.zero, padding: const EdgeInsets.all(20.0),
onPressed: () => Navigator.pop(context), child: Column(
child: const Icon(CupertinoIcons.back), crossAxisAlignment: CrossAxisAlignment.stretch,
),
),
child: SafeArea(
child: Padding(
padding: const EdgeInsets.all(
20.0), // Increased padding around the content
child: ListView(
// crossAxisAlignment:
// CrossAxisAlignment.stretch, // Stretch children to full width
children: [ children: [
Container( Container(
padding: padding: const EdgeInsets.all(18.0),
const EdgeInsets.all(18.0), // Slightly increased padding
decoration: BoxDecoration( decoration: BoxDecoration(
color: color: theme.colorScheme.surfaceVariant.withOpacity(0.5),
CupertinoTheme.of(context).brightness == Brightness.light borderRadius: BorderRadius.circular(15.0),
? CupertinoColors.systemGrey6 border: Border.all(color: theme.dividerColor),
: CupertinoColors.darkBackgroundGray,
borderRadius:
BorderRadius.circular(15.0), // More rounded corners
), ),
child: Text( child: Text(
'If you need any help or have questions, this is the right place to do that. You are welcome!' 'If you need any help or have questions, this is the right place to do that. You are welcome!'
.tr, .tr,
style: style: theme.textTheme.bodyLarge,
CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 16, // Slightly larger font size
color: CupertinoColors.label.resolveFrom(
context), // Ensure text color adapts to theme
),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
), ),
const SizedBox(height: 20), const SizedBox(height: 24),
CupertinoFormSection.insetGrouped( Text(
// Using CupertinoFormSection for better styling 'Submit Your Question'.tr,
header: Text('Submit Your Question'.tr), style: theme.textTheme.titleMedium
margin: EdgeInsets.zero, ?.copyWith(fontWeight: FontWeight.bold),
children: [ ),
GetBuilder<HelpController>( const SizedBox(height: 12),
builder: (helpController) => Form( GetBuilder<HelpController>(
key: helpController.formKey, builder: (helpController) => Form(
child: CupertinoTextFormFieldRow( key: helpController.formKey,
child: Column(
children: [
TextFormField(
controller: helpController.helpQuestionController, controller: helpController.helpQuestionController,
placeholder: 'Enter your Question here'.tr, decoration: InputDecoration(
autovalidateMode: AutovalidateMode.onUserInteraction, hintText: 'Enter your Question here'.tr,
prefixIcon:
const Icon(Icons.question_answer_outlined),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12)),
),
maxLines: 3,
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
return 'Please enter your question'.tr; return 'Please enter your question'.tr;
} }
return null; return null;
}, },
prefix: const Icon(CupertinoIcons
.question_circle), // Added a prefix icon
), ),
), const SizedBox(height: 16),
), helpController.isLoading
Padding( ? const Center(child: CircularProgressIndicator())
padding: const EdgeInsets.symmetric( : SizedBox(
horizontal: 16.0, vertical: 10.0), width: double.infinity,
child: GetBuilder<HelpController>( child: ElevatedButton(
builder: (helpController) => helpController.isLoading onPressed: () {
? const CupertinoActivityIndicator() if (helpController.formKey.currentState!
: CupertinoButton.filled( .validate()) {
onPressed: () { helpController.addHelpQuestion();
if (helpController.formKey.currentState! helpController.helpQuestionController
.validate()) { .clear();
helpController.addHelpQuestion(); }
helpController.helpQuestionController
.clear(); // Clear the text field
}
},
child: Text('Submit Question'.tr),
),
),
),
],
),
const SizedBox(height: 20),
Text(
'Your Questions'.tr,
style: CupertinoTheme.of(context)
.textTheme
.navTitleTextStyle
.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
Expanded(
child: GetBuilder<HelpController>(
builder: (helpController) =>
CupertinoListSection.insetGrouped(
margin: EdgeInsets.zero,
children: helpController.helpQuestionDate['message'] != null
? List.generate(
helpController.helpQuestionDate['message'].length,
(index) {
var list = helpController
.helpQuestionDate['message'][index];
return CupertinoListTile(
title: Text(
EncryptionHelper.instance
.decryptData(list['helpQuestion']),
overflow: TextOverflow.ellipsis,
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
list['datecreated'],
style: CupertinoTheme.of(context)
.textTheme
.tabLabelTextStyle,
),
const Icon(CupertinoIcons.chevron_forward),
],
),
onTap: () {
helpController.getIndex(
int.parse(EncryptionHelper.instance
.decryptData(list['id'])),
EncryptionHelper.instance
.decryptData(list['helpQuestion']));
helpController
.getHelpRepley(list['id'].toString());
Get.to(() => const HelpDetailsReplayPage());
}, },
); style: ElevatedButton.styleFrom(
}, backgroundColor: AppColor.primaryColor,
) foregroundColor: Colors.white,
: [ padding:
Center( const EdgeInsets.symmetric(vertical: 14),
child: Text('No questions asked yet.'.tr), shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
),
child: Text('Submit Question'.tr),
),
), ),
], ],
), ),
), ),
), ),
const SizedBox(height: 32),
Text(
'Your Questions'.tr,
style: theme.textTheme.titleMedium
?.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
GetBuilder<HelpController>(
builder: (helpController) {
final questions = helpController.helpQuestionDate['message'];
if (questions == null || questions.isEmpty) {
return Center(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Text('No questions asked yet.'.tr),
),
);
}
return ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: questions.length,
separatorBuilder: (context, index) =>
const SizedBox(height: 12),
itemBuilder: (context, index) {
var list = questions[index];
return Card(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(color: theme.dividerColor),
),
child: ListTile(
title: Text(
EncryptionHelper.instance
.decryptData(list['helpQuestion']),
style: const TextStyle(fontWeight: FontWeight.w500),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
subtitle: Text(
list['datecreated'],
style: theme.textTheme.bodySmall,
),
trailing:
const Icon(Icons.arrow_forward_ios, size: 16),
onTap: () {
helpController.getIndex(
int.parse(EncryptionHelper.instance
.decryptData(list['id'])),
EncryptionHelper.instance
.decryptData(list['helpQuestion']));
helpController.getHelpRepley(list['id'].toString());
Get.to(() => const HelpDetailsReplayPage());
},
),
);
},
);
},
),
], ],
), ),
), ),
), ],
); );
} }
} }
// class HelpCaptain extends StatelessWidget { // class HelpCaptain extends StatelessWidget {
// HelpCaptain({super.key}); // HelpCaptain({super.key});

View File

@@ -19,65 +19,90 @@ class HelpDetailsReplayPage extends StatelessWidget {
body: [ body: [
helpController.isLoading helpController.isLoading
? const MyCircularProgressIndicator() ? const MyCircularProgressIndicator()
: Column( : Padding(
children: [ padding: const EdgeInsets.all(16.0),
Padding( child: Column(
padding: const EdgeInsets.symmetric(horizontal: 10), children: [
child: Row( // Question Bubble (Aligned to Start/End based on locale, usually start for sender)
mainAxisAlignment: MainAxisAlignment.start, Align(
children: [ alignment: AlignmentDirectional.centerStart,
Card( child: Container(
elevation: 3, constraints: BoxConstraints(maxWidth: Get.width * 0.75),
child: Container( padding: const EdgeInsets.all(12),
width: Get.width * .66, decoration: BoxDecoration(
color: Colors.transparent, color: Theme.of(context).colorScheme.primaryContainer,
child: Padding( borderRadius: const BorderRadius.only(
padding: const EdgeInsets.all(8.0), topRight: Radius.circular(16),
child: Text( bottomLeft: Radius.circular(16),
helpController.qustion, bottomRight: Radius.circular(16),
style: AppStyle.title,
),
),
),
),
]),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Card(
elevation: 3,
child: Container(
color: Colors.transparent,
width: Get.width * .66,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: helpController.status ==
'not yet' ||
EncryptionHelper.instance
.decryptData(helpController
.helpQuestionRepleyDate[
'message']['replay'])
.toString() ==
'not yet'
? Text(
'No Response yet.'.tr,
style: AppStyle.title,
)
: Text(
EncryptionHelper.instance
.decryptData(helpController
.helpQuestionRepleyDate[
'message']['replay'])
.toString(),
style: AppStyle.title,
),
),
), ),
), ),
]), child: Column(
], crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Your Question'.tr,
style: Theme.of(context).textTheme.labelSmall?.copyWith(
color: Theme.of(context).colorScheme.primary,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
helpController.qustion,
style: Theme.of(context).textTheme.bodyLarge,
),
],
),
),
),
const SizedBox(height: 24),
// Reply Bubble (Aligned to opposite side)
Align(
alignment: AlignmentDirectional.centerEnd,
child: Container(
constraints: BoxConstraints(maxWidth: Get.width * 0.75),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.secondaryContainer,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(16),
bottomLeft: Radius.circular(16),
bottomRight: Radius.circular(16),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Support Reply'.tr,
style: Theme.of(context).textTheme.labelSmall?.copyWith(
color: Theme.of(context).colorScheme.secondary,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Builder(builder: (context) {
final replayData = helpController.helpQuestionRepleyDate['message'];
final isNoReply = helpController.status == 'not yet' ||
replayData == null ||
EncryptionHelper.instance.decryptData(replayData['replay']).toString() == 'not yet';
return Text(
isNoReply
? 'No Response yet.'.tr
: EncryptionHelper.instance.decryptData(replayData['replay']).toString(),
style: Theme.of(context).textTheme.bodyLarge,
);
}),
],
),
),
),
],
),
) )
], ],
isleading: true, isleading: true,
)); ));

File diff suppressed because it is too large Load Diff

View File

@@ -1,83 +1,50 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:latlong2/latlong.dart'; import 'package:intaleq_maps/intaleq_maps.dart';
import '../../../../constant/api_key.dart';
import '../../../../controller/functions/location_controller.dart'; import '../../../../controller/functions/location_controller.dart';
import '../../../../controller/home/captin/home_captain_controller.dart'; import '../../../../controller/home/captin/home_captain_controller.dart';
// هذه ويدجت بديلة للـ _MapView في الكود الخاص بك
// V3 - MapView Replacement using flutter_map
class OsmMapView extends StatelessWidget { class OsmMapView extends StatelessWidget {
const OsmMapView({super.key}); const OsmMapView({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// افترض أنك تحصل على الموقع والاتجاه بنفس الطريقة من الكونترولر
final LocationController locationController = final LocationController locationController =
Get.find<LocationController>(); Get.find<LocationController>();
final HomeCaptainController homeCaptainController = final HomeCaptainController homeCaptainController =
Get.find<HomeCaptainController>(); Get.find<HomeCaptainController>();
// يمكنك استخدام GetBuilder لمراقبة التغييرات في الموقع
return Obx(() { return Obx(() {
final LatLng currentLocation = LatLng( final LatLng currentLocation = LatLng(
locationController.myLocation.latitude, locationController.myLocation.latitude,
locationController.myLocation.longitude); locationController.myLocation.longitude);
final double currentHeading = locationController.heading; final double currentHeading = locationController.heading;
return FlutterMap( return IntaleqMap(
// يمكنك ربط هذا بـ MapController الخاص بـ flutter_map apiKey: AK.mapSaasKey,
// mapController: homeCaptainController.flutterMapController, initialCameraPosition: CameraPosition(
options: MapOptions( target: currentLocation,
initialCenter: currentLocation, zoom: 15,
initialZoom: 15, bearing: currentHeading,
maxZoom: 18,
minZoom: 6,
// تدوير الخريطة (اختياري)
initialRotation: currentHeading,
), ),
children: [ markers: {
// 1. طبقة الخريطة الأساسية (Tiles) Marker(
// هذا هو الرابط لخرائط OSM الأساسية markerId: const MarkerId('myLocation'),
TileLayer( position: currentLocation,
urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', rotation: currentHeading,
subdomains: ['a', 'b', 'c'], icon: homeCaptainController.carIcon,
userAgentPackageName: anchor: const Offset(0.5, 0.5),
'com.example.app', // استبدل باسم الباكج الخاص بك flat: true,
zIndex: 2,
// لاستخدام الخرائط الأوفلاين (بعد إعداد flutter_map_tile_caching) )
// tileProvider: CachedTileProvider(), },
), onMapCreated: (IntaleqMapController controller) {
// You can assign this controller if needed
// 2. طبقة العلامات (Markers) },
MarkerLayer(
markers: [
Marker(
width: 80.0,
height: 80.0,
point: currentLocation,
child: Transform.rotate(
angle: currentHeading *
(3.1415926535 / 180), // تحويل من درجات إلى راديان
child: Image.asset(
'assets/images/car_icon.png', // تأكد أن لديك أيقونة السيارة
// يمكنك استخدام نفس الـ carIcon من الكونترولر
// icon: homeCaptainController.carIcon, (ملاحظة: flutter_map تستخدم ويدجت)
),
),
),
],
),
// يمكنك إضافة طبقات أخرى هنا (مثل الخطوط Polylines أو المضلعات Polygons)
],
); );
}); });
} }
} }
// ملاحظة: ستحتاج إلى تعديل بسيط في HomeCaptainController
// لإنشاء MapController الخاص بـ flutter_map بدلاً من GoogleMapController
// import 'package:flutter_map/flutter_map.dart';
// MapController flutterMapController = MapController();

View File

@@ -5,7 +5,6 @@ import 'package:get/get.dart';
import 'package:sefer_driver/controller/home/payment/captain_wallet_controller.dart'; import 'package:sefer_driver/controller/home/payment/captain_wallet_controller.dart';
import '../../../../../constant/style.dart'; import '../../../../../constant/style.dart';
import '../../../../../controller/functions/encrypt_decrypt.dart';
import '../../../../widgets/elevated_btn.dart'; import '../../../../widgets/elevated_btn.dart';
import '../../../../../controller/home/captin/home_captain_controller.dart'; import '../../../../../controller/home/captin/home_captain_controller.dart';
@@ -91,45 +90,56 @@ class ConnectWidget extends StatelessWidget {
: Container( : Container(
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: homeCaptainController.isActive colors: homeCaptainController.isActive
? [Colors.green.shade400, Colors.green.shade700] ? [const Color(0xFF00C853), const Color(0xFF00E676)]
: [Colors.grey.shade400, Colors.grey.shade700], : [Colors.grey.shade600, Colors.grey.shade400],
), ),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(20),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: homeCaptainController.isActive color: (homeCaptainController.isActive
? Colors.green.withOpacity(0.3) ? const Color(0xFF00C853)
: Colors.grey.withOpacity(0.3), : Colors.grey)
spreadRadius: 1, .withOpacity(0.4),
blurRadius: 8, spreadRadius: 0,
offset: const Offset(0, 2), blurRadius: 15,
offset: const Offset(0, 5),
), ),
], ],
), ),
child: CupertinoButton( child: CupertinoButton(
onPressed: homeCaptainController.onButtonSelected, onPressed: homeCaptainController.onButtonSelected,
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 24, vertical: 12), horizontal: 20, vertical: 10),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Icon( Container(
homeCaptainController.isActive padding: const EdgeInsets.all(4),
? CupertinoIcons.check_mark_circled_solid decoration: BoxDecoration(
: CupertinoIcons.circle, color: Colors.white.withOpacity(0.2),
color: Colors.white, shape: BoxShape.circle,
size: 24, ),
child: Icon(
homeCaptainController.isActive
? Icons.power_settings_new_rounded
: Icons.power_off_rounded,
color: Colors.white,
size: 20,
),
), ),
const SizedBox(width: 8), const SizedBox(width: 10),
Text( Text(
homeCaptainController.isActive homeCaptainController.isActive
? 'Connected'.tr ? 'Online'.tr
: 'Not Connected'.tr, : 'Offline'.tr,
style: const TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.bold,
letterSpacing: 0.5,
), ),
), ),
], ],

View File

@@ -2,7 +2,6 @@ import 'package:flutter_overlay_window/flutter_overlay_window.dart';
import 'package:sefer_driver/constant/box_name.dart'; import 'package:sefer_driver/constant/box_name.dart';
import 'package:sefer_driver/controller/firebase/local_notification.dart'; import 'package:sefer_driver/controller/firebase/local_notification.dart';
import 'package:sefer_driver/main.dart'; import 'package:sefer_driver/main.dart';
import 'package:sefer_driver/views/auth/captin/otp_page.dart';
import 'package:sefer_driver/views/home/Captin/driver_map_page.dart'; import 'package:sefer_driver/views/home/Captin/driver_map_page.dart';
import 'package:sefer_driver/views/home/Captin/orderCaptin/vip_order_page.dart'; import 'package:sefer_driver/views/home/Captin/orderCaptin/vip_order_page.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -13,7 +12,6 @@ import 'package:sefer_driver/views/widgets/mydialoug.dart';
import '../../../../../constant/colors.dart'; import '../../../../../constant/colors.dart';
import '../../../../../constant/links.dart'; import '../../../../../constant/links.dart';
import '../../../../../controller/firebase/firbase_messge.dart';
import '../../../../../controller/firebase/notification_service.dart'; import '../../../../../controller/firebase/notification_service.dart';
import '../../../../../controller/functions/crud.dart'; import '../../../../../controller/functions/crud.dart';
import '../../../../../controller/home/captin/order_request_controller.dart'; import '../../../../../controller/home/captin/order_request_controller.dart';
@@ -21,319 +19,279 @@ import '../../../../../controller/home/navigation/navigation_view.dart';
import '../../../../../print.dart'; import '../../../../../print.dart';
import '../../../../Rate/ride_calculate_driver.dart'; import '../../../../Rate/ride_calculate_driver.dart';
// ─────────────────────────────────────────────
// Design Tokens (Responsive)
// ─────────────────────────────────────────────
class _T {
static Color surface(BuildContext context) =>
Theme.of(context).brightness == Brightness.dark
? const Color(0xFF16213E)
: Colors.white;
static const Color accent = Color(0xFFF0A500);
static const Color blue = Color(0xFF3498DB);
static Color border(BuildContext context) =>
Theme.of(context).brightness == Brightness.dark
? const Color(0xFF2A2A4A)
: Colors.grey.withOpacity(0.2);
static const double radius = 14.0;
}
// ─────────────────────────────────────────────
// Left Side Menu
// ─────────────────────────────────────────────
/// Returns the vertical icon column anchored to the left-center of the map.
GetBuilder<HomeCaptainController> leftMainMenuCaptainIcons() { GetBuilder<HomeCaptainController> leftMainMenuCaptainIcons() {
final firebaseMessagesController =
Get.isRegistered<FirebaseMessagesController>()
? Get.find<FirebaseMessagesController>()
: Get.put(FirebaseMessagesController());
return GetBuilder<HomeCaptainController>( return GetBuilder<HomeCaptainController>(
builder: (controller) => Positioned( builder: (ctrl) => Positioned(
bottom: Get.height * .2, // Place just above the bottom status bar
left: 6, bottom: 100,
child: Column( left: 10,
children: [ child: Builder(builder: (context) {
AnimatedContainer( return Container(
duration: const Duration(microseconds: 200), decoration: BoxDecoration(
width: controller.widthMapTypeAndTraffic, color: _T.surface(context),
decoration: BoxDecoration( borderRadius: BorderRadius.circular(_T.radius),
color: AppColor.secondaryColor, border: Border.all(color: _T.border(context)),
border: Border.all(color: AppColor.blueColor), boxShadow: [
borderRadius: BorderRadius.circular(15)), BoxShadow(
child: Builder(builder: (context) { color: Colors.black.withOpacity(0.3),
return IconButton( blurRadius: 10,
onPressed: () async { offset: const Offset(2, 4),
await checkForPendingOrderFromServer();
box.read(BoxName.rideArgumentsFromBackground) != 'failure'
? Get.to(() => PassengerLocationMapPage(),
arguments:
box.read(BoxName.rideArgumentsFromBackground))
: MyDialog().getDialog(
'Ride info'.tr,
'you dont have accepted ride'.tr,
() {
Get.back();
},
);
// 'box.read(BoxName.rideArgumentsFromBackground): ${box.read(BoxName.rideArgumentsFromBackground)}');
},
icon: Icon(
Icons.directions_car_rounded,
size: 29,
color:
box.read(BoxName.rideArgumentsFromBackground) == 'failure'
? AppColor.redColor
: AppColor.greenColor,
),
);
}),
),
const SizedBox(
height: 5,
),
AnimatedContainer(
duration: const Duration(microseconds: 200),
width: controller.widthMapTypeAndTraffic,
decoration: BoxDecoration(
color: AppColor.secondaryColor,
border: Border.all(color: AppColor.blueColor),
borderRadius: BorderRadius.circular(15)),
child: IconButton(
onLongPress: () {
box.write(BoxName.statusDriverLocation, 'off');
},
onPressed: () {
// NotificationController1()
// .showNotification('Sefer Driver'.tr, ''.tr, '', '');
final now = DateTime.now();
DateTime? lastRequestTime =
box.read(BoxName.lastTimeStaticThrottle);
if (lastRequestTime == null ||
now.difference(lastRequestTime).inMinutes >= 2) {
// Update the last request time to now
lastRequestTime = now;
box.write(BoxName.lastTimeStaticThrottle, lastRequestTime);
// Navigate to the RideCalculateDriver page
Get.to(() => RideCalculateDriver());
} else {
// Optionally show a message or handle the throttling case
final minutesLeft =
2 - now.difference(lastRequestTime).inMinutes;
// Get.snackbar(
// '${'Please wait'.tr} $minutesLeft ${"minutes before trying again.".tr}',
// '');
NotificationController().showNotification(
'Intaleq Driver'.tr,
'${'Please wait'.tr} $minutesLeft ${"minutes before trying again.".tr}',
'ding',
'');
}
},
icon: const Icon(
FontAwesome5.chart_bar,
size: 29,
color: AppColor.blueColor,
), ),
), ],
), ),
padding: const EdgeInsets.symmetric(vertical: 6),
const SizedBox( child: Column(
height: 5, mainAxisSize: MainAxisSize.min,
), children: [
// Platform.isAndroid // ── 1. Active Ride shortcut ──────────
// ? _MenuIcon(
int.parse(box.read(BoxName.carYear).toString()) > 2023 icon: Icons.directions_car_rounded,
? AnimatedContainer( color:
duration: const Duration(microseconds: 200), box.read(BoxName.rideArgumentsFromBackground) == 'failure'
width: controller.widthMapTypeAndTraffic, ? Colors.red.shade400
decoration: BoxDecoration( : Colors.green.shade400,
color: AppColor.secondaryColor, tooltip: 'Active Ride'.tr,
border: Border.all(color: AppColor.blueColor), onTap: () async {
borderRadius: BorderRadius.circular(15)), await checkForPendingOrderFromServer();
child: Builder(builder: (context) { if (box.read(BoxName.rideArgumentsFromBackground) != 'failure') {
return IconButton( Get.to(
onPressed: () async { () => PassengerLocationMapPage(),
// mySnakeBarError('ad'); arguments: box.read(BoxName.rideArgumentsFromBackground),
Get.to(() => const VipOrderPage());
},
icon: const Icon(
Octicons.watch,
size: 29,
color: AppColor.blueColor,
),
); );
}), } else {
) MyDialog().getDialog(
: const SizedBox(), 'Ride info'.tr,
// const SizedBox( 'you dont have accepted ride'.tr,
// height: 5, () => Get.back(),
// ), );
}
AnimatedContainer(
duration: const Duration(microseconds: 200),
width: controller.widthMapTypeAndTraffic,
decoration: BoxDecoration(
color: AppColor.secondaryColor,
border: Border.all(color: AppColor.blueColor),
borderRadius: BorderRadius.circular(15)),
child: Builder(builder: (context) {
return IconButton(
onPressed: () async {
box.remove(BoxName.agreeTerms);
Get.to(() => const NavigationView());
// box.write(BoxName.statusDriverLocation, 'off');
}, },
icon: const Icon( ),
FontAwesome5.map,
size: 29,
color: AppColor.blueColor,
),
);
}),
),
// AnimatedContainer(
// duration: const Duration(microseconds: 200),
// width: controller.widthMapTypeAndTraffic,
// decoration: BoxDecoration(
// color: AppColor.secondaryColor,
// border: Border.all(color: AppColor.blueColor),
// borderRadius: BorderRadius.circular(15)),
// child: Builder(builder: (context) {
// return IconButton(
// onPressed: () async {
// NotificationService.sendNotification(
// target: 'service', // الإرسال لجميع المشتركين في "service"
// title: 'طلب خدمة جديد',
// body: 'تم استلام طلب خدمة جديد. الرجاء مراجعة التفاصيل.',
// isTopic: true,
// category: 'new_service_request', // فئة توضح نوع الإشعار
// );
// },
// icon: const Icon(
// FontAwesome5.grin_tears,
// size: 29,
// color: AppColor.blueColor,
// ),
// );
// }),
// ),
const SizedBox( _Divider(context),
height: 5,
// ── 2. Earnings Chart ────────────────
_MenuIcon(
icon: FontAwesome5.chart_bar,
color: _T.blue,
tooltip: 'Earnings'.tr,
onTap: () {
final now = DateTime.now();
DateTime? lastTime = box.read(BoxName.lastTimeStaticThrottle);
if (lastTime == null ||
now.difference(lastTime).inMinutes >= 2) {
box.write(BoxName.lastTimeStaticThrottle, now);
Get.to(() => RideCalculateDriver());
} else {
final left = 2 - now.difference(lastTime).inMinutes;
NotificationController().showNotification(
'Intaleq Driver'.tr,
'${'Please wait'.tr} $left ${"minutes before trying again.".tr}',
'ding',
'',
);
}
},
onLongPress: () =>
box.write(BoxName.statusDriverLocation, 'off'),
),
// ── 3. VIP Orders (2023+ cars only) ──
if (int.tryParse(box.read(BoxName.carYear).toString()) != null &&
int.parse(box.read(BoxName.carYear).toString()) > 2023) ...[
_Divider(context),
_MenuIcon(
icon: Octicons.watch,
color: _T.accent,
tooltip: 'VIP Orders'.tr,
onTap: () => Get.to(() => const VipOrderPage()),
),
],
],
), ),
], );
), }),
), ),
); );
} }
// ─────────────────────────────────────────────
// Reusable sub-widgets
// ─────────────────────────────────────────────
class _MenuIcon extends StatelessWidget {
final IconData icon;
final Color color;
final String tooltip;
final VoidCallback onTap;
final VoidCallback? onLongPress;
const _MenuIcon({
required this.icon,
required this.color,
required this.tooltip,
required this.onTap,
this.onLongPress,
});
@override
Widget build(BuildContext context) => Tooltip(
message: tooltip,
child: InkWell(
onTap: onTap,
onLongPress: onLongPress,
borderRadius: BorderRadius.circular(10),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
child: Icon(icon, color: color, size: 26),
),
),
);
}
/// Thin separator between icons
class _Divider extends StatelessWidget {
final BuildContext context;
const _Divider(this.context);
@override
Widget build(BuildContext context) => Container(
height: 1,
width: 32,
margin: const EdgeInsets.symmetric(horizontal: 6),
color: _T.border(this.context),
);
}
// ─────────────────────────────────────────────
// Server Helpers (unchanged logic)
// ─────────────────────────────────────────────
Future<void> checkForPendingOrderFromServer() async { Future<void> checkForPendingOrderFromServer() async {
bool _isProcessingOrder = false; bool isProcessing = false;
if (_isProcessingOrder) return; if (isProcessing) return;
final driverId = box.read(BoxName.driverID)?.toString(); final driverId = box.read(BoxName.driverID)?.toString();
if (driverId == null) return; // Can't check without a driver ID if (driverId == null) return;
_isProcessingOrder = true; // Lock isProcessing = true;
try { try {
// You need to create this CRUD method
var response = await CRUD().post( var response = await CRUD().post(
link: AppLink.getArgumentAfterAppliedFromBackground, link: AppLink.getArgumentAfterAppliedFromBackground,
payload: {'driver_id': driverId}, payload: {'driver_id': driverId},
); );
Log.print('response: ${response}'); Log.print('response: $response');
// Assuming the server returns order data if found, or 'failure'/'none' if not
if (response['status'] == 'success') { if (response['status'] == 'success') {
final Map<String, dynamic> orderInfoFromServer = response['message']; final Map<String, dynamic> orderInfo = response['message'];
final Map<String, dynamic> rideArguments = final Map<String, dynamic> rideArgs =
_transformServerDataToAppArguments(orderInfoFromServer); _transformServerDataToAppArguments(orderInfo);
// 2. Build the new arguments map, matching your Flutter structure
///////////// final customerToken = response['message']['token_passenger'];
final customerToken = (response)['message']['token_passenger']; final orderId = response['message']['ride_id'].toString();
final orderId = (response)['message']['ride_id'].toString();
box.write(BoxName.rideArgumentsFromBackground, rideArguments); box.write(BoxName.rideArgumentsFromBackground, rideArgs);
box.write(BoxName.statusDriverLocation, 'on'); box.write(BoxName.statusDriverLocation, 'on');
box.write(BoxName.rideStatus, 'Apply'); box.write(BoxName.rideStatus, 'Apply');
Get.put(OrderRequestController()).changeApplied(); Get.put(OrderRequestController()).changeApplied();
// MyDialog().getDialog(orderId.toString(), customerToken, () {});
// Now proceed with the UI flow Get.to(
// _sendAcceptanceNotification(customerToken, orderId.toString()); () => PassengerLocationMapPage(),
// await _bringAppToForegroundAndNavigate(orderId); arguments: box.read(BoxName.rideArgumentsFromBackground),
Get.to(() => PassengerLocationMapPage(), );
arguments: box.read(BoxName.rideArgumentsFromBackground));
} else { } else {
box.write(BoxName.rideArgumentsFromBackground, 'failure'); box.write(BoxName.rideArgumentsFromBackground, 'failure');
} }
} catch (e) { } catch (_) {
// silent
} finally { } finally {
_isProcessingOrder = false; // Release lock isProcessing = false;
} }
} }
Map<String, dynamic> _transformServerDataToAppArguments( Map<String, dynamic> _transformServerDataToAppArguments(
Map<String, dynamic> serverData) { Map<String, dynamic> d) {
// Helper function to safely get and convert values to String String s(String key, [String def = 'unknown']) => d[key]?.toString() ?? def;
String _getString(String key, [String defaultValue = 'unknown']) {
// serverData[key] might be an int, double, or string. .toString() handles all.
// If it's null, use the default value.
return serverData[key]?.toString() ?? defaultValue;
}
return { return {
'passengerLocation': _getString('passenger_location'), 'passengerLocation': s('passenger_location'),
'passengerDestination': _getString('passenger_destination'), 'passengerDestination': s('passenger_destination'),
'Duration': _getString('duration'), 'Duration': s('duration'),
'totalCost': _getString('total_cost'), 'totalCost': s('total_cost'),
'Distance': _getString('distance'), 'Distance': s('distance'),
'name': _getString('name'), 'name': s('name'),
'phone': _getString('phone'), 'phone': s('phone'),
'email': _getString('email'), 'email': s('email'),
'tokenPassenger': _getString('token_passenger'), 'tokenPassenger': s('token_passenger'),
'direction': _getString('direction_url'), 'direction': s('direction_url'),
'DurationToPassenger': _getString('duration_to_passenger'), 'DurationToPassenger': s('duration_to_passenger'),
'rideId': _getString('ride_id'), 'rideId': s('ride_id'),
'passengerId': _getString('passenger_id'), 'passengerId': s('passenger_id'),
'driverId': _getString('driver_id'), 'driverId': s('driver_id'),
'durationOfRideValue': _getString('duration_of_ride'), 'durationOfRideValue': s('duration_of_ride'),
'paymentAmount': _getString('payment_amount'), 'paymentAmount': s('payment_amount'),
'paymentMethod': _getString('payment_method'), 'paymentMethod': s('payment_method'),
'passengerWalletBurc': _getString('passenger_wallet_burc'), 'passengerWalletBurc': s('passenger_wallet_burc'),
'timeOfOrder': _getString('time_of_order'), 'timeOfOrder': s('time_of_order'),
'totalPassenger': _getString('total_passenger'), 'totalPassenger': s('total_passenger'),
'carType': _getString('car_type'), 'carType': s('car_type'),
'kazan': _getString('kazan'), 'kazan': s('kazan'),
'startNameLocation': _getString('start_name_location'), 'startNameLocation': s('start_name_location'),
'endNameLocation': _getString('end_name_location'), 'endNameLocation': s('end_name_location'),
'step0': s('step0'),
// --- Special Handling --- 'step1': s('step1'),
'step2': s('step2'),
// Steps (handle null values by providing an empty string) 'step3': s('step3'),
'step0': _getString('step0'), 'step4': s('step4'),
'step1': _getString('step1'), 'WalletChecked': (d['wallet_checked'] == 1).toString(),
'step2': _getString('step2'), 'isHaveSteps': (d['has_steps'] == 1) ? 'startEnd' : 'noSteps',
'step3': _getString('step3'),
'step4': _getString('step4'),
// Boolean conversion (1/0 from server to 'true'/'false' string for the app)
'WalletChecked': (serverData['wallet_checked'] == 1).toString(),
// Logic-based conversion for isHaveSteps
// Your app's `rideArguments` expects 'startEnd', so we provide that if has_steps is 1.
// You might need to adjust this logic if 'haveSteps' is also a possibility.
'isHaveSteps': (serverData['has_steps'] == 1)
? 'startEnd'
: 'noSteps', // Providing a default
}; };
} }
void _sendAcceptanceNotification(String? customerToken, rideId) { void _sendAcceptanceNotification(String? customerToken, dynamic rideId) {
try { if (customerToken == null || customerToken.isEmpty) return;
if (customerToken == null) return;
List<String> bodyToPassenger = [ List<String> body = [
box.read(BoxName.driverID).toString(), box.read(BoxName.driverID).toString(),
box.read(BoxName.nameDriver).toString(), box.read(BoxName.nameDriver).toString(),
box.read(BoxName.tokenDriver).toString(), box.read(BoxName.tokenDriver).toString(),
rideId.toString() rideId.toString(),
]; ];
// Safely check for customer token NotificationService.sendNotification(
final String? token = customerToken; target: customerToken,
if (token != null && token.isNotEmpty) { title: 'Accepted Ride'.tr,
NotificationService.sendNotification( body: 'your ride is Accepted'.tr,
target: token.toString(), isTopic: false,
title: 'Accepted Ride'.tr, tone: 'start',
body: 'your ride is Accepted'.tr, driverList: body,
isTopic: false, // Important: this is a token category: 'Accepted Ride',
tone: 'start', );
driverList: bodyToPassenger, category: 'Accepted Ride',
);
} else {}
} catch (e) {}
} }

View File

@@ -2,7 +2,7 @@ import 'dart:convert';
import 'dart:math'; import 'dart:math';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:intaleq_maps/intaleq_maps.dart';
class ZonesController extends GetxController { class ZonesController extends GetxController {
Map<String, List<LatLng>> generateZoneMap( Map<String, List<LatLng>> generateZoneMap(

View File

@@ -132,10 +132,11 @@ class MaintainCenterPage extends StatelessWidget {
"When you complete 600 trips, you will be eligible to receive offers for maintenance of your car." "When you complete 600 trips, you will be eligible to receive offers for maintenance of your car."
.tr, .tr,
style: Theme.of(context).textTheme.titleMedium!.copyWith( style: Theme.of(context).textTheme.titleMedium!.copyWith(
color: Colors.grey[700], color: Theme.of(context).textTheme.bodyMedium?.color?.withOpacity(0.8),
height: 1.4, height: 1.4,
), ),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
// Trip Count Section in a Card // Trip Count Section in a Card
@@ -192,10 +193,11 @@ class MaintainCenterPage extends StatelessWidget {
.textTheme .textTheme
.bodyMedium! .bodyMedium!
.copyWith( .copyWith(
color: Colors.grey[800], color: Theme.of(context).textTheme.bodyMedium?.color?.withOpacity(0.9),
height: 1.5, height: 1.5,
), ),
), ),
], ],
), ),
), ),

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:google_maps_flutter/google_maps_flutter.dart'; import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:sefer_driver/constant/api_key.dart';
import '../../../../controller/functions/location_controller.dart'; import '../../../../controller/functions/location_controller.dart';
import '../../../../controller/home/captin/map_driver_controller.dart'; import '../../../../controller/home/captin/map_driver_controller.dart';
@@ -21,76 +22,24 @@ class GoogleDriverMap extends StatelessWidget {
final double mapPaddingBottom = MediaQuery.of(context).size.height * 0.3; final double mapPaddingBottom = MediaQuery.of(context).size.height * 0.3;
return GetBuilder<MapDriverController>( return GetBuilder<MapDriverController>(
builder: (controller) => GoogleMap( builder: (controller) => IntaleqMap(
apiKey: AK.mapAPIKEY,
onMapCreated: (mapController) { onMapCreated: (mapController) {
controller.onMapCreated(mapController); controller.onMapCreated(mapController);
// New: تطبيق الـ padding بعد إنشاء الخريطة مباشرة
mapController.setMapStyle('[]'); // يمكنك إضافة تصميم مخصص للخريطة هنا
// يمكنك استخدام CameraUpdate.scrollBy لتحريك الكاميرا إذا رغبت بذلك:
// controller.mapController!.animateCamera(CameraUpdate.scrollBy(0, mapPaddingBottom));
}, },
// New: إضافة padding لتحريك مركز الخريطة للأعلى، مما يجعل أيقونة السائق تظهر في الأسفل zoomControlsEnabled: false,
zoomControlsEnabled: false, // Changed: تم إخفاء أزرار الزوم الافتراضية
initialCameraPosition: CameraPosition( initialCameraPosition: CameraPosition(
target: locationController.myLocation, target: locationController.myLocation,
zoom: 17, zoom: 17,
bearing: locationController.heading, // استخدام اتجاه السائق bearing: locationController.heading,
tilt: 60, // زاوية ميل tilt: 60,
), ),
onCameraMove: (position) { // padding: EdgeInsets.only(bottom: 50, top: Get.height * 0.7),
CameraPosition( // minMaxZoomPreference: const MinMaxZoomPreference(8, 18),
target: locationController.myLocation, myLocationEnabled: false,
zoom: 17.5,
tilt: 50.0,
bearing: locationController.heading,
);
},
padding: EdgeInsets.only(bottom: 50, top: Get.height * 0.7),
minMaxZoomPreference: const MinMaxZoomPreference(8, 18),
myLocationEnabled: false, // Changed: تم الاعتماد على ماركر مخصص
myLocationButtonEnabled: true, myLocationButtonEnabled: true,
compassEnabled: true, compassEnabled: true,
mapType: MapType.terrain, polylines: controller.polyLines.toSet(),
trafficEnabled: true, // Changed: تفعيل عرض حركة المرور
buildingsEnabled: true,
polylines: {
// Polyline(
// zIndex: 2,
// polylineId: const PolylineId('route1'),
// points: controller.polylineCoordinates,
// color: const Color.fromARGB(255, 163, 81, 246),
// width: 6, // Changed: زيادة عرض الخط
// startCap: Cap.roundCap,
// endCap: Cap.roundCap,
// ),
// Polyline(
// zIndex: 2,
// polylineId: const PolylineId('route'),
// points: controller.polylineCoordinatesDestination,
// color: const Color.fromARGB(255, 10, 29, 126),
// width: 6, // Changed: زيادة عرض الخط
// startCap: Cap.roundCap,
// endCap: Cap.roundCap,
// ),
Polyline(
polylineId: const PolylineId('upcoming_route'),
points: controller.upcomingPathPoints,
color: Colors.blue, // أو أي لون آخر تختاره للمسار
width: 8,
zIndex: 2,
),
// 2. الخط المقطوع (تحت)
Polyline(
polylineId: const PolylineId('traveled_route'),
points: controller.traveledPathPoints,
color: Colors.grey.withOpacity(0.8),
width: 7,
zIndex: 1,
),
},
markers: { markers: {
Marker( Marker(
markerId: MarkerId('MyLocation'.tr), markerId: MarkerId('MyLocation'.tr),

View File

@@ -48,7 +48,7 @@ class GoogleMapApp extends StatelessWidget {
border: Border.all( border: Border.all(
color: AppColor.blueColor.withOpacity(0.2), width: 1), color: AppColor.blueColor.withOpacity(0.2), width: 1),
), ),
child: const Icon( child: Icon(
MaterialCommunityIcons.google_maps, MaterialCommunityIcons.google_maps,
size: 28, size: 28,
color: AppColor.secondaryColor, color: AppColor.secondaryColor,

View File

@@ -1,11 +1,11 @@
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
class MarkerGenerator { class MarkerGenerator {
// دالة لرسم ماركر يحتوي على نص (للوقت والمسافة) // دالة لرسم ماركر يحتوي على نص (للوقت والمسافة)
static Future<BitmapDescriptor> createCustomMarkerBitmap({ static Future<InlqBitmap> createCustomMarkerBitmap({
required String title, required String title,
required String subtitle, required String subtitle,
required Color color, required Color color,
@@ -19,15 +19,15 @@ class MarkerGenerator {
const double height = 110.0; const double height = 110.0;
const double circleRadius = 25.0; const double circleRadius = 25.0;
// 1. رسم المربع (Info Box) // 1. رسم المربع (Info Box) مع تدرج لوني بسيط
final Paint paint = Paint()..color = color; final Paint paint = Paint()..color = color;
final RRect rRect = RRect.fromRectAndRadius( final RRect rRect = RRect.fromRectAndRadius(
const Rect.fromLTWH(0, 0, width, 60), const Rect.fromLTWH(0, 0, width, 65),
const Radius.circular(15), const Radius.circular(20), // زوايا أكثر استدارة لشكل عصري
); );
// ظل خفيف // ظل أقوى لمظهر بارز (Premium Feel)
canvas.drawShadow(Path()..addRRect(rRect), Colors.black, 5.0, true); canvas.drawShadow(Path()..addRRect(rRect), Colors.black54, 10.0, true);
canvas.drawRRect(rRect, paint); canvas.drawRRect(rRect, paint);
// 2. رسم مثلث صغير أسفل المربع (Arrow Tail) // 2. رسم مثلث صغير أسفل المربع (Arrow Tail)
@@ -96,11 +96,11 @@ class MarkerGenerator {
); );
final ByteData? data = final ByteData? data =
await image.toByteData(format: ui.ImageByteFormat.png); await image.toByteData(format: ui.ImageByteFormat.png);
return BitmapDescriptor.fromBytes(data!.buffer.asUint8List()); return InlqBitmap.fromBytes(data!.buffer.asUint8List());
} }
// دالة خاصة لرسم ماركر السائق (دائرة وخلفها سهم) // دالة خاصة لرسم ماركر السائق (دائرة وخلفها سهم)
static Future<BitmapDescriptor> createDriverMarker() async { static Future<InlqBitmap> createDriverMarker() async {
final ui.PictureRecorder pictureRecorder = ui.PictureRecorder(); final ui.PictureRecorder pictureRecorder = ui.PictureRecorder();
final Canvas canvas = Canvas(pictureRecorder); final Canvas canvas = Canvas(pictureRecorder);
const double size = 100.0; const double size = 100.0;
@@ -137,6 +137,6 @@ class MarkerGenerator {
.toImage(size.toInt(), size.toInt()); .toImage(size.toInt(), size.toInt());
final ByteData? data = final ByteData? data =
await image.toByteData(format: ui.ImageByteFormat.png); await image.toByteData(format: ui.ImageByteFormat.png);
return BitmapDescriptor.fromBytes(data!.buffer.asUint8List()); return InlqBitmap.fromBytes(data!.buffer.asUint8List());
} }
} }

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:google_maps_flutter/google_maps_flutter.dart'; import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:sefer_driver/constant/api_key.dart';
import 'package:sefer_driver/constant/colors.dart'; import 'package:sefer_driver/constant/colors.dart';
import 'package:sefer_driver/controller/home/captin/order_request_controller.dart'; import 'package:sefer_driver/controller/home/captin/order_request_controller.dart';
@@ -63,20 +64,21 @@ class OrderRequestPage extends StatelessWidget {
// 1. الخارطة // 1. الخارطة
Positioned.fill( Positioned.fill(
bottom: 300, bottom: 300,
child: GoogleMap( child: IntaleqMap(
mapType: MapType.normal, apiKey: AK.mapAPIKEY,
initialCameraPosition: CameraPosition( initialCameraPosition: CameraPosition(
target: LatLng( target: LatLng(
controller.latPassenger, controller.lngPassenger), controller.latPassenger, controller.lngPassenger),
zoom: 13.0, zoom: 13.0,
), ),
markers: controller.markers, markers: controller.markers,
mapType: Get.isDarkMode
? IntaleqMapType.normal
: IntaleqMapType.light,
polylines: controller.polylines, polylines: controller.polylines,
zoomControlsEnabled: false, zoomControlsEnabled: false,
myLocationButtonEnabled: false, myLocationButtonEnabled: false,
compassEnabled: false, compassEnabled: false,
padding: const EdgeInsets.only(
top: 80, bottom: 20, left: 20, right: 20),
onMapCreated: (c) { onMapCreated: (c) {
controller.onMapCreated(c); controller.onMapCreated(c);
controller.update(); controller.update();
@@ -124,15 +126,16 @@ class OrderRequestPage extends StatelessWidget {
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
child: Container( child: Container(
height: 360, height: 360,
decoration: const BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Theme.of(context).cardColor,
borderRadius: BorderRadius.only( borderRadius: const BorderRadius.only(
topLeft: Radius.circular(25), topLeft: Radius.circular(25),
topRight: Radius.circular(25), topRight: Radius.circular(25),
), ),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black12, color:
Theme.of(context).shadowColor.withOpacity(0.1),
blurRadius: 20, blurRadius: 20,
spreadRadius: 5) spreadRadius: 5)
], ],
@@ -146,8 +149,9 @@ class OrderRequestPage extends StatelessWidget {
width: 40, width: 40,
height: 4, height: 4,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.grey[300], color: Theme.of(context).dividerColor,
borderRadius: BorderRadius.circular(2)))), borderRadius: BorderRadius.circular(2)))),
const SizedBox(height: 15), const SizedBox(height: 15),
// الصف الأول: الراكب والسعر // الصف الأول: الراكب والسعر
@@ -156,11 +160,14 @@ class OrderRequestPage extends StatelessWidget {
children: [ children: [
Row( Row(
children: [ children: [
const CircleAvatar( CircleAvatar(
radius: 24, radius: 24,
backgroundColor: Color(0xFFF5F5F5), backgroundColor: Theme.of(context)
.colorScheme
.surfaceVariant,
child: Icon(Icons.person, child: Icon(Icons.person,
color: Colors.grey, size: 28), color: Theme.of(context).hintColor,
size: 28),
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
Column( Column(
@@ -233,27 +240,45 @@ class OrderRequestPage extends StatelessWidget {
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
vertical: 10, horizontal: 10), vertical: 10, horizontal: 10),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFF8F9FA), color: Theme.of(context)
.colorScheme
.surfaceVariant
.withOpacity(0.5),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey.shade200), border: Border.all(
color: Theme.of(context).dividerColor),
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [ children: [
_buildInfoItem( _buildInfoItem(
carIcon, carTypeLabel, carTypeColor), context, carIcon, carTypeLabel, carTypeColor),
Container( Container(
height: 20, height: 20,
width: 1, width: 1,
color: Colors.grey.shade300), color: Theme.of(context).dividerColor),
_buildInfoItem(Icons.route, _buildInfoItem(
controller.totalTripDistance, Colors.black87), context,
Icons.route,
controller.totalTripDistance,
Theme.of(context)
.textTheme
.bodyLarge
?.color ??
Colors.black87),
Container( Container(
height: 20, height: 20,
width: 1, width: 1,
color: Colors.grey.shade300), color: Theme.of(context).dividerColor),
_buildInfoItem(Icons.access_time_filled, _buildInfoItem(
controller.totalTripDuration, Colors.black87), context,
Icons.access_time_filled,
controller.totalTripDuration,
Theme.of(context)
.textTheme
.bodyLarge
?.color ??
Colors.black87),
], ],
), ),
), ),
@@ -397,7 +422,8 @@ class OrderRequestPage extends StatelessWidget {
); );
} }
Widget _buildInfoItem(IconData icon, String text, Color color) { Widget _buildInfoItem(
BuildContext context, IconData icon, String text, Color color) {
return Row( return Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [

View File

@@ -2,7 +2,7 @@
// import 'package:flutter/material.dart'; // import 'package:flutter/material.dart';
// import 'package:get/get.dart'; // import 'package:get/get.dart';
// import 'package:google_maps_flutter/google_maps_flutter.dart'; // import 'package:intaleq_maps/intaleq_maps.dart';
// import 'package:sefer_driver/controller/home/captin/home_captain_controller.dart'; // import 'package:sefer_driver/controller/home/captin/home_captain_controller.dart';
// import 'package:sefer_driver/constant/box_name.dart'; // import 'package:sefer_driver/constant/box_name.dart';

View File

@@ -1,7 +1,8 @@
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:sefer_driver/constant/api_key.dart';
import 'dart:math' as math; import 'dart:math' as math;
import '../../../../constant/colors.dart'; import '../../../../constant/colors.dart';
@@ -60,7 +61,8 @@ class _OrderRequestPageTestState extends State<OrderRequestPageTest> {
children: [ children: [
SizedBox( SizedBox(
height: Get.height * .33, height: Get.height * .33,
child: Obx(() => GoogleMap( child: Obx(() => IntaleqMap(
apiKey: AK.mapAPIKEY,
initialCameraPosition: CameraPosition( initialCameraPosition: CameraPosition(
zoom: 12, zoom: 12,
target: target:
@@ -69,13 +71,13 @@ class _OrderRequestPageTestState extends State<OrderRequestPageTest> {
cameraTargetBounds: CameraTargetBounds( cameraTargetBounds: CameraTargetBounds(
_orderRequestController.mapBounds.value), _orderRequestController.mapBounds.value),
myLocationButtonEnabled: true, myLocationButtonEnabled: true,
trafficEnabled: false, // trafficEnabled: false,
buildingsEnabled: false, // buildingsEnabled: false,
mapToolbarEnabled: true, // mapToolbarEnabled: true,
myLocationEnabled: true, myLocationEnabled: true,
markers: _orderRequestController.markers.value, markers: _orderRequestController.markers.value,
polylines: _orderRequestController.polylines.value, polylines: _orderRequestController.polylines.value,
onMapCreated: (GoogleMapController controller) { onMapCreated: (IntaleqMapController controller) {
_orderRequestController.mapController.value = _orderRequestController.mapController.value =
controller; controller;
}, },
@@ -104,11 +106,11 @@ class OrderRequestController extends GetxController {
Rx<Set<Marker>> markers = Rx<Set<Marker>>({}); Rx<Set<Marker>> markers = Rx<Set<Marker>>({});
Rx<Set<Polyline>> polylines = Rx<Set<Polyline>>({}); Rx<Set<Polyline>> polylines = Rx<Set<Polyline>>({});
Rx<GoogleMapController?> mapController = Rx<GoogleMapController?>(null); Rx<IntaleqMapController?> mapController = Rx<IntaleqMapController?>(null);
// Icons for start and end markers // Icons for start and end markers
late BitmapDescriptor startIcon; late InlqBitmap startIcon;
late BitmapDescriptor endIcon; late InlqBitmap endIcon;
// Coordinates for passenger location and destination // Coordinates for passenger location and destination
Rx<LatLng?> passengerLocation = Rx<LatLng?>(null); Rx<LatLng?> passengerLocation = Rx<LatLng?>(null);
@@ -123,12 +125,8 @@ class OrderRequestController extends GetxController {
void _initializeMarkerIcons() async { void _initializeMarkerIcons() async {
// Load custom marker icons // Load custom marker icons
startIcon = await BitmapDescriptor.fromAssetImage( startIcon = InlqBitmap.fromAsset('assets/start_marker.png');
const ImageConfiguration(size: Size(48, 48)), endIcon = InlqBitmap.fromAsset('assets/end_marker.png');
'assets/start_marker.png');
endIcon = await BitmapDescriptor.fromAssetImage(
const ImageConfiguration(size: Size(48, 48)), 'assets/end_marker.png');
} }
void parseCoordinates(List myList) { void parseCoordinates(List myList) {
@@ -184,10 +182,10 @@ class OrderRequestController extends GetxController {
polylines.value = { polylines.value = {
Polyline( Polyline(
zIndex: 1, zIndex: 1,
consumeTapEvents: true, // consumeTapEvents: true,
geodesic: true, geodesic: true,
endCap: Cap.buttCap, // endCap: Cap.buttCap,
startCap: Cap.buttCap, // startCap: Cap.buttCap,
visible: true, visible: true,
polylineId: const PolylineId('routeOrder'), polylineId: const PolylineId('routeOrder'),
points: [passengerLocation.value!, passengerDestination.value!], points: [passengerLocation.value!, passengerDestination.value!],

View File

@@ -2,30 +2,35 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:sefer_driver/constant/colors.dart'; import 'package:sefer_driver/constant/colors.dart';
import 'package:sefer_driver/constant/style.dart'; // Assuming this has your text styles import 'package:sefer_driver/constant/style.dart';
import 'package:sefer_driver/views/widgets/mycircular.dart'; // Assuming this is your loading widget import 'package:sefer_driver/constant/finance_design_system.dart';
import 'package:sefer_driver/views/widgets/mycircular.dart';
import '../../../controller/payment/driver_payment_controller.dart'; import '../../../controller/payment/driver_payment_controller.dart';
import 'widgets/transaction_preview_item.dart';
class PaymentHistoryDriverPage extends StatelessWidget { class PaymentHistoryDriverPage extends StatelessWidget {
const PaymentHistoryDriverPage({super.key}); const PaymentHistoryDriverPage({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// Initialize your controller
Get.put(DriverWalletHistoryController()); Get.put(DriverWalletHistoryController());
return Scaffold( return Scaffold(
backgroundColor: FinanceDesignSystem.backgroundColor,
appBar: AppBar( appBar: AppBar(
title: Text('Payment History'.tr), title: Text('Payment History'.tr,
backgroundColor: Colors.white, style: const TextStyle(fontWeight: FontWeight.bold, color: FinanceDesignSystem.primaryDark)),
elevation: 1, backgroundColor: Colors.transparent,
elevation: 0,
centerTitle: true,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios_new_rounded, color: FinanceDesignSystem.primaryDark, size: 20),
onPressed: () => Get.back(),
),
), ),
backgroundColor: Colors.grey[100],
body: GetBuilder<DriverWalletHistoryController>( body: GetBuilder<DriverWalletHistoryController>(
builder: (controller) { builder: (controller) {
if (controller.isLoading) { if (controller.isLoading) {
// Using your custom loading indicator
return const Center(child: MyCircularProgressIndicator()); return const Center(child: MyCircularProgressIndicator());
} }
@@ -34,16 +39,10 @@ class PaymentHistoryDriverPage extends StatelessWidget {
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon(Icons.account_balance_wallet_outlined, Icon(Icons.history_rounded, size: 80, color: Colors.grey.shade300),
size: 80, color: Colors.grey[400]),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text('No transactions yet'.tr,
'No transactions yet'.tr, style: TextStyle(color: Colors.grey.shade400, fontWeight: FontWeight.bold)),
style: Theme.of(context)
.textTheme
.headlineSmall
?.copyWith(color: Colors.grey[600]),
),
], ],
), ),
); );
@@ -51,18 +50,26 @@ class PaymentHistoryDriverPage extends StatelessWidget {
return AnimationLimiter( return AnimationLimiter(
child: ListView.builder( child: ListView.builder(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
itemCount: controller.archive.length, itemCount: controller.archive.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
var transaction = controller.archive[index]; final tx = controller.archive[index];
final double amount = double.tryParse(tx['amount']?.toString() ?? '0') ?? 0;
return AnimationConfiguration.staggeredList( return AnimationConfiguration.staggeredList(
position: index, position: index,
duration: const Duration(milliseconds: 375), duration: const Duration(milliseconds: 375),
child: SlideAnimation( child: SlideAnimation(
verticalOffset: 50.0, verticalOffset: 50.0,
child: FadeInAnimation( child: FadeInAnimation(
child: _TransactionCard(transaction: transaction), child: TransactionPreviewItem(
title: amount >= 0 ? 'Credit'.tr : 'Debit'.tr,
subtitle: tx['created_at'] ?? '',
amount: amount.abs().toStringAsFixed(0),
date: tx['created_at']?.split(' ')[0] ?? '',
type: amount >= 0 ? 'credit' : 'debit',
onTap: () {},
),
), ),
), ),
); );
@@ -74,71 +81,3 @@ class PaymentHistoryDriverPage extends StatelessWidget {
); );
} }
} }
// A dedicated widget for displaying a single transaction with a modern UI.
class _TransactionCard extends StatelessWidget {
final Map<String, dynamic> transaction;
const _TransactionCard({required this.transaction});
@override
Widget build(BuildContext context) {
// Safely parse the amount to avoid errors
final double amount =
double.tryParse(transaction['amount']?.toString() ?? '0') ?? 0;
final bool isCredit = amount >= 0;
final Color indicatorColor =
isCredit ? AppColor.greenColor : AppColor.redColor;
final IconData iconData =
isCredit ? Icons.arrow_upward_rounded : Icons.arrow_downward_rounded;
final String transactionType = (isCredit ? 'Credit'.tr : 'Debit'.tr).tr;
return Card(
elevation: 2,
shadowColor: Colors.black.withOpacity(0.05),
margin: const EdgeInsets.only(bottom: 12.0),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
clipBehavior: Clip.antiAlias, // Ensures the color bar is clipped neatly
child: IntrinsicHeight(
// Ensures the color bar and content have the same height
child: Row(
children: [
// Left-side color indicator bar
Container(width: 6, color: indicatorColor),
Expanded(
child: ListTile(
leading: Icon(iconData, color: indicatorColor, size: 30),
title: Text(
// Use .abs() to remove the negative sign from the display
'${amount.abs().toStringAsFixed(2)} ${'SYP'.tr}',
style: AppStyle.title.copyWith(
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
subtitle: Text(
transaction['created_at'] ?? 'No date',
style: AppStyle.title.copyWith(
fontSize: 12,
color: Colors.grey[600],
),
),
trailing: Text(
transactionType,
style: AppStyle.title.copyWith(
fontSize: 14,
color: indicatorColor,
fontWeight: FontWeight.w600,
),
),
),
),
],
),
),
);
}
}

View File

@@ -11,6 +11,7 @@ import 'package:sefer_driver/controller/payment/smsPaymnet/payment_services.dart
import 'package:webview_flutter/webview_flutter.dart'; import 'package:webview_flutter/webview_flutter.dart';
import '../../../constant/box_name.dart'; import '../../../constant/box_name.dart';
import '../../../constant/finance_design_system.dart';
import '../../../constant/links.dart'; import '../../../constant/links.dart';
import '../../../controller/functions/crud.dart'; import '../../../controller/functions/crud.dart';
import '../../../controller/payment/mtn_new/mtn_payment_new_screen.dart'; import '../../../controller/payment/mtn_new/mtn_payment_new_screen.dart';
@@ -21,8 +22,8 @@ import '../../widgets/my_textField.dart';
import 'ecash.dart'; import 'ecash.dart';
class PointsCaptain extends StatelessWidget { class PointsCaptain extends StatelessWidget {
PaymentController paymentController = Get.put(PaymentController()); final PaymentController paymentController = Get.put(PaymentController());
CaptainWalletController captainWalletController = final CaptainWalletController captainWalletController =
Get.put(CaptainWalletController()); Get.put(CaptainWalletController());
PointsCaptain({ PointsCaptain({
@@ -31,263 +32,67 @@ class PointsCaptain extends StatelessWidget {
required this.countPoint, required this.countPoint,
required this.pricePoint, required this.pricePoint,
}); });
final Color kolor; final Color kolor;
final String countPoint; final String countPoint;
double pricePoint; final double pricePoint;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return InkWell( return Container(
onTap: () async { margin: const EdgeInsets.only(right: 12, bottom: 4),
Get.defaultDialog( child: Material(
title: 'Which method you will pay'.tr, color: Colors.white,
titleStyle: AppStyle.title, borderRadius: BorderRadius.circular(20),
content: Column( elevation: 4,
crossAxisAlignment: CrossAxisAlignment.center, shadowColor: kolor.withOpacity(0.3),
child: InkWell(
onTap: () => _showPaymentOptions(context),
borderRadius: BorderRadius.circular(20),
child: Container(
width: 130,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
kolor.withOpacity(0.05),
Colors.white,
],
),
border: Border.all(color: kolor.withOpacity(0.2), width: 1.5),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Text( Container(
'${'you can buy '.tr}$countPoint ${'L.S'.tr}${'by '.tr}${'$pricePoint'.tr}', padding: const EdgeInsets.all(10),
style: AppStyle.title, decoration: BoxDecoration(
color: kolor.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(Icons.account_balance_wallet_rounded,
color: kolor, size: 24),
), ),
const SizedBox(height: 10),
// Add some spacing between buttons
GestureDetector(
onTap: () async {
Get.back();
payWithEcashDriver(context, pricePoint.toString());
},
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Pay with Debit Card'.tr),
const SizedBox(width: 10),
Icon(Icons.credit_card_sharp,
color: AppColor.blueColor, size: 70),
],
)),
// GestureDetector(
// onTap: () async {
// Get.back();
// Get.defaultDialog(
// barrierDismissible: false,
// title: 'Insert Wallet phone number'.tr,
// content: Form(
// key: paymentController.formKey,
// child: MyTextForm(
// controller:
// paymentController.walletphoneController,
// label: 'Insert Wallet phone number'.tr,
// hint: '963941234567',
// type: TextInputType.phone)),
// confirm: MyElevatedButton(
// title: 'OK'.tr,
// onPressed: () async {
// Get.back();
// if (paymentController.formKey.currentState!
// .validate()) {
// box.write(
// BoxName.phoneWallet,
// paymentController
// .walletphoneController.text);
// await payWithMTNWallet(
// context, pricePoint.toString(), 'SYP');
// }
// }));
// },
// child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Text('Pay by MTN Wallet'.tr),
// const SizedBox(width: 10),
// Image.asset(
// 'assets/images/cashMTN.png',
// width: 70,
// height: 70,
// fit: BoxFit.fill,
// ),
// ],
// )),
GestureDetector(
onTap: () async {
Get.back();
Get.defaultDialog(
barrierDismissible: false,
title: 'Insert Wallet phone number'.tr,
content: Form(
key: paymentController.formKey,
child: MyTextForm(
controller:
paymentController.walletphoneController,
label: 'Insert Wallet phone number'.tr,
hint: '963991234567',
type: TextInputType.phone)),
confirm: MyElevatedButton(
title: 'OK'.tr,
onPressed: () async {
Get.back();
if (paymentController.formKey.currentState!
.validate()) {
box.write(
BoxName.phoneWallet,
paymentController
.walletphoneController.text);
await payWithSyriaTelWallet(
context, pricePoint.toString(), 'SYP');
}
}));
},
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Pay by Syriatel Wallet'.tr),
const SizedBox(width: 10),
Image.asset(
'assets/images/syriatel.jpeg',
width: 70,
height: 70,
fit: BoxFit.fill,
),
],
)),
GestureDetector(
onTap: () async {
// التحقق بالبصمة قبل أي شيء
bool isAuthSupported =
await LocalAuthentication().isDeviceSupported();
if (isAuthSupported) {
bool didAuthenticate =
await LocalAuthentication().authenticate(
localizedReason:
'استخدم بصمة الإصبع أو الوجه لتأكيد الدفع',
);
if (!didAuthenticate) {
print("❌ User did not authenticate with biometrics");
return;
}
}
// الانتقال مباشرة لإنشاء الفاتورة بعد النجاح بالبصمة
Get.to(
() => PaymentScreenSmsProvider(amount: pricePoint));
},
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Pay by Sham Cash'.tr),
const SizedBox(width: 10),
Image.asset(
'assets/images/shamCash.png',
width: 70,
height: 70,
fit: BoxFit.fill,
),
],
)),
// GestureDetector(
// onTap: () async {
// Get.back();
// Get.defaultDialog(
// barrierDismissible: false,
// title: 'Insert Wallet phone number'.tr,
// content: Form(
// key: paymentController.formKey,
// child: MyTextForm(
// controller:
// paymentController.walletphoneController,
// label: 'Insert Wallet phone number'.tr,
// hint: '963941234567',
// type: TextInputType.phone)),
// confirm: MyElevatedButton(
// title: 'OK'.tr,
// onPressed: () async {
// Get.back();
// if (paymentController.formKey.currentState!
// .validate()) {
// box.write(
// BoxName.phoneWallet,
// paymentController
// .walletphoneController.text);
// // await payWithSyriaTelWallet(
// // context, pricePoint.toString(), 'SYP');
// bool isAuthSupported =
// await LocalAuthentication()
// .isDeviceSupported();
// if (isAuthSupported) {
// bool didAuthenticate =
// await LocalAuthentication()
// .authenticate(
// localizedReason:
// 'استخدم بصمة الإصبع أو الوجه لتأكيد الدفع',
// );
// if (!didAuthenticate) {
// if (Get.isDialogOpen ?? false) Get.back();
// print(
// "❌ User did not authenticate with biometrics");
// return;
// }
// }
// Get.to(() => PaymentScreenMtn(
// amount: pricePoint,
// userType: 'Driver',
// ));
// }
// }));
// },
// child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Text('Pay by MTN Wallet'.tr),
// const SizedBox(width: 10),
// Image.asset(
// 'assets/images/cashMTN.png',
// width: 70,
// height: 70,
// fit: BoxFit.fill,
// ),
// ],
// )),
],
));
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 3, vertical: 8),
child: Container(
width: Get.width * .22,
height: Get.width * .22,
margin: const EdgeInsets.all(4),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
kolor.withOpacity(0.3),
kolor,
kolor.withOpacity(0.7),
kolor,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
border: Border.all(color: AppColor.accentColor),
borderRadius: BorderRadius.circular(12),
shape: BoxShape.rectangle,
),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text( Text(
'$countPoint ${'L.S'.tr}', '$countPoint ${'SYP'.tr}',
style: AppStyle.subtitle style: const TextStyle(
.copyWith(color: AppColor.secondaryColor), fontWeight: FontWeight.w900,
fontSize: 15,
color: FinanceDesignSystem.primaryDark,
),
), ),
const SizedBox(height: 4),
Text( Text(
'$pricePoint ${'L.S'.tr}', '${'Price:'.tr} ${pricePoint.toStringAsFixed(0)} ${'SYP'.tr}',
style: style: TextStyle(
AppStyle.title.copyWith(color: AppColor.secondaryColor), fontSize: 11,
textAlign: TextAlign.center, color: Colors.grey.shade600,
fontWeight: FontWeight.w600,
),
), ),
], ],
), ),
@@ -296,6 +101,183 @@ class PointsCaptain extends StatelessWidget {
), ),
); );
} }
void _showPaymentOptions(BuildContext context) {
Get.bottomSheet(
isScrollControlled: true,
Container(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.8,
),
padding: const EdgeInsets.fromLTRB(24, 24, 24, 32),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(32)),
),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("Select Payment Method".tr,
style: FinanceDesignSystem.headingStyle),
IconButton(
icon: const Icon(Icons.close_rounded, color: Colors.grey),
onPressed: () => Get.back(),
),
],
),
const SizedBox(height: 8),
Text(
"${'Amount to charge:'.tr} $countPoint ${'SYP'.tr}",
style: FinanceDesignSystem.subHeadingStyle),
const SizedBox(height: 24),
_buildPaymentMethodTile(
icon: Icons.credit_card_rounded,
title: 'Debit Card'.tr,
subtitle: 'E-Cash payment gateway'.tr,
color: Colors.blue,
onTap: () {
Get.back();
payWithEcashDriver(context, pricePoint.toString());
},
),
const SizedBox(height: 16),
_buildPaymentMethodTile(
image: 'assets/images/syriatel.jpeg',
title: 'Syriatel Cash'.tr,
subtitle: 'Pay using Syriatel mobile wallet'.tr,
color: Colors.red,
onTap: () => _showPhoneInputDialog(context, 'Syriatel'),
),
const SizedBox(height: 16),
_buildPaymentMethodTile(
image: 'assets/images/shamCash.png',
title: 'Sham Cash'.tr,
subtitle: 'Pay using Sham Cash wallet'.tr,
color: Colors.orange,
onTap: () async {
Get.back();
bool isAuthSupported =
await LocalAuthentication().isDeviceSupported();
if (isAuthSupported) {
bool didAuthenticate =
await LocalAuthentication().authenticate(
localizedReason: 'Confirm payment with biometrics'.tr,
);
if (!didAuthenticate) return;
}
Get.to(() => PaymentScreenSmsProvider(amount: pricePoint));
},
),
],
),
),
),
);
}
Widget _buildPaymentMethodTile({
IconData? icon,
String? image,
required String title,
required String subtitle,
required Color color,
required VoidCallback onTap,
}) {
return Material(
color: Colors.grey.shade50,
borderRadius: BorderRadius.circular(20),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(20),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
border: Border.all(color: Colors.grey.shade200, width: 1),
),
child: Row(
children: [
Container(
width: 56,
height: 56,
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(14),
boxShadow: [
BoxShadow(
color: color.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: image != null
? Image.asset(image, fit: BoxFit.contain)
: Icon(icon, color: color, size: 30),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
color: FinanceDesignSystem.primaryDark)),
const SizedBox(height: 2),
Text(subtitle,
style: TextStyle(
color: Colors.grey.shade600, fontSize: 12)),
],
),
),
Icon(Icons.arrow_forward_ios_rounded,
size: 14, color: Colors.grey.shade400),
],
),
),
),
);
}
void _showPhoneInputDialog(BuildContext context, String provider) {
Get.back();
Get.defaultDialog(
title: 'Wallet Phone Number'.tr,
content: Form(
key: paymentController.formKey,
child: MyTextForm(
controller: paymentController.walletphoneController,
label: 'Phone Number'.tr,
hint: provider == 'Syriatel' ? '963991234567' : '963941234567',
type: TextInputType.phone,
),
),
confirm: MyElevatedButton(
title: 'Confirm'.tr,
onPressed: () async {
if (paymentController.formKey.currentState!.validate()) {
Get.back();
box.write(BoxName.phoneWallet,
paymentController.walletphoneController.text);
if (provider == 'Syriatel') {
await payWithSyriaTelWallet(
context, pricePoint.toString(), 'SYP');
} else {
await payWithMTNWallet(context, pricePoint.toString(), 'SYP');
}
}
},
),
);
}
} }
class PaymentScreen extends StatefulWidget { class PaymentScreen extends StatefulWidget {

View File

@@ -1,521 +1,370 @@
import 'package:local_auth/local_auth.dart'; import 'package:local_auth/local_auth.dart';
import 'package:sefer_driver/constant/links.dart';
import 'package:sefer_driver/controller/functions/crud.dart';
import 'package:sefer_driver/controller/functions/tts.dart';
import 'package:sefer_driver/views/home/my_wallet/payment_history_driver_page.dart';
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
import 'package:sefer_driver/views/widgets/mydialoug.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:sefer_driver/constant/box_name.dart'; import 'package:sefer_driver/constant/finance_design_system.dart';
import 'package:sefer_driver/constant/colors.dart';
import 'package:sefer_driver/constant/info.dart';
import 'package:sefer_driver/constant/style.dart';
import 'package:sefer_driver/controller/home/payment/captain_wallet_controller.dart'; import 'package:sefer_driver/controller/home/payment/captain_wallet_controller.dart';
import 'package:sefer_driver/main.dart'; import 'package:sefer_driver/views/widgets/mycircular.dart';
import 'package:sefer_driver/views/widgets/elevated_btn.dart'; import 'package:sefer_driver/views/widgets/elevated_btn.dart';
import 'package:sefer_driver/views/widgets/my_textField.dart'; import 'package:sefer_driver/views/widgets/my_textField.dart';
import 'package:sefer_driver/views/widgets/mycircular.dart'; import 'package:sefer_driver/views/widgets/mydialoug.dart';
import 'package:sefer_driver/views/widgets/error_snakbar.dart';
import 'package:sefer_driver/constant/box_name.dart';
import 'package:sefer_driver/constant/links.dart';
import 'package:sefer_driver/controller/functions/crud.dart';
import 'package:sefer_driver/main.dart';
import 'package:sefer_driver/views/home/my_wallet/payment_history_driver_page.dart';
import 'package:sefer_driver/controller/payment/driver_payment_controller.dart';
import '../../../controller/payment/driver_payment_controller.dart'; // Import new widgets
import '../../widgets/my_scafold.dart';
import 'card_wallet_widget.dart';
import 'points_captain.dart'; import 'points_captain.dart';
import 'transfer_budget_page.dart'; import 'transfer_budget_page.dart';
import 'weekly_payment_page.dart'; import 'widgets/balance_card.dart';
import 'widgets/quick_actions.dart';
import 'widgets/financial_summary_card.dart';
import 'widgets/promo_gamification_card.dart';
import 'widgets/transaction_preview_item.dart';
class WalletCaptainRefactored extends StatelessWidget { class WalletCaptainRefactored extends StatelessWidget {
WalletCaptainRefactored({super.key}); WalletCaptainRefactored({super.key});
final CaptainWalletController captainWalletController = final CaptainWalletController controller = Get.put(CaptainWalletController());
Get.put(CaptainWalletController());
// دالة مساعدة لتحديد لون خلفية النقاط
Color _getPointsColor(String pointsStr) {
final points = double.tryParse(pointsStr) ?? 0.0;
if (points < -30000) {
return AppColor.redColor;
} else if (points < 0 && points >= -30000) {
return AppColor.yellowColor;
} else {
return AppColor.greenColor;
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
captainWalletController.refreshCaptainWallet(); controller.refreshCaptainWallet();
return MyScafolld( return Scaffold(
title: 'Driver Balance'.tr, backgroundColor: FinanceDesignSystem.backgroundColor,
isleading: true, appBar: AppBar(
action: IconButton( title: Text('Driver Balance'.tr,
icon: const Icon(Icons.refresh), style: const TextStyle(
onPressed: () => captainWalletController.refreshCaptainWallet(), fontWeight: FontWeight.bold,
tooltip: 'Refresh'.tr, color: FinanceDesignSystem.primaryDark)),
backgroundColor: Colors.transparent,
elevation: 0,
centerTitle: true,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios_new_rounded,
color: FinanceDesignSystem.primaryDark, size: 20),
onPressed: () => Get.back(),
),
actions: [
IconButton(
icon: const Icon(Icons.refresh_rounded,
color: FinanceDesignSystem.primaryDark),
onPressed: () => controller.refreshCaptainWallet(),
tooltip: 'Refresh'.tr,
),
],
), ),
body: [ body: GetBuilder<CaptainWalletController>(
GetBuilder<CaptainWalletController>( builder: (controller) {
builder: (controller) { if (controller.isLoading) {
if (controller.isLoading) { return const Center(child: MyCircularProgressIndicator());
return const MyCircularProgressIndicator(); }
} return SingleChildScrollView(
return SingleChildScrollView( padding: const EdgeInsets.symmetric(
padding: const EdgeInsets.all(16.0), horizontal: FinanceDesignSystem.horizontalPadding,
child: Column( vertical: 10),
crossAxisAlignment: CrossAxisAlignment.stretch, child: Column(
children: [ crossAxisAlignment: CrossAxisAlignment.stretch,
_buildTotalPointsSection(context, controller), children: [
const SizedBox(height: 16), // 1. Header / Balance
const CardSeferWalletDriver(), // This can be redesigned if needed BalanceCard(
const SizedBox(height: 16), balance: controller.totalPoints.toString(),
_buildWalletDetailsCard(context, controller), isNegative:
const SizedBox(height: 24), double.tryParse(controller.totalPoints.toString()) !=
_buildPromoSection(controller), null &&
const SizedBox(height: 24), double.parse(controller.totalPoints.toString()) <
_buildNavigationButtons(), -30000,
], lastUpdated: "Just now".tr,
),
);
},
)
],
);
}
/// القسم العلوي لعرض النقاط الإجمالية
Widget _buildTotalPointsSection(
BuildContext context, CaptainWalletController controller) {
return Card(
elevation: 4,
color: _getPointsColor(controller.totalPoints.toString()),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: InkWell(
onTap: () {
showCupertinoDialog(
context: context,
builder: (BuildContext context) => CupertinoAlertDialog(
title: Text('Info'.tr),
content: Text(
'The 30000 points equal 30000 S.P for you \nSo go and gain your money'
.tr),
actions: [
CupertinoDialogAction(
child: Text("OK".tr),
onPressed: () => Navigator.of(context).pop(),
), ),
const SizedBox(
height: FinanceDesignSystem.verticalSectionPadding),
// 2. Quick Actions
QuickActionsGrid(
onAddBalance: () =>
_showAddBalanceOptions(context, controller),
onWithdraw: () => addSyrianPaymentMethod(controller),
onTransfer: () => Get.to(() => TransferBudgetPage()),
onHistory: () async {
await Get.put(DriverWalletHistoryController())
.getArchivePayment();
Get.to(() => const PaymentHistoryDriverPage());
},
),
const SizedBox(
height: FinanceDesignSystem.verticalSectionPadding),
// 3. Earnings Summary
FinancialSummaryCard(
title: 'Earnings Summary'.tr,
subtitle: 'ملخص الأرباح'.tr,
items: [
SummaryItem(
icon: Icons.money_rounded,
label: 'Cash Earnings'.tr,
amount: controller.totalAmount,
color: FinanceDesignSystem.successGreen,
),
SummaryItem(
icon: Icons.credit_card_rounded,
label: 'Card Earnings'.tr,
amount: controller.totalAmountVisa,
color: FinanceDesignSystem.accentBlue,
),
],
),
const SizedBox(
height: FinanceDesignSystem.verticalSectionPadding),
// 3. Recharge Balance Packages
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Recharge Balance'.tr,
style: FinanceDesignSystem.headingStyle),
Icon(Icons.info_outline_rounded,
size: 18, color: Colors.grey.shade400),
],
),
const SizedBox(height: 12),
SizedBox(
height: 140, // Increased height for modern cards
child: ListView(
scrollDirection: Axis.horizontal,
physics: const BouncingScrollPhysics(),
children: [
PointsCaptain(
kolor: Colors.blueGrey,
pricePoint: 100,
countPoint: '100'),
PointsCaptain(
kolor: Colors.brown,
pricePoint: 200,
countPoint: '210'),
PointsCaptain(
kolor: Colors.amber,
pricePoint: 400,
countPoint: '450'),
PointsCaptain(
kolor: Colors.orange,
pricePoint: 1000,
countPoint: '1100'),
],
),
),
const SizedBox(
height: FinanceDesignSystem.verticalSectionPadding),
// 5. Promotions
Text('Promotions'.tr, style: FinanceDesignSystem.headingStyle),
const SizedBox(height: 12),
PromoGamificationCard(
title: 'Morning Promo'.tr,
subtitle: "from 7:00am to 10:00am".tr,
currentProgress: controller.walletDate['message']?[0]
?['morning_count'] ??
0,
targetProgress: 5,
reward: "+50 SYP",
onTap: () =>
controller.addDriverWalletFromPromo('Morning Promo', 50),
),
const SizedBox(height: 16),
PromoGamificationCard(
title: 'Afternoon Promo'.tr,
subtitle: "from 3:00pm to 6:00 pm".tr,
currentProgress: controller.walletDate['message']?[0]
?['afternoon_count'] ??
0,
targetProgress: 5,
reward: "+50 SYP",
onTap: () => controller.addDriverWalletFromPromo(
'Afternoon Promo', 50),
),
const SizedBox(
height: FinanceDesignSystem.verticalSectionPadding),
// 6. Transactions Preview
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Recent Transactions'.tr,
style: FinanceDesignSystem.headingStyle),
TextButton(
onPressed: () async {
await Get.put(DriverWalletHistoryController())
.getArchivePayment();
Get.to(() => const PaymentHistoryDriverPage());
},
child: Text('View All'.tr,
style: const TextStyle(
color: FinanceDesignSystem.accentBlue,
fontWeight: FontWeight.bold)),
),
],
),
GetBuilder<DriverWalletHistoryController>(
init: DriverWalletHistoryController(),
builder: (historyController) {
if (historyController.archive.isEmpty) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 20),
child: Center(
child: Text("No transactions yet".tr,
style: TextStyle(color: Colors.grey.shade400))),
);
}
// Show only last 3
final lastThree =
historyController.archive.take(3).toList();
return Column(
children: lastThree.map((tx) {
final double amount =
double.tryParse(tx['amount']?.toString() ?? '0') ??
0;
return TransactionPreviewItem(
title: amount >= 0 ? 'Credit'.tr : 'Debit'.tr,
subtitle: tx['created_at'] ?? '',
amount: amount.abs().toStringAsFixed(0),
date: tx['created_at']?.split(' ')[0] ?? '',
type: amount >= 0 ? 'credit' : 'debit',
onTap: () {},
);
}).toList(),
);
},
),
const SizedBox(height: 40),
], ],
), ),
); );
}, },
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 8),
child: Column(
children: [
Text(
'رصيد التشغيل 💎',
style: AppStyle.headTitle2.copyWith(
color: Colors.white,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
controller.totalPoints.toString(),
style: AppStyle.headTitle2.copyWith(
color: Colors.white,
fontSize: 28,
fontWeight: FontWeight.w900),
textAlign: TextAlign.center,
),
if (double.parse(controller.totalPoints.toString()) < -30000)
Padding(
padding: const EdgeInsets.only(top: 12.0),
child: CupertinoButton(
color: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 20),
onPressed: () {
// Add your charge account logic here
},
child: Text(
'Charge your Account'.tr,
style: TextStyle(
color: AppColor.redColor,
fontWeight: FontWeight.bold),
),
),
),
],
),
),
), ),
); );
} }
/// بطاقة لعرض تفاصيل المحفظة وخيارات الشراء void _showAddBalanceOptions(
Widget _buildWalletDetailsCard(
BuildContext context, CaptainWalletController controller) { BuildContext context, CaptainWalletController controller) {
return Card( Get.bottomSheet(
elevation: 4, Container(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), padding: const EdgeInsets.all(24),
child: Padding( decoration: const BoxDecoration(
padding: const EdgeInsets.all(16.0), color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_BudgetInfoRow( Text("Add Balance".tr, style: FinanceDesignSystem.headingStyle),
title: 'Total Budget from trips is '.tr,
amount: controller.totalAmount,
onTap: () {
Get.snackbar(
icon: InkWell(
onTap: () async => await Get.put(TextToSpeechController())
.speakText(
'This amount for all trip I get from Passengers'
.tr),
child: const Icon(Icons.headphones)),
'${'Total Amount:'.tr} ${controller.totalAmount} ${'SYP'.tr}',
'This amount for all trip I get from Passengers'.tr,
duration: const Duration(seconds: 6),
backgroundColor: AppColor.yellowColor,
snackPosition: SnackPosition.BOTTOM,
);
},
),
const Divider(height: 32),
_BudgetInfoRow(
title: 'Total Budget from trips by\nCredit card is '.tr,
amount: controller.totalAmountVisa,
onTap: () {
Get.snackbar(
icon: InkWell(
onTap: () async => await Get.find<TextToSpeechController>()
.speakText(
'This amount for all trip I get from Passengers and Collected For me in'
.tr +
' Intaleq Wallet'.tr),
child: const Icon(Icons.headphones),
),
'${'Total Amount:'.tr} ${controller.totalAmountVisa} ${'SYP'.tr}',
'This amount for all trip I get from Passengers and Collected For me in'
.tr +
' ${AppInformation.appName} Wallet'.tr,
duration: const Duration(seconds: 6),
backgroundColor: AppColor.redColor,
snackPosition: SnackPosition.BOTTOM,
);
},
),
const SizedBox(height: 24),
_buildBuyPointsButton(controller),
const SizedBox(height: 16),
// _buildTransferBudgetButton(controller), // Uncomment if needed
_buildPurchaseInstructions(),
const SizedBox(height: 8), const SizedBox(height: 8),
_buildPointsOptions(), Text("Select how you want to charge your account".tr,
], style: FinanceDesignSystem.subHeadingStyle),
), const SizedBox(height: 24),
), ListTile(
); leading: Container(
} padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
/// قسم العروض الترويجية color: FinanceDesignSystem.accentBlue.withOpacity(0.1),
Widget _buildPromoSection(CaptainWalletController controller) { borderRadius: BorderRadius.circular(12)),
return Column( child: const Icon(Icons.account_balance_wallet_rounded,
crossAxisAlignment: CrossAxisAlignment.start, color: FinanceDesignSystem.accentBlue),
children: [
Text("Today's Promo".tr, style: AppStyle.headTitle),
const SizedBox(height: 10),
_PromoProgressCard(
title: 'Morning Promo'.tr,
timePromo: 'Morning Promo',
count: (controller.walletDate['message'][0]['morning_count']),
maxCount: 5,
description:
"this is count of your all trips in the morning promo today from 7:00am to 10:00am"
.tr,
controller: controller,
),
const SizedBox(height: 16),
_PromoProgressCard(
title: 'Afternoon Promo'.tr,
timePromo: 'Afternoon Promo',
count: (controller.walletDate['message'][0]['afternoon_count']),
maxCount: 5,
description:
"this is count of your all trips in the Afternoon promo today from 3:00pm to 6:00 pm"
.tr,
controller: controller,
),
],
);
}
/// أزرار التنقل السفلية
Widget _buildNavigationButtons() {
return Row(
children: [
Expanded(
child: MyElevatedButton(
kolor: AppColor.blueColor,
title: 'Payment History'.tr,
onPressed: () async {
await Get.put(DriverWalletHistoryController())
.getArchivePayment();
Get.to(() => const PaymentHistoryDriverPage(),
transition: Transition.size);
},
),
),
const SizedBox(width: 16),
Expanded(
child: MyElevatedButton(
kolor: AppColor.blueColor,
title: 'Weekly Budget'.tr,
onPressed: () async {
await Get.put(DriverWalletHistoryController())
.getWeekllyArchivePayment();
Get.to(() => const WeeklyPaymentPage(),
transition: Transition.size);
},
),
),
],
);
}
// --- حافظت على هذه الدوال كما هي لأنها تحتوي على منطق مهم ---
Widget _buildBuyPointsButton(CaptainWalletController controller) {
return MyElevatedButton(
title: 'You can buy points from your budget'.tr,
onPressed: () {
Get.defaultDialog(
title: 'Pay from my budget'.tr,
content: Form(
key: controller.formKey,
child: MyTextForm(
controller: controller.amountFromBudgetController,
label:
'${'You have in account'.tr} ${controller.totalAmountVisa}',
hint: '${'You have in account'.tr} ${controller.totalAmountVisa}',
type: TextInputType.number,
),
),
confirm: MyElevatedButton(
title: 'Pay'.tr,
onPressed: () async {
bool isAvailable =
await LocalAuthentication().isDeviceSupported();
if (isAvailable) {
// Authenticate the user
bool didAuthenticate = await LocalAuthentication().authenticate(
localizedReason:
'Use Touch ID or Face ID to confirm payment'.tr,
// options: const AuthenticationOptions(
biometricOnly: true,
sensitiveTransaction: true,
);
if (didAuthenticate) {
if (double.parse(controller.amountFromBudgetController.text) <
double.parse(controller.totalAmountVisa)) {
await controller.payFromBudget();
} else {
Get.back();
mySnackeBarError('Your Budget less than needed'.tr);
}
} else {
// Authentication failed, handle accordingly
MyDialog().getDialog('Authentication failed'.tr, ''.tr, () {
Get.back();
});
}
} else {
MyDialog().getDialog('Biometric Authentication'.tr,
'You should use Touch ID or Face ID to confirm payment'.tr,
() {
Get.back();
});
}
},
),
cancel: MyElevatedButton(
title: 'Cancel'.tr,
onPressed: () {
Get.back();
},
),
);
},
);
}
Widget _buildPurchaseInstructions() {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColor.accentColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.accentColor.withOpacity(0.3)),
),
child: Column(
children: [
Text(
"You can purchase a budget to enable online access through the options listed below"
.tr,
textAlign: TextAlign.center,
style: AppStyle.title.copyWith(fontSize: 14),
),
],
),
),
);
}
Widget _buildPointsOptions() {
return SizedBox(
height: Get.height * 0.19,
child: ListView(
scrollDirection: Axis.horizontal,
children: [
PointsCaptain(
kolor: AppColor.greyColor, pricePoint: 100, countPoint: '100'),
PointsCaptain(
kolor: AppColor.bronze, pricePoint: 200, countPoint: '210'),
PointsCaptain(
kolor: AppColor.goldenBronze, pricePoint: 400, countPoint: '450'),
PointsCaptain(
kolor: AppColor.gold, pricePoint: 1000, countPoint: '1100'),
],
),
);
}
}
/// ويدجت مُحسّن لعرض صف معلومات الرصيد
class _BudgetInfoRow extends StatelessWidget {
final String title;
final String amount;
final VoidCallback onTap;
const _BudgetInfoRow({
required this.title,
required this.amount,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
title,
style: AppStyle.title.copyWith(fontSize: 16),
), ),
title: Text("Pay from my budget".tr),
subtitle: Text(
"${'You have in account'.tr} ${controller.totalAmountVisa}"),
onTap: () {
Get.back();
_showPayFromBudgetDialog(controller);
},
), ),
const SizedBox(width: 10), const Divider(),
Container( const SizedBox(height: 16),
decoration: BoxDecoration( Text("Recharge Balance Packages".tr,
color: AppColor.accentColor.withOpacity(0.1), style: FinanceDesignSystem.subHeadingStyle
borderRadius: BorderRadius.circular(12), .copyWith(fontWeight: FontWeight.bold)),
), const SizedBox(height: 12),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), SizedBox(
child: Text( height: 140,
'$amount ${'S.P'.tr}', child: ListView(
style: scrollDirection: Axis.horizontal,
const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), physics: const BouncingScrollPhysics(),
),
),
],
),
),
);
}
}
/// ويدجت مُحسّن لعرض بطاقة العرض الترويجي
class _PromoProgressCard extends StatelessWidget {
final String title;
final String timePromo;
final int count;
final int maxCount;
final String description;
final CaptainWalletController controller;
const _PromoProgressCard({
required this.title,
required this.timePromo,
required this.count,
required this.maxCount,
required this.description,
required this.controller,
});
@override
Widget build(BuildContext context) {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: () {
MyDialog().getDialog(title, description, () async {
if (count >= maxCount) {
controller.addDriverWalletFromPromo(timePromo, 50);
}
Get.back();
});
},
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text(title, PointsCaptain(
style: kolor: Colors.blueGrey,
AppStyle.title.copyWith(fontWeight: FontWeight.bold)), pricePoint: 100,
Text( countPoint: '100'),
'$count / $maxCount', PointsCaptain(
style: AppStyle.title.copyWith(color: AppColor.blueColor), kolor: Colors.brown, pricePoint: 200, countPoint: '210'),
), PointsCaptain(
kolor: Colors.amber, pricePoint: 400, countPoint: '450'),
PointsCaptain(
kolor: Colors.orange,
pricePoint: 1000,
countPoint: '1100'),
], ],
), ),
const SizedBox(height: 12), ),
ClipRRect( ],
borderRadius: BorderRadius.circular(10),
child: LinearProgressIndicator(
minHeight: 12,
value: count / maxCount,
backgroundColor: AppColor.accentColor.withOpacity(0.2),
color: count >= maxCount
? AppColor.greenColor
: AppColor.blueColor,
),
),
],
),
), ),
), ),
); );
} }
void _showPayFromBudgetDialog(CaptainWalletController controller) {
Get.defaultDialog(
title: 'Pay from my budget'.tr,
content: Form(
key: controller.formKey,
child: MyTextForm(
controller: controller.amountFromBudgetController,
label: '${'You have in account'.tr} ${controller.totalAmountVisa}',
hint: '${'You have in account'.tr} ${controller.totalAmountVisa}',
type: TextInputType.number,
),
),
confirm: MyElevatedButton(
title: 'Pay'.tr,
onPressed: () async {
bool isAvailable = await LocalAuthentication().isDeviceSupported();
if (isAvailable) {
bool didAuthenticate = await LocalAuthentication().authenticate(
localizedReason: 'Use Touch ID or Face ID to confirm payment'.tr,
biometricOnly: true,
sensitiveTransaction: true,
);
if (didAuthenticate) {
if (double.parse(controller.amountFromBudgetController.text) <
double.parse(controller.totalAmountVisa)) {
await controller.payFromBudget();
} else {
Get.back();
mySnackeBarError('Your Budget less than needed'.tr);
}
} else {
MyDialog().getDialog(
'Authentication failed'.tr, ''.tr, () => Get.back());
}
} else {
MyDialog().getDialog(
'Biometric Authentication'.tr,
'You should use Touch ID or Face ID to confirm payment'.tr,
() => Get.back());
}
},
),
cancel: MyElevatedButton(title: 'Cancel'.tr, onPressed: () => Get.back()),
);
}
} }
// --- الدوال والويدجتس الخاصة بالدفع في سوريا تبقى كما هي ---
// This function is a placeholder for adding Syrian payment methods.
// You would implement the UI and logic for mobile wallets or other local options here.
Future<dynamic> addSyrianPaymentMethod( Future<dynamic> addSyrianPaymentMethod(
CaptainWalletController captainWalletController) { CaptainWalletController captainWalletController) {
return Get.defaultDialog( return Get.defaultDialog(
@@ -528,15 +377,12 @@ Future<dynamic> addSyrianPaymentMethod(
"Insert your mobile wallet details to receive your money weekly" "Insert your mobile wallet details to receive your money weekly"
.tr), .tr),
MyTextForm( MyTextForm(
controller: captainWalletController controller: captainWalletController.cardBank,
.cardBank, // Re-using for mobile number
label: "Insert mobile wallet number".tr, label: "Insert mobile wallet number".tr,
hint: '0912 345 678', hint: '0912 345 678',
type: TextInputType.phone), type: TextInputType.phone),
const SizedBox( const SizedBox(height: 10),
height: 10, MyDropDownSyria()
),
MyDropDownSyria() // Dropdown for Syrian providers
], ],
)), )),
confirm: MyElevatedButton( confirm: MyElevatedButton(
@@ -545,7 +391,6 @@ Future<dynamic> addSyrianPaymentMethod(
if (captainWalletController.formKeyAccount.currentState! if (captainWalletController.formKeyAccount.currentState!
.validate()) { .validate()) {
Get.back(); Get.back();
// Replace with your actual API endpoint and payload for Syria
var res = var res =
await CRUD().post(link: AppLink.updateAccountBank, payload: { await CRUD().post(link: AppLink.updateAccountBank, payload: {
"paymentProvider": "paymentProvider":
@@ -554,7 +399,6 @@ Future<dynamic> addSyrianPaymentMethod(
captainWalletController.cardBank.text.toString(), captainWalletController.cardBank.text.toString(),
"id": box.read(BoxName.driverID).toString() "id": box.read(BoxName.driverID).toString()
}); });
print('res: $res');
if (res != 'failure') { if (res != 'failure') {
mySnackbarSuccess('Payment details added successfully'.tr); mySnackbarSuccess('Payment details added successfully'.tr);
} }
@@ -562,10 +406,8 @@ Future<dynamic> addSyrianPaymentMethod(
})); }));
} }
// A new GetX controller for the Syrian payout dropdown
class SyrianPayoutController extends GetxController { class SyrianPayoutController extends GetxController {
String dropdownValue = 'syriatel'; String dropdownValue = 'syriatel';
void changeValue(String? newValue) { void changeValue(String? newValue) {
if (newValue != null) { if (newValue != null) {
dropdownValue = newValue; dropdownValue = newValue;
@@ -574,33 +416,25 @@ class SyrianPayoutController extends GetxController {
} }
} }
// A new Dropdown widget for Syrian mobile wallet providers
class MyDropDownSyria extends StatelessWidget { class MyDropDownSyria extends StatelessWidget {
const MyDropDownSyria({super.key}); const MyDropDownSyria({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Get.put(SyrianPayoutController()); Get.put(SyrianPayoutController());
final theme = Theme.of(context);
return GetBuilder<SyrianPayoutController>(builder: (controller) { return GetBuilder<SyrianPayoutController>(builder: (controller) {
return DropdownButton<String>( return DropdownButton<String>(
value: controller.dropdownValue, value: controller.dropdownValue,
icon: const Icon(Icons.arrow_drop_down), icon: const Icon(Icons.arrow_drop_down),
elevation: 16, elevation: 16,
isExpanded: true, isExpanded: true,
style: const TextStyle(color: Colors.deepPurple), dropdownColor: theme.cardColor,
underline: Container( style: TextStyle(color: theme.textTheme.bodyLarge?.color),
height: 2, underline: Container(height: 2, color: theme.primaryColor),
color: Colors.deepPurpleAccent, onChanged: (String? newValue) => controller.changeValue(newValue),
),
onChanged: (String? newValue) {
controller.changeValue(newValue);
},
items: <String>['syriatel', 'mtn'] items: <String>['syriatel', 'mtn']
.map<DropdownMenuItem<String>>((String value) { .map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>( return DropdownMenuItem<String>(value: value, child: Text(value.tr));
value: value,
child: Text(value.tr),
);
}).toList(), }).toList(),
); );
}); });

View File

@@ -2,11 +2,12 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:sefer_driver/constant/colors.dart'; import 'package:sefer_driver/constant/colors.dart';
import 'package:sefer_driver/constant/style.dart'; import 'package:sefer_driver/constant/style.dart';
import 'package:sefer_driver/constant/finance_design_system.dart';
import 'package:sefer_driver/views/widgets/mycircular.dart'; import 'package:sefer_driver/views/widgets/mycircular.dart';
import '../../../controller/payment/driver_payment_controller.dart'; import '../../../controller/payment/driver_payment_controller.dart';
import 'widgets/transaction_preview_item.dart';
class WeeklyPaymentPage extends StatelessWidget { class WeeklyPaymentPage extends StatelessWidget {
const WeeklyPaymentPage({super.key}); const WeeklyPaymentPage({super.key});
@@ -16,12 +17,18 @@ class WeeklyPaymentPage extends StatelessWidget {
Get.put(DriverWalletHistoryController()); Get.put(DriverWalletHistoryController());
return Scaffold( return Scaffold(
backgroundColor: FinanceDesignSystem.backgroundColor,
appBar: AppBar( appBar: AppBar(
title: Text('Weekly Summary'.tr), title: Text('Weekly Summary'.tr,
backgroundColor: Colors.white, style: const TextStyle(fontWeight: FontWeight.bold, color: FinanceDesignSystem.primaryDark)),
elevation: 1, backgroundColor: Colors.transparent,
elevation: 0,
centerTitle: true,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios_new_rounded, color: FinanceDesignSystem.primaryDark, size: 20),
onPressed: () => Get.back(),
),
), ),
backgroundColor: Colors.grey[100],
body: GetBuilder<DriverWalletHistoryController>( body: GetBuilder<DriverWalletHistoryController>(
builder: (controller) { builder: (controller) {
if (controller.isLoading) { if (controller.isLoading) {
@@ -30,45 +37,46 @@ class WeeklyPaymentPage extends StatelessWidget {
return Column( return Column(
children: [ children: [
// 1. Prominent Summary Card at the top _buildWeeklyStatsHeader(controller),
_buildSummaryCard(controller), const SizedBox(height: 16),
_buildWeekSelector(),
// 2. A title for the transactions list const SizedBox(height: 16),
Padding( Padding(
padding: const EdgeInsets.fromLTRB(20, 20, 20, 10), padding: const EdgeInsets.symmetric(horizontal: 20),
child: Row( child: Row(
children: [ children: [
Icon(Icons.list_alt, color: Colors.grey[600]), Text('Transactions this week'.tr, style: FinanceDesignSystem.headingStyle),
const SizedBox(width: 8), const Spacer(),
Text( Icon(Icons.list_rounded, color: Colors.grey.shade400, size: 20),
'Transactions this week'.tr,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
color: Colors.grey[700],
),
),
], ],
), ),
), ),
const SizedBox(height: 8),
// 3. The animated list of transactions
Expanded( Expanded(
child: controller.weeklyList.isEmpty child: controller.weeklyList.isEmpty
? _buildEmptyState(context) ? _buildEmptyState(context)
: AnimationLimiter( : AnimationLimiter(
child: ListView.builder( child: ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 16.0), padding: const EdgeInsets.fromLTRB(16, 8, 16, 24),
itemCount: controller.weeklyList.length, itemCount: controller.weeklyList.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
var transaction = controller.weeklyList[index]; final tx = controller.weeklyList[index];
final double amount = double.tryParse(tx['amount']?.toString() ?? '0') ?? 0;
return AnimationConfiguration.staggeredList( return AnimationConfiguration.staggeredList(
position: index, position: index,
duration: const Duration(milliseconds: 375), duration: const Duration(milliseconds: 250),
child: SlideAnimation( child: SlideAnimation(
verticalOffset: 50.0, verticalOffset: 25,
child: FadeInAnimation( child: FadeInAnimation(
child: _TransactionListItem( child: TransactionPreviewItem(
transaction: transaction), title: amount >= 0 ? 'Credit'.tr : 'Debit'.tr,
subtitle: tx['dateUpdated'] ?? '',
amount: amount.abs().toStringAsFixed(0),
date: tx['dateUpdated']?.split(' ')[0] ?? '',
type: amount >= 0 ? 'credit' : 'debit',
method: tx['paymentMethod'],
onTap: () {},
),
), ),
), ),
); );
@@ -83,92 +91,87 @@ class WeeklyPaymentPage extends StatelessWidget {
); );
} }
// A widget for the top summary card. Widget _buildWeeklyStatsHeader(DriverWalletHistoryController controller) {
Widget _buildSummaryCard(DriverWalletHistoryController controller) { final totalAmount = controller.weeklyList.isEmpty
final String totalAmount = controller.weeklyList.isEmpty
? '0.00' ? '0.00'
: controller.weeklyList[0]['totalAmount']?.toString() ?? '0.00'; : controller.weeklyList[0]['totalAmount']?.toString() ?? '0.00';
final double earnings = double.tryParse(totalAmount) ?? 0;
final int trips = controller.weeklyList.length;
final double commission = earnings * 0.15; // Example 15%
final double netProfit = earnings - commission;
return Card( return Container(
margin: const EdgeInsets.all(16.0), margin: const EdgeInsets.all(16),
elevation: 4, padding: const EdgeInsets.all(24),
shadowColor: AppColor.primaryColor.withOpacity(0.2), decoration: BoxDecoration(
clipBehavior: Clip.antiAlias, gradient: FinanceDesignSystem.balanceGradient,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), borderRadius: BorderRadius.circular(24),
child: Container( boxShadow: [
padding: const EdgeInsets.all(20.0), BoxShadow(
decoration: BoxDecoration( color: FinanceDesignSystem.primaryDark.withOpacity(0.3),
gradient: LinearGradient( blurRadius: 20,
colors: [AppColor.primaryColor, AppColor.greenColor], offset: const Offset(0, 8),
begin: Alignment.topLeft,
end: Alignment.bottomRight,
), ),
), ],
child: Row( ),
children: [ child: Column(
const Icon(Icons.account_balance_wallet, children: [
color: Colors.white, size: 40), Text('Total Weekly Earnings'.tr,
const SizedBox(width: 16), style: TextStyle(color: Colors.white.withOpacity(0.8), fontSize: 14)),
Expanded( const SizedBox(height: 8),
child: Column( Text('${earnings.toStringAsFixed(0)} SYP',
crossAxisAlignment: CrossAxisAlignment.start, style: const TextStyle(color: Colors.white, fontSize: 36, fontWeight: FontWeight.bold)),
children: [ const SizedBox(height: 24),
Text( Row(
'Total Weekly Earnings'.tr, mainAxisAlignment: MainAxisAlignment.spaceBetween,
style: AppStyle.title children: [
.copyWith(color: Colors.white70, fontSize: 16), _buildStatItem('Total Trips'.tr, trips.toString(), Icons.directions_car_rounded),
), _buildStatItem('Commission'.tr, '${commission.toStringAsFixed(0)}', Icons.percent_rounded),
const SizedBox(height: 4), _buildStatItem('Net Profit'.tr, '${netProfit.toStringAsFixed(0)}', Icons.account_balance_rounded),
Text( ],
'$totalAmount ${'SYP'.tr}', ),
style: AppStyle.number ],
.copyWith(color: Colors.white, fontSize: 32),
),
],
),
),
],
),
), ),
); );
} }
// A dedicated widget for the list item. Widget _buildStatItem(String label, String value, IconData icon) {
Widget _TransactionListItem({required Map<String, dynamic> transaction}) { return Column(
final String paymentMethod = transaction['paymentMethod'] ?? 'Unknown'; children: [
final String amount = transaction['amount']?.toString() ?? '0'; Container(
final DateTime? date = DateTime.tryParse(transaction['dateUpdated'] ?? ''); padding: const EdgeInsets.all(8),
decoration: BoxDecoration(color: Colors.white.withOpacity(0.1), borderRadius: BorderRadius.circular(10)),
child: Icon(icon, color: Colors.white, size: 18),
),
const SizedBox(height: 8),
Text(value, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 14)),
Text(label, style: TextStyle(color: Colors.white.withOpacity(0.6), fontSize: 10)),
],
);
}
return Card( Widget _buildWeekSelector() {
elevation: 2, return Padding(
shadowColor: Colors.black.withOpacity(0.05), padding: const EdgeInsets.symmetric(horizontal: 16),
margin: const EdgeInsets.only(bottom: 12.0), child: Container(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: ListTile( decoration: BoxDecoration(
leading: CircleAvatar( color: Colors.white,
backgroundColor: AppColor.primaryColor.withOpacity(0.1), borderRadius: BorderRadius.circular(16),
child: Icon(_getPaymentIcon(paymentMethod), boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.02), blurRadius: 10)],
color: AppColor.primaryColor, size: 22),
), ),
title: Text( child: Row(
'$amount ${'SYP'.tr}', children: [
style: AppStyle.title.copyWith(fontWeight: FontWeight.bold), IconButton(icon: const Icon(Icons.chevron_left_rounded), onPressed: () {}),
), Expanded(
subtitle: Text( child: Center(
date != null child: Text('Dec 15 - Dec 21, 2024'.tr,
? DateFormat('EEEE, hh:mm a', Get.locale?.toString()) style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14, color: FinanceDesignSystem.primaryDark)),
.format(date) // e.g., Tuesday, 10:11 AM ),
: 'Invalid Date', ),
style: AppStyle.title.copyWith(fontSize: 12, color: Colors.grey[600]), IconButton(icon: const Icon(Icons.chevron_right_rounded), onPressed: () {}),
), ],
trailing: Chip(
label: Text(
_getTranslatedPaymentMethod(paymentMethod),
style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600),
),
backgroundColor: Colors.grey.shade200,
padding: const EdgeInsets.symmetric(horizontal: 6),
side: BorderSide.none,
), ),
), ),
); );
@@ -179,45 +182,11 @@ class WeeklyPaymentPage extends StatelessWidget {
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon(Icons.receipt_long_outlined, size: 80, color: Colors.grey[400]), Icon(Icons.bar_chart_rounded, size: 64, color: Colors.grey.shade300),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text('No transactions this week'.tr, style: const TextStyle(fontWeight: FontWeight.bold, color: Colors.grey)),
'No transactions this week'.tr,
style: Theme.of(context)
.textTheme
.headlineSmall
?.copyWith(color: Colors.grey[600]),
),
], ],
), ),
); );
} }
// Helper to get a specific icon for each payment method.
IconData _getPaymentIcon(String paymentMethod) {
switch (paymentMethod.toLowerCase()) {
case 'visa':
return Icons.credit_card;
case 'frombudget':
return Icons.account_balance_wallet_outlined;
case 'remainder':
return Icons.receipt_long;
case 'cash':
return Icons.money;
default:
return Icons.payment;
}
}
// Helper to get translated or formatted payment method names.
String _getTranslatedPaymentMethod(String paymentMethod) {
switch (paymentMethod) {
case 'Remainder':
return 'Remainder'.tr;
case 'fromBudget':
return 'From Budget'.tr;
default:
return paymentMethod;
}
}
} }

View File

@@ -0,0 +1,124 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../../constant/finance_design_system.dart';
class BalanceCard extends StatelessWidget {
final String balance;
final bool isNegative;
final String lastUpdated;
const BalanceCard({
super.key,
required this.balance,
required this.isNegative,
this.lastUpdated = "Just now",
});
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(FinanceDesignSystem.cardRadius),
gradient: isNegative
? FinanceDesignSystem.dangerGradient
: FinanceDesignSystem.balanceGradient,
boxShadow: [
BoxShadow(
color: (isNegative
? FinanceDesignSystem.dangerRed
: FinanceDesignSystem.primaryDark)
.withOpacity(0.3),
blurRadius: 15,
offset: const Offset(0, 8),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Available Balance".tr,
style: TextStyle(
color: Colors.white.withOpacity(0.8),
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
Text(
"الرصيد المتاح".tr,
style: TextStyle(
color: Colors.white.withOpacity(0.5),
fontSize: 12,
),
),
],
),
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
shape: BoxShape.circle,
),
child: Icon(
isNegative
? Icons.warning_rounded
: Icons.account_balance_wallet_rounded,
color: Colors.white,
size: 20,
),
),
],
),
const SizedBox(height: 20),
Row(
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
children: [
Text(
balance,
style: FinanceDesignSystem.balanceStyle,
),
const SizedBox(width: 8),
Text(
"SYP".tr,
style: TextStyle(
color: Colors.white.withOpacity(0.7),
fontSize: 18,
fontWeight: FontWeight.w500,
),
),
],
),
const SizedBox(height: 16),
Divider(color: Colors.white.withOpacity(0.15)),
const SizedBox(height: 8),
Row(
children: [
Icon(
Icons.history_rounded,
color: Colors.white.withOpacity(0.5),
size: 14,
),
const SizedBox(width: 6),
Text(
"${'Last updated:'.tr} $lastUpdated",
style: TextStyle(
color: Colors.white.withOpacity(0.5),
fontSize: 12,
),
),
],
),
],
),
);
}
}

View File

@@ -0,0 +1,142 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../../constant/finance_design_system.dart';
class FinancialSummaryCard extends StatelessWidget {
final String title;
final String? subtitle;
final List<SummaryItem> items;
const FinancialSummaryCard({
super.key,
required this.title,
this.subtitle,
required this.items,
});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: FinanceDesignSystem.headingStyle),
if (subtitle != null)
Text(subtitle!, style: FinanceDesignSystem.subHeadingStyle),
],
),
],
),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(FinanceDesignSystem.cardRadius),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.03),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: items.length,
separatorBuilder: (context, index) =>
Divider(color: Colors.grey.withOpacity(0.1), height: 24),
itemBuilder: (context, index) {
final item = items[index];
return Row(
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: item.color.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Icon(item.icon, color: item.color, size: 20),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.label,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: FinanceDesignSystem.primaryDark,
),
),
if (item.subLabel != null)
Text(
item.subLabel!,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade500,
),
),
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
"${item.amount} ${'SYP'.tr}",
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: FinanceDesignSystem.primaryDark,
),
),
if (item.trend != null)
Text(
item.trend!,
style: TextStyle(
fontSize: 11,
color: item.trendColor ??
FinanceDesignSystem.successGreen,
fontWeight: FontWeight.w500,
),
),
],
),
],
);
},
),
),
],
);
}
}
class SummaryItem {
final IconData icon;
final String label;
final String? subLabel;
final String amount;
final Color color;
final String? trend;
final Color? trendColor;
SummaryItem({
required this.icon,
required this.label,
this.subLabel,
required this.amount,
required this.color,
this.trend,
this.trendColor,
});
}

View File

@@ -0,0 +1,163 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../../constant/finance_design_system.dart';
class PromoGamificationCard extends StatelessWidget {
final String title;
final String subtitle;
final int currentProgress;
final int targetProgress;
final String reward;
final VoidCallback? onTap;
const PromoGamificationCard({
super.key,
required this.title,
required this.subtitle,
required this.currentProgress,
required this.targetProgress,
required this.reward,
this.onTap,
});
@override
Widget build(BuildContext context) {
final double progress = (currentProgress / targetProgress).clamp(0.0, 1.0);
final bool isCompleted = progress >= 1.0;
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(FinanceDesignSystem.cardRadius),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.03),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: FinanceDesignSystem.primaryDark,
),
),
Text(
subtitle,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade500,
),
),
],
),
),
Container(
padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: (isCompleted
? FinanceDesignSystem.successGreen
: FinanceDesignSystem.accentBlue)
.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
),
child: Text(
reward,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: isCompleted
? FinanceDesignSystem.successGreen
: FinanceDesignSystem.accentBlue,
),
),
),
],
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"${'Progress:'.tr} $currentProgress / $targetProgress",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Colors.grey.shade700,
),
),
if (isCompleted)
Icon(Icons.check_circle_rounded,
color: FinanceDesignSystem.successGreen, size: 16),
],
),
const SizedBox(height: 8),
Stack(
children: [
Container(
height: 10,
width: double.infinity,
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(5),
),
),
AnimatedContainer(
duration: const Duration(milliseconds: 500),
height: 10,
width: MediaQuery.of(context).size.width *
progress, // Simplified for now
decoration: BoxDecoration(
gradient: LinearGradient(
colors: isCompleted
? [
FinanceDesignSystem.successGreen,
Colors.lightGreenAccent
]
: [FinanceDesignSystem.accentBlue, Colors.blueAccent],
),
borderRadius: BorderRadius.circular(5),
),
),
],
),
if (isCompleted && onTap != null) ...[
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: onTap,
style: ElevatedButton.styleFrom(
backgroundColor: FinanceDesignSystem.successGreen,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(FinanceDesignSystem.buttonRadius),
),
elevation: 0,
padding: const EdgeInsets.symmetric(vertical: 12),
),
child: Text("Claim Reward".tr),
),
),
],
],
),
);
}
}

View File

@@ -0,0 +1,120 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../../constant/finance_design_system.dart';
class QuickActionsGrid extends StatelessWidget {
final VoidCallback onAddBalance;
final VoidCallback onWithdraw;
final VoidCallback onTransfer;
final VoidCallback onHistory;
const QuickActionsGrid({
super.key,
required this.onAddBalance,
required this.onWithdraw,
required this.onTransfer,
required this.onHistory,
});
@override
Widget build(BuildContext context) {
return GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 2,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
childAspectRatio: 1.5,
children: [
_ActionItem(
icon: Icons.add_circle_outline_rounded,
label: "Add Balance".tr,
subLabel: "شحن",
color: FinanceDesignSystem.accentBlue,
onTap: onAddBalance,
),
_ActionItem(
icon: Icons.file_download_outlined,
label: "Withdraw".tr,
subLabel: "سحب",
color: FinanceDesignSystem.successGreen,
onTap: onWithdraw,
),
_ActionItem(
icon: Icons.swap_horiz_rounded,
label: "Transfer".tr,
subLabel: "تحويل",
color: Colors.orange,
onTap: onTransfer,
),
_ActionItem(
icon: Icons.history_rounded,
label: "History".tr,
subLabel: "السجل",
color: FinanceDesignSystem.primaryDark,
onTap: onHistory,
),
],
);
}
}
class _ActionItem extends StatelessWidget {
final IconData icon;
final String label;
final String subLabel;
final Color color;
final VoidCallback onTap;
const _ActionItem({
required this.icon,
required this.label,
required this.subLabel,
required this.color,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return Material(
color: Colors.white,
borderRadius: BorderRadius.circular(FinanceDesignSystem.buttonRadius),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(FinanceDesignSystem.buttonRadius),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.withOpacity(0.1)),
borderRadius:
BorderRadius.circular(FinanceDesignSystem.buttonRadius),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: color, size: 24),
const SizedBox(height: 8),
Text(
label,
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.bold,
color: FinanceDesignSystem.primaryDark,
),
textAlign: TextAlign.center,
),
Text(
subLabel,
style: TextStyle(
fontSize: 11,
color: Colors.grey.shade500,
),
textAlign: TextAlign.center,
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,128 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../../constant/finance_design_system.dart';
class TransactionPreviewItem extends StatelessWidget {
final String title;
final String subtitle;
final String amount;
final String date;
final String type; // 'credit' or 'debit'
final String? method;
final VoidCallback? onTap;
const TransactionPreviewItem({
super.key,
required this.title,
required this.subtitle,
required this.amount,
required this.date,
required this.type,
this.method,
this.onTap,
});
@override
Widget build(BuildContext context) {
final bool isCredit = type.toLowerCase() == 'credit';
final Color statusColor = isCredit
? FinanceDesignSystem.successGreen
: FinanceDesignSystem.dangerRed;
return Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(FinanceDesignSystem.mainRadius),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 4),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: statusColor.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(
isCredit
? Icons.arrow_downward_rounded
: Icons.arrow_upward_rounded,
color: statusColor,
size: 20,
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: FinanceDesignSystem.primaryDark,
),
),
const SizedBox(height: 4),
Row(
children: [
Text(
date,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade500,
),
),
if (method != null) ...[
const SizedBox(width: 8),
Container(
width: 3,
height: 3,
decoration: BoxDecoration(
color: Colors.grey.shade400,
shape: BoxShape.circle,
),
),
const SizedBox(width: 8),
Text(
method!,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade500,
),
),
],
],
),
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
"${isCredit ? '+' : '-'}$amount ${'SYP'.tr}",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: statusColor,
),
),
Text(
subtitle,
style: TextStyle(
fontSize: 11,
color: Colors.grey.shade400,
),
),
],
),
],
),
),
),
);
}
}

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 '../../../constant/colors.dart';
import '../../../controller/home/captin/behavior_controller.dart'; import '../../../controller/home/captin/behavior_controller.dart';
class BehaviorPage extends StatelessWidget { class BehaviorPage extends StatelessWidget {
@@ -12,6 +13,7 @@ class BehaviorPage extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final controller = Get.put(DriverBehaviorController()); final controller = Get.put(DriverBehaviorController());
controller.fetchDriverBehavior(); controller.fetchDriverBehavior();
final theme = Theme.of(context);
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
@@ -37,20 +39,23 @@ class BehaviorPage extends StatelessWidget {
child: Column( child: Column(
children: [ children: [
Text("Overall Behavior Score".tr, Text("Overall Behavior Score".tr,
style: TextStyle( style: theme.textTheme.titleLarge
fontSize: 20, fontWeight: FontWeight.bold)), ?.copyWith(fontWeight: FontWeight.bold)),
const SizedBox(height: 10), const SizedBox(height: 10),
Text( Text(
"${controller.overallScore.value.toStringAsFixed(1)} / 100", "${controller.overallScore.value.toStringAsFixed(1)} / 100",
style: const TextStyle( style: TextStyle(
fontSize: 28, color: Colors.blue)), fontSize: 28,
fontWeight: FontWeight.bold,
color: AppColor.primaryColor)),
], ],
), ),
), ),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
Text("Last 10 Trips".tr, Text("Last 10 Trips".tr,
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), style: theme.textTheme.titleMedium
?.copyWith(fontWeight: FontWeight.bold)),
const SizedBox(height: 10), const SizedBox(height: 10),
ListView.builder( ListView.builder(
shrinkWrap: true, shrinkWrap: true,
@@ -62,7 +67,7 @@ class BehaviorPage extends StatelessWidget {
elevation: 3, elevation: 3,
child: ListTile( child: ListTile(
leading: CircleAvatar( leading: CircleAvatar(
backgroundColor: Colors.blue, backgroundColor: AppColor.primaryColor,
child: Text("${index + 1}", child: Text("${index + 1}",
style: const TextStyle(color: Colors.white)), style: const TextStyle(color: Colors.white)),
), ),

View File

@@ -41,50 +41,91 @@ class CaptainsCars extends StatelessWidget {
itemBuilder: (context, index) { itemBuilder: (context, index) {
final car = controller.cars[index]; final car = controller.cars[index];
return Padding( return Padding(
padding: const EdgeInsets.all(4.0), padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 6.0),
child: Card( child: Card(
color: car['isDefault'].toString() == '0'
? AppColor.accentColor
: AppColor.blueColor,
elevation: 2, elevation: 2,
color: car['isDefault'].toString() == '1'
? AppColor.primaryColor
: Theme.of(context).cardColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: car['isDefault'].toString() == '1'
? BorderSide.none
: BorderSide(color: Theme.of(context).dividerColor)),
child: ListTile( child: ListTile(
leading: Icon( contentPadding: const EdgeInsets.all(12),
Fontisto.car, leading: Container(
size: 50, padding: const EdgeInsets.all(8),
color: Color(int.parse(car['color_hex'] decoration: BoxDecoration(
.replaceFirst('#', '0xff'))), color: Colors.white12,
borderRadius: BorderRadius.circular(8),
),
child: Icon(
Fontisto.car,
size: 32,
color: car['isDefault'].toString() == '1'
? Colors.white
: Color(int.parse(car['color_hex']
.replaceFirst('#', '0xff'))),
),
), ),
title: Text( title: Text(
car['make'], car['make'],
style: AppStyle.title, style: TextStyle(
), // Assuming `make` is a field in each car item fontWeight: FontWeight.bold,
subtitle: Row( fontSize: 16,
mainAxisAlignment: color: car['isDefault'].toString() == '1'
MainAxisAlignment.spaceBetween, ? Colors.white
children: [ : Theme.of(context).textTheme.bodyLarge?.color,
Text( ),
car['model'], ),
style: AppStyle.title, subtitle: Padding(
), padding: const EdgeInsets.only(top: 8.0),
Container( child: Row(
decoration: BoxDecoration( children: [
border: Border.all( Text(
color: AppColor.blueColor)), car['model'],
child: Padding( style: TextStyle(
padding: const EdgeInsets.symmetric( color: car['isDefault'].toString() == '1'
horizontal: 4), ? Colors.white70
child: Text( : Theme.of(context).hintColor,
(car['car_plate']),
style: AppStyle.title,
), ),
), ),
), const SizedBox(width: 12),
Text( Container(
car['year'], padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
style: AppStyle.title, decoration: BoxDecoration(
), borderRadius: BorderRadius.circular(4),
], border: Border.all(
), // Assuming `model` is a field in each car item color: car['isDefault'].toString() == '1'
? Colors.white54
: AppColor.primaryColor,
),
),
child: Text(
car['car_plate'],
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: car['isDefault'].toString() == '1'
? Colors.white
: AppColor.primaryColor,
),
),
),
const Spacer(),
Text(
car['year'],
style: TextStyle(
color: car['isDefault'].toString() == '1'
? Colors.white70
: Theme.of(context).hintColor,
),
),
],
),
),
// trailing: IconButton( // trailing: IconButton(
// icon: const Icon( // icon: const Icon(
// Icons.delete, // Icons.delete,

View File

@@ -99,29 +99,30 @@ class ProfileHeader extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context);
return Column( return Column(
children: [ children: [
CircleAvatar( CircleAvatar(
radius: 50, radius: 50,
backgroundColor: Get.theme.primaryColor.withOpacity(0.1), backgroundColor: theme.primaryColor.withOpacity(0.1),
child: Icon(Icons.person, size: 60, color: Get.theme.primaryColor), child: Icon(Icons.person, size: 60, color: theme.primaryColor),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
Text( Text(
name, name,
style: Get.textTheme.headlineSmall style: theme.textTheme.headlineSmall
?.copyWith(fontWeight: FontWeight.bold), ?.copyWith(fontWeight: FontWeight.bold),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Row( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon(Icons.star, color: Colors.amber, size: 20), const Icon(Icons.star, color: Colors.amber, size: 20),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
'${rating.toStringAsFixed(1)} (${'reviews'.tr} $ratingCount)', '${rating.toStringAsFixed(1)} (${'reviews'.tr} $ratingCount)',
style: Get.textTheme.titleMedium style: theme.textTheme.titleMedium
?.copyWith(color: Colors.grey.shade600), ?.copyWith(color: theme.hintColor),
), ),
], ],
), ),
@@ -130,6 +131,7 @@ class ProfileHeader extends StatelessWidget {
} }
} }
/// 2. ويدجت شبكة الأزرار /// 2. ويدجت شبكة الأزرار
class ActionsGrid extends StatelessWidget { class ActionsGrid extends StatelessWidget {
const ActionsGrid({super.key}); const ActionsGrid({super.key});
@@ -187,20 +189,23 @@ void showShamCashInput() {
TextEditingController(text: existingCode); TextEditingController(text: existingCode);
Get.bottomSheet( Get.bottomSheet(
Container( Builder(builder: (context) {
padding: const EdgeInsets.all(25), final theme = Theme.of(context);
decoration: const BoxDecoration( return Container(
color: Colors.white, padding: const EdgeInsets.all(25),
borderRadius: BorderRadius.vertical(top: Radius.circular(30)), decoration: BoxDecoration(
boxShadow: [ color: theme.cardColor,
BoxShadow( borderRadius: const BorderRadius.vertical(top: Radius.circular(30)),
color: Colors.black26, blurRadius: 10, offset: Offset(0, -2)) boxShadow: [
], BoxShadow(
), color: theme.shadowColor.withOpacity(0.2), blurRadius: 10, offset: const Offset(0, -2))
child: SingleChildScrollView( ],
child: Column( ),
mainAxisSize: MainAxisSize.min, child: SingleChildScrollView(
crossAxisAlignment: CrossAxisAlignment.stretch, child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
// --- 1. المقبض العلوي --- // --- 1. المقبض العلوي ---
Center( Center(
@@ -208,12 +213,13 @@ void showShamCashInput() {
height: 5, height: 5,
width: 50, width: 50,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.grey[300], color: theme.dividerColor,
borderRadius: BorderRadius.circular(10)), borderRadius: BorderRadius.circular(10)),
margin: const EdgeInsets.only(bottom: 20), margin: const EdgeInsets.only(bottom: 20),
), ),
), ),
// --- 2. العنوان والأيقونة --- // --- 2. العنوان والأيقونة ---
Image.asset( Image.asset(
'assets/images/shamCash.png', 'assets/images/shamCash.png',
@@ -386,11 +392,13 @@ void showShamCashInput() {
], ],
), ),
), ),
), );
isScrollControlled: true, // ضروري لرفع النافذة عند فتح الكيبورد }),
isScrollControlled: true,
); );
} }
/// ويدجت داخلية لزر في الشبكة /// ويدجت داخلية لزر في الشبكة
class _ActionTile extends StatelessWidget { class _ActionTile extends StatelessWidget {
final String title; final String title;
@@ -402,8 +410,9 @@ class _ActionTile extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context);
return Material( return Material(
color: Colors.grey.shade100, color: theme.colorScheme.surfaceVariant.withOpacity(0.5),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
child: InkWell( child: InkWell(
onTap: onTap, onTap: onTap,
@@ -413,12 +422,12 @@ class _ActionTile extends StatelessWidget {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon(icon, color: Get.theme.primaryColor, size: 20), Icon(icon, color: theme.primaryColor, size: 20),
const SizedBox(width: 8), const SizedBox(width: 8),
Flexible( Flexible(
child: Text( child: Text(
title, title,
style: Get.textTheme.labelLarge, style: theme.textTheme.labelLarge,
textAlign: TextAlign.center, textAlign: TextAlign.center,
)), )),
], ],
@@ -429,6 +438,7 @@ class _ActionTile extends StatelessWidget {
} }
} }
/// 3. بطاقة المعلومات الشخصية /// 3. بطاقة المعلومات الشخصية
class PersonalInfoCard extends StatelessWidget { class PersonalInfoCard extends StatelessWidget {
final Map<String, dynamic> data; final Map<String, dynamic> data;
@@ -551,19 +561,21 @@ class _InfoRow extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context);
return Padding( return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0), padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row( child: Row(
children: [ children: [
Icon(icon, color: Colors.grey.shade500, size: 20), Icon(icon, color: theme.hintColor.withOpacity(0.6), size: 20),
const SizedBox(width: 16), const SizedBox(width: 16),
Text(label, style: Get.textTheme.bodyLarge), Text(label, style: theme.textTheme.bodyLarge),
const Spacer(), const Spacer(),
Flexible( Flexible(
child: Text( child: Text(
value, value,
style: Get.textTheme.bodyLarge?.copyWith( style: theme.textTheme.bodyLarge?.copyWith(
color: Colors.grey.shade700, fontWeight: FontWeight.w500), color: theme.textTheme.bodyLarge?.color?.withOpacity(0.8),
fontWeight: FontWeight.w500),
textAlign: TextAlign.end, textAlign: TextAlign.end,
), ),
), ),
@@ -572,3 +584,4 @@ class _InfoRow extends StatelessWidget {
); );
} }
} }

View File

@@ -26,77 +26,91 @@ class PromosPassengerPage extends StatelessWidget {
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
final rides = orderHistoryController.promoList[index]; final rides = orderHistoryController.promoList[index];
return Padding( return Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.symmetric(
child: Container( horizontal: 16, vertical: 8),
decoration: const BoxDecoration( child: Card(
borderRadius: BorderRadius.all(Radius.circular(12)), elevation: 4,
color: AppColor.secondaryColor, shadowColor:
boxShadow: [ Theme.of(context).shadowColor.withOpacity(0.1),
BoxShadow( shape: RoundedRectangleBorder(
color: AppColor.accentColor, borderRadius: BorderRadius.circular(16)),
offset: Offset(-3, -3),
blurRadius: 0,
spreadRadius: 0,
blurStyle: BlurStyle.outer),
BoxShadow(
color: AppColor.accentColor,
offset: Offset(3, 3),
blurRadius: 0,
spreadRadius: 0,
blurStyle: BlurStyle.outer)
]),
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(16.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
Row( Row(
mainAxisAlignment: mainAxisAlignment:
MainAxisAlignment.spaceBetween, MainAxisAlignment.spaceBetween,
children: [ children: [
Column( Expanded(
crossAxisAlignment: child: Column(
CrossAxisAlignment.start, crossAxisAlignment:
children: [ CrossAxisAlignment.start,
AnimatedTextKit( children: [
animatedTexts: [ SizedBox(
ScaleAnimatedText(rides['promo_code'], height: 30,
textStyle: AppStyle.title), child: AnimatedTextKit(
WavyAnimatedText(rides['promo_code'], animatedTexts: [
textStyle: AppStyle.title), ScaleAnimatedText(
FlickerAnimatedText( rides['promo_code'],
rides['promo_code'], textStyle: Theme.of(context)
textStyle: AppStyle.title), .textTheme
WavyAnimatedText(rides['promo_code'], .titleLarge
textStyle: AppStyle.title), ?.copyWith(
], color: AppColor
isRepeatingAnimation: true, .primaryColor,
onTap: () {}, fontWeight:
), FontWeight.bold,
Text( )),
rides['description'], WavyAnimatedText(
style: AppStyle.title, rides['promo_code'],
), textStyle: Theme.of(context)
], .textTheme
.titleLarge
?.copyWith(
color: AppColor
.primaryColor,
fontWeight:
FontWeight.bold,
)),
],
repeatForever: true,
),
),
const SizedBox(height: 8),
Text(
rides['description'],
style: Theme.of(context)
.textTheme
.bodyMedium,
),
],
),
), ),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
Text( _buildDateBadge(context,
rides['validity_start_date'], rides['validity_start_date'], true),
style: AppStyle.title, const SizedBox(height: 4),
), _buildDateBadge(context,
Text( rides['validity_end_date'], false),
rides['validity_end_date'],
style: AppStyle.title,
),
], ],
), ),
], ],
), ),
const Divider(height: 24),
Text( Text(
'Copy this Promo to use it in your Ride!'.tr, 'Copy this Promo to use it in your Ride!'.tr,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: AppStyle.headTitle2 style: Theme.of(context)
.copyWith(color: AppColor.accentColor), .textTheme
.labelMedium
?.copyWith(
color: Theme.of(context).hintColor,
fontStyle: FontStyle.italic,
),
) )
], ],
), ),
@@ -109,4 +123,24 @@ class PromosPassengerPage extends StatelessWidget {
], ],
); );
} }
Widget _buildDateBadge(BuildContext context, String date, bool isStart) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: isStart
? Colors.green.withOpacity(0.1)
: Colors.red.withOpacity(0.1),
borderRadius: BorderRadius.circular(6),
),
child: Text(
date,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.bold,
color: isStart ? Colors.green : Colors.red,
),
),
);
}
} }

View File

@@ -5,6 +5,8 @@ import 'package:get/get.dart';
import 'package:sefer_driver/constant/style.dart'; import 'package:sefer_driver/constant/style.dart';
import 'package:sefer_driver/views/widgets/my_scafold.dart'; import 'package:sefer_driver/views/widgets/my_scafold.dart';
import '../../../constant/colors.dart';
class TaarifPage extends StatelessWidget { class TaarifPage extends StatelessWidget {
const TaarifPage({super.key}); const TaarifPage({super.key});
@@ -18,47 +20,35 @@ class TaarifPage extends StatelessWidget {
// crossAxisAlignment: CrossAxisAlignment.stretch, // crossAxisAlignment: CrossAxisAlignment.stretch,
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
children: [ children: [
Table( _buildTariffItem(
defaultVerticalAlignment: TableCellVerticalAlignment.middle, context,
border: TableBorder.symmetric(), 'Minimum fare'.tr,
textBaseline: TextBaseline.alphabetic, box.read(BoxName.countryCode) == 'Jordan'
children: [ ? '1 ${'JOD'.tr}'
TableRow( : '20 ${'LE'.tr}'),
// decoration: AppStyle.boxDecoration, _buildTariffItem(
children: [ context,
Text('Minimum fare'.tr, style: AppStyle.title), 'Maximum fare'.tr,
box.read(BoxName.countryCode) == 'Jordan' box.read(BoxName.countryCode) == 'Jordan'
? Text('1 ${'JOD'.tr}', style: AppStyle.title) ? '200 ${'JOD'.tr}'
: Text('20 ${'LE'.tr}', style: AppStyle.title), : '15000 ${'LE'.tr}'),
], _buildTariffItem(
), context,
TableRow( 'Flag-down fee'.tr,
children: [ box.read(BoxName.countryCode) == 'Jordan'
Text('Maximum fare'.tr, style: AppStyle.title), ? '0.47 ${'JOD'.tr}'
box.read(BoxName.countryCode) == 'Jordan' : '15 ${'LE'.tr}'),
? Text('200 ${'JOD'.tr}', style: AppStyle.title) _buildTariffItem(
: Text('15000 ${'LE'.tr}', style: AppStyle.title), context,
], 'Rate'.tr,
), box.read(BoxName.countryCode) == 'Jordan'
TableRow( ? '0.05 ${'JOD'.tr}/min and 0.21 ${'JOD'.tr}/km'
children: [ : '1 ${'LE'.tr}/min and 4 ${'LE'.tr}/km'),
Text('Flag-down fee'.tr, style: AppStyle.title), Padding(
box.read(BoxName.countryCode) == 'Jordan' padding: const EdgeInsets.symmetric(vertical: 8.0),
? Text('0.47 ${'JOD'.tr}', style: AppStyle.title) child: Text('Including Tax'.tr,
: Text('15 ${'LE'.tr}', style: AppStyle.title), style: AppStyle.subtitle
], .copyWith(color: Theme.of(context).hintColor)),
),
TableRow(
children: [
box.read(BoxName.countryCode) == 'Jordan'
? Text('0.05 ${'JOD'.tr}/min and 0.21 ${'JOD'.tr}/km',
style: AppStyle.title)
: Text('1 ${'LE'.tr}/min and 4 ${'LE'.tr}/km',
style: AppStyle.title),
Text('Including Tax'.tr, style: AppStyle.title),
],
),
],
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
Text('BookingFee'.tr, style: AppStyle.headTitle2), Text('BookingFee'.tr, style: AppStyle.headTitle2),
@@ -85,4 +75,21 @@ class TaarifPage extends StatelessWidget {
), ),
]); ]);
} }
Widget _buildTariffItem(BuildContext context, String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 12.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(label, style: AppStyle.title),
),
Text(value,
style: AppStyle.title.copyWith(
fontWeight: FontWeight.bold, color: AppColor.primaryColor)),
],
),
);
}
} }

View File

@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:sefer_driver/views/home/Captin/home_captain/home_captin.dart'; import 'package:sefer_driver/views/home/Captin/home_captain/home_captin.dart';
import '../../constant/finance_design_system.dart';
import '../../controller/local/local_controller.dart'; import '../../controller/local/local_controller.dart';
class Language extends StatelessWidget { class Language extends StatelessWidget {
@@ -32,33 +33,9 @@ class Language extends StatelessWidget {
physics: const BouncingScrollPhysics(), physics: const BouncingScrollPhysics(),
children: [ children: [
_buildLanguageButton( _buildLanguageButton(
'العربية', 'ar', controller, context, '🇪🇬'), 'العربية', 'ar', controller, context, 'AR'),
_buildLanguageButton('العربية (الخليج)', 'ar-gulf',
controller, context, '🇸🇦'),
_buildLanguageButton('العربية (المغرب)', 'ar-ma',
controller, context, '🇲🇦'),
_buildLanguageButton( _buildLanguageButton(
'English', 'en', controller, context, '🇺🇸'), 'English', 'en', controller, context, 'EN'),
_buildLanguageButton(
'Türkçe', 'tr', controller, context, '🇹🇷'),
_buildLanguageButton(
'Français', 'fr', controller, context, '🇫🇷'),
_buildLanguageButton(
'Italiano', 'it', controller, context, '🇮🇹'),
_buildLanguageButton(
'Deutsch', 'de', controller, context, '🇩🇪'),
_buildLanguageButton(
'Ελληνικά', 'el', controller, context, '🇬🇷'),
_buildLanguageButton(
'Español', 'es', controller, context, '🇪🇸'),
_buildLanguageButton(
'فارسی', 'fa', controller, context, '🇮🇷'),
_buildLanguageButton(
'中文', 'zh', controller, context, '🇨🇳'),
_buildLanguageButton(
'Русский', 'ru', controller, context, '🇷🇺'),
_buildLanguageButton(
'हिन्दी', 'hi', controller, context, '🇮🇳'),
], ],
), ),
), ),
@@ -104,25 +81,43 @@ class Language extends StatelessWidget {
Widget _buildLanguageButton(String title, String langCode, Widget _buildLanguageButton(String title, String langCode,
LocaleController controller, BuildContext context, String flagIcon) { LocaleController controller, BuildContext context, String flagIcon) {
return Container( return Container(
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: CupertinoColors.white, color: CupertinoColors.white,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(16),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: CupertinoColors.systemGrey5.withOpacity(0.5), color: Colors.black.withOpacity(0.03),
spreadRadius: 1, spreadRadius: 1,
blurRadius: 3, blurRadius: 10,
offset: const Offset(0, 2), offset: const Offset(0, 4),
), ),
], ],
), ),
child: ListTile( child: ListTile(
leading: Text(flagIcon, contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
style: const TextStyle(fontSize: 28)), // Using flag icon as leading leading: Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: FinanceDesignSystem.primaryDark.withOpacity(0.05),
borderRadius: BorderRadius.circular(12),
),
alignment: Alignment.center,
child: Text(
flagIcon,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: FinanceDesignSystem.primaryDark,
),
),
),
title: Text( title: Text(
title, title,
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.w500, fontWeight: FontWeight.w600,
fontSize: 16,
), ),
), ),
trailing: const Icon(CupertinoIcons.chevron_forward, trailing: const Icon(CupertinoIcons.chevron_forward,

View File

@@ -2,7 +2,7 @@ import 'dart:convert';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'; // لتحديد الأنواع إذا لزم import 'package:intaleq_maps/intaleq_maps.dart'; // لتحديد الأنواع إذا لزم
import '../../constant/box_name.dart'; import '../../constant/box_name.dart';
import '../../constant/colors.dart'; import '../../constant/colors.dart';
@@ -114,7 +114,7 @@ class RideAvailableCard extends StatelessWidget {
margin: const EdgeInsets.only(bottom: 16.0), margin: const EdgeInsets.only(bottom: 16.0),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
elevation: 4, elevation: 4,
shadowColor: Colors.black.withOpacity(0.1), shadowColor: Theme.of(context).shadowColor.withOpacity(0.1),
child: Padding( child: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: Column( child: Column(
@@ -179,7 +179,7 @@ class RideAvailableCard extends StatelessWidget {
Container( Container(
height: 30, height: 30,
width: 1, width: 1,
color: Colors.grey.shade300, color: Theme.of(Get.context!).dividerColor,
margin: const EdgeInsets.symmetric(vertical: 4), margin: const EdgeInsets.symmetric(vertical: 4),
), ),
const Icon(Icons.location_on, color: Colors.red, size: 18), const Icon(Icons.location_on, color: Colors.red, size: 18),
@@ -218,15 +218,17 @@ class RideAvailableCard extends StatelessWidget {
); );
} }
Widget _infoItem(IconData icon, String text, Widget _infoItem(IconData icon, String text, {Color? iconColor}) {
{Color iconColor = Colors.grey}) { return Builder(builder: (context) {
return Row( final theme = Theme.of(context);
children: [ return Row(
Icon(icon, size: 16, color: iconColor), children: [
const SizedBox(width: 4), Icon(icon, size: 16, color: iconColor ?? theme.hintColor),
Text(text, style: AppStyle.subtitle.copyWith(fontSize: 13)), const SizedBox(width: 4),
], Text(text, style: theme.textTheme.bodySmall?.copyWith(fontSize: 13)),
); ],
);
});
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@@ -6,65 +6,72 @@ import 'mydialoug.dart';
class MyCircleContainer extends StatelessWidget { class MyCircleContainer extends StatelessWidget {
final Widget child; final Widget child;
final Color backgroundColor; final Color? backgroundColor;
final Color borderColor; final Color? borderColor;
MyCircleContainer({ MyCircleContainer({
Key? key, super.key,
required this.child, required this.child,
this.backgroundColor = AppColor.secondaryColor, this.backgroundColor,
this.borderColor = AppColor.accentColor, this.borderColor,
}) : super(key: key); });
final controller = Get.put(CircleController()); final controller = Get.put(CircleController());
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final effectiveBorderColor = borderColor ?? AppColor.borderColor;
return GetBuilder<CircleController>( return GetBuilder<CircleController>(
builder: ((controller) => GestureDetector( builder: ((controller) {
onTap: () { final effectiveBackgroundColor = backgroundColor ??
controller.changeColor(); (controller.isActive ? AppColor.accentColor : AppColor.secondaryColor);
MyDialog().getDialog(
'Rejected Orders Count'.tr, return GestureDetector(
'This is the total number of rejected orders per day after accepting the orders' onTap: () {
.tr, () { controller.toggleActive();
Get.back(); MyDialog().getDialog(
}); 'Rejected Orders Count'.tr,
}, 'This is the total number of rejected orders per day after accepting the orders'
child: AnimatedContainer( .tr, () {
onEnd: () { Get.back();
controller.onEnd(); });
}, },
duration: const Duration(milliseconds: 300), child: AnimatedContainer(
width: controller.size, onEnd: () {
height: controller.size, controller.resetSize();
decoration: BoxDecoration( },
shape: BoxShape.circle, duration: const Duration(milliseconds: 300),
color: controller.backgroundColor, width: controller.size,
border: Border.all( height: controller.size,
color: borderColor, decoration: BoxDecoration(
width: 1, shape: BoxShape.circle,
color: effectiveBackgroundColor,
border: Border.all(
color: effectiveBorderColor,
width: 1,
),
), ),
child: Center(child: child),
), ),
child: Center(child: child), );
), }));
)));
} }
} }
class CircleController extends GetxController { class CircleController extends GetxController {
Color backgroundColor = AppColor.secondaryColor; bool isActive = false;
double size = 40; double size = 40;
void changeColor() {
backgroundColor = backgroundColor == AppColor.secondaryColor void toggleActive() {
? AppColor.accentColor isActive = !isActive;
: AppColor.secondaryColor;
size = 60; size = 60;
update(); update();
} }
void onEnd() { void resetSize() {
size = 40; size = 40;
update(); update();
} }
} }

View File

@@ -51,8 +51,14 @@ class MyElevatedButton extends StatelessWidget {
child: Text( child: Text(
title, title,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: AppStyle.title.copyWith(color: AppColor.secondaryColor), style: AppStyle.title.copyWith(
color: (kolor == AppColor.primaryColor || kolor == AppColor.blueColor || kolor == AppColor.redColor || kolor == AppColor.greenColor)
? Colors.white
: AppColor.writeColor,
fontWeight: FontWeight.bold,
),
), ),
); );
} }
} }

View File

@@ -1,123 +1,292 @@
import 'dart:ui';
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 '../../constant/colors.dart'; import '../../constant/colors.dart';
class SnackbarConfig { // ─────────────────────────────────────────────────────────────────────────────
static const duration = Duration(seconds: 3); // Snackbar variant definition
static const animationDuration = Duration(milliseconds: 300); // ─────────────────────────────────────────────────────────────────────────────
static const margin = EdgeInsets.symmetric(horizontal: 16, vertical: 10); enum _SnackVariant { success, error, info, warning }
static const borderRadius = 12.0;
static const elevation = 6.0;
static final BoxShadow shadow = BoxShadow( extension _VariantProps on _SnackVariant {
color: Colors.black.withOpacity(0.1), Color get baseColor => switch (this) {
blurRadius: 10, _SnackVariant.success => const Color(0xFF1A9E5C),
offset: const Offset(0, 2), _SnackVariant.error => const Color(0xFFD93025),
); _SnackVariant.info => const Color(0xFF1A73E8),
_SnackVariant.warning => const Color(0xFFF29900),
};
Color get surfaceColor => switch (this) {
_SnackVariant.success => const Color(0xFFF0FBF5),
_SnackVariant.error => const Color(0xFFFEF2F1),
_SnackVariant.info => const Color(0xFFF0F6FF),
_SnackVariant.warning => const Color(0xFFFFF8E6),
};
IconData get icon => switch (this) {
_SnackVariant.success => Icons.check_circle_rounded,
_SnackVariant.error => Icons.error_rounded,
_SnackVariant.info => Icons.info_rounded,
_SnackVariant.warning => Icons.warning_amber_rounded,
};
String get label => switch (this) {
_SnackVariant.success => 'Success',
_SnackVariant.error => 'Error',
_SnackVariant.info => 'Info',
_SnackVariant.warning => 'Warning',
};
HapticFeedbackType get haptic => switch (this) {
_SnackVariant.error => HapticFeedbackType.medium,
_SnackVariant.warning => HapticFeedbackType.medium,
_SnackVariant.success => HapticFeedbackType.light,
_SnackVariant.info => HapticFeedbackType.selection,
};
} }
SnackbarController mySnackeBarError(String message) { enum HapticFeedbackType { light, medium, selection }
// Trigger error haptic feedback
HapticFeedback.mediumImpact(); // ─────────────────────────────────────────────────────────────────────────────
// Core snackbar widget
// ─────────────────────────────────────────────────────────────────────────────
class _SnackContent extends StatefulWidget {
final String message;
final _SnackVariant variant;
const _SnackContent({required this.message, required this.variant});
@override
State<_SnackContent> createState() => _SnackContentState();
}
class _SnackContentState extends State<_SnackContent>
with TickerProviderStateMixin {
late final AnimationController _ctrl;
late final Animation<double> _scaleAnim;
late final Animation<double> _progressAnim;
static const Duration _displayDuration = Duration(seconds: 4);
@override
void initState() {
super.initState();
_ctrl = AnimationController(vsync: this, duration: _displayDuration);
_scaleAnim = CurvedAnimation(
parent: AnimationController(
vsync: this,
duration: const Duration(milliseconds: 500),
)..forward(),
curve: Curves.elasticOut,
);
_progressAnim = Tween<double>(begin: 1.0, end: 0.0).animate(
CurvedAnimation(parent: _ctrl, curve: Curves.linear),
);
_ctrl.forward();
}
@override
void dispose() {
_ctrl.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final v = widget.variant;
final accent = v.baseColor;
final surface = v.surfaceColor;
return Container(
margin: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
decoration: BoxDecoration(
color: surface,
borderRadius: BorderRadius.circular(18),
border: Border.all(color: accent.withOpacity(0.18), width: 1.2),
boxShadow: [
BoxShadow(
color: accent.withOpacity(0.12),
blurRadius: 20,
spreadRadius: -2,
offset: const Offset(0, 6),
),
BoxShadow(
color: Colors.black.withOpacity(0.06),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(18),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// ── Main row ──────────────────────────────────────────────────
Padding(
padding: const EdgeInsets.fromLTRB(14, 14, 10, 12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Animated icon badge
ScaleTransition(
scale: _scaleAnim,
child: Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: accent.withOpacity(0.12),
shape: BoxShape.circle,
),
child: Icon(v.icon, color: accent, size: 22),
),
),
const SizedBox(width: 12),
// Text content
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
v.label.tr,
style: TextStyle(
color: accent,
fontSize: 13,
fontWeight: FontWeight.w700,
letterSpacing: 0.2,
),
),
const SizedBox(height: 3),
Text(
widget.message,
style: TextStyle(
color: Colors.grey[800],
fontSize: 13.5,
height: 1.4,
fontWeight: FontWeight.w400,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
// Close button
GestureDetector(
onTap: () {
HapticFeedback.lightImpact();
Get.closeCurrentSnackbar();
},
child: Container(
width: 30,
height: 30,
margin: const EdgeInsets.only(left: 6),
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(
Icons.close_rounded,
size: 16,
color: Colors.grey[500],
),
),
),
],
),
),
// ── Thin progress strip ───────────────────────────────────────
AnimatedBuilder(
animation: _progressAnim,
builder: (_, __) => Stack(
children: [
// Track
Container(
height: 3,
color: accent.withOpacity(0.08),
),
// Fill
FractionallySizedBox(
widthFactor: _progressAnim.value,
child: Container(
height: 3,
decoration: BoxDecoration(
color: accent.withOpacity(0.45),
borderRadius: const BorderRadius.only(
topRight: Radius.circular(4),
bottomRight: Radius.circular(4),
),
),
),
),
],
),
),
],
),
),
);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// Internal dispatcher — single source of truth
// ─────────────────────────────────────────────────────────────────────────────
SnackbarController _show(_SnackVariant variant, String message) {
// Dismiss any existing snackbar first (no stacking)
if (Get.isSnackbarOpen) Get.closeCurrentSnackbar();
switch (variant.haptic) {
case HapticFeedbackType.light:
HapticFeedback.lightImpact();
case HapticFeedbackType.medium:
HapticFeedback.mediumImpact();
case HapticFeedbackType.selection:
HapticFeedback.selectionClick();
}
return Get.snackbar( return Get.snackbar(
'Error'.tr, '',
message, '',
backgroundColor: AppColor.redColor.withOpacity(0.95),
colorText: AppColor.secondaryColor,
icon: const Icon(
Icons.error_outline_rounded,
color: AppColor.secondaryColor,
size: 28,
),
shouldIconPulse: true,
snackPosition: SnackPosition.TOP, snackPosition: SnackPosition.TOP,
margin: SnackbarConfig.margin, backgroundColor: Colors.transparent,
borderRadius: SnackbarConfig.borderRadius, margin: EdgeInsets.zero,
duration: SnackbarConfig.duration, padding: EdgeInsets.zero,
animationDuration: SnackbarConfig.animationDuration, duration: const Duration(seconds: 4),
forwardAnimationCurve: Curves.easeOutCirc, animationDuration: const Duration(milliseconds: 380),
reverseAnimationCurve: Curves.easeInCirc, barBlur: 0,
boxShadows: [SnackbarConfig.shadow], overlayBlur: 0,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), overlayColor: Colors.transparent,
titleText: Text(
'Error'.tr,
style: const TextStyle(
fontWeight: FontWeight.w700,
color: Colors.white,
fontSize: 16,
letterSpacing: 0.2,
),
),
messageText: Text(
message,
style: TextStyle(
color: Colors.white.withOpacity(0.95),
fontSize: 14,
height: 1.3,
),
),
onTap: (_) {
HapticFeedback.lightImpact();
Get.closeCurrentSnackbar();
},
isDismissible: true, isDismissible: true,
dismissDirection: DismissDirection.horizontal, dismissDirection: DismissDirection.up,
overlayBlur: 0.8, forwardAnimationCurve: Curves.easeOutCubic,
overlayColor: Colors.black12, reverseAnimationCurve: Curves.easeInCubic,
snackStyle: SnackStyle.FLOATING,
userInputForm: Form(
child: _SnackContent(message: message, variant: variant),
),
); );
} }
SnackbarController mySnackbarSuccess(String message) { // ─────────────────────────────────────────────────────────────────────────────
// Trigger success haptic feedback // Public API — drop-in replacements for the old functions
HapticFeedback.lightImpact(); // ─────────────────────────────────────────────────────────────────────────────
SnackbarController mySnackbarSuccess(String message) =>
_show(_SnackVariant.success, message);
return Get.snackbar( SnackbarController mySnackeBarError(String message) =>
'Success'.tr, _show(_SnackVariant.error, message);
message,
backgroundColor: AppColor.greenColor.withOpacity(0.95), SnackbarController mySnackbarInfo(String message) =>
colorText: AppColor.secondaryColor, _show(_SnackVariant.info, message);
icon: const Icon(
Icons.check_circle_outline_rounded, SnackbarController mySnackbarWarning(String message) =>
color: AppColor.secondaryColor, _show(_SnackVariant.warning, message);
size: 28,
),
shouldIconPulse: true,
snackPosition: SnackPosition.TOP,
margin: SnackbarConfig.margin,
borderRadius: SnackbarConfig.borderRadius,
duration: SnackbarConfig.duration,
animationDuration: SnackbarConfig.animationDuration,
forwardAnimationCurve: Curves.easeOutCirc,
reverseAnimationCurve: Curves.easeInCirc,
boxShadows: [SnackbarConfig.shadow],
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
titleText: Text(
'Success'.tr,
style: const TextStyle(
fontWeight: FontWeight.w700,
color: Colors.white,
fontSize: 16,
letterSpacing: 0.2,
),
),
messageText: Text(
message,
style: TextStyle(
color: Colors.white.withOpacity(0.95),
fontSize: 14,
height: 1.3,
),
),
onTap: (_) {
HapticFeedback.lightImpact();
Get.closeCurrentSnackbar();
},
isDismissible: true,
dismissDirection: DismissDirection.horizontal,
overlayBlur: 0.8,
overlayColor: Colors.black12,
);
}

View File

@@ -26,18 +26,18 @@ class IconWidgetMenu extends StatelessWidget {
children: [ children: [
Container( Container(
width: 50, width: 50,
decoration: const BoxDecoration( decoration: BoxDecoration(
color: AppColor.secondaryColor, color: AppColor.secondaryColor,
shape: BoxShape.circle, shape: BoxShape.circle,
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: AppColor.secondaryColor, color: AppColor.secondaryColor,
offset: Offset(-2, -2), offset: const Offset(-2, -2),
blurRadius: 0, blurRadius: 0,
spreadRadius: 0, spreadRadius: 0,
blurStyle: BlurStyle.outer, blurStyle: BlurStyle.outer,
), ),
BoxShadow( const BoxShadow(
color: AppColor.accentColor, color: AppColor.accentColor,
offset: Offset(3, 3), offset: Offset(3, 3),
blurRadius: 0, blurRadius: 0,

View File

@@ -9,29 +9,27 @@ class MyScafolld extends StatelessWidget {
super.key, super.key,
required this.title, required this.title,
required this.body, required this.body,
this.action = const Icon( this.action,
Icons.clear,
color: AppColor.secondaryColor,
),
required this.isleading, required this.isleading,
}); });
final String title; final String title;
final List<Widget> body; final List<Widget> body;
final Widget action; final Widget? action;
final bool isleading; final bool isleading;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold( return Scaffold(
backgroundColor: AppColor.secondaryColor, backgroundColor: theme.scaffoldBackgroundColor,
appBar: AppBar( appBar: AppBar(
backgroundColor: AppColor.secondaryColor, backgroundColor: theme.appBarTheme.backgroundColor,
elevation: 0, elevation: 0,
leading: isleading leading: isleading
? IconButton( ? IconButton(
onPressed: () { onPressed: () {
Get.back(); Navigator.maybePop(context);
}, },
icon: const Icon( icon: const Icon(
Icons.arrow_back_ios_new, Icons.arrow_back_ios_new,
@@ -39,12 +37,19 @@ class MyScafolld extends StatelessWidget {
), ),
) )
: const SizedBox(), : const SizedBox(),
actions: [action], actions: [
action ??
Icon(
Icons.clear,
color: Colors.transparent,
)
],
title: Text( title: Text(
title, title,
style: AppStyle.title.copyWith(fontSize: 30), style: theme.textTheme.titleLarge?.copyWith(fontSize: 24, fontWeight: FontWeight.bold),
), ),
), ),
body: SafeArea(child: Stack(children: body))); body: SafeArea(child: Stack(children: body)));
} }
} }

View File

@@ -1,4 +1,5 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart'; import 'package:get_storage/get_storage.dart';
import 'package:sefer_driver/constant/box_name.dart'; import 'package:sefer_driver/constant/box_name.dart';
@@ -18,6 +19,7 @@ class MyTextForm extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context);
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 10), padding: const EdgeInsets.only(bottom: 10),
child: SizedBox( child: SizedBox(
@@ -27,9 +29,7 @@ class MyTextForm extends StatelessWidget {
children: [ children: [
Text( Text(
label.tr, label.tr,
style: TextStyle( style: theme.textTheme.bodyLarge?.copyWith(
color: CupertinoColors.label,
fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
), ),
@@ -40,13 +40,14 @@ class MyTextForm extends StatelessWidget {
placeholder: hint.tr, placeholder: hint.tr,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: CupertinoColors.systemBackground, color: theme.cardColor,
border: Border.all(color: CupertinoColors.systemGrey4), border: Border.all(color: theme.dividerColor),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
style: const TextStyle(color: CupertinoColors.label), style: theme.textTheme.bodyLarge,
placeholderStyle: placeholderStyle: theme.textTheme.bodyMedium?.copyWith(
const TextStyle(color: CupertinoColors.placeholderText), color: theme.hintColor,
),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
ValueListenableBuilder<TextEditingValue>( ValueListenableBuilder<TextEditingValue>(
@@ -56,9 +57,10 @@ class MyTextForm extends StatelessWidget {
return errorText != null return errorText != null
? Text( ? Text(
errorText, errorText,
style: const TextStyle( style: TextStyle(
color: CupertinoColors.destructiveRed, color: theme.colorScheme.error,
fontSize: 12), fontSize: 12,
),
) )
: const SizedBox.shrink(); : const SizedBox.shrink();
}, },

View File

@@ -44,11 +44,9 @@ class MyDialog extends GetxController {
sigmaX: DialogConfig.blurStrength, sigmaX: DialogConfig.blurStrength,
sigmaY: DialogConfig.blurStrength, sigmaY: DialogConfig.blurStrength,
), ),
child: Theme( child: Builder(builder: (context) {
data: ThemeData.light().copyWith( final theme = Theme.of(context);
dialogBackgroundColor: CupertinoColors.systemBackground, return CupertinoAlertDialog(
),
child: CupertinoAlertDialog(
title: Column( title: Column(
children: [ children: [
Text( Text(
@@ -92,7 +90,7 @@ class MyDialog extends GetxController {
style: AppStyle.title.copyWith( style: AppStyle.title.copyWith(
fontSize: 16, fontSize: 16,
height: 1.3, height: 1.3,
color: Colors.black87, color: theme.textTheme.bodyLarge?.color ?? AppColor.writeColor,
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
@@ -102,7 +100,7 @@ class MyDialog extends GetxController {
CupertinoDialogAction( CupertinoDialogAction(
onPressed: () { onPressed: () {
HapticFeedback.lightImpact(); HapticFeedback.lightImpact();
Get.back(); Navigator.of(context, rootNavigator: true).pop();
}, },
child: Text( child: Text(
'Cancel'.tr, 'Cancel'.tr,
@@ -116,6 +114,7 @@ class MyDialog extends GetxController {
CupertinoDialogAction( CupertinoDialogAction(
onPressed: () { onPressed: () {
HapticFeedback.mediumImpact(); HapticFeedback.mediumImpact();
Navigator.of(context, rootNavigator: true).pop();
onPressed(); onPressed();
}, },
child: Text( child: Text(
@@ -128,10 +127,11 @@ class MyDialog extends GetxController {
), ),
), ),
], ],
), );
), }),
), ),
), ),
barrierDismissible: true, barrierDismissible: true,
barrierColor: Colors.black.withAlpha(102), // 0.4 opacity barrierColor: Colors.black.withAlpha(102), // 0.4 opacity
); );
@@ -159,11 +159,8 @@ class MyDialogContent extends GetxController {
sigmaX: DialogConfig.blurStrength, sigmaX: DialogConfig.blurStrength,
sigmaY: DialogConfig.blurStrength, sigmaY: DialogConfig.blurStrength,
), ),
child: Theme( child: Builder(builder: (context) {
data: ThemeData.light().copyWith( return CupertinoAlertDialog(
dialogBackgroundColor: CupertinoColors.systemBackground,
),
child: CupertinoAlertDialog(
title: Column( title: Column(
children: [ children: [
Text( Text(
@@ -208,7 +205,7 @@ class MyDialogContent extends GetxController {
CupertinoDialogAction( CupertinoDialogAction(
onPressed: () { onPressed: () {
HapticFeedback.lightImpact(); HapticFeedback.lightImpact();
Get.back(); Navigator.of(context, rootNavigator: true).pop();
}, },
child: Text( child: Text(
'Cancel', 'Cancel',
@@ -222,6 +219,7 @@ class MyDialogContent extends GetxController {
CupertinoDialogAction( CupertinoDialogAction(
onPressed: () { onPressed: () {
HapticFeedback.mediumImpact(); HapticFeedback.mediumImpact();
Navigator.of(context, rootNavigator: true).pop();
onPressed(); onPressed();
}, },
child: Text( child: Text(
@@ -234,10 +232,11 @@ class MyDialogContent extends GetxController {
), ),
), ),
], ],
), );
), }),
), ),
), ),
barrierDismissible: true, barrierDismissible: true,
barrierColor: Colors.black.withAlpha(102), // 0.4 opacity barrierColor: Colors.black.withAlpha(102), // 0.4 opacity
); );

View File

@@ -8,7 +8,6 @@
#include <file_selector_linux/file_selector_plugin.h> #include <file_selector_linux/file_selector_plugin.h>
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h> #include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
#include <objectbox_flutter_libs/objectbox_flutter_libs_plugin.h>
#include <record_linux/record_linux_plugin.h> #include <record_linux/record_linux_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h> #include <url_launcher_linux/url_launcher_plugin.h>
@@ -19,9 +18,6 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
g_autoptr(FlPluginRegistrar) objectbox_flutter_libs_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "ObjectboxFlutterLibsPlugin");
objectbox_flutter_libs_plugin_register_with_registrar(objectbox_flutter_libs_registrar);
g_autoptr(FlPluginRegistrar) record_linux_registrar = g_autoptr(FlPluginRegistrar) record_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "RecordLinuxPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "RecordLinuxPlugin");
record_linux_plugin_register_with_registrar(record_linux_registrar); record_linux_plugin_register_with_registrar(record_linux_registrar);

View File

@@ -5,7 +5,6 @@
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
file_selector_linux file_selector_linux
flutter_secure_storage_linux flutter_secure_storage_linux
objectbox_flutter_libs
record_linux record_linux
url_launcher_linux url_launcher_linux
) )

View File

@@ -24,7 +24,6 @@ import google_sign_in_ios
import just_audio import just_audio
import local_auth_darwin import local_auth_darwin
import location import location
import objectbox_flutter_libs
import package_info_plus import package_info_plus
import record_macos import record_macos
import share_plus import share_plus
@@ -55,7 +54,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin")) JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin"))
LocalAuthPlugin.register(with: registry.registrar(forPlugin: "LocalAuthPlugin")) LocalAuthPlugin.register(with: registry.registrar(forPlugin: "LocalAuthPlugin"))
LocationPlugin.register(with: registry.registrar(forPlugin: "LocationPlugin")) LocationPlugin.register(with: registry.registrar(forPlugin: "LocationPlugin"))
ObjectboxFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "ObjectboxFlutterLibsPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
RecordMacOsPlugin.register(with: registry.registrar(forPlugin: "RecordMacOsPlugin")) RecordMacOsPlugin.register(with: registry.registrar(forPlugin: "RecordMacOsPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))

View File

@@ -184,46 +184,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.0.6" version: "0.0.6"
camera:
dependency: "direct main"
description:
name: camera
sha256: "034c38cb8014d29698dcae6d20276688a1bf74e6487dfeb274d70ea05d5f7777"
url: "https://pub.dev"
source: hosted
version: "0.12.0+1"
camera_android_camerax:
dependency: transitive
description:
name: camera_android_camerax
sha256: "2c178975759aac0f0ef7ce1ec698b6e2acd792127ea7f38fa79a424fbebeae7f"
url: "https://pub.dev"
source: hosted
version: "0.7.1+2"
camera_avfoundation:
dependency: transitive
description:
name: camera_avfoundation
sha256: "90e4cc3fde331581a3b2d35d83be41dbb7393af0ab857eb27b732174289cb96d"
url: "https://pub.dev"
source: hosted
version: "0.10.1"
camera_platform_interface:
dependency: transitive
description:
name: camera_platform_interface
sha256: "98cfc9357e04bad617671b4c1f78a597f25f08003089dd94050709ae54effc63"
url: "https://pub.dev"
source: hosted
version: "2.12.0"
camera_web:
dependency: transitive
description:
name: camera_web
sha256: "57f49a635c8bf249d07fb95eb693d7e4dda6796dedb3777f9127fb54847beba7"
url: "https://pub.dev"
source: hosted
version: "0.3.5+3"
characters: characters:
dependency: transitive dependency: transitive
description: description:
@@ -248,14 +208,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.13.0" version: "1.13.0"
cli_util:
dependency: transitive
description:
name: cli_util
sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
url: "https://pub.dev"
source: hosted
version: "0.4.2"
clock: clock:
dependency: transitive dependency: transitive
description: description:
@@ -344,22 +296,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.9" version: "1.0.9"
dart_earcut:
dependency: transitive
description:
name: dart_earcut
sha256: e485001bfc05dcbc437d7bfb666316182e3522d4c3f9668048e004d0eb2ce43b
url: "https://pub.dev"
source: hosted
version: "1.2.0"
dart_polylabel2:
dependency: transitive
description:
name: dart_polylabel2
sha256: "7eeab15ce72894e4bdba6a8765712231fc81be0bd95247de4ad9966abc57adc6"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
dart_style: dart_style:
dependency: transitive dependency: transitive
description: description:
@@ -380,10 +316,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: device_info_plus name: device_info_plus
sha256: b4fed1b2835da9d670d7bed7db79ae2a94b0f5ad6312268158a9b5479abbacdd sha256: e3fc9a65820fef83035af8ee8c09004a719d5d1d54e6de978fcb0d84bbeb241a
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "12.4.0" version: "11.2.2"
device_info_plus_platform_interface: device_info_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
@@ -584,14 +520,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" version: "1.2.0"
flat_buffers:
dependency: transitive
description:
name: flat_buffers
sha256: "380bdcba5664a718bfd4ea20a45d39e13684f5318fcd8883066a55e21f37f4c3"
url: "https://pub.dev"
source: hosted
version: "23.5.26"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@@ -781,14 +709,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.0" version: "0.6.0"
flutter_launcher_icons:
dependency: "direct main"
description:
name: flutter_launcher_icons
sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7"
url: "https://pub.dev"
source: hosted
version: "0.14.4"
flutter_lints: flutter_lints:
dependency: "direct dev" dependency: "direct dev"
description: description:
@@ -829,22 +749,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.0" version: "3.0.0"
flutter_map:
dependency: "direct main"
description:
name: flutter_map
sha256: "391e7dc95cc3f5190748210a69d4cfeb5d8f84dcdfa9c3235d0a9d7742ccb3f8"
url: "https://pub.dev"
source: hosted
version: "8.2.2"
flutter_map_tile_caching:
dependency: "direct main"
description:
name: flutter_map_tile_caching
sha256: "90e097223d8ab74425cf15b449a03adfa4d4c28406dc757e1c396aff0f9beba7"
url: "https://pub.dev"
source: hosted
version: "10.1.1"
flutter_overlay_window: flutter_overlay_window:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1122,18 +1026,16 @@ packages:
get: get:
dependency: "direct main" dependency: "direct main"
description: description:
name: get path: "../Intaleq/packages/get"
sha256: "5ed34a7925b85336e15d472cc4cfe7d9ebf4ab8e8b9f688585bf6b50f4c3d79a" relative: true
url: "https://pub.dev" source: path
source: hosted version: "4.6.5"
version: "4.7.3"
get_storage: get_storage:
dependency: "direct main" dependency: "direct main"
description: description:
name: get_storage path: "../Intaleq/packages/get_storage"
sha256: "39db1fffe779d0c22b3a744376e86febe4ade43bf65e06eab5af707dc84185a2" relative: true
url: "https://pub.dev" source: path
source: hosted
version: "2.1.1" version: "2.1.1"
glob: glob:
dependency: transitive dependency: transitive
@@ -1159,62 +1061,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.3+1" version: "0.3.3+1"
google_maps:
dependency: transitive
description:
name: google_maps
sha256: "5d410c32112d7c6eb7858d359275b2aa04778eed3e36c745aeae905fb2fa6468"
url: "https://pub.dev"
source: hosted
version: "8.2.0"
google_maps_flutter:
dependency: "direct main"
description:
name: google_maps_flutter
sha256: fc714bf8072e2c121d4277cb6dca23bbfae954b6c7b5d6dd73f1bc8d09762921
url: "https://pub.dev"
source: hosted
version: "2.17.0"
google_maps_flutter_android:
dependency: transitive
description:
name: google_maps_flutter_android
sha256: "9c14d3d58a398d6182c2c674b17d36c16a1d64c2c60891e9acf019856319c512"
url: "https://pub.dev"
source: hosted
version: "2.19.5"
google_maps_flutter_ios:
dependency: transitive
description:
name: google_maps_flutter_ios
sha256: "5ed8d8d0f93dfa7f5039c409c500948e98e59068f8f6fcf9105bfd07e3709d7f"
url: "https://pub.dev"
source: hosted
version: "2.18.1"
google_maps_flutter_platform_interface:
dependency: transitive
description:
name: google_maps_flutter_platform_interface
sha256: ddbe34435dfb34e83fca295c6a8dcc53c3b51487e9eec3c737ce4ae605574347
url: "https://pub.dev"
source: hosted
version: "2.15.0"
google_maps_flutter_web:
dependency: transitive
description:
name: google_maps_flutter_web
sha256: "6cefe4ef4cc61dc0dfba4c413dec4bd105cb6b9461bfbe1465ddd09f80af377d"
url: "https://pub.dev"
source: hosted
version: "0.6.2"
google_polyline_algorithm:
dependency: "direct main"
description:
name: google_polyline_algorithm
sha256: "357874f00d3f93c3ba1bf4b4d9a154aa9ee87147c068238c1e8392012b686a03"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
google_sign_in: google_sign_in:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1328,7 +1174,7 @@ packages:
source: hosted source: hosted
version: "4.1.2" version: "4.1.2"
image: image:
dependency: "direct main" dependency: transitive
description: description:
name: image name: image
sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce
@@ -1423,6 +1269,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: "../map-saas/packages/flutter-sdk"
relative: true
source: path
version: "2.2.0"
internet_connection_checker: internet_connection_checker:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1503,14 +1356,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:
@@ -1543,14 +1388,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.0" version: "6.1.0"
lists:
dependency: transitive
description:
name: lists
sha256: "4ca5c19ae4350de036a7e996cdd1ee39c93ac0a2b840f4915459b7d0a7d4ab27"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
live_activities: live_activities:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1623,14 +1460,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.1" version: "6.0.1"
logger:
dependency: transitive
description:
name: logger
sha256: "25aee487596a6257655a1e091ec2ae66bc30e7af663592cc3a27e6591e05035c"
url: "https://pub.dev"
source: hosted
version: "2.7.0"
logging: logging:
dependency: transitive dependency: transitive
description: description:
@@ -1647,6 +1476,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.3.2" version: "3.3.2"
maplibre_gl:
dependency: transitive
description:
name: maplibre_gl
sha256: d9773555ae4ebab94bbc3ae2176b077cfda486ec729eefe01e1613f164cb8410
url: "https://pub.dev"
source: hosted
version: "0.25.0"
maplibre_gl_platform_interface:
dependency: transitive
description:
name: maplibre_gl_platform_interface
sha256: bd7de401dea24dd7e8a6f2fa736ddee7dbbee3e24a9027f0afdd619994702047
url: "https://pub.dev"
source: hosted
version: "0.25.0"
maplibre_gl_web:
dependency: transitive
description:
name: maplibre_gl_web
sha256: af0e48bf96e8dd99f8b958a1953126971eb8a0527b9735441d4f24df3913f5a2
url: "https://pub.dev"
source: hosted
version: "0.25.0"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
@@ -1671,14 +1524,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.0" version: "1.17.0"
mgrs_dart:
dependency: transitive
description:
name: mgrs_dart
sha256: fb89ae62f05fa0bb90f70c31fc870bcbcfd516c843fb554452ab3396f78586f7
url: "https://pub.dev"
source: hosted
version: "2.0.0"
mime: mime:
dependency: transitive dependency: transitive
description: description:
@@ -1711,22 +1556,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.0" version: "0.5.0"
objectbox:
dependency: transitive
description:
name: objectbox
sha256: "3cc186749178a3556e1020c9082d0897d0f9ecbdefcc27320e65c5bc650f0e57"
url: "https://pub.dev"
source: hosted
version: "4.3.1"
objectbox_flutter_libs:
dependency: transitive
description:
name: objectbox_flutter_libs
sha256: cd754766e04229a4f51250f121813d9a3c1a74fc21cd68e48b3c6085cbcd6c85
url: "https://pub.dev"
source: hosted
version: "4.3.1"
objective_c: objective_c:
dependency: transitive dependency: transitive
description: description:
@@ -1927,14 +1756,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.5.0" version: "6.5.0"
proj4dart:
dependency: transitive
description:
name: proj4dart
sha256: c8a659ac9b6864aa47c171e78d41bbe6f5e1d7bd790a5814249e6b68bc44324e
url: "https://pub.dev"
source: hosted
version: "2.1.0"
provider: provider:
dependency: transitive dependency: transitive
description: description:
@@ -2071,14 +1892,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.28.0" version: "0.28.0"
sanitize_html:
dependency: transitive
description:
name: sanitize_html
sha256: "12669c4a913688a26555323fb9cec373d8f9fbe091f2d01c40c723b33caa8989"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
secure_string_operations: secure_string_operations:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -2338,14 +2151,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.0" version: "1.4.0"
unicode:
dependency: transitive
description:
name: unicode
sha256: "0f69e46593d65245774d4f17125c6084d2c20b4e473a983f6e21b7d7762218f1"
url: "https://pub.dev"
source: hosted
version: "0.3.1"
upower: upower:
dependency: transitive dependency: transitive
description: description:
@@ -2355,7 +2160,7 @@ packages:
source: hosted source: hosted
version: "0.7.0" version: "0.7.0"
url_launcher: url_launcher:
dependency: "direct main" dependency: transitive
description: description:
name: url_launcher name: url_launcher
sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8
@@ -2614,18 +2419,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: win32_registry name: win32_registry
sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae" sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.0" version: "1.1.5"
wkt_parser:
dependency: transitive
description:
name: wkt_parser
sha256: "8a555fc60de3116c00aad67891bcab20f81a958e4219cc106e3c037aa3937f13"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:

View File

@@ -10,111 +10,87 @@ environment:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
secure_string_operations:
path: ./secure_string_operations # Local Packages
# flutter_overlay_apps:
# path: ./flutter_overlay_apps-main
bubble_head: bubble_head:
path: ./bubble-master path: ./bubble-master
get:
path: ../Intaleq/packages/get
get_storage:
path: ../Intaleq/packages/get_storage
intaleq_maps:
path: ../map-saas/packages/flutter-sdk/
secure_string_operations:
path: ./secure_string_operations
trip_overlay_plugin: trip_overlay_plugin:
path: ./trip_overlay_plugin path: ./trip_overlay_plugin
# flutter_overlay_window: ^0.4.4
cupertino_icons: ^1.0.2 # Core & UI
firebase_messaging: ^16.1.3
firebase_core: ^4.6.0
flutter_local_notifications: ^21.0.0
google_maps_flutter: ^2.10.1
sqflite: ^2.3.0
path: ^1.8.3
# lottie: ^2.5.0
intl: ^0.20.2
google_fonts: ^8.0.2
http: ^1.2.2
get: ^4.6.5
get_storage: ^2.1.1
url_launcher: ^6.1.12
location: ^8.0.0
google_polyline_algorithm: ^3.1.0
# custom_searchable_dropdown: ^2.1.1
animated_text_kit: ^4.2.2 animated_text_kit: ^4.2.2
flutter_secure_storage: ^10.0.0
geolocator: ^14.0.2
flutter_paypal: ^0.2.0
flutter_launcher_icons: ^0.14.2 #to be remove
# crypto: ^3.0.3
encrypt: ^5.0.3
flutter_rating_bar: ^4.0.1
flutter_font_icons: ^2.2.5
image_picker: ^1.0.4
flutter_stripe: ^12.4.0
camera: ^0.12.0+1 #to be remove
flutter_widget_from_html: ^0.17.1
local_auth: ^3.0.1
image: ^4.1.3 #to be remove
image_cropper: ^12.1.1
envied: ^1.0.0
# cached_network_image: ^3.3.0 #to be remove
calendar_builder: ^0.0.6 calendar_builder: ^0.0.6
cupertino_icons: ^1.0.2
fl_chart: ^1.2.0 fl_chart: ^1.2.0
# agora_rtc_engine: ^6.2.6
flutter_tts: ^4.0.2
permission_handler: ^12.0.1
# google_generative_ai: ^0.0.1-dev
vibration: ^3.1.8
wakelock_plus:
# background_location: ^0.13.0
# background_location:
# git:
# url: https://github.com/dharmik-dalwadi-seaflux/background_location.git
# ref: master
# flutter_overlay_apps:
# git:
# url: https://github.com/Hamza-Ayed/flutter_overlay_apps.git
# ref: main
record: ^6.2.0
dio: ^5.4.3+1
webview_flutter: ^4.9.0
just_audio: ^0.10.5
share_plus: ^12.0.2
google_sign_in: ^7.2.0
# google_mlkit_text_recognition: ^0.14.0
sign_in_with_apple: ^7.0.1
firebase_auth: ^6.3.0
package_info_plus: ^9.0.1
flutter_image_compress: ^2.3.0
flutter_contacts: ^1.1.8
flutter_overlay_window: ^0.5.0
googleapis_auth: ^2.0.0
video_player: ^2.9.2
youtube_player_flutter: ^9.0.4
flutter_confetti: ^0.5.1 flutter_confetti: ^0.5.1
slide_to_act: ^2.0.2 flutter_font_icons: ^2.2.5
live_activities: ^2.3.0 flutter_rating_bar: ^4.0.1
quick_actions: ^1.1.0
jwt_decoder: ^2.0.1
jailbreak_root_detection: ^1.1.5
device_info_plus: ^12.4.0
flutter_web_browser: ^0.17.3
# flutter_isolate: ^2.1.0
# lingo_hunter: ^1.0.3
shimmer: ^3.0.0
flutter_svg: ^2.2.0
lottie: ^3.3.1
flutter_staggered_animations: ^1.1.1 flutter_staggered_animations: ^1.1.1
flutter_svg: ^2.2.0
flutter_widget_from_html: ^0.17.1
google_fonts: ^8.0.2
lottie: ^3.3.1
shimmer: ^3.0.0
slide_to_act: ^2.0.2
# Services & Hardware
battery_plus: ^7.0.0 battery_plus: ^7.0.0
internet_connection_checker: ^3.0.1
connectivity_plus: ^6.1.5 connectivity_plus: ^6.1.5
# pip_view: ^0.9.7 device_info_plus: 11.2.2
flutter_map: ^8.2.2 # (استخدم أحدث إصدار دائماً) envied: ^1.0.0
firebase_auth: ^6.3.0
# مكتبة التخزين لتحميل الخرائط أوفلاين firebase_core: ^4.6.0
flutter_map_tile_caching: ^10.1.1 # (استخدم أحدث إصدار) firebase_messaging: ^16.1.3
latlong2: ^0.9.1
socket_io_client: ^1.0.2
flutter_background_service: ^5.1.0 flutter_background_service: ^5.1.0
flutter_contacts: ^1.1.8
flutter_local_notifications: ^21.0.0
flutter_overlay_window: ^0.5.0
flutter_tts: ^4.0.2
geolocator: ^14.0.2
google_sign_in: ^7.2.0
googleapis_auth: ^2.0.0
image_cropper: ^12.1.1
image_picker: ^1.0.4
internet_connection_checker: ^3.0.1
jailbreak_root_detection: ^1.1.5
just_audio: ^0.10.5
live_activities: ^2.3.0
local_auth: ^3.0.1
location: ^8.0.0
package_info_plus: ^9.0.1
permission_handler: ^12.0.1
quick_actions: ^1.1.0
record: ^6.2.0
share_plus: ^12.0.2
sign_in_with_apple: ^7.0.1
socket_io_client: ^1.0.2
vibration: ^3.1.8
video_player: ^2.9.2
wakelock_plus:
webview_flutter: ^4.9.0
youtube_player_flutter: ^9.0.4
# مكتبة لتحديد النقاط على الخريطة (مثل latLng) # Data & Network
dio: ^5.4.3+1
encrypt: ^5.0.3
flutter_image_compress: ^2.3.0
flutter_paypal: ^0.2.0
flutter_secure_storage: ^10.0.0
flutter_stripe: ^12.4.0
flutter_web_browser: ^0.17.3
http: ^1.2.2
intl: ^0.20.2
jwt_decoder: ^2.0.1
path: ^1.8.3
sqflite: ^2.3.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@@ -162,3 +138,5 @@ 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:
path: ../Intaleq/packages/get

41
scratch/extract_keys.py Normal file
View File

@@ -0,0 +1,41 @@
import os
import re
# Regex for .tr keys: matches 'string'.tr or "string".tr
tr_pattern = re.compile(r'[\'"]([^\'"]+)[\'"]\.tr')
# Regex for Text widgets or other common UI strings that might be hardcoded
# This is a bit broad, but helps find things like Text("Hello")
text_pattern = re.compile(r'Text\(\s*[\'"]([^\'"]+)[\'"]')
keys = set()
def scan_file(filepath):
try:
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
# Find all .tr usages
for match in tr_pattern.finditer(content):
keys.add(match.group(1))
# Find potential hardcoded strings in Text widgets
# (We only care about those NOT already using .tr)
# This is just for my manual review to see if I missed any
# for match in text_pattern.finditer(content):
# keys.add(match.group(1))
except Exception as e:
pass
def main():
root_dir = 'lib'
for root, dirs, files in os.walk(root_dir):
for file in files:
if file.endswith('.dart'):
scan_file(os.path.join(root, file))
# Print all found keys
for key in sorted(keys):
print(key)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,82 @@
import re
filepath = 'lib/controller/local/translations.dart'
with open(filepath, 'r', encoding='utf-8') as f:
lines = f.readlines()
new_lines = []
seen_keys_en = set()
seen_keys_ar = set()
in_en = False
in_ar = False
for line in lines:
if '"en": {' in line:
in_en = True
in_ar = False
new_lines.append(line)
continue
if '"ar": {' in line:
in_en = False
in_ar = True
new_lines.append(line)
continue
if '};' in line or ' }' in line:
if in_en or in_ar:
if ' }' in line:
in_en = False
in_ar = False
new_lines.append(line)
continue
# Try to extract key and value using a more careful approach
# Looking for: "key": "value",
# We find the first " and the last " and the middle separator ": "
stripped = line.strip()
if stripped.startswith('"') and (stripped.endswith('",') or stripped.endswith('"')):
# Split by ": " but only once
try:
# We need to find the ": " that separates key and value
# Since keys and values can contain ": ", we look for the one surrounded by quotes
# A common pattern is "...": "..."
parts = re.split(r'":\s+"', stripped)
if len(parts) >= 2:
# Key is from index 1 to second-to-last char of first part
key = parts[0][1:]
# Value is from index 0 to last char (excluding optional comma and final quote)
val_part = parts[1]
if val_part.endswith(','):
val = val_part[:-2]
comma = ','
else:
val = val_part[:-1]
comma = ''
# FIX 1: Trailing backslashes
if key.endswith('\\') and not key.endswith('\\\\'):
key += '\\'
if val.endswith('\\') and not val.endswith('\\\\'):
val += '\\'
# FIX 2: Unescaped dollar signs
key = re.sub(r'(?<!\\)\$', r'\$', key)
val = re.sub(r'(?<!\\)\$', r'\$', val)
# FIX 3: Deduplication
if in_en:
if key in seen_keys_en: continue
seen_keys_en.add(key)
if in_ar:
if key in seen_keys_ar: continue
seen_keys_ar.add(key)
new_lines.append(f' "{key}": "{val}"{comma}\n')
continue
except:
pass
new_lines.append(line)
with open(filepath, 'w', encoding='utf-8') as f:
f.writelines(new_lines)

View File

@@ -14,12 +14,4 @@ import 'package:trip_overlay_plugin/trip_overlay_plugin.dart';
void main() { void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('getPlatformVersion test', (WidgetTester tester) async {
final TripOverlayPlugin plugin = TripOverlayPlugin();
final String? version = await plugin.getPlatformVersion();
// The version string depends on the host platform running the test, so
// just assert that some non-empty string is returned.
expect(version?.isNotEmpty, true);
});
} }

View File

@@ -19,11 +19,5 @@ void main() {
expect(initialPlatform, isInstanceOf<MethodChannelTripOverlayPlugin>()); expect(initialPlatform, isInstanceOf<MethodChannelTripOverlayPlugin>());
}); });
test('getPlatformVersion', () async {
TripOverlayPlugin tripOverlayPlugin = TripOverlayPlugin();
MockTripOverlayPluginPlatform fakePlatform = MockTripOverlayPluginPlatform();
TripOverlayPluginPlatform.instance = fakePlatform;
expect(await tripOverlayPlugin.getPlatformVersion(), '42');
});
} }

View File

@@ -16,7 +16,6 @@
#include <flutter_tts/flutter_tts_plugin.h> #include <flutter_tts/flutter_tts_plugin.h>
#include <geolocator_windows/geolocator_windows.h> #include <geolocator_windows/geolocator_windows.h>
#include <local_auth_windows/local_auth_plugin.h> #include <local_auth_windows/local_auth_plugin.h>
#include <objectbox_flutter_libs/objectbox_flutter_libs_plugin.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h> #include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <record_windows/record_windows_plugin_c_api.h> #include <record_windows/record_windows_plugin_c_api.h>
#include <share_plus/share_plus_windows_plugin_c_api.h> #include <share_plus/share_plus_windows_plugin_c_api.h>
@@ -43,8 +42,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("GeolocatorWindows")); registry->GetRegistrarForPlugin("GeolocatorWindows"));
LocalAuthPluginRegisterWithRegistrar( LocalAuthPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("LocalAuthPlugin")); registry->GetRegistrarForPlugin("LocalAuthPlugin"));
ObjectboxFlutterLibsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ObjectboxFlutterLibsPlugin"));
PermissionHandlerWindowsPluginRegisterWithRegistrar( PermissionHandlerWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
RecordWindowsPluginCApiRegisterWithRegistrar( RecordWindowsPluginCApiRegisterWithRegistrar(

View File

@@ -13,7 +13,6 @@ list(APPEND FLUTTER_PLUGIN_LIST
flutter_tts flutter_tts
geolocator_windows geolocator_windows
local_auth_windows local_auth_windows
objectbox_flutter_libs
permission_handler_windows permission_handler_windows
record_windows record_windows
share_plus share_plus