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

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

View File

@@ -27,6 +27,7 @@ class AK {
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 mapAPIKEY = Env.mapAPIKEY;
static final String mapSaasKey = Env.mapSaasKey;
static final String twilloRecoveryCode =
X.r(X.r(X.r(Env.twilloRecoveryCode, cn), cC), cs);
static final String authTokenTwillo =

View File

@@ -1,33 +1,69 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class AppColor {
// --- Core Brand Colors ---
/// **Primary Color:** The brand's signature blue.
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 goldenBronze = Color(0xFFB87333); // Golden bronze color
static const Color gold = Color(0xFFD4AF37);
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 goldenBronze = Color(0xFFB87333);
static const Color twitterColor = Color(0xFF1DA1F2);
static const Color redColor = Color(0xFFEA4335); // Google Red
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);
// --- Utility Colors ---
// For dynamic elements like gradients
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 Color get greyColor => grayColor;
static List<Color> secondaryGradientStartEnd = [
const Color(0xFF1DA1F2), // Start with Twitter blue
const Color(0xFF0C7ABF), // End with a slightly darker shade of Twitter blue
];
static Color get cyanBlue => const Color(0xFF1DA1F2);
static Color get cyanAccent => const Color(0xFF1DA1F2).withOpacity(0.12);
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
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
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 =
'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 =
"https://routesjo.intaleq.xyz/route/v1/driving";
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';
class AppStyle {
static TextStyle headTitle = TextStyle(
static TextStyle get headTitle => TextStyle(
fontWeight: FontWeight.bold,
fontSize: 36,
color: AppColor.accentColor,
@@ -13,57 +13,61 @@ class AppStyle {
// ?GoogleFonts.markaziText().fontFamily
? GoogleFonts.markaziText().fontFamily
: GoogleFonts.inter().fontFamily);
static TextStyle headTitle2 = TextStyle(
static TextStyle get headTitle2 => TextStyle(
fontWeight: FontWeight.bold,
fontSize: 24,
color: AppColor.writeColor,
fontFamily: box.read(BoxName.lang) == 'ar'
? GoogleFonts.markaziText().fontFamily
: GoogleFonts.inter().fontFamily);
static TextStyle title = TextStyle(
static TextStyle get title => TextStyle(
fontWeight: FontWeight.normal,
fontSize: 16,
color: AppColor.writeColor,
fontFamily: box.read(BoxName.lang) == 'ar'
? GoogleFonts.markaziText().fontFamily
: GoogleFonts.inter().fontFamily);
static TextStyle subtitle = TextStyle(
static TextStyle get subtitle => TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12,
color: AppColor.writeColor,
fontFamily: box.read(BoxName.lang) == 'ar'
? GoogleFonts.markaziText().fontFamily
: GoogleFonts.inter().fontFamily);
static TextStyle number = const TextStyle(
static TextStyle get number => TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
color: AppColor.writeColor,
fontFamily: 'digit');
static BoxDecoration boxDecoration = const BoxDecoration(
static BoxDecoration get boxDecoration => BoxDecoration(
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(
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,
borderRadius: BorderRadius.all(
borderRadius: const BorderRadius.all(
Radius.elliptical(15, 30),
));
static BoxDecoration boxDecoration1 = const BoxDecoration(
static BoxDecoration get boxDecoration1 => BoxDecoration(
boxShadow: [
BoxShadow(
color: Color.fromARGB(255, 237, 230, 230),
blurRadius: 5,
offset: Offset(2, 4)),
color: Colors.black.withValues(alpha: 0.05),
blurRadius: 10,
offset: const Offset(0, 4)),
BoxShadow(
color: Color.fromARGB(255, 242, 237, 237),
color: AppColor.primaryColor.withValues(alpha: 0.02),
blurRadius: 5,
offset: Offset(-2, -2))
offset: const Offset(-2, -2))
],
color: AppColor.secondaryColor,
borderRadius: BorderRadius.all(
borderRadius: const BorderRadius.all(
Radius.elliptical(15, 30),
),
);

View File

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

View File

@@ -379,16 +379,20 @@ class LoginDriverController extends GetxController {
box.read(BoxName.tokenDriver).toString() ||
serverData['data'][0]['fingerPrint'].toString() !=
fingerPrint.toString()) {
await Get.defaultDialog(
Get.defaultDialog(
barrierDismissible: false,
title: 'Device Change Detected'.tr,
middleText: 'Please verify your identity'.tr,
textConfirm: 'Verify'.tr,
confirmTextColor: Colors.white,
onConfirm: () {
Get.back();
// انتقل لصفحة OTP الجديدة
Get.to(
// نغلق الـ Dialog أولاً بشكل صريح
if (Get.isDialogOpen ?? false) {
Get.back();
}
// ثم ننتقل لصفحة OTP
Get.offAll(
() => OtpVerificationPage(
phone: d['phone'].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:convert';
import 'dart:ui';
import 'dart:io';
import 'package:flutter/material.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_overlay_window/flutter_overlay_window.dart';
import 'package:socket_io_client/socket_io_client.dart' as IO;
import 'package:flutter_overlay_window/flutter_overlay_window.dart' as Overlay;
import 'package:get_storage/get_storage.dart';
import 'package:geolocator/geolocator.dart' as geo;
import '../../constant/box_name.dart';
import '../firebase/local_notification.dart';
const String notificationChannelId = 'driver_service_channel';
const int notificationId = 888;
@@ -35,14 +37,18 @@ Future<bool> onStart(ServiceInstance service) async {
IO.OptionBuilder()
.setTransports(['websocket'])
.disableAutoConnect()
.setQuery({'driver_id': driverId, 'token': token})
.setQuery({
'driver_id': driverId,
'token': token,
'EIO': '3', // توافقية مع Workerman
})
.setReconnectionAttempts(double.infinity)
.build());
socket.connect();
socket.onConnect((_) {
print("✅ Background Service: Socket Connected!");
print("✅ Background Service: Socket Connected! ID: ${socket?.id}");
if (service is AndroidServiceInstance) {
flutterLocalNotificationsPlugin.show(
id: notificationId,
@@ -70,39 +76,94 @@ Future<bool> onStart(ServiceInstance service) async {
final box = GetStorage();
bool isAppInForeground = box.read(BoxName.isAppInForeground) ?? false;
// 🔥 Check إضافي: هل الـ Overlay مفتوح بالفعل؟
bool overlayActive = await Overlay.FlutterOverlayWindow.isActive();
// 🔥 Check إضافي: هل الـ Overlay مفتوح بالفعل؟ (للأندرويد فقط)
bool overlayActive = false;
if (Platform.isAndroid) {
overlayActive = await Overlay.FlutterOverlayWindow.isActive();
}
if (isAppInForeground || overlayActive) {
print("🛑 App is FOREGROUND or Overlay already shown. Skipping.");
return;
}
// عرض الـ Overlay
print("🚀 App is BACKGROUND. Showing Overlay...");
try {
await Overlay.FlutterOverlayWindow.showOverlay(
enableDrag: true,
overlayTitle: "طلب جديد",
overlayContent: "لديك طلب جديد وصل للتو!",
flag: OverlayFlag.focusPointer,
positionGravity: PositionGravity.auto,
height: WindowSize.matchParent,
width: WindowSize.matchParent,
startPosition: const OverlayPosition(0, -30),
);
await Overlay.FlutterOverlayWindow.shareData(data);
} catch (e) {
print("Overlay Error: $e");
// عرض الـ Overlay (للأندرويد فقط)
if (Platform.isAndroid) {
print("🚀 App is BACKGROUND. Showing Overlay...");
try {
await Overlay.FlutterOverlayWindow.showOverlay(
enableDrag: true,
overlayTitle: "طلب جديد",
overlayContent: "لديك طلب جديد وصل للتو!",
flag: OverlayFlag.focusPointer,
positionGravity: PositionGravity.auto,
height: WindowSize.matchParent,
width: WindowSize.matchParent,
startPosition: const OverlayPosition(0, -30),
);
await Overlay.FlutterOverlayWindow.shareData(data);
} 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) {
socket?.disconnect();
socket?.clearListeners();
socket?.dispose();
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 {
if (service is AndroidServiceInstance) {
if (await service.isForegroundService()) {

View File

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

View File

@@ -604,6 +604,59 @@ class CRUD {
);
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 {

View File

@@ -1,7 +1,11 @@
import 'dart:io';
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:sefer_driver/views/widgets/error_snakbar.dart';
import 'package:sefer_driver/views/widgets/mydialoug.dart';
import 'background_service.dart';
@@ -28,18 +32,19 @@ class PermissionsHelper {
if (Platform.isAndroid) {
final androidInfo = await DeviceInfoPlugin().androidInfo;
// Android 13+ (API 33+) يحتاج إذن POST_NOTIFICATIONS
if (androidInfo.version.sdkInt >= 33) {
final status = await Permission.notification.request();
if (status.isDenied) {
print('⚠️ إذن الإشعارات مرفوض');
mySnackbarWarning(
"يرجى منح صلاحية الإشعارات لضمان وصول الطلبات إليك");
return false;
}
if (status.isPermanentlyDenied) {
print('⚠️ إذن الإشعارات مرفوض بشكل دائم - افتح الإعدادات');
await openAppSettings();
mySnackbarWarning('يرجى فتح الإعدادات وتفعيل صلاحية الإشعارات');
return false;
}
}
@@ -50,25 +55,31 @@ class PermissionsHelper {
/// طلب جميع الإذونات المطلوبة
static Future<bool> requestAllPermissions() async {
// إذن الإشعارات أولاً
bool notificationGranted = await requestNotificationPermission();
if (!notificationGranted) return false;
// إذن الإشعارات (اختياري)
await requestNotificationPermission();
// إذن الموقع
final locationStatus = await Permission.location.request();
if (!locationStatus.isGranted) {
print('⚠️ إذن الموقع مرفوض');
// 1. طلب إذن الموقع الأساسي فقط إذا كان مرفوضاً
var status = await Permission.location.status;
if (status.isDenied) {
status = await Permission.location.request();
}
if (status.isPermanentlyDenied) {
_showSettingsDialog('الموقع');
return false;
}
// إذن الموقع في الخلفية
if (Platform.isAndroid) {
final bgLocationStatus = await Permission.locationAlways.request();
if (!bgLocationStatus.isGranted) {
print('⚠️ إذن الموقع في الخلفية مرفوض');
}
}
return status.isGranted || status.isLimited;
}
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:convert';
import 'dart:io';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
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:battery_plus/battery_plus.dart';
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:sefer_driver/constant/table_names.dart';
import 'package:trip_overlay_plugin/trip_overlay_plugin.dart';
@@ -17,6 +15,7 @@ import '../../constant/box_name.dart';
import '../../constant/links.dart';
import '../../main.dart';
import '../../print.dart';
import '../firebase/local_notification.dart';
import '../home/captin/home_captain_controller.dart';
import '../home/captin/map_driver_controller.dart';
import '../home/payment/captain_wallet_controller.dart';
@@ -63,6 +62,7 @@ class LocationController extends GetxController with WidgetsBindingObserver {
bool _isPowerSavingMode = false;
final List<Map<String, dynamic>> _trackBuffer = [];
final List<Map<String, dynamic>> _behaviorBuffer = [];
LatLng? _lastPosForDistance;
LatLng? _lastRecordedRealLoc;
@@ -137,26 +137,22 @@ class LocationController extends GetxController with WidgetsBindingObserver {
Log.print("📱 Lifecycle: App is in FOREGROUND");
box.write(BoxName.isAppInForeground, true);
// إيقاف خدمة الخلفية لأننا في الواجهة الآن
// إيقاف خدمة الخلفية
BackgroundServiceHelper.stopService();
// التأكد من أن السوكيت متصل، وإذا لا، نعيد الاتصال فوراً
if (socket == null || !socket!.connected) {
Log.print("🔄 Socket disconnected in background. Reconnecting now...");
if (socket == null || (!socket!.connected && !_isInitializingSocket)) {
Log.print("🔄 Initializing Socket on resume...");
initSocket();
} else {
// إذا كان متصلاً، ننعش المستمعين فقط للتأكد
_setupSocketListeners();
}
} else if (state == AppLifecycleState.paused ||
state == AppLifecycleState.detached) {
Log.print("📱 Lifecycle: App is in BACKGROUND");
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) ======
// ===================================================================
bool _isInitializingSocket = false;
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 token = box.read(BoxName.tokenDriver).toString();
String platform = Platform.isIOS ? 'ios' : 'android';
// 1. إذا كان السوكيت موجوداً، فقط تأكد من اتصاله
_isInitializingSocket = true;
// تنظيف السوكيت القديم فقط إذا كان موجوداً وغير متصل
if (socket != null) {
if (!socket!.connected) {
Log.print("🟡 Socket exists but disconnected. Reconnecting...");
socket!.connect();
}
_setupSocketListeners(); // تحديث المستمعين
return;
Log.print("🧹 Cleaning up old socket instance...");
socket!.clearListeners();
socket!.dispose();
socket = null;
}
Log.print(
"🟡 [LocationController] Creating NEW Socket for Driver: $driverId");
"🟡 [LocationController] Initializing NEW Socket for Driver: $driverId");
// 2. إنشاء الاتصال
socket = IO.io(
'https://location.intaleq.xyz',
IO.OptionBuilder()
.setTransports(['websocket'])
.enableAutoConnect() // تفعيل إعادة الاتصال التلقائي
.setQuery(
{'driver_id': driverId, 'token': token, 'platform': platform})
.setReconnectionAttempts(double.infinity)
.setReconnectionDelay(2000)
.build());
try {
// العودة للـ Websocket حصراً لأنه الوحيد الذي ينجح في فتح القناة
socket = IO.io(
'https://location.intaleq.xyz',
IO.OptionBuilder()
.setTransports(['websocket'])
.setQuery({'driver_id': driverId, 'token': token, 'EIO': '3'})
.enableForceNew()
.build());
socket!.connect();
_setupSocketListeners();
_setupSocketListeners();
socket!.connect();
} catch (e) {
_isInitializingSocket = false;
Log.print("❌ Socket Initialization Exception: $e");
}
}
// دالة منفصلة لضمان عدم تكرار المستمعين
void _setupSocketListeners() {
if (socket == null) return;
// تنظيف القديم أولاً
socket!.off('connect');
socket!.off('disconnect');
socket!.off('new_ride_request');
socket!.off('ride_cancelled');
socket!.off('connect_error');
socket!.off('error');
socket!.onConnect((_) {
Log.print('✅ Socket Connected! ID: ${socket?.id}');
isSocketConnected = true;
_startHeartbeat();
_isInitializingSocket = false;
// ننتظر قليلاً للتأكد من تعبئة الـ 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((_) {
Log.print('❌ Socket Disconnected');
socket!.onDisconnect((data) {
_isInitializingSocket = false;
Log.print('❌ Socket Disconnected: $data');
isSocketConnected = false;
_stopHeartbeat();
});
socket!.onConnectError((err) {
_isInitializingSocket = false;
Log.print('❌ Socket Connect Error: $err');
});
socket!.onConnectTimeout((data) {
_isInitializingSocket = false;
Log.print('❌ Socket Connect Timeout: $data');
});
socket!.onError((err) {
_isInitializingSocket = false;
Log.print('❌ Socket General Error: $err');
});
socket!.on('reconnect_attempt', (attempt) {
Log.print('🔄 Socket Reconnecting... Attempt: $attempt');
});
// 🔥 الاستماع للطلبات الجديدة
socket!.on('new_ride_request', (data) {
Log.print("🔔 Socket: New Ride Request Arrived!");
@@ -296,8 +330,17 @@ class LocationController extends GetxController with WidgetsBindingObserver {
if (!isAppInForeground) {
Log.print(
"🛑 التطبيق في الخلفية. السوكيت سيتجاهل التوجيه ويترك المهمة للـ Overlay.");
return; // 👈 هذا السطر يمنع السوكيت من إكمال العمل وفتح الصفحة
"📱 [LocationController] Order received in background (iOS/Android). Source: $source");
if (Platform.isIOS) {
// على iOS، نقوم بإظهار إشعار محلي لأن الـ Overlay غير مدعوم
NotificationController().showNotification(
"طلب رحلة جديد 🚖",
"لديك طلب رحلة جديد، افتح التطبيق للموافقة عليه",
jsonEncode(rideData),
'ding.wav');
}
return;
}
try {
@@ -309,14 +352,23 @@ class LocationController extends GetxController with WidgetsBindingObserver {
// 3. تجهيز البيانات (DriverList)
List<dynamic> driverList = [];
if (rideData.length > 0) {
var sortedKeys = rideData.keys.map((e) => int.tryParse(e) ?? 0).toList()
..sort();
if (rideData.isNotEmpty) {
var sortedKeys = rideData.keys
.where((e) => int.tryParse(e) != null)
.map((e) => int.parse(e))
.toList()..sort();
for (var key in sortedKeys) {
driverList.add(rideData[key.toString()]);
}
}
// الحماية ضد البنية غير المكتملة
if (driverList.length <= 16) {
Log.print("❌ Socket Error: Parsed driver list is incomplete.");
return;
}
// 4. إغلاق النافذة (إن وجدت بالخطأ) والتنقل
try {
if (await TripOverlayPlugin.isOverlayActive()) {
@@ -459,21 +511,16 @@ class LocationController extends GetxController with WidgetsBindingObserver {
_uploadBatchTimer?.cancel();
_socketHeartbeat?.cancel();
if (socket != null && socket!.connected) {
String driverId = box.read(BoxName.driverID).toString();
socket!.emit('update_location', {
'driver_id': driverId,
'lat': myLocation.latitude,
'lng': myLocation.longitude,
'heading': heading,
'speed': speed * 3.6,
'status': 'close', // Changed to off
'distance': totalDistance
});
socket!.disconnect();
if (socket != null) {
socket!.clearListeners();
socket!
.dispose(); // استخدام dispose بدلاً من disconnect لضمان تحرير الموارد على iOS
}
if (!Platform.isIOS) {
await BackgroundServiceHelper.stopService();
}
await BackgroundServiceHelper.stopService();
socket = null;
isSocketConnected = false;
_isReady = false;
@@ -526,16 +573,31 @@ class LocationController extends GetxController with WidgetsBindingObserver {
Future<void> _flushBufferToServer() async {
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();
try {
await CRUD().post(
var res = await CRUD().post(
link: '${AppLink.locationServer}/add_batch.php',
payload: {'driver_id': driverId, 'batch_data': jsonEncode(batch)},
);
if (res != 'failure') {
_trackBuffer.removeRange(0, itemsToTake);
} else {
_enforceBufferLimit();
}
} catch (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 (previousMode != _isPowerSavingMode) {
_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,
{required double currentSpeed}) async {
final dist =
(_lastSqlLoc == null) ? 999.0 : _calculateDistance(_lastSqlLoc!, pos);
if (dist < 15.0) return;
final accel = _calcAcceleration(currentSpeed, now) ?? 0.0;
try {
await sql.insertData({
'driver_id': (box.read(BoxName.driverID) ?? '').toString(),
'latitude': pos.latitude,
'longitude': pos.longitude,
'acceleration': accel,
'created_at': now.toIso8601String(),
'updated_at': now.toIso8601String(),
}, TableName.behavior);
_lastSqlLoc = pos;
} catch (e) {
Log.print('SQLite Error: $e');
_lastSqlLoc = pos;
_behaviorBuffer.add({
'driver_id': (box.read(BoxName.driverID) ?? '').toString(),
'latitude': pos.latitude,
'longitude': pos.longitude,
'acceleration': accel,
'created_at': now.toIso8601String(),
'updated_at': now.toIso8601String(),
});
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 في باقي الكود أيضاً
// لأنها تعتمد على C++ في الأندرويد و Obj-C في الآيفون (Native Speed)
double _calculateDistance(LatLng a, LatLng b) {

View File

@@ -217,8 +217,12 @@ class SecurityHelper {
isNotTrust = await JailbreakRootDetection.instance.isNotTrust;
isJailBroken = await JailbreakRootDetection.instance.isJailBroken;
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 =
await JailbreakRootDetection.instance.checkForIssues;
@@ -230,7 +234,6 @@ class SecurityHelper {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
bundleId = packageInfo.packageName;
if (bundleId.isNotEmpty) {
// Pass the CORRECT bundle ID to isTampered
isTampered = await JailbreakRootDetection.instance.isTampered(bundleId);
}
} catch (e) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,109 +4,57 @@ import 'package:get/get.dart';
import '../../constant/box_name.dart';
import '../../main.dart';
import '../themes/themes.dart';
import '../profile/setting_controller.dart';
class LocaleController extends GetxController {
Locale? language;
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) {
Locale locale;
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;
}
Locale locale = Locale(langcode);
box.write(BoxName.lang, langcode);
Get.changeTheme(appTheme);
Get.updateLocale(locale);
update();
}
@override
void onInit() {
String? storedLang = box.read(BoxName.lang);
if (storedLang == null) {
// Use device language if no language is stored
storedLang = Get.deviceLocale!.languageCode;
box.write(BoxName.lang, storedLang);
}
changeLang(storedLang);
language = Locale(storedLang);
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/links.dart';
import '../../main.dart';
import '../../print.dart';
import '../functions/crud.dart';
import '../functions/encrypt_decrypt.dart';
import '../functions/toast.dart';
import 'paymob/paymob_wallet.dart';
@@ -170,8 +168,8 @@ class PaymentController extends GetxController {
)),
allowsDelayedPaymentMethods: true,
customerEphemeralKeySecret: Stripe.merchantIdentifier,
appearance: const PaymentSheetAppearance(
shapes: PaymentSheetShape(borderRadius: 12),
appearance: PaymentSheetAppearance(
shapes: const PaymentSheetShape(borderRadius: 12),
colors: PaymentSheetAppearanceColors(
background: AppColor.secondaryColor,
),
@@ -725,7 +723,6 @@ class PaymentController extends GetxController {
box.write(BoxName.passengerWalletTotal, '0');
}
// getPassengerWallet();
final localAuth = LocalAuthentication();
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/main.dart';
import 'package:get/get.dart';
import '../local/local_controller.dart';
class SettingController extends GetxController {
bool isGoogleMapsEnabled = false;
bool isMapDarkMode = false;
bool isDarkMode = false;
void onChangMapApp() {
if (!isGoogleMapsEnabled) {
isGoogleMapsEnabled = true;
box.write(BoxName.googlaMapApp, true);
update();
} else {
isGoogleMapsEnabled = false;
box.write(BoxName.googlaMapApp, false);
update();
isGoogleMapsEnabled = !isGoogleMapsEnabled;
box.write(BoxName.googlaMapApp, isGoogleMapsEnabled);
update();
}
void toggleMapTheme() {
isMapDarkMode = !isMapDarkMode;
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
void onInit() {
if (box.read(BoxName.googlaMapApp) != null) {
isGoogleMapsEnabled = box.read(BoxName.googlaMapApp);
}
update();
isGoogleMapsEnabled = box.read(BoxName.googlaMapApp) ?? false;
isMapDarkMode = box.read('isMapDarkMode') ?? false;
isDarkMode = box.read('isDarkMode') ?? false;
super.onInit();
}
}

View File

@@ -2,146 +2,89 @@ import 'package:flutter/material.dart';
import 'package:sefer_driver/constant/style.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,
fontFamily: "SFPro",
textTheme: TextTheme(
displaySmall: AppStyle.title,
displayLarge: AppStyle.headTitle,
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,
),
scaffoldBackgroundColor: Colors.white,
cardColor: Colors.white,
dividerColor: Colors.black12,
);
ThemeData darkThemeEnglish = ThemeData(
ThemeData darkThemeEnglish = _createTheme(
brightness: Brightness.dark,
fontFamily: "SFPro",
textTheme: TextTheme(
displaySmall: AppStyle.title,
displayLarge: AppStyle.headTitle,
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,
),
scaffoldBackgroundColor: const Color(0xFF121212),
cardColor: const Color(0xFF1E1E1E),
dividerColor: Colors.white10,
);
ThemeData lightThemeArabic = ThemeData(
ThemeData lightThemeArabic = _createTheme(
brightness: Brightness.light,
fontFamily: 'SFArabic',
textTheme: TextTheme(
displaySmall: AppStyle.title,
displayLarge: AppStyle.headTitle,
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,
),
scaffoldBackgroundColor: Colors.white,
cardColor: Colors.white,
dividerColor: Colors.black12,
);
ThemeData darkThemeArabic = ThemeData(
ThemeData darkThemeArabic = _createTheme(
brightness: Brightness.dark,
fontFamily: 'SFArabic',
textTheme: TextTheme(
displaySmall: AppStyle.title,
displayLarge: AppStyle.headTitle,
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,
),
scaffoldBackgroundColor: const Color(0xFF121212),
cardColor: const Color(0xFF1E1E1E),
dividerColor: Colors.white10,
);

View File

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

3
lib/env/env.dart vendored
View File

@@ -12,6 +12,9 @@ abstract class Env {
@EnviedField(varName: 'mapAPIKEYIOS', obfuscate: true)
static final String mapAPIKEYIOS = _Env.mapAPIKEYIOS;
@EnviedField(varName: 'mapSaasKey', obfuscate: true)
static final String mapSaasKey = _Env.mapSaasKey;
@EnviedField(varName: 'email', obfuscate: true)
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 'views/home/Captin/orderCaptin/order_request_page.dart';
import 'views/home/Captin/driver_map_page.dart';
import 'controller/profile/setting_controller.dart';
final box = GetStorage();
const storage = FlutterSecureStorage();
@@ -315,10 +316,15 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
Future<void> _initApp() async {
try {
if (!Get.isRegistered<NotificationController>())
if (!Get.isRegistered<NotificationController>()) {
Get.put(NotificationController());
if (!Get.isRegistered<FirebaseMessagesController>())
}
if (!Get.isRegistered<FirebaseMessagesController>()) {
Get.put(FirebaseMessagesController()).getToken();
}
if (!Get.isRegistered<SettingController>()) {
Get.put(SettingController());
}
await FirebaseMessaging.instance.requestPermission();
await NotificationController().initNotifications();
@@ -433,12 +439,15 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
print("📥 Server Response: $res");
if (Get.isDialogOpen == true) Get.back();
// إغلاق أي ديالوج مفتوح باستخدام المفتاح العالمي لضمان الموثوقية
if (Get.isDialogOpen == true) {
navigatorKey.currentState?.pop();
}
bool isFailure = false;
if (res is Map && res['status'] == 'failure')
if (res is Map && res['status'] == 'failure') {
isFailure = true;
else if (res == 'failure') isFailure = true;
} else if (res == 'failure') isFailure = true;
if (isFailure) {
Get.defaultDialog(
@@ -465,7 +474,9 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
Get.offAll(() => PassengerLocationMapPage(), arguments: rideArgs);
} catch (e) {
if (Get.isDialogOpen == true) Get.back();
if (Get.isDialogOpen == true) {
navigatorKey.currentState?.pop();
}
print("❌ Error in accept process: $e");
Get.snackbar("خطأ", "حدث خطأ غير متوقع");
}
@@ -511,13 +522,18 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
@override
Widget build(BuildContext context) {
final LocaleController localController = Get.put(LocaleController());
final SettingController settingController = Get.put(SettingController());
return GetMaterialApp(
navigatorKey: navigatorKey,
title: AppInformation.appName,
translations: MyTranslation(),
debugShowCheckedModeBanner: false,
locale: localController.language,
theme: localController.appTheme,
theme: localController.lightTheme,
darkTheme: localController.darkTheme,
themeMode: settingController.isDarkMode ? ThemeMode.dark : ThemeMode.light,
initialRoute: '/',
getPages: [
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
import 'package:get/get.dart';
class OrderData {
final String customerName;
final String customerToken;
@@ -122,17 +124,17 @@ class OrderData {
static String _getRideType(String type) {
switch (type) {
case 'Comfort':
return '‏كمفورت ❄️';
return 'Comfort ❄️'.tr;
case 'Lady':
return '‏ليدي 👩';
return 'Lady 👩'.tr;
case 'Speed':
return '‏‏‏سبيد 🔻';
return 'Speed 🔻'.tr;
case 'Mashwari':
return '‏مشواري';
return 'Mashwari'.tr;
case 'Rayeh Gai':
return 'رايح جاي';
return 'Rayeh Gai'.tr;
default:
return type;
return type.tr;
}
}

View File

@@ -4,7 +4,7 @@ class Log {
Log._();
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) {

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": "",
"Female": "",
"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": "",
"Female": "",
"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
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[50],
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
appBar: AppBar(
title: Text('Trip Completed'.tr,
style: const TextStyle(color: Colors.black)),
title: Text('Trip Completed'.tr),
centerTitle: true,
automaticallyImplyLeading: false,
backgroundColor: Colors.white,
elevation: 0,
),
body: GetBuilder<RateController>(
@@ -156,23 +154,23 @@ class RatePassenger extends StatelessWidget {
Widget _buildWalletSection(BuildContext context, RateController controller) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: Colors.grey.shade200),
border: Border.all(color: Theme.of(context).dividerColor),
),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: controller.ispassengerWantWalletFromDriver
? _buildAmountInput(controller)
: _buildWalletQuery(controller),
? _buildAmountInput(context, controller)
: _buildWalletQuery(context, controller),
),
),
);
}
Widget _buildWalletQuery(RateController controller) {
Widget _buildWalletQuery(BuildContext context, RateController controller) {
return Column(
key: const ValueKey('walletQuery'),
children: [
@@ -181,11 +179,11 @@ class RatePassenger extends StatelessWidget {
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.orange.withOpacity(0.1),
color: Colors.amber.withOpacity(0.1),
shape: BoxShape.circle,
),
child: const Icon(Icons.account_balance_wallet,
color: Colors.orange),
child:
const Icon(Icons.account_balance_wallet, color: Colors.amber),
),
const SizedBox(width: 15),
Expanded(
@@ -206,8 +204,8 @@ class RatePassenger extends StatelessWidget {
child: OutlinedButton(
onPressed: () {}, // Optional logic
style: OutlinedButton.styleFrom(
foregroundColor: Colors.grey,
side: BorderSide(color: Colors.grey.shade300),
foregroundColor: Theme.of(context).hintColor,
side: BorderSide(color: Theme.of(context).dividerColor),
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
@@ -235,14 +233,17 @@ class RatePassenger extends StatelessWidget {
);
}
Widget _buildAmountInput(RateController controller) {
Widget _buildAmountInput(BuildContext context, RateController controller) {
return Column(
key: const ValueKey('amountInput'),
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"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),
Form(
@@ -279,11 +280,12 @@ class RatePassenger extends StatelessWidget {
Text(
'Rate Passenger'.tr,
style: TextStyle(
color: Colors.grey[600],
color: Theme.of(context).hintColor,
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 12),
RatingBar.builder(
initialRating: 0,
@@ -306,19 +308,20 @@ class RatePassenger extends StatelessWidget {
TextField(
controller: controller.comment,
maxLines: 2,
style: Theme.of(context).textTheme.bodyLarge,
decoration: InputDecoration(
hintText: 'Any comments about the passenger?'.tr,
filled: true,
fillColor: Colors.white,
fillColor: Theme.of(context).cardColor,
contentPadding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
borderSide: BorderSide(color: Theme.of(context).dividerColor),
),
enabledBorder: OutlineInputBorder(
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:flutter/material.dart';
import 'package:get/get.dart';
import 'package:sefer_driver/views/widgets/elevated_btn.dart';
// import 'package:camera/camera.dart';
// import 'package:flutter/material.dart';
// import 'package:get/get.dart';
// import 'package:sefer_driver/views/widgets/elevated_btn.dart';
import '../../../../constant/colors.dart';
import '../../../../constant/style.dart';
import '../../../../controller/functions/camer_controller.dart';
import '../../../../controller/functions/ocr_controller.dart';
import '../../../widgets/my_scafold.dart';
// import '../../../../constant/colors.dart';
// import '../../../../constant/style.dart';
// import '../../../../controller/functions/camer_controller.dart';
// import '../../../../controller/functions/ocr_controller.dart';
// import '../../../widgets/my_scafold.dart';
class CameraLisencePage extends StatelessWidget {
CameraLisencePage.CameraLicensePage({super.key});
final CameraClassController cameraClassController =
Get.put(CameraClassController());
// class CameraLisencePage extends StatelessWidget {
// CameraLisencePage.CameraLicensePage({super.key});
// final CameraClassController cameraClassController =
// Get.put(CameraClassController());
@override
Widget build(BuildContext context) {
return MyScafolld(
title: 'Scan Driver License'.tr,
body: [
Column(children: [
Text(
'Please put your licence in these border'.tr,
style: AppStyle.title.copyWith(color: AppColor.greenColor),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
child: GetBuilder<CameraClassController>(
builder: (cameraClassController) =>
cameraClassController.isCameraInitialized
? Stack(
children: [
Container(
decoration: AppStyle.boxDecoration,
child: CameraPreview(
cameraClassController.cameraController,
),
),
Positioned(
top: Get.height * .1,
right: 5,
left: 5,
child: Container(
height: Get.width * 3 / 4,
width: Get.width * .9,
decoration: BoxDecoration(
// color: AppColor.blueColor,
border: Border.all(
color: AppColor.yellowColor, width: 2),
),
),
),
],
)
: Container(
decoration: AppStyle.boxDecoration,
height: Get.width * 3 / 4,
width: Get.width,
child: Center(
child: Text(
'Camera not initialized yet',
style: AppStyle.title,
),
),
),
),
),
const SizedBox(
height: 20,
),
MyElevatedButton(
title: 'Take Image'.tr,
onPressed: () {
ScanDocumentsByApi().scanDocumentsByApi();
},
)
]),
],
isleading: true,
);
}
}
// @override
// Widget build(BuildContext context) {
// return MyScafolld(
// title: 'Scan Driver License'.tr,
// body: [
// Column(children: [
// Text(
// 'Please put your licence in these border'.tr,
// style: AppStyle.title.copyWith(color: AppColor.greenColor),
// ),
// Padding(
// padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
// child: GetBuilder<CameraClassController>(
// builder: (cameraClassController) =>
// cameraClassController.isCameraInitialized
// ? Stack(
// children: [
// Container(
// decoration: AppStyle.boxDecoration,
// child: CameraPreview(
// cameraClassController.cameraController,
// ),
// ),
// Positioned(
// top: Get.height * .1,
// right: 5,
// left: 5,
// child: Container(
// height: Get.width * 3 / 4,
// width: Get.width * .9,
// decoration: BoxDecoration(
// // color: AppColor.blueColor,
// border: Border.all(
// color: AppColor.yellowColor, width: 2),
// ),
// ),
// ),
// ],
// )
// : Container(
// decoration: AppStyle.boxDecoration,
// height: Get.width * 3 / 4,
// width: Get.width,
// child: Center(
// child: Text(
// 'Camera not initialized yet',
// style: AppStyle.title,
// ),
// ),
// ),
// ),
// ),
// const SizedBox(
// height: 20,
// ),
// MyElevatedButton(
// title: 'Take Image'.tr,
// onPressed: () {
// ScanDocumentsByApi().scanDocumentsByApi();
// },
// )
// ]),
// ],
// isleading: true,
// );
// }
// }

View File

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

View File

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

View File

@@ -59,6 +59,14 @@ class SettingsCaptain extends StatelessWidget {
_buildSectionHeader('App Preferences'.tr, context),
_buildSettingsCard(
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(
icon: Icons.map_outlined,
color: AppColor.redColor,
@@ -68,6 +76,15 @@ class SettingsCaptain extends StatelessWidget {
valueGetter: (ctrl) => (ctrl).isGoogleMapsEnabled,
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(
icon: Icons.vibration,
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:get/get.dart';
@@ -9,208 +6,171 @@ import '../../../controller/home/captin/help/assurance_controller.dart';
import 'package:flutter/cupertino.dart';
import '../../widgets/my_scafold.dart';
class AssuranceHealthPage extends StatelessWidget {
AssuranceHealthPage({super.key});
AssuranceHealthController assuranceHealthController =
final AssuranceHealthController assuranceHealthController =
Get.put(AssuranceHealthController());
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text("Health Insurance".tr),
),
child: SafeArea(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: GetBuilder<AssuranceHealthController>(
builder: (assuranceHealthController) {
return Column(
children: [
Text(
"When you complete 500 trips, you will be eligible for exclusive health insurance offers."
.tr,
textAlign: TextAlign.center,
style: CupertinoTheme.of(context).textTheme.navTitleTextStyle,
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
CupertinoButton.filled(
child: Text("Show My Trip Count".tr),
onPressed: () async {
assuranceHealthController.getTripCountByCaptain();
},
final theme = Theme.of(context);
return MyScafolld(
title: "Health Insurance".tr,
isleading: true,
body: [
GetBuilder<AssuranceHealthController>(
builder: (controller) {
return SingleChildScrollView(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
"When you complete 500 trips, you will be eligible for exclusive health insurance offers."
.tr,
textAlign: TextAlign.center,
style: theme.textTheme.titleMedium?.copyWith(
color:
theme.textTheme.bodyMedium?.color?.withOpacity(0.8),
height: 1.5,
),
_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(
padding: const EdgeInsets.all(14),
const SizedBox(height: 24),
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(
"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,
style: CupertinoTheme.of(context)
.textTheme
.textStyle
.copyWith(fontWeight: FontWeight.w600),
style: theme.textTheme.bodyLarge
?.copyWith(fontWeight: FontWeight.w500),
textAlign: TextAlign.center,
),
),
),
const SizedBox(height: 10),
CupertinoButton.filled(
disabledColor: AppColor.blueColor,
padding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 15),
borderRadius: BorderRadius.circular(12),
child: Text(
"Would you like to proceed with health insurance?".tr,
style: const TextStyle(
fontSize: 16, fontWeight: FontWeight.bold),
const SizedBox(height: 32),
ElevatedButton(
onPressed: () => _showInsuranceDialog(context, controller),
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.primaryColor,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
),
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) {
return Container(
width: 80,
height: 80,
width: 70,
height: 70,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: const RadialGradient(
colors: [
Color(0xFF42A5F5),
Color(0xFF1976D2),
], // Health theme colors
center: Alignment.center,
radius: 0.8,
gradient: const LinearGradient(
colors: [Color(0xFF42A5F5), Color(0xFF1976D2)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
boxShadow: [
BoxShadow(
color: CupertinoColors.black.withOpacity(0.2),
color: Colors.black.withOpacity(0.1),
blurRadius: 8,
offset: const Offset(0, 4),
),
@@ -220,9 +180,9 @@ class AssuranceHealthPage extends StatelessWidget {
child: Text(
count,
style: const TextStyle(
fontSize: 22,
fontSize: 20,
fontWeight: FontWeight.bold,
color: CupertinoColors.white,
color: Colors.white,
),
),
),

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,8 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import '../../../../constant/finance_design_system.dart';
import '../../../../controller/auth/captin/history_captain.dart';
import 'package:sefer_driver/views/widgets/mydialoug.dart';
@@ -13,74 +14,159 @@ class HistoryCaptain extends StatelessWidget {
Get.put(HistoryCaptainController());
return Scaffold(
backgroundColor: Colors.grey[100], // A softer background color
appBar: AppBar(
title: Text('Ride History'.tr),
backgroundColor: Colors.white,
foregroundColor: Colors.black,
elevation: 1,
),
backgroundColor: Colors.white,
body: GetBuilder<HistoryCaptainController>(
builder: (controller) {
if (controller.isloading) {
return const Center(child: CircularProgressIndicator());
}
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]),
),
],
),
return const Center(
child: CircularProgressIndicator(
color: FinanceDesignSystem.primaryDark),
);
}
// 动画: Wrap ListView with AnimationLimiter for staggered animations
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];
final List trips = controller.historyData['message'] ?? [];
// 动画: Apply animation to each list item
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 375),
child: SlideAnimation(
verticalOffset: 50.0,
child: FadeInAnimation(
child: _AnimatedHistoryCard(
trip: trip,
onTap: () {
// Your original logic is preserved here
if (trip['status'] != 'Cancel') {
controller.getHistoryDetails(trip['order_id']);
} else {
MyDialog().getDialog(
'This Trip Was Cancelled'.tr,
'This Trip Was Cancelled'.tr,
() => Get.back(),
);
}
},
),
return CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: [
// 1. Custom App Bar
SliverAppBar(
expandedHeight: 180,
pinned: true,
stretch: true,
backgroundColor: FinanceDesignSystem.primaryDark,
flexibleSpace: FlexibleSpaceBar(
centerTitle: true,
title: Text(
'Ride History'.tr,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
);
},
),
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 _AnimatedHistoryCard extends StatefulWidget {
final Map<String, dynamic> trip;
class _TripHistoryCard extends StatelessWidget {
final Map trip;
final VoidCallback onTap;
const _AnimatedHistoryCard({required this.trip, required this.onTap});
const _TripHistoryCard({required this.trip, required this.onTap});
@override
__AnimatedHistoryCardState createState() => __AnimatedHistoryCardState();
}
class __AnimatedHistoryCardState extends State<_AnimatedHistoryCard>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scaleAnimation;
@override
void initState() {
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();
String _formatDateToSyria(String? dateStr) {
if (dateStr == null) return '';
try {
// Parse GMT date
DateTime date = DateTime.parse(dateStr);
// Add 3 hours for Syria (GMT+3)
DateTime syriaDate = date.add(const Duration(hours: 3));
// Format to readable string
return DateFormat('yyyy-MM-dd HH:mm').format(syriaDate);
} catch (e) {
return dateStr;
}
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTapDown: _onTapDown,
onTapUp: _onTapUp,
onTapCancel: _onTapCancel,
child: ScaleTransition(
scale: _scaleAnimation,
child: Card(
elevation: 4,
shadowColor: Colors.black.withOpacity(0.1),
margin: const EdgeInsets.only(bottom: 16.0),
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
return Container(
margin: const EdgeInsets.only(bottom: 20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(FinanceDesignSystem.cardRadius),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.04),
blurRadius: 15,
offset: const Offset(0, 8),
),
],
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(FinanceDesignSystem.cardRadius),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
padding: const EdgeInsets.all(20),
child: Column(
children: [
Icon(Icons.receipt_long,
color: Theme.of(context).primaryColor, size: 40),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${'OrderId'.tr}: ${widget.trip['order_id']}',
style:
Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
widget.trip['created_at'],
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Colors.grey[600],
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: FinanceDesignSystem.primaryDark
.withOpacity(0.05),
borderRadius: BorderRadius.circular(12),
),
child: const Icon(
Icons.receipt_long_rounded,
color: FinanceDesignSystem.primaryDark,
size: 20,
),
),
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) {
Color chipColor;
Color textColor;
String statusText = status;
IconData iconData;
Widget _buildStatusChip(String? status) {
Color color;
IconData icon;
String label = status ?? 'Unknown';
List<Color> gradientColors;
switch (status) {
case 'Apply':
chipColor = Colors.green.shade50;
textColor = Colors.green.shade800;
iconData = Icons.check_circle;
break;
case 'Refused':
chipColor = Colors.red.shade50;
textColor = Colors.red.shade800;
iconData = Icons.cancel;
break;
case 'Cancel':
chipColor = Colors.orange.shade50;
textColor = Colors.orange.shade800;
iconData = Icons.info;
statusText = 'Cancelled';
break;
default:
chipColor = Colors.grey.shade200;
textColor = Colors.grey.shade800;
iconData = Icons.hourglass_empty;
switch (status) {
case 'Apply':
color = FinanceDesignSystem.successGreen;
icon = Icons.check_circle_rounded;
label = 'Completed'.tr;
gradientColors = [color.withOpacity(0.2), color.withOpacity(0.05)];
break;
case 'Refused':
color = FinanceDesignSystem.dangerRed;
icon = Icons.cancel_rounded;
label = 'Refused'.tr;
gradientColors = [color.withOpacity(0.2), color.withOpacity(0.05)];
break;
case 'Cancel':
color = Colors.orange;
icon = Icons.info_rounded;
label = 'Cancelled'.tr;
gradientColors = [color.withOpacity(0.2), color.withOpacity(0.05)];
break;
case 'CancelByPassenger':
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: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:sefer_driver/controller/auth/captin/history_captain.dart';
@@ -168,7 +169,7 @@ class _HistoryDetailsPageState extends State<HistoryDetailsPage> {
return Card(
elevation: 4,
shadowColor: Colors.black.withOpacity(0.1),
shadowColor: Colors.black.withValues(alpha: 0.1),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
clipBehavior:
Clip.antiAlias, // Ensures the map respects the border radius
@@ -176,7 +177,8 @@ class _HistoryDetailsPageState extends State<HistoryDetailsPage> {
children: [
SizedBox(
height: 250,
child: GoogleMap(
child: IntaleqMap(
apiKey: AK.mapAPIKEY,
initialCameraPosition: initialCameraPosition,
markers: markers,
polylines: {
@@ -188,7 +190,7 @@ class _HistoryDetailsPageState extends State<HistoryDetailsPage> {
width: 5,
),
},
onMapCreated: (GoogleMapController mapController) {
onMapCreated: (IntaleqMapController mapController) {
// Animate camera to fit the route
if (startLocation != null && endLocation != null) {
LatLngBounds bounds = LatLngBounds(
@@ -210,7 +212,7 @@ class _HistoryDetailsPageState extends State<HistoryDetailsPage> {
),
);
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) {
return Card(
elevation: 2,
shadowColor: Colors.black.withOpacity(0.05),
shadowColor: Colors.black.withValues(alpha: 0.05),
margin: const EdgeInsets.only(bottom: 16.0),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
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 '../../../../constant/colors.dart';
import '../../../../controller/functions/encrypt_decrypt.dart';
import '../../../widgets/my_scafold.dart';
class HelpCaptain extends StatelessWidget {
HelpCaptain({super.key});
@@ -13,162 +15,158 @@ class HelpCaptain extends StatelessWidget {
@override
Widget build(BuildContext context) {
Get.put(HelpController());
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text(
'Helping Page'.tr,
style: const TextStyle(fontWeight: FontWeight.bold),
),
leading: CupertinoButton(
padding: EdgeInsets.zero,
onPressed: () => Navigator.pop(context),
child: const Icon(CupertinoIcons.back),
),
),
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
final theme = Theme.of(context);
return MyScafolld(
title: 'Helping Page'.tr,
isleading: true,
body: [
SingleChildScrollView(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(
padding:
const EdgeInsets.all(18.0), // Slightly increased padding
padding: const EdgeInsets.all(18.0),
decoration: BoxDecoration(
color:
CupertinoTheme.of(context).brightness == Brightness.light
? CupertinoColors.systemGrey6
: CupertinoColors.darkBackgroundGray,
borderRadius:
BorderRadius.circular(15.0), // More rounded corners
color: theme.colorScheme.surfaceVariant.withOpacity(0.5),
borderRadius: BorderRadius.circular(15.0),
border: Border.all(color: theme.dividerColor),
),
child: Text(
'If you need any help or have questions, this is the right place to do that. You are welcome!'
.tr,
style:
CupertinoTheme.of(context).textTheme.textStyle.copyWith(
fontSize: 16, // Slightly larger font size
color: CupertinoColors.label.resolveFrom(
context), // Ensure text color adapts to theme
),
style: theme.textTheme.bodyLarge,
textAlign: TextAlign.center,
),
),
const SizedBox(height: 20),
CupertinoFormSection.insetGrouped(
// Using CupertinoFormSection for better styling
header: Text('Submit Your Question'.tr),
margin: EdgeInsets.zero,
children: [
GetBuilder<HelpController>(
builder: (helpController) => Form(
key: helpController.formKey,
child: CupertinoTextFormFieldRow(
const SizedBox(height: 24),
Text(
'Submit Your Question'.tr,
style: theme.textTheme.titleMedium
?.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
GetBuilder<HelpController>(
builder: (helpController) => Form(
key: helpController.formKey,
child: Column(
children: [
TextFormField(
controller: helpController.helpQuestionController,
placeholder: 'Enter your Question here'.tr,
autovalidateMode: AutovalidateMode.onUserInteraction,
decoration: InputDecoration(
hintText: 'Enter your Question here'.tr,
prefixIcon:
const Icon(Icons.question_answer_outlined),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12)),
),
maxLines: 3,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter your question'.tr;
}
return null;
},
prefix: const Icon(CupertinoIcons
.question_circle), // Added a prefix icon
),
),
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0, vertical: 10.0),
child: GetBuilder<HelpController>(
builder: (helpController) => helpController.isLoading
? const CupertinoActivityIndicator()
: CupertinoButton.filled(
onPressed: () {
if (helpController.formKey.currentState!
.validate()) {
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());
const SizedBox(height: 16),
helpController.isLoading
? const Center(child: CircularProgressIndicator())
: SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
if (helpController.formKey.currentState!
.validate()) {
helpController.addHelpQuestion();
helpController.helpQuestionController
.clear();
}
},
);
},
)
: [
Center(
child: Text('No questions asked yet.'.tr),
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.primaryColor,
foregroundColor: Colors.white,
padding:
const EdgeInsets.symmetric(vertical: 14),
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 {
// HelpCaptain({super.key});

View File

@@ -19,65 +19,90 @@ class HelpDetailsReplayPage extends StatelessWidget {
body: [
helpController.isLoading
? const MyCircularProgressIndicator()
: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Card(
elevation: 3,
child: Container(
width: Get.width * .66,
color: Colors.transparent,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
helpController.qustion,
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,
),
),
: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
// Question Bubble (Aligned to Start/End based on locale, usually start for sender)
Align(
alignment: AlignmentDirectional.centerStart,
child: Container(
constraints: BoxConstraints(maxWidth: Get.width * 0.75),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primaryContainer,
borderRadius: const BorderRadius.only(
topRight: Radius.circular(16),
bottomLeft: Radius.circular(16),
bottomRight: Radius.circular(16),
),
),
]),
],
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,
));

File diff suppressed because it is too large Load Diff

View File

@@ -1,83 +1,50 @@
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.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/home/captin/home_captain_controller.dart';
// هذه ويدجت بديلة للـ _MapView في الكود الخاص بك
// V3 - MapView Replacement using flutter_map
class OsmMapView extends StatelessWidget {
const OsmMapView({super.key});
@override
Widget build(BuildContext context) {
// افترض أنك تحصل على الموقع والاتجاه بنفس الطريقة من الكونترولر
final LocationController locationController =
Get.find<LocationController>();
final HomeCaptainController homeCaptainController =
Get.find<HomeCaptainController>();
// يمكنك استخدام GetBuilder لمراقبة التغييرات في الموقع
return Obx(() {
final LatLng currentLocation = LatLng(
locationController.myLocation.latitude,
locationController.myLocation.longitude);
final double currentHeading = locationController.heading;
return FlutterMap(
// يمكنك ربط هذا بـ MapController الخاص بـ flutter_map
// mapController: homeCaptainController.flutterMapController,
options: MapOptions(
initialCenter: currentLocation,
initialZoom: 15,
maxZoom: 18,
minZoom: 6,
// تدوير الخريطة (اختياري)
initialRotation: currentHeading,
return IntaleqMap(
apiKey: AK.mapSaasKey,
initialCameraPosition: CameraPosition(
target: currentLocation,
zoom: 15,
bearing: currentHeading,
),
children: [
// 1. طبقة الخريطة الأساسية (Tiles)
// هذا هو الرابط لخرائط OSM الأساسية
TileLayer(
urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
subdomains: ['a', 'b', 'c'],
userAgentPackageName:
'com.example.app', // استبدل باسم الباكج الخاص بك
// لاستخدام الخرائط الأوفلاين (بعد إعداد flutter_map_tile_caching)
// tileProvider: CachedTileProvider(),
),
// 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)
],
markers: {
Marker(
markerId: const MarkerId('myLocation'),
position: currentLocation,
rotation: currentHeading,
icon: homeCaptainController.carIcon,
anchor: const Offset(0.5, 0.5),
flat: true,
zIndex: 2,
)
},
onMapCreated: (IntaleqMapController controller) {
// You can assign this controller if needed
},
);
});
}
}
// ملاحظة: ستحتاج إلى تعديل بسيط في 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 '../../../../../constant/style.dart';
import '../../../../../controller/functions/encrypt_decrypt.dart';
import '../../../../widgets/elevated_btn.dart';
import '../../../../../controller/home/captin/home_captain_controller.dart';
@@ -91,45 +90,56 @@ class ConnectWidget extends StatelessWidget {
: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: homeCaptainController.isActive
? [Colors.green.shade400, Colors.green.shade700]
: [Colors.grey.shade400, Colors.grey.shade700],
? [const Color(0xFF00C853), const Color(0xFF00E676)]
: [Colors.grey.shade600, Colors.grey.shade400],
),
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: homeCaptainController.isActive
? Colors.green.withOpacity(0.3)
: Colors.grey.withOpacity(0.3),
spreadRadius: 1,
blurRadius: 8,
offset: const Offset(0, 2),
color: (homeCaptainController.isActive
? const Color(0xFF00C853)
: Colors.grey)
.withOpacity(0.4),
spreadRadius: 0,
blurRadius: 15,
offset: const Offset(0, 5),
),
],
),
child: CupertinoButton(
onPressed: homeCaptainController.onButtonSelected,
padding: const EdgeInsets.symmetric(
horizontal: 24, vertical: 12),
horizontal: 20, vertical: 10),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
homeCaptainController.isActive
? CupertinoIcons.check_mark_circled_solid
: CupertinoIcons.circle,
color: Colors.white,
size: 24,
Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
shape: BoxShape.circle,
),
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(
homeCaptainController.isActive
? 'Connected'.tr
: 'Not Connected'.tr,
? 'Online'.tr
: 'Offline'.tr,
style: const TextStyle(
color: Colors.white,
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/controller/firebase/local_notification.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/orderCaptin/vip_order_page.dart';
import 'package:flutter/material.dart';
@@ -13,7 +12,6 @@ import 'package:sefer_driver/views/widgets/mydialoug.dart';
import '../../../../../constant/colors.dart';
import '../../../../../constant/links.dart';
import '../../../../../controller/firebase/firbase_messge.dart';
import '../../../../../controller/firebase/notification_service.dart';
import '../../../../../controller/functions/crud.dart';
import '../../../../../controller/home/captin/order_request_controller.dart';
@@ -21,319 +19,279 @@ import '../../../../../controller/home/navigation/navigation_view.dart';
import '../../../../../print.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() {
final firebaseMessagesController =
Get.isRegistered<FirebaseMessagesController>()
? Get.find<FirebaseMessagesController>()
: Get.put(FirebaseMessagesController());
return GetBuilder<HomeCaptainController>(
builder: (controller) => Positioned(
bottom: Get.height * .2,
left: 6,
child: Column(
children: [
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 {
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,
builder: (ctrl) => Positioned(
// Place just above the bottom status bar
bottom: 100,
left: 10,
child: Builder(builder: (context) {
return Container(
decoration: BoxDecoration(
color: _T.surface(context),
borderRadius: BorderRadius.circular(_T.radius),
border: Border.all(color: _T.border(context)),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.3),
blurRadius: 10,
offset: const Offset(2, 4),
),
),
],
),
const SizedBox(
height: 5,
),
// Platform.isAndroid
// ?
int.parse(box.read(BoxName.carYear).toString()) > 2023
? 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 {
// mySnakeBarError('ad');
Get.to(() => const VipOrderPage());
},
icon: const Icon(
Octicons.watch,
size: 29,
color: AppColor.blueColor,
),
padding: const EdgeInsets.symmetric(vertical: 6),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// ── 1. Active Ride shortcut ──────────
_MenuIcon(
icon: Icons.directions_car_rounded,
color:
box.read(BoxName.rideArgumentsFromBackground) == 'failure'
? Colors.red.shade400
: Colors.green.shade400,
tooltip: 'Active Ride'.tr,
onTap: () async {
await checkForPendingOrderFromServer();
if (box.read(BoxName.rideArgumentsFromBackground) != 'failure') {
Get.to(
() => PassengerLocationMapPage(),
arguments: box.read(BoxName.rideArgumentsFromBackground),
);
}),
)
: const SizedBox(),
// 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: Builder(builder: (context) {
return IconButton(
onPressed: () async {
box.remove(BoxName.agreeTerms);
Get.to(() => const NavigationView());
// box.write(BoxName.statusDriverLocation, 'off');
} else {
MyDialog().getDialog(
'Ride info'.tr,
'you dont have accepted ride'.tr,
() => Get.back(),
);
}
},
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(
height: 5,
_Divider(context),
// ── 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 {
bool _isProcessingOrder = false;
if (_isProcessingOrder) return;
bool isProcessing = false;
if (isProcessing) return;
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 {
// You need to create this CRUD method
var response = await CRUD().post(
link: AppLink.getArgumentAfterAppliedFromBackground,
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') {
final Map<String, dynamic> orderInfoFromServer = response['message'];
final Map<String, dynamic> rideArguments =
_transformServerDataToAppArguments(orderInfoFromServer);
// 2. Build the new arguments map, matching your Flutter structure
final Map<String, dynamic> orderInfo = response['message'];
final Map<String, dynamic> rideArgs =
_transformServerDataToAppArguments(orderInfo);
/////////////
final customerToken = (response)['message']['token_passenger'];
final orderId = (response)['message']['ride_id'].toString();
box.write(BoxName.rideArgumentsFromBackground, rideArguments);
final customerToken = response['message']['token_passenger'];
final orderId = response['message']['ride_id'].toString();
box.write(BoxName.rideArgumentsFromBackground, rideArgs);
box.write(BoxName.statusDriverLocation, 'on');
box.write(BoxName.rideStatus, 'Apply');
Get.put(OrderRequestController()).changeApplied();
// MyDialog().getDialog(orderId.toString(), customerToken, () {});
// Now proceed with the UI flow
// _sendAcceptanceNotification(customerToken, orderId.toString());
// await _bringAppToForegroundAndNavigate(orderId);
Get.to(() => PassengerLocationMapPage(),
arguments: box.read(BoxName.rideArgumentsFromBackground));
Get.to(
() => PassengerLocationMapPage(),
arguments: box.read(BoxName.rideArgumentsFromBackground),
);
} else {
box.write(BoxName.rideArgumentsFromBackground, 'failure');
}
} catch (e) {
} catch (_) {
// silent
} finally {
_isProcessingOrder = false; // Release lock
isProcessing = false;
}
}
Map<String, dynamic> _transformServerDataToAppArguments(
Map<String, dynamic> serverData) {
// Helper function to safely get and convert values to String
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;
}
Map<String, dynamic> d) {
String s(String key, [String def = 'unknown']) => d[key]?.toString() ?? def;
return {
'passengerLocation': _getString('passenger_location'),
'passengerDestination': _getString('passenger_destination'),
'Duration': _getString('duration'),
'totalCost': _getString('total_cost'),
'Distance': _getString('distance'),
'name': _getString('name'),
'phone': _getString('phone'),
'email': _getString('email'),
'tokenPassenger': _getString('token_passenger'),
'direction': _getString('direction_url'),
'DurationToPassenger': _getString('duration_to_passenger'),
'rideId': _getString('ride_id'),
'passengerId': _getString('passenger_id'),
'driverId': _getString('driver_id'),
'durationOfRideValue': _getString('duration_of_ride'),
'paymentAmount': _getString('payment_amount'),
'paymentMethod': _getString('payment_method'),
'passengerWalletBurc': _getString('passenger_wallet_burc'),
'timeOfOrder': _getString('time_of_order'),
'totalPassenger': _getString('total_passenger'),
'carType': _getString('car_type'),
'kazan': _getString('kazan'),
'startNameLocation': _getString('start_name_location'),
'endNameLocation': _getString('end_name_location'),
// --- Special Handling ---
// Steps (handle null values by providing an empty string)
'step0': _getString('step0'),
'step1': _getString('step1'),
'step2': _getString('step2'),
'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
'passengerLocation': s('passenger_location'),
'passengerDestination': s('passenger_destination'),
'Duration': s('duration'),
'totalCost': s('total_cost'),
'Distance': s('distance'),
'name': s('name'),
'phone': s('phone'),
'email': s('email'),
'tokenPassenger': s('token_passenger'),
'direction': s('direction_url'),
'DurationToPassenger': s('duration_to_passenger'),
'rideId': s('ride_id'),
'passengerId': s('passenger_id'),
'driverId': s('driver_id'),
'durationOfRideValue': s('duration_of_ride'),
'paymentAmount': s('payment_amount'),
'paymentMethod': s('payment_method'),
'passengerWalletBurc': s('passenger_wallet_burc'),
'timeOfOrder': s('time_of_order'),
'totalPassenger': s('total_passenger'),
'carType': s('car_type'),
'kazan': s('kazan'),
'startNameLocation': s('start_name_location'),
'endNameLocation': s('end_name_location'),
'step0': s('step0'),
'step1': s('step1'),
'step2': s('step2'),
'step3': s('step3'),
'step4': s('step4'),
'WalletChecked': (d['wallet_checked'] == 1).toString(),
'isHaveSteps': (d['has_steps'] == 1) ? 'startEnd' : 'noSteps',
};
}
void _sendAcceptanceNotification(String? customerToken, rideId) {
try {
if (customerToken == null) return;
void _sendAcceptanceNotification(String? customerToken, dynamic rideId) {
if (customerToken == null || customerToken.isEmpty) return;
List<String> bodyToPassenger = [
box.read(BoxName.driverID).toString(),
box.read(BoxName.nameDriver).toString(),
box.read(BoxName.tokenDriver).toString(),
rideId.toString()
];
List<String> body = [
box.read(BoxName.driverID).toString(),
box.read(BoxName.nameDriver).toString(),
box.read(BoxName.tokenDriver).toString(),
rideId.toString(),
];
// Safely check for customer token
final String? token = customerToken;
if (token != null && token.isNotEmpty) {
NotificationService.sendNotification(
target: token.toString(),
title: 'Accepted Ride'.tr,
body: 'your ride is Accepted'.tr,
isTopic: false, // Important: this is a token
tone: 'start',
driverList: bodyToPassenger, category: 'Accepted Ride',
);
} else {}
} catch (e) {}
NotificationService.sendNotification(
target: customerToken,
title: 'Accepted Ride'.tr,
body: 'your ride is Accepted'.tr,
isTopic: false,
tone: 'start',
driverList: body,
category: 'Accepted Ride',
);
}

View File

@@ -2,7 +2,7 @@ import 'dart:convert';
import 'dart:math';
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 {
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."
.tr,
style: Theme.of(context).textTheme.titleMedium!.copyWith(
color: Colors.grey[700],
color: Theme.of(context).textTheme.bodyMedium?.color?.withOpacity(0.8),
height: 1.4,
),
),
const SizedBox(height: 20),
// Trip Count Section in a Card
@@ -192,10 +193,11 @@ class MaintainCenterPage extends StatelessWidget {
.textTheme
.bodyMedium!
.copyWith(
color: Colors.grey[800],
color: Theme.of(context).textTheme.bodyMedium?.color?.withOpacity(0.9),
height: 1.5,
),
),
],
),
),

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.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/home/captin/map_driver_controller.dart';
@@ -21,76 +22,24 @@ class GoogleDriverMap extends StatelessWidget {
final double mapPaddingBottom = MediaQuery.of(context).size.height * 0.3;
return GetBuilder<MapDriverController>(
builder: (controller) => GoogleMap(
builder: (controller) => IntaleqMap(
apiKey: AK.mapAPIKEY,
onMapCreated: (mapController) {
controller.onMapCreated(mapController);
// New: تطبيق الـ padding بعد إنشاء الخريطة مباشرة
mapController.setMapStyle('[]'); // يمكنك إضافة تصميم مخصص للخريطة هنا
// يمكنك استخدام CameraUpdate.scrollBy لتحريك الكاميرا إذا رغبت بذلك:
// controller.mapController!.animateCamera(CameraUpdate.scrollBy(0, mapPaddingBottom));
},
// New: إضافة padding لتحريك مركز الخريطة للأعلى، مما يجعل أيقونة السائق تظهر في الأسفل
zoomControlsEnabled: false, // Changed: تم إخفاء أزرار الزوم الافتراضية
zoomControlsEnabled: false,
initialCameraPosition: CameraPosition(
target: locationController.myLocation,
zoom: 17,
bearing: locationController.heading, // استخدام اتجاه السائق
tilt: 60, // زاوية ميل
bearing: locationController.heading,
tilt: 60,
),
onCameraMove: (position) {
CameraPosition(
target: locationController.myLocation,
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: تم الاعتماد على ماركر مخصص
// padding: EdgeInsets.only(bottom: 50, top: Get.height * 0.7),
// minMaxZoomPreference: const MinMaxZoomPreference(8, 18),
myLocationEnabled: false,
myLocationButtonEnabled: true,
compassEnabled: true,
mapType: MapType.terrain,
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,
),
},
polylines: controller.polyLines.toSet(),
markers: {
Marker(
markerId: MarkerId('MyLocation'.tr),

View File

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

View File

@@ -1,11 +1,11 @@
import 'dart:ui' as ui;
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';
class MarkerGenerator {
// دالة لرسم ماركر يحتوي على نص (للوقت والمسافة)
static Future<BitmapDescriptor> createCustomMarkerBitmap({
static Future<InlqBitmap> createCustomMarkerBitmap({
required String title,
required String subtitle,
required Color color,
@@ -19,15 +19,15 @@ class MarkerGenerator {
const double height = 110.0;
const double circleRadius = 25.0;
// 1. رسم المربع (Info Box)
// 1. رسم المربع (Info Box) مع تدرج لوني بسيط
final Paint paint = Paint()..color = color;
final RRect rRect = RRect.fromRectAndRadius(
const Rect.fromLTWH(0, 0, width, 60),
const Radius.circular(15),
const Rect.fromLTWH(0, 0, width, 65),
const Radius.circular(20), // زوايا أكثر استدارة لشكل عصري
);
// ظل خفيف
canvas.drawShadow(Path()..addRRect(rRect), Colors.black, 5.0, true);
// ظل أقوى لمظهر بارز (Premium Feel)
canvas.drawShadow(Path()..addRRect(rRect), Colors.black54, 10.0, true);
canvas.drawRRect(rRect, paint);
// 2. رسم مثلث صغير أسفل المربع (Arrow Tail)
@@ -96,11 +96,11 @@ class MarkerGenerator {
);
final ByteData? data =
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 Canvas canvas = Canvas(pictureRecorder);
const double size = 100.0;
@@ -137,6 +137,6 @@ class MarkerGenerator {
.toImage(size.toInt(), size.toInt());
final ByteData? data =
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: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/controller/home/captin/order_request_controller.dart';
@@ -63,20 +64,21 @@ class OrderRequestPage extends StatelessWidget {
// 1. الخارطة
Positioned.fill(
bottom: 300,
child: GoogleMap(
mapType: MapType.normal,
child: IntaleqMap(
apiKey: AK.mapAPIKEY,
initialCameraPosition: CameraPosition(
target: LatLng(
controller.latPassenger, controller.lngPassenger),
zoom: 13.0,
),
markers: controller.markers,
mapType: Get.isDarkMode
? IntaleqMapType.normal
: IntaleqMapType.light,
polylines: controller.polylines,
zoomControlsEnabled: false,
myLocationButtonEnabled: false,
compassEnabled: false,
padding: const EdgeInsets.only(
top: 80, bottom: 20, left: 20, right: 20),
onMapCreated: (c) {
controller.onMapCreated(c);
controller.update();
@@ -124,15 +126,16 @@ class OrderRequestPage extends StatelessWidget {
alignment: Alignment.bottomCenter,
child: Container(
height: 360,
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(25),
topRight: Radius.circular(25),
),
boxShadow: [
BoxShadow(
color: Colors.black12,
color:
Theme.of(context).shadowColor.withOpacity(0.1),
blurRadius: 20,
spreadRadius: 5)
],
@@ -146,8 +149,9 @@ class OrderRequestPage extends StatelessWidget {
width: 40,
height: 4,
decoration: BoxDecoration(
color: Colors.grey[300],
color: Theme.of(context).dividerColor,
borderRadius: BorderRadius.circular(2)))),
const SizedBox(height: 15),
// الصف الأول: الراكب والسعر
@@ -156,11 +160,14 @@ class OrderRequestPage extends StatelessWidget {
children: [
Row(
children: [
const CircleAvatar(
CircleAvatar(
radius: 24,
backgroundColor: Color(0xFFF5F5F5),
backgroundColor: Theme.of(context)
.colorScheme
.surfaceVariant,
child: Icon(Icons.person,
color: Colors.grey, size: 28),
color: Theme.of(context).hintColor,
size: 28),
),
const SizedBox(width: 10),
Column(
@@ -233,27 +240,45 @@ class OrderRequestPage extends StatelessWidget {
padding: const EdgeInsets.symmetric(
vertical: 10, horizontal: 10),
decoration: BoxDecoration(
color: const Color(0xFFF8F9FA),
color: Theme.of(context)
.colorScheme
.surfaceVariant
.withOpacity(0.5),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey.shade200),
border: Border.all(
color: Theme.of(context).dividerColor),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildInfoItem(
carIcon, carTypeLabel, carTypeColor),
context, carIcon, carTypeLabel, carTypeColor),
Container(
height: 20,
width: 1,
color: Colors.grey.shade300),
_buildInfoItem(Icons.route,
controller.totalTripDistance, Colors.black87),
color: Theme.of(context).dividerColor),
_buildInfoItem(
context,
Icons.route,
controller.totalTripDistance,
Theme.of(context)
.textTheme
.bodyLarge
?.color ??
Colors.black87),
Container(
height: 20,
width: 1,
color: Colors.grey.shade300),
_buildInfoItem(Icons.access_time_filled,
controller.totalTripDuration, Colors.black87),
color: Theme.of(context).dividerColor),
_buildInfoItem(
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(
mainAxisSize: MainAxisSize.min,
children: [

View File

@@ -2,7 +2,7 @@
// import 'package:flutter/material.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/constant/box_name.dart';

View File

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

View File

@@ -2,30 +2,35 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.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/views/widgets/mycircular.dart'; // Assuming this is your loading widget
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 '../../../controller/payment/driver_payment_controller.dart';
import 'widgets/transaction_preview_item.dart';
class PaymentHistoryDriverPage extends StatelessWidget {
const PaymentHistoryDriverPage({super.key});
@override
Widget build(BuildContext context) {
// Initialize your controller
Get.put(DriverWalletHistoryController());
return Scaffold(
backgroundColor: FinanceDesignSystem.backgroundColor,
appBar: AppBar(
title: Text('Payment History'.tr),
backgroundColor: Colors.white,
elevation: 1,
title: Text('Payment History'.tr,
style: const TextStyle(fontWeight: FontWeight.bold, 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(),
),
),
backgroundColor: Colors.grey[100],
body: GetBuilder<DriverWalletHistoryController>(
builder: (controller) {
if (controller.isLoading) {
// Using your custom loading indicator
return const Center(child: MyCircularProgressIndicator());
}
@@ -34,16 +39,10 @@ class PaymentHistoryDriverPage extends StatelessWidget {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.account_balance_wallet_outlined,
size: 80, color: Colors.grey[400]),
Icon(Icons.history_rounded, size: 80, color: Colors.grey.shade300),
const SizedBox(height: 16),
Text(
'No transactions yet'.tr,
style: Theme.of(context)
.textTheme
.headlineSmall
?.copyWith(color: Colors.grey[600]),
),
Text('No transactions yet'.tr,
style: TextStyle(color: Colors.grey.shade400, fontWeight: FontWeight.bold)),
],
),
);
@@ -51,18 +50,26 @@ class PaymentHistoryDriverPage extends StatelessWidget {
return AnimationLimiter(
child: ListView.builder(
padding: const EdgeInsets.all(16.0),
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
itemCount: controller.archive.length,
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(
position: index,
duration: const Duration(milliseconds: 375),
child: SlideAnimation(
verticalOffset: 50.0,
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 '../../../constant/box_name.dart';
import '../../../constant/finance_design_system.dart';
import '../../../constant/links.dart';
import '../../../controller/functions/crud.dart';
import '../../../controller/payment/mtn_new/mtn_payment_new_screen.dart';
@@ -21,8 +22,8 @@ import '../../widgets/my_textField.dart';
import 'ecash.dart';
class PointsCaptain extends StatelessWidget {
PaymentController paymentController = Get.put(PaymentController());
CaptainWalletController captainWalletController =
final PaymentController paymentController = Get.put(PaymentController());
final CaptainWalletController captainWalletController =
Get.put(CaptainWalletController());
PointsCaptain({
@@ -31,263 +32,67 @@ class PointsCaptain extends StatelessWidget {
required this.countPoint,
required this.pricePoint,
});
final Color kolor;
final String countPoint;
double pricePoint;
final double pricePoint;
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () async {
Get.defaultDialog(
title: 'Which method you will pay'.tr,
titleStyle: AppStyle.title,
content: Column(
crossAxisAlignment: CrossAxisAlignment.center,
return Container(
margin: const EdgeInsets.only(right: 12, bottom: 4),
child: Material(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
elevation: 4,
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,
children: [
Text(
'${'you can buy '.tr}$countPoint ${'L.S'.tr}${'by '.tr}${'$pricePoint'.tr}',
style: AppStyle.title,
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: kolor.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(Icons.account_balance_wallet_rounded,
color: kolor, size: 24),
),
// 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: [
const SizedBox(height: 10),
Text(
'$countPoint ${'L.S'.tr}',
style: AppStyle.subtitle
.copyWith(color: AppColor.secondaryColor),
'$countPoint ${'SYP'.tr}',
style: const TextStyle(
fontWeight: FontWeight.w900,
fontSize: 15,
color: FinanceDesignSystem.primaryDark,
),
),
const SizedBox(height: 4),
Text(
'$pricePoint ${'L.S'.tr}',
style:
AppStyle.title.copyWith(color: AppColor.secondaryColor),
textAlign: TextAlign.center,
'${'Price:'.tr} ${pricePoint.toStringAsFixed(0)} ${'SYP'.tr}',
style: TextStyle(
fontSize: 11,
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 {

View File

@@ -1,521 +1,370 @@
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:get/get.dart';
import 'package:sefer_driver/constant/box_name.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/constant/finance_design_system.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/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 '../../widgets/my_scafold.dart';
import 'card_wallet_widget.dart';
// Import new widgets
import 'points_captain.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 {
WalletCaptainRefactored({super.key});
final CaptainWalletController 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;
}
}
final CaptainWalletController controller = Get.put(CaptainWalletController());
@override
Widget build(BuildContext context) {
captainWalletController.refreshCaptainWallet();
return MyScafolld(
title: 'Driver Balance'.tr,
isleading: true,
action: IconButton(
icon: const Icon(Icons.refresh),
onPressed: () => captainWalletController.refreshCaptainWallet(),
tooltip: 'Refresh'.tr,
controller.refreshCaptainWallet();
return Scaffold(
backgroundColor: FinanceDesignSystem.backgroundColor,
appBar: AppBar(
title: Text('Driver Balance'.tr,
style: const TextStyle(
fontWeight: FontWeight.bold,
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: [
GetBuilder<CaptainWalletController>(
builder: (controller) {
if (controller.isLoading) {
return const MyCircularProgressIndicator();
}
return SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildTotalPointsSection(context, controller),
const SizedBox(height: 16),
const CardSeferWalletDriver(), // This can be redesigned if needed
const SizedBox(height: 16),
_buildWalletDetailsCard(context, controller),
const SizedBox(height: 24),
_buildPromoSection(controller),
const SizedBox(height: 24),
_buildNavigationButtons(),
],
),
);
},
)
],
);
}
/// القسم العلوي لعرض النقاط الإجمالية
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(),
body: GetBuilder<CaptainWalletController>(
builder: (controller) {
if (controller.isLoading) {
return const Center(child: MyCircularProgressIndicator());
}
return SingleChildScrollView(
padding: const EdgeInsets.symmetric(
horizontal: FinanceDesignSystem.horizontalPadding,
vertical: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// 1. Header / Balance
BalanceCard(
balance: controller.totalPoints.toString(),
isNegative:
double.tryParse(controller.totalPoints.toString()) !=
null &&
double.parse(controller.totalPoints.toString()) <
-30000,
lastUpdated: "Just now".tr,
),
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),
),
),
),
],
),
),
),
);
}
/// بطاقة لعرض تفاصيل المحفظة وخيارات الشراء
Widget _buildWalletDetailsCard(
void _showAddBalanceOptions(
BuildContext context, CaptainWalletController controller) {
return Card(
elevation: 4,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.all(16.0),
Get.bottomSheet(
Container(
padding: const EdgeInsets.all(24),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_BudgetInfoRow(
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(),
Text("Add Balance".tr, style: FinanceDesignSystem.headingStyle),
const SizedBox(height: 8),
_buildPointsOptions(),
],
),
),
);
}
/// قسم العروض الترويجية
Widget _buildPromoSection(CaptainWalletController controller) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
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),
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),
borderRadius: BorderRadius.circular(12)),
child: const Icon(Icons.account_balance_wallet_rounded,
color: FinanceDesignSystem.accentBlue),
),
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),
Container(
decoration: BoxDecoration(
color: AppColor.accentColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Text(
'$amount ${'S.P'.tr}',
style:
const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
),
],
),
),
);
}
}
/// ويدجت مُحسّن لعرض بطاقة العرض الترويجي
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,
const Divider(),
const SizedBox(height: 16),
Text("Recharge Balance Packages".tr,
style: FinanceDesignSystem.subHeadingStyle
.copyWith(fontWeight: FontWeight.bold)),
const SizedBox(height: 12),
SizedBox(
height: 140,
child: ListView(
scrollDirection: Axis.horizontal,
physics: const BouncingScrollPhysics(),
children: [
Text(title,
style:
AppStyle.title.copyWith(fontWeight: FontWeight.bold)),
Text(
'$count / $maxCount',
style: AppStyle.title.copyWith(color: AppColor.blueColor),
),
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: 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(
CaptainWalletController captainWalletController) {
return Get.defaultDialog(
@@ -528,15 +377,12 @@ Future<dynamic> addSyrianPaymentMethod(
"Insert your mobile wallet details to receive your money weekly"
.tr),
MyTextForm(
controller: captainWalletController
.cardBank, // Re-using for mobile number
controller: captainWalletController.cardBank,
label: "Insert mobile wallet number".tr,
hint: '0912 345 678',
type: TextInputType.phone),
const SizedBox(
height: 10,
),
MyDropDownSyria() // Dropdown for Syrian providers
const SizedBox(height: 10),
MyDropDownSyria()
],
)),
confirm: MyElevatedButton(
@@ -545,7 +391,6 @@ Future<dynamic> addSyrianPaymentMethod(
if (captainWalletController.formKeyAccount.currentState!
.validate()) {
Get.back();
// Replace with your actual API endpoint and payload for Syria
var res =
await CRUD().post(link: AppLink.updateAccountBank, payload: {
"paymentProvider":
@@ -554,7 +399,6 @@ Future<dynamic> addSyrianPaymentMethod(
captainWalletController.cardBank.text.toString(),
"id": box.read(BoxName.driverID).toString()
});
print('res: $res');
if (res != 'failure') {
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 {
String dropdownValue = 'syriatel';
void changeValue(String? newValue) {
if (newValue != null) {
dropdownValue = newValue;
@@ -574,33 +416,25 @@ class SyrianPayoutController extends GetxController {
}
}
// A new Dropdown widget for Syrian mobile wallet providers
class MyDropDownSyria extends StatelessWidget {
const MyDropDownSyria({super.key});
@override
Widget build(BuildContext context) {
Get.put(SyrianPayoutController());
final theme = Theme.of(context);
return GetBuilder<SyrianPayoutController>(builder: (controller) {
return DropdownButton<String>(
value: controller.dropdownValue,
icon: const Icon(Icons.arrow_drop_down),
elevation: 16,
isExpanded: true,
style: const TextStyle(color: Colors.deepPurple),
underline: Container(
height: 2,
color: Colors.deepPurpleAccent,
),
onChanged: (String? newValue) {
controller.changeValue(newValue);
},
dropdownColor: theme.cardColor,
style: TextStyle(color: theme.textTheme.bodyLarge?.color),
underline: Container(height: 2, color: theme.primaryColor),
onChanged: (String? newValue) => controller.changeValue(newValue),
items: <String>['syriatel', 'mtn']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value.tr),
);
return DropdownMenuItem<String>(value: value, child: Text(value.tr));
}).toList(),
);
});

View File

@@ -2,11 +2,12 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:sefer_driver/constant/colors.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 '../../../controller/payment/driver_payment_controller.dart';
import 'widgets/transaction_preview_item.dart';
class WeeklyPaymentPage extends StatelessWidget {
const WeeklyPaymentPage({super.key});
@@ -16,12 +17,18 @@ class WeeklyPaymentPage extends StatelessWidget {
Get.put(DriverWalletHistoryController());
return Scaffold(
backgroundColor: FinanceDesignSystem.backgroundColor,
appBar: AppBar(
title: Text('Weekly Summary'.tr),
backgroundColor: Colors.white,
elevation: 1,
title: Text('Weekly Summary'.tr,
style: const TextStyle(fontWeight: FontWeight.bold, 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(),
),
),
backgroundColor: Colors.grey[100],
body: GetBuilder<DriverWalletHistoryController>(
builder: (controller) {
if (controller.isLoading) {
@@ -30,45 +37,46 @@ class WeeklyPaymentPage extends StatelessWidget {
return Column(
children: [
// 1. Prominent Summary Card at the top
_buildSummaryCard(controller),
// 2. A title for the transactions list
_buildWeeklyStatsHeader(controller),
const SizedBox(height: 16),
_buildWeekSelector(),
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.fromLTRB(20, 20, 20, 10),
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Row(
children: [
Icon(Icons.list_alt, color: Colors.grey[600]),
const SizedBox(width: 8),
Text(
'Transactions this week'.tr,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
color: Colors.grey[700],
),
),
Text('Transactions this week'.tr, style: FinanceDesignSystem.headingStyle),
const Spacer(),
Icon(Icons.list_rounded, color: Colors.grey.shade400, size: 20),
],
),
),
// 3. The animated list of transactions
const SizedBox(height: 8),
Expanded(
child: controller.weeklyList.isEmpty
? _buildEmptyState(context)
: AnimationLimiter(
child: ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
padding: const EdgeInsets.fromLTRB(16, 8, 16, 24),
itemCount: controller.weeklyList.length,
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(
position: index,
duration: const Duration(milliseconds: 375),
duration: const Duration(milliseconds: 250),
child: SlideAnimation(
verticalOffset: 50.0,
verticalOffset: 25,
child: FadeInAnimation(
child: _TransactionListItem(
transaction: transaction),
child: TransactionPreviewItem(
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 _buildSummaryCard(DriverWalletHistoryController controller) {
final String totalAmount = controller.weeklyList.isEmpty
Widget _buildWeeklyStatsHeader(DriverWalletHistoryController controller) {
final totalAmount = controller.weeklyList.isEmpty
? '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(
margin: const EdgeInsets.all(16.0),
elevation: 4,
shadowColor: AppColor.primaryColor.withOpacity(0.2),
clipBehavior: Clip.antiAlias,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
child: Container(
padding: const EdgeInsets.all(20.0),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [AppColor.primaryColor, AppColor.greenColor],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
return Container(
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
gradient: FinanceDesignSystem.balanceGradient,
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: FinanceDesignSystem.primaryDark.withOpacity(0.3),
blurRadius: 20,
offset: const Offset(0, 8),
),
),
child: Row(
children: [
const Icon(Icons.account_balance_wallet,
color: Colors.white, size: 40),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Total Weekly Earnings'.tr,
style: AppStyle.title
.copyWith(color: Colors.white70, fontSize: 16),
),
const SizedBox(height: 4),
Text(
'$totalAmount ${'SYP'.tr}',
style: AppStyle.number
.copyWith(color: Colors.white, fontSize: 32),
),
],
),
),
],
),
],
),
child: Column(
children: [
Text('Total Weekly Earnings'.tr,
style: TextStyle(color: Colors.white.withOpacity(0.8), fontSize: 14)),
const SizedBox(height: 8),
Text('${earnings.toStringAsFixed(0)} SYP',
style: const TextStyle(color: Colors.white, fontSize: 36, fontWeight: FontWeight.bold)),
const SizedBox(height: 24),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_buildStatItem('Total Trips'.tr, trips.toString(), Icons.directions_car_rounded),
_buildStatItem('Commission'.tr, '${commission.toStringAsFixed(0)}', Icons.percent_rounded),
_buildStatItem('Net Profit'.tr, '${netProfit.toStringAsFixed(0)}', Icons.account_balance_rounded),
],
),
],
),
);
}
// A dedicated widget for the list item.
Widget _TransactionListItem({required Map<String, dynamic> transaction}) {
final String paymentMethod = transaction['paymentMethod'] ?? 'Unknown';
final String amount = transaction['amount']?.toString() ?? '0';
final DateTime? date = DateTime.tryParse(transaction['dateUpdated'] ?? '');
Widget _buildStatItem(String label, String value, IconData icon) {
return Column(
children: [
Container(
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(
elevation: 2,
shadowColor: Colors.black.withOpacity(0.05),
margin: const EdgeInsets.only(bottom: 12.0),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
child: ListTile(
leading: CircleAvatar(
backgroundColor: AppColor.primaryColor.withOpacity(0.1),
child: Icon(_getPaymentIcon(paymentMethod),
color: AppColor.primaryColor, size: 22),
Widget _buildWeekSelector() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.02), blurRadius: 10)],
),
title: Text(
'$amount ${'SYP'.tr}',
style: AppStyle.title.copyWith(fontWeight: FontWeight.bold),
),
subtitle: Text(
date != null
? DateFormat('EEEE, hh:mm a', Get.locale?.toString())
.format(date) // e.g., Tuesday, 10:11 AM
: 'Invalid Date',
style: AppStyle.title.copyWith(fontSize: 12, color: Colors.grey[600]),
),
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,
child: Row(
children: [
IconButton(icon: const Icon(Icons.chevron_left_rounded), onPressed: () {}),
Expanded(
child: Center(
child: Text('Dec 15 - Dec 21, 2024'.tr,
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14, color: FinanceDesignSystem.primaryDark)),
),
),
IconButton(icon: const Icon(Icons.chevron_right_rounded), onPressed: () {}),
],
),
),
);
@@ -179,45 +182,11 @@ class WeeklyPaymentPage extends StatelessWidget {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
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),
Text(
'No transactions this week'.tr,
style: Theme.of(context)
.textTheme
.headlineSmall
?.copyWith(color: Colors.grey[600]),
),
Text('No transactions this week'.tr, style: const TextStyle(fontWeight: FontWeight.bold, color: Colors.grey)),
],
),
);
}
// 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:get/get.dart';
import '../../../constant/colors.dart';
import '../../../controller/home/captin/behavior_controller.dart';
class BehaviorPage extends StatelessWidget {
@@ -12,6 +13,7 @@ class BehaviorPage extends StatelessWidget {
Widget build(BuildContext context) {
final controller = Get.put(DriverBehaviorController());
controller.fetchDriverBehavior();
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
@@ -37,20 +39,23 @@ class BehaviorPage extends StatelessWidget {
child: Column(
children: [
Text("Overall Behavior Score".tr,
style: TextStyle(
fontSize: 20, fontWeight: FontWeight.bold)),
style: theme.textTheme.titleLarge
?.copyWith(fontWeight: FontWeight.bold)),
const SizedBox(height: 10),
Text(
"${controller.overallScore.value.toStringAsFixed(1)} / 100",
style: const TextStyle(
fontSize: 28, color: Colors.blue)),
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: AppColor.primaryColor)),
],
),
),
),
const SizedBox(height: 20),
Text("Last 10 Trips".tr,
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
style: theme.textTheme.titleMedium
?.copyWith(fontWeight: FontWeight.bold)),
const SizedBox(height: 10),
ListView.builder(
shrinkWrap: true,
@@ -62,7 +67,7 @@ class BehaviorPage extends StatelessWidget {
elevation: 3,
child: ListTile(
leading: CircleAvatar(
backgroundColor: Colors.blue,
backgroundColor: AppColor.primaryColor,
child: Text("${index + 1}",
style: const TextStyle(color: Colors.white)),
),

View File

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

View File

@@ -99,29 +99,30 @@ class ProfileHeader extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Column(
children: [
CircleAvatar(
radius: 50,
backgroundColor: Get.theme.primaryColor.withOpacity(0.1),
child: Icon(Icons.person, size: 60, color: Get.theme.primaryColor),
backgroundColor: theme.primaryColor.withOpacity(0.1),
child: Icon(Icons.person, size: 60, color: theme.primaryColor),
),
const SizedBox(height: 12),
Text(
name,
style: Get.textTheme.headlineSmall
style: theme.textTheme.headlineSmall
?.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.star, color: Colors.amber, size: 20),
const Icon(Icons.star, color: Colors.amber, size: 20),
const SizedBox(width: 4),
Text(
'${rating.toStringAsFixed(1)} (${'reviews'.tr} $ratingCount)',
style: Get.textTheme.titleMedium
?.copyWith(color: Colors.grey.shade600),
style: theme.textTheme.titleMedium
?.copyWith(color: theme.hintColor),
),
],
),
@@ -130,6 +131,7 @@ class ProfileHeader extends StatelessWidget {
}
}
/// 2. ويدجت شبكة الأزرار
class ActionsGrid extends StatelessWidget {
const ActionsGrid({super.key});
@@ -187,20 +189,23 @@ void showShamCashInput() {
TextEditingController(text: existingCode);
Get.bottomSheet(
Container(
padding: const EdgeInsets.all(25),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(30)),
boxShadow: [
BoxShadow(
color: Colors.black26, blurRadius: 10, offset: Offset(0, -2))
],
),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
Builder(builder: (context) {
final theme = Theme.of(context);
return Container(
padding: const EdgeInsets.all(25),
decoration: BoxDecoration(
color: theme.cardColor,
borderRadius: const BorderRadius.vertical(top: Radius.circular(30)),
boxShadow: [
BoxShadow(
color: theme.shadowColor.withOpacity(0.2), blurRadius: 10, offset: const Offset(0, -2))
],
),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// --- 1. المقبض العلوي ---
Center(
@@ -208,12 +213,13 @@ void showShamCashInput() {
height: 5,
width: 50,
decoration: BoxDecoration(
color: Colors.grey[300],
color: theme.dividerColor,
borderRadius: BorderRadius.circular(10)),
margin: const EdgeInsets.only(bottom: 20),
),
),
// --- 2. العنوان والأيقونة ---
Image.asset(
'assets/images/shamCash.png',
@@ -386,11 +392,13 @@ void showShamCashInput() {
],
),
),
),
isScrollControlled: true, // ضروري لرفع النافذة عند فتح الكيبورد
);
}),
isScrollControlled: true,
);
}
/// ويدجت داخلية لزر في الشبكة
class _ActionTile extends StatelessWidget {
final String title;
@@ -402,8 +410,9 @@ class _ActionTile extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Material(
color: Colors.grey.shade100,
color: theme.colorScheme.surfaceVariant.withOpacity(0.5),
borderRadius: BorderRadius.circular(12),
child: InkWell(
onTap: onTap,
@@ -413,12 +422,12 @@ class _ActionTile extends StatelessWidget {
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: Get.theme.primaryColor, size: 20),
Icon(icon, color: theme.primaryColor, size: 20),
const SizedBox(width: 8),
Flexible(
child: Text(
title,
style: Get.textTheme.labelLarge,
style: theme.textTheme.labelLarge,
textAlign: TextAlign.center,
)),
],
@@ -429,6 +438,7 @@ class _ActionTile extends StatelessWidget {
}
}
/// 3. بطاقة المعلومات الشخصية
class PersonalInfoCard extends StatelessWidget {
final Map<String, dynamic> data;
@@ -551,19 +561,21 @@ class _InfoRow extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
children: [
Icon(icon, color: Colors.grey.shade500, size: 20),
Icon(icon, color: theme.hintColor.withOpacity(0.6), size: 20),
const SizedBox(width: 16),
Text(label, style: Get.textTheme.bodyLarge),
Text(label, style: theme.textTheme.bodyLarge),
const Spacer(),
Flexible(
child: Text(
value,
style: Get.textTheme.bodyLarge?.copyWith(
color: Colors.grey.shade700, fontWeight: FontWeight.w500),
style: theme.textTheme.bodyLarge?.copyWith(
color: theme.textTheme.bodyLarge?.color?.withOpacity(0.8),
fontWeight: FontWeight.w500),
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) {
final rides = orderHistoryController.promoList[index];
return Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(12)),
color: AppColor.secondaryColor,
boxShadow: [
BoxShadow(
color: AppColor.accentColor,
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)
]),
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 8),
child: Card(
elevation: 4,
shadowColor:
Theme.of(context).shadowColor.withOpacity(0.1),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.all(8.0),
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
AnimatedTextKit(
animatedTexts: [
ScaleAnimatedText(rides['promo_code'],
textStyle: AppStyle.title),
WavyAnimatedText(rides['promo_code'],
textStyle: AppStyle.title),
FlickerAnimatedText(
rides['promo_code'],
textStyle: AppStyle.title),
WavyAnimatedText(rides['promo_code'],
textStyle: AppStyle.title),
],
isRepeatingAnimation: true,
onTap: () {},
),
Text(
rides['description'],
style: AppStyle.title,
),
],
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
SizedBox(
height: 30,
child: AnimatedTextKit(
animatedTexts: [
ScaleAnimatedText(
rides['promo_code'],
textStyle: Theme.of(context)
.textTheme
.titleLarge
?.copyWith(
color: AppColor
.primaryColor,
fontWeight:
FontWeight.bold,
)),
WavyAnimatedText(
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(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
rides['validity_start_date'],
style: AppStyle.title,
),
Text(
rides['validity_end_date'],
style: AppStyle.title,
),
_buildDateBadge(context,
rides['validity_start_date'], true),
const SizedBox(height: 4),
_buildDateBadge(context,
rides['validity_end_date'], false),
],
),
],
),
const Divider(height: 24),
Text(
'Copy this Promo to use it in your Ride!'.tr,
textAlign: TextAlign.center,
style: AppStyle.headTitle2
.copyWith(color: AppColor.accentColor),
style: Theme.of(context)
.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/views/widgets/my_scafold.dart';
import '../../../constant/colors.dart';
class TaarifPage extends StatelessWidget {
const TaarifPage({super.key});
@@ -18,47 +20,35 @@ class TaarifPage extends StatelessWidget {
// crossAxisAlignment: CrossAxisAlignment.stretch,
clipBehavior: Clip.hardEdge,
children: [
Table(
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
border: TableBorder.symmetric(),
textBaseline: TextBaseline.alphabetic,
children: [
TableRow(
// decoration: AppStyle.boxDecoration,
children: [
Text('Minimum fare'.tr, style: AppStyle.title),
box.read(BoxName.countryCode) == 'Jordan'
? Text('1 ${'JOD'.tr}', style: AppStyle.title)
: Text('20 ${'LE'.tr}', style: AppStyle.title),
],
),
TableRow(
children: [
Text('Maximum fare'.tr, style: AppStyle.title),
box.read(BoxName.countryCode) == 'Jordan'
? Text('200 ${'JOD'.tr}', style: AppStyle.title)
: Text('15000 ${'LE'.tr}', style: AppStyle.title),
],
),
TableRow(
children: [
Text('Flag-down fee'.tr, style: AppStyle.title),
box.read(BoxName.countryCode) == 'Jordan'
? Text('0.47 ${'JOD'.tr}', style: AppStyle.title)
: Text('15 ${'LE'.tr}', style: AppStyle.title),
],
),
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),
],
),
],
_buildTariffItem(
context,
'Minimum fare'.tr,
box.read(BoxName.countryCode) == 'Jordan'
? '1 ${'JOD'.tr}'
: '20 ${'LE'.tr}'),
_buildTariffItem(
context,
'Maximum fare'.tr,
box.read(BoxName.countryCode) == 'Jordan'
? '200 ${'JOD'.tr}'
: '15000 ${'LE'.tr}'),
_buildTariffItem(
context,
'Flag-down fee'.tr,
box.read(BoxName.countryCode) == 'Jordan'
? '0.47 ${'JOD'.tr}'
: '15 ${'LE'.tr}'),
_buildTariffItem(
context,
'Rate'.tr,
box.read(BoxName.countryCode) == 'Jordan'
? '0.05 ${'JOD'.tr}/min and 0.21 ${'JOD'.tr}/km'
: '1 ${'LE'.tr}/min and 4 ${'LE'.tr}/km'),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Text('Including Tax'.tr,
style: AppStyle.subtitle
.copyWith(color: Theme.of(context).hintColor)),
),
const SizedBox(height: 10),
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:sefer_driver/views/home/Captin/home_captain/home_captin.dart';
import '../../constant/finance_design_system.dart';
import '../../controller/local/local_controller.dart';
class Language extends StatelessWidget {
@@ -32,33 +33,9 @@ class Language extends StatelessWidget {
physics: const BouncingScrollPhysics(),
children: [
_buildLanguageButton(
'العربية', 'ar', controller, context, '🇪🇬'),
_buildLanguageButton('العربية (الخليج)', 'ar-gulf',
controller, context, '🇸🇦'),
_buildLanguageButton('العربية (المغرب)', 'ar-ma',
controller, context, '🇲🇦'),
'العربية', 'ar', controller, context, 'AR'),
_buildLanguageButton(
'English', 'en', controller, context, '🇺🇸'),
_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, '🇮🇳'),
'English', 'en', controller, context, 'EN'),
],
),
),
@@ -104,25 +81,43 @@ class Language extends StatelessWidget {
Widget _buildLanguageButton(String title, String langCode,
LocaleController controller, BuildContext context, String flagIcon) {
return Container(
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
color: CupertinoColors.white,
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: CupertinoColors.systemGrey5.withOpacity(0.5),
color: Colors.black.withOpacity(0.03),
spreadRadius: 1,
blurRadius: 3,
offset: const Offset(0, 2),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: ListTile(
leading: Text(flagIcon,
style: const TextStyle(fontSize: 28)), // Using flag icon as leading
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
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,
style: const TextStyle(
fontWeight: FontWeight.w500,
fontWeight: FontWeight.w600,
fontSize: 16,
),
),
trailing: const Icon(CupertinoIcons.chevron_forward,

View File

@@ -2,7 +2,7 @@ import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.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/colors.dart';
@@ -114,7 +114,7 @@ class RideAvailableCard extends StatelessWidget {
margin: const EdgeInsets.only(bottom: 16.0),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
elevation: 4,
shadowColor: Colors.black.withOpacity(0.1),
shadowColor: Theme.of(context).shadowColor.withOpacity(0.1),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
@@ -179,7 +179,7 @@ class RideAvailableCard extends StatelessWidget {
Container(
height: 30,
width: 1,
color: Colors.grey.shade300,
color: Theme.of(Get.context!).dividerColor,
margin: const EdgeInsets.symmetric(vertical: 4),
),
const Icon(Icons.location_on, color: Colors.red, size: 18),
@@ -218,15 +218,17 @@ class RideAvailableCard extends StatelessWidget {
);
}
Widget _infoItem(IconData icon, String text,
{Color iconColor = Colors.grey}) {
return Row(
children: [
Icon(icon, size: 16, color: iconColor),
const SizedBox(width: 4),
Text(text, style: AppStyle.subtitle.copyWith(fontSize: 13)),
],
);
Widget _infoItem(IconData icon, String text, {Color? iconColor}) {
return Builder(builder: (context) {
final theme = Theme.of(context);
return Row(
children: [
Icon(icon, size: 16, color: iconColor ?? theme.hintColor),
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 {
final Widget child;
final Color backgroundColor;
final Color borderColor;
final Color? backgroundColor;
final Color? borderColor;
MyCircleContainer({
Key? key,
super.key,
required this.child,
this.backgroundColor = AppColor.secondaryColor,
this.borderColor = AppColor.accentColor,
}) : super(key: key);
this.backgroundColor,
this.borderColor,
});
final controller = Get.put(CircleController());
@override
Widget build(BuildContext context) {
final effectiveBorderColor = borderColor ?? AppColor.borderColor;
return GetBuilder<CircleController>(
builder: ((controller) => GestureDetector(
onTap: () {
controller.changeColor();
MyDialog().getDialog(
'Rejected Orders Count'.tr,
'This is the total number of rejected orders per day after accepting the orders'
.tr, () {
Get.back();
});
},
child: AnimatedContainer(
onEnd: () {
controller.onEnd();
builder: ((controller) {
final effectiveBackgroundColor = backgroundColor ??
(controller.isActive ? AppColor.accentColor : AppColor.secondaryColor);
return GestureDetector(
onTap: () {
controller.toggleActive();
MyDialog().getDialog(
'Rejected Orders Count'.tr,
'This is the total number of rejected orders per day after accepting the orders'
.tr, () {
Get.back();
});
},
duration: const Duration(milliseconds: 300),
width: controller.size,
height: controller.size,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: controller.backgroundColor,
border: Border.all(
color: borderColor,
width: 1,
child: AnimatedContainer(
onEnd: () {
controller.resetSize();
},
duration: const Duration(milliseconds: 300),
width: controller.size,
height: controller.size,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: effectiveBackgroundColor,
border: Border.all(
color: effectiveBorderColor,
width: 1,
),
),
child: Center(child: child),
),
child: Center(child: child),
),
)));
);
}));
}
}
class CircleController extends GetxController {
Color backgroundColor = AppColor.secondaryColor;
bool isActive = false;
double size = 40;
void changeColor() {
backgroundColor = backgroundColor == AppColor.secondaryColor
? AppColor.accentColor
: AppColor.secondaryColor;
void toggleActive() {
isActive = !isActive;
size = 60;
update();
}
void onEnd() {
void resetSize() {
size = 40;
update();
}
}

View File

@@ -51,8 +51,14 @@ class MyElevatedButton extends StatelessWidget {
child: Text(
title,
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/services.dart';
import 'package:get/get.dart';
import '../../constant/colors.dart';
class SnackbarConfig {
static const duration = Duration(seconds: 3);
static const animationDuration = Duration(milliseconds: 300);
static const margin = EdgeInsets.symmetric(horizontal: 16, vertical: 10);
static const borderRadius = 12.0;
static const elevation = 6.0;
// ─────────────────────────────────────────────────────────────────────────────
// Snackbar variant definition
// ─────────────────────────────────────────────────────────────────────────────
enum _SnackVariant { success, error, info, warning }
static final BoxShadow shadow = BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 2),
);
extension _VariantProps on _SnackVariant {
Color get baseColor => switch (this) {
_SnackVariant.success => const Color(0xFF1A9E5C),
_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) {
// Trigger error haptic feedback
HapticFeedback.mediumImpact();
enum HapticFeedbackType { light, medium, selection }
// ─────────────────────────────────────────────────────────────────────────────
// 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(
'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,
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(
'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();
},
backgroundColor: Colors.transparent,
margin: EdgeInsets.zero,
padding: EdgeInsets.zero,
duration: const Duration(seconds: 4),
animationDuration: const Duration(milliseconds: 380),
barBlur: 0,
overlayBlur: 0,
overlayColor: Colors.transparent,
isDismissible: true,
dismissDirection: DismissDirection.horizontal,
overlayBlur: 0.8,
overlayColor: Colors.black12,
dismissDirection: DismissDirection.up,
forwardAnimationCurve: Curves.easeOutCubic,
reverseAnimationCurve: Curves.easeInCubic,
snackStyle: SnackStyle.FLOATING,
userInputForm: Form(
child: _SnackContent(message: message, variant: variant),
),
);
}
SnackbarController mySnackbarSuccess(String message) {
// Trigger success haptic feedback
HapticFeedback.lightImpact();
// ─────────────────────────────────────────────────────────────────────────────
// Public API — drop-in replacements for the old functions
// ─────────────────────────────────────────────────────────────────────────────
SnackbarController mySnackbarSuccess(String message) =>
_show(_SnackVariant.success, message);
return Get.snackbar(
'Success'.tr,
message,
backgroundColor: AppColor.greenColor.withOpacity(0.95),
colorText: AppColor.secondaryColor,
icon: const Icon(
Icons.check_circle_outline_rounded,
color: AppColor.secondaryColor,
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,
);
}
SnackbarController mySnackeBarError(String message) =>
_show(_SnackVariant.error, message);
SnackbarController mySnackbarInfo(String message) =>
_show(_SnackVariant.info, message);
SnackbarController mySnackbarWarning(String message) =>
_show(_SnackVariant.warning, message);

View File

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

View File

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

View File

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

View File

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