first commit

This commit is contained in:
Hamza-Ayed
2026-06-09 08:40:31 +03:00
commit d8901e1a87
3161 changed files with 536187 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
// import 'package:flutter/material.dart';
// import 'package:ride/constant/api_key.dart';
// import 'package:ride/constant/box_name.dart';
// import 'package:ride/main.dart';
// import 'package:zego_uikit_prebuilt_call/zego_uikit_prebuilt_call.dart';
// class CallPage extends StatelessWidget {
// const CallPage({Key? key, required this.callID}) : super(key: key);
// final String callID;
// @override
// Widget build(BuildContext context) {
// return ZegoUIKitPrebuiltCall(
// appID: AK
// .zegoCloudAppID, // Fill in the appID that you get from ZEGOCLOUD Admin Console.
// appSign: AK
// .zegoCloudAppSIGN, // Fill in the appSign that you get from ZEGOCLOUD Admin Console.
// userID: box.read(BoxName.passengerID) ?? box.read(BoxName.driverID),
// userName: box.read(BoxName.name) ?? box.read(BoxName.nameDriver),
// callID: callID,
// // You can also use groupVideo/groupVoice/oneOnOneVoice to make more types of calls.
// config: ZegoUIKitPrebuiltCallConfig.oneOnOneVoiceCall(),
// );
// }
// }

View File

@@ -0,0 +1,142 @@
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:flutter/services.dart';
class MarkerGenerator {
// دالة لرسم ماركر يحتوي على نص (للوقت والمسافة)
static Future<InlqBitmap> createCustomMarkerBitmap({
required String title,
required String subtitle,
required Color color,
required IconData iconData,
}) async {
final ui.PictureRecorder pictureRecorder = ui.PictureRecorder();
final Canvas canvas = Canvas(pictureRecorder);
// إعدادات القياسات
const double width = 220.0;
const double height = 110.0;
const double circleRadius = 25.0;
// 1. رسم المربع (Info Box) مع تدرج لوني بسيط
final Paint paint = Paint()..color = color;
final RRect rRect = RRect.fromRectAndRadius(
const Rect.fromLTWH(0, 0, width, 65),
const Radius.circular(20), // زوايا أكثر استدارة لشكل عصري
);
// ظل أقوى لمظهر بارز (Premium Feel)
canvas.drawShadow(Path()..addRRect(rRect), Colors.black54, 10.0, true);
canvas.drawRRect(rRect, paint);
// 2. رسم مثلث صغير أسفل المربع (Arrow Tail)
final Path path = Path();
path.moveTo(width / 2 - 10, 60);
path.lineTo(width / 2, 75);
path.lineTo(width / 2 + 10, 60);
path.close();
canvas.drawPath(path, paint);
// 3. رسم الدائرة (مكان الأيقونة)
canvas.drawCircle(const Offset(width / 2, 85), circleRadius, paint);
// 4. رسم الأيقونة داخل الدائرة
TextPainter iconPainter = TextPainter(textDirection: TextDirection.ltr);
iconPainter.text = TextSpan(
text: String.fromCharCode(iconData.codePoint),
style: TextStyle(
fontSize: 30.0,
fontFamily: iconData.fontFamily,
color: Colors.white,
),
);
iconPainter.layout();
iconPainter.paint(
canvas,
Offset((width - iconPainter.width) / 2, 85 - (iconPainter.height / 2)),
);
// 5. رسم النصوص (العنوان والوصف) داخل المربع
// العنوان (مثلاً: المدة)
TextPainter titlePainter = TextPainter(
textDirection: TextDirection.rtl,
textAlign: TextAlign.center,
);
titlePainter.text = TextSpan(
text: title,
style: const TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
color: Colors.white,
),
);
titlePainter.layout(minWidth: width);
titlePainter.paint(canvas, const Offset(0, 8));
// الوصف (مثلاً: المسافة)
TextPainter subTitlePainter = TextPainter(
textDirection: TextDirection.rtl,
textAlign: TextAlign.center,
);
subTitlePainter.text = TextSpan(
text: subtitle,
style: const TextStyle(
fontSize: 16.0,
color: Colors.white70,
),
);
subTitlePainter.layout(minWidth: width);
subTitlePainter.paint(canvas, const Offset(0, 32));
// تحويل الرسم إلى صورة
final ui.Image image = await pictureRecorder.endRecording().toImage(
width.toInt(),
(height + 20).toInt(), // مساحة إضافية
);
final ByteData? data =
await image.toByteData(format: ui.ImageByteFormat.png);
return InlqBitmap.fromBytes(data!.buffer.asUint8List());
}
// دالة خاصة لرسم ماركر السائق (دائرة وخلفها سهم)
static Future<InlqBitmap> createDriverMarker() async {
final ui.PictureRecorder pictureRecorder = ui.PictureRecorder();
final Canvas canvas = Canvas(pictureRecorder);
const double size = 100.0;
final Paint paint = Paint()..color = const Color(0xFF2E7D32); // أخضر غامق
final Paint borderPaint = Paint()
..color = Colors.white
..style = PaintingStyle.stroke
..strokeWidth = 4.0;
// الدائرة
canvas.drawCircle(const Offset(size / 2, size / 2), size / 2.5, paint);
canvas.drawCircle(
const Offset(size / 2, size / 2), size / 2.5, borderPaint);
// رسم السهم (Arrow Up)
TextPainter iconPainter = TextPainter(textDirection: TextDirection.ltr);
iconPainter.text = TextSpan(
text: String.fromCharCode(Icons.navigation.codePoint), // سهم ملاحة
style: TextStyle(
fontSize: 40.0,
fontFamily: Icons.navigation.fontFamily,
color: Colors.white,
),
);
iconPainter.layout();
iconPainter.paint(
canvas,
Offset((size - iconPainter.width) / 2, (size - iconPainter.height) / 2),
);
final ui.Image image = await pictureRecorder
.endRecording()
.toImage(size.toInt(), size.toInt());
final ByteData? data =
await image.toByteData(format: ui.ImageByteFormat.png);
return InlqBitmap.fromBytes(data!.buffer.asUint8List());
}
}

View File

@@ -0,0 +1,814 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_overlay_window/flutter_overlay_window.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:just_audio/just_audio.dart';
import 'package:siro_driver/constant/api_key.dart';
import 'package:siro_driver/models/overlay_service.dart';
import '../../../../constant/box_name.dart';
import '../../../../constant/links.dart';
import '../../../../controller/firebase/firbase_messge.dart';
import '../../../../controller/firebase/local_notification.dart';
import '../../../../controller/firebase/notification_service.dart';
import '../../../../controller/functions/crud.dart';
import '../../../../main.dart';
import '../../../../models/model/order_data.dart';
import '../../../../print.dart';
// === Enhanced Colors for Better Readability ===
class AppColors {
static const primary = Color(0xFF1A252F);
static const card = Color(0xFF2C3E50);
static const white = Colors.white;
static const gray = Color(0xFFBDC3C7);
static const lightGray = Color(0xFFECF0F1);
static const accent = Color(0xFF00BCD4);
static const accept = Color(0xFF4CAF50);
static const reject = Color(0xFFFF5722);
static const highlight = Color(0xFFFFC107);
static const priceHighlight = Color(0xFF00E676);
static const urgentRed = Color(0xFFD32F2F);
}
class OrderOverlay extends StatefulWidget {
const OrderOverlay({Key? key}) : super(key: key);
@override
State<OrderOverlay> createState() => _OrderOverlayState();
}
class _OrderOverlayState extends State<OrderOverlay>
with WidgetsBindingObserver {
// === State Variables ===
OrderData? orderData;
Timer? timer;
int remainingSeconds = 10;
final AudioPlayer audioPlayer = AudioPlayer();
bool buttonsEnabled = true;
final String mapApiKey = AK.mapAPIKEY;
final CRUD _crud = CRUD();
final NotificationController notificationController =
Get.put(NotificationController());
// === Getters ===
bool get canShowMap {
if (orderData == null || mapApiKey.isEmpty) return false;
final start = orderData!.startCoordinates;
final end = orderData!.endCoordinates;
return start?['lat'] != null &&
start?['lng'] != null &&
end?['lat'] != null &&
end?['lng'] != null;
}
String get staticMapUrl {
if (!canShowMap) return "";
final start = orderData!.startCoordinates!;
final end = orderData!.endCoordinates!;
final startMarker = Uri.encodeComponent("${start['lat']},${start['lng']}");
final endMarker = Uri.encodeComponent("${end['lat']},${end['lng']}");
return "https://maps.googleapis.com/maps/api/staticmap?"
"size=600x150&maptype=roadmap"
"&markers=color:green%7Clabel:S%7C$startMarker"
"&markers=color:red%7Clabel:D%7C$endMarker"
"&path=color:0x007bff%7Cweight:5%7C$startMarker%7C$endMarker"
"&key=$mapApiKey";
}
// === Lifecycle ===
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
FlutterOverlayWindow.overlayListener.listen((event) {
if (mounted) _processEventData(event);
});
}
@override
void dispose() {
timer?.cancel();
_stopAudio();
audioPlayer.dispose();
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
_checkOverlayStatus();
}
}
List myList = [];
// === Setup & Listeners ===
void _setupOverlayListener() {
FlutterOverlayWindow.overlayListener.listen((event) {
if (mounted) _processEventData(event);
});
}
void _processEventData(dynamic event) {
_log("Received event: $event");
if (event is List<dynamic>) {
try {
myList = event;
final newOrder = OrderData.fromList(event);
_log("Parsed OrderData: ${newOrder.toMap()}");
setState(() {
orderData = newOrder;
});
_resetAndStartTimer();
} catch (e, s) {
_log("Error parsing OrderData: $e\nStackTrace: $s");
}
} else {
_log("Unexpected data format: $event");
}
}
void _checkOverlayStatus() async {
bool isActive = await FlutterOverlayWindow.isActive();
if (isActive && mounted && orderData != null) {
if (remainingSeconds > 0 && (timer == null || !timer!.isActive)) {
_resetAndStartTimer();
}
}
}
// === Timer Management ===
void _resetAndStartTimer() {
timer?.cancel();
audioPlayer.stop();
setState(() {
buttonsEnabled = true;
remainingSeconds = _calculateTimerDuration();
});
_playAudio();
_startTimer();
}
int _calculateTimerDuration() {
if (orderData?.durationToPassengerMinutes != null &&
orderData!.durationToPassengerMinutes > 0) {
int duration = orderData!.durationToPassengerMinutes * 60;
return duration > 10 ? 10 : duration;
}
return 10;
}
void _startTimer() {
if (orderData == null) return;
timer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (!mounted) {
timer.cancel();
_stopAudio();
return;
}
setState(() {
if (remainingSeconds > 0) {
remainingSeconds--;
} else {
timer.cancel();
_stopAudio();
if (buttonsEnabled) _handleOrderTimeout();
}
});
});
}
// === Audio Management ===
void _playAudio() async {
try {
await audioPlayer.setAsset('assets/order.mp3', preload: true);
await audioPlayer.setLoopMode(LoopMode.one);
await audioPlayer.play();
} catch (e) {
_log('Error playing audio: $e');
}
}
void _stopAudio() {
audioPlayer.stop();
}
String _getData(int index, {String defaultValue = ''}) {
if (myList.length > index && myList[index] != null) {
return myList[index].toString();
}
return defaultValue;
}
// === Order Actions ===
Future<void> _acceptOrder() async {
if (!buttonsEnabled || orderData == null) return;
_disableButtonsAndProcess();
_log("Order ACCEPTED: ${orderData!.orderId}");
try {
final driverId = box.read(BoxName.driverID)?.toString();
if (driverId == null) {
_log("Error: Driver ID is null. Closing overlay.");
await _closeOverlay();
return;
}
var res = await CRUD()
.post(link: "${AppLink.ride}/rides/acceptRide.php", payload: {
'id': orderData!.orderId,
'rideTimeStart': DateTime.now().toString(),
'status': 'Apply',
'driver_id': box.read(BoxName.driverID),
'passengerToken': _getData(9),
});
final payload = {
// بيانات أساسية
'driver_id': driverId,
'status': 'Apply',
'passengerLocation': '${_getData(0)},${_getData(1)}',
'passengerDestination': '${_getData(3)},${_getData(4)}',
'Duration': _getData(4),
'totalCost': _getData(26),
'Distance': _getData(5),
'name': _getData(8),
'phone': _getData(10),
'email': _getData(28),
'WalletChecked': _getData(13),
'tokenPassenger': _getData(9),
'direction': staticMapUrl.toString(),
'DurationToPassenger': _getData(15),
'rideId': orderData!.orderId,
'passengerId': _getData(7),
'durationOfRideValue': _getData(19),
'paymentAmount': _getData(2),
'paymentMethod': _getData(13) == 'true' ? 'visa' : 'cash',
'isHaveSteps': _getData(20),
'step0': myList[21].toString(),
'step1': myList[22].toString(),
'step2': myList[23].toString(),
'step3': myList[24].toString(),
'step4': myList[25].toString(),
'passengerWalletBurc': myList[26].toString(),
'carType': myList[31].toString(),
'kazan': myList[32].toString(),
'startNameLocation': myList[29].toString(),
'endNameLocation': myList[30].toString(),
// الحقول الإضافية التي يجب تضمينها
'timeOfOrder': DateTime.now().toIso8601String(),
'totalPassenger': _getData(2),
};
Log.print('payload: ${payload}');
CRUD().post(
link: AppLink.addOverLayStatus,
payload: payload,
);
if (res != "failure") {
// Using rideId (_getData(16)) for order_id consistently
_log("Server update successful. Writing to storage.");
notificationController.showNotification(
"Order Accepted".tr,
"Open app and go to passenger".tr,
'ding',
'',
);
// 3. الخطوة الأهم: فتح التطبيق وإغلاق النافذة
try {
// استدعاء الميثود التي تم تحديثها في الخطوة 1
await OverlayMethodChannel.bringToForeground();
} catch (e) {
_log("Failed to bring app to foreground: $e");
}
await _closeOverlay();
} else {
_log("Failed to update order status on server: $res");
notificationController.showNotification(
"Order Accepted by another driver".tr,
"Open app and go to passenger".tr,
'ding',
'',
);
await _closeOverlay();
}
} catch (e, s) {
_log(
"A critical error occurred during server update: $e\nStackTrace: $s");
if (mounted) setState(() => buttonsEnabled = true);
_log("Error in accept order: $e");
await _closeOverlay();
// حتى في حال الخطأ، نحاول فتح التطبيق ليرى السائق ما حدث
await OverlayMethodChannel.bringToForeground();
return;
}
}
// Your list parsing for 'customerToken' should be something like:
// customerToken: list.length > a_certain_index ? list[a_certain_index].toString() : null,
Future<void> _rejectOrder() async {
if (!buttonsEnabled || orderData == null) return;
_disableButtonsAndProcess();
_log("Order REJECTED: ${orderData!.orderId}");
box.write(BoxName.rideStatus, 'reject');
Log.print('rideStatus from overlay 303 : ${box.read(BoxName.rideStatus)}');
await _apiRefuseOrder(orderData!.orderId);
await _closeOverlay();
}
void _handleOrderTimeout() {
if (orderData == null) return;
_log("Order TIMED OUT: ${orderData!.orderId}");
_rejectOrder();
}
Future<void> _apiRefuseOrder(String orderID) async {
if (orderID == "N/A") {
_log("Cannot refuse order with N/A ID");
return;
}
try {
final driverId = box.read(BoxName.driverID)?.toString();
if (driverId == null) {
_log("Driver ID is null, cannot refuse order");
return;
}
CRUD().post(link: AppLink.addDriverOrder, payload: {
'driver_id': driverId,
'order_id': orderID,
'status': 'Refused'
});
_log("Order $orderID refused successfully");
} catch (e) {
_log("Error in _apiRefuseOrder for $orderID: $e");
}
}
// === Helper Methods ===
void _disableButtonsAndProcess() {
setState(() => buttonsEnabled = false);
timer?.cancel();
_stopAudio();
}
Future<void> _closeOverlay() async {
_stopAudio();
timer?.cancel();
if (await FlutterOverlayWindow.isActive()) {
await FlutterOverlayWindow.closeOverlay();
}
}
void _log(String message) {
// A simple logger to distinguish overlay logs
print("OVERLAY_LOG: $message");
}
// === UI Build Methods ===
@override
Widget build(BuildContext context) {
// ... (Your entire UI build method remains unchanged) ...
// The UI code is excellent and doesn't need modification.
if (orderData == null) {
return const Material(
color: Colors.transparent,
child: Center(
child: CircularProgressIndicator(color: AppColors.accent)));
}
return Material(
color: Colors.black.withOpacity(0.4),
child: Center(
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration(
color: AppColors.card,
borderRadius: BorderRadius.circular(20.0),
border: Border.all(
color: AppColors.accent.withOpacity(0.3), width: 1.5),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.6),
blurRadius: 15,
spreadRadius: 2,
)
],
),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildQuickHeader(),
const SizedBox(height: 12),
_buildPrimaryInfo(),
const SizedBox(height: 12),
if (canShowMap) _buildCompactMap(),
if (canShowMap) const SizedBox(height: 12),
_buildSecondaryInfo(),
const SizedBox(height: 16),
_buildEnhancedActionButtons(),
],
),
),
),
),
);
}
// All your _build... widget methods (_buildQuickHeader, _buildPrimaryInfo, etc.)
// are perfectly fine and do not need to be changed.
// ... Paste all your existing _build... methods here ...
Widget _buildQuickHeader() {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
remainingSeconds <= 3
? AppColors.urgentRed
: remainingSeconds <= 5
? AppColors.highlight
: AppColors.accent,
remainingSeconds <= 3
? AppColors.urgentRed.withOpacity(0.7)
: remainingSeconds <= 5
? AppColors.highlight.withOpacity(0.7)
: AppColors.accent.withOpacity(0.7),
],
),
borderRadius: BorderRadius.circular(15),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Icon(Icons.drive_eta_rounded, color: AppColors.white, size: 24),
const SizedBox(width: 8),
Text(
"طلب جديد".tr,
style: const TextStyle(
color: AppColors.white,
fontSize: 18,
fontWeight: FontWeight.w600),
),
],
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
decoration: BoxDecoration(
color: AppColors.white.withOpacity(0.9),
borderRadius: BorderRadius.circular(20),
),
child: Text(
"$remainingSeconds ث",
style: TextStyle(
color: remainingSeconds <= 3
? AppColors.urgentRed
: remainingSeconds <= 5
? AppColors.highlight
: AppColors.accent,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
],
),
);
}
Widget _buildPrimaryInfo() {
final order = orderData!;
// FIX: Parse the price to a number safely before formatting
// This handles cases where order.price is a String like "173"
final num priceValue = num.tryParse(order.price.toString()) ?? 0;
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.primary.withOpacity(0.6),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: AppColors.priceHighlight.withOpacity(0.3), width: 1),
),
child: Column(
children: [
// Price and Distance - Most Important Info
Row(
children: [
Expanded(
flex: 3,
child: _buildHighlightInfo(
// FIX: Use the parsed priceValue here
"${NumberFormat('#,##0').format(priceValue)} ل.س",
"السعر".tr,
Icons.monetization_on_rounded,
AppColors.priceHighlight,
isLarge: true,
),
),
const SizedBox(width: 12),
Expanded(
flex: 2,
child: _buildHighlightInfo(
// Ensure tripDistanceKm is treated safely too
"${(num.tryParse(order.tripDistanceKm.toString()) ?? 0).toStringAsFixed(1)} كم",
"المسافة".tr,
Icons.straighten_rounded,
AppColors.accent,
),
),
],
),
const SizedBox(height: 12),
Divider(color: AppColors.gray.withOpacity(0.2), thickness: 1),
const SizedBox(height: 12),
// Passenger Info and ETA
Row(
children: [
Expanded(
flex: 2,
child: _buildPassengerQuickInfo(),
),
const SizedBox(width: 12),
Expanded(
child: _buildHighlightInfo(
"${order.durationToPassengerMinutes} د",
"للوصول".tr,
Icons.access_time_filled_rounded,
order.durationToPassengerMinutes <= 3
? AppColors.priceHighlight
: AppColors.gray,
),
),
],
),
],
),
);
}
Widget _buildHighlightInfo(
String value, String label, IconData icon, Color color,
{bool isLarge = false}) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: color.withOpacity(0.3), width: 1),
),
child: Column(
children: [
Icon(icon, color: color, size: isLarge ? 24 : 20),
const SizedBox(height: 4),
Text(
value,
style: TextStyle(
color: AppColors.white,
fontSize: isLarge ? 20 : 16,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
Text(
label,
style: TextStyle(
color: AppColors.lightGray.withOpacity(0.7),
fontSize: 11,
),
textAlign: TextAlign.center,
),
],
),
);
}
Widget _buildPassengerQuickInfo() {
final order = orderData!;
return Container(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
decoration: BoxDecoration(
color: AppColors.highlight.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
border:
Border.all(color: AppColors.highlight.withOpacity(0.3), width: 1),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.person_rounded, color: AppColors.highlight, size: 18),
const SizedBox(width: 4),
Expanded(
child: Text(
order.customerName,
style: const TextStyle(
color: AppColors.white,
fontSize: 14,
fontWeight: FontWeight.w600,
),
overflow: TextOverflow.ellipsis,
),
),
],
),
const SizedBox(height: 2),
Text(
order.rideType,
style: TextStyle(
color: AppColors.lightGray.withOpacity(0.7),
fontSize: 11,
),
),
],
),
);
}
Widget _buildCompactMap() {
return ClipRRect(
borderRadius: BorderRadius.circular(12.0),
child: Image.network(
staticMapUrl,
height: 100, // Reduced from 110
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Container(
height: 100,
decoration: BoxDecoration(
color: AppColors.primary.withOpacity(0.3),
borderRadius: BorderRadius.circular(12),
),
child: const Center(
child:
Icon(Icons.map_outlined, color: AppColors.gray, size: 32)),
);
},
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return SizedBox(
height: 100,
child: Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
color: AppColors.accent,
strokeWidth: 2.0,
),
),
);
},
),
);
}
Widget _buildSecondaryInfo() {
final order = orderData!;
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColors.primary.withOpacity(0.4),
borderRadius: BorderRadius.circular(10),
),
child: Column(
children: [
_buildLocationRow(
Icons.trip_origin_rounded,
"من".tr,
order.startLocationAddress,
Colors.green.shade300,
),
const SizedBox(height: 8),
_buildLocationRow(
Icons.flag_rounded,
"إلى".tr,
order.endLocationAddress,
Colors.red.shade300,
),
if (order.tripDurationMinutes > 0) ...[
const SizedBox(height: 8),
Divider(color: AppColors.gray.withOpacity(0.2), thickness: 0.5),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.timer_outlined, color: AppColors.accent, size: 16),
const SizedBox(width: 4),
Text(
"مدة الرحلة: ${order.tripDurationMinutes} دقيقة".tr,
style: const TextStyle(
color: AppColors.lightGray,
fontSize: 12,
),
),
],
),
]
],
),
);
}
Widget _buildLocationRow(
IconData icon, String label, String address, Color iconColor) {
return Row(
children: [
Icon(icon, color: iconColor, size: 16),
const SizedBox(width: 8),
Text(
"$label: ",
style: TextStyle(
color: AppColors.lightGray.withOpacity(0.8),
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
Expanded(
child: Text(
address,
style: const TextStyle(
color: AppColors.white,
fontSize: 12,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
],
);
}
Widget _buildEnhancedActionButtons() {
return Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: buttonsEnabled ? _rejectOrder : null,
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.reject,
foregroundColor: AppColors.white,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
disabledBackgroundColor: AppColors.reject.withOpacity(0.3),
elevation: 3,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.close_rounded, size: 20),
const SizedBox(width: 6),
Text(
"رفض".tr,
style: const TextStyle(
fontSize: 16, fontWeight: FontWeight.w600),
),
],
),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton(
onPressed: buttonsEnabled ? _acceptOrder : null,
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.accept,
foregroundColor: AppColors.white,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
disabledBackgroundColor: AppColors.accept.withOpacity(0.3),
elevation: 3,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.check_circle_rounded, size: 20),
const SizedBox(width: 6),
Text(
"قبول".tr,
style: const TextStyle(
fontSize: 16, fontWeight: FontWeight.w600),
),
],
),
),
),
],
);
}
}

View File

@@ -0,0 +1,438 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:siro_driver/constant/api_key.dart';
import 'package:siro_driver/constant/colors.dart';
import 'package:siro_driver/controller/home/captin/order_request_controller.dart';
class OrderRequestPage extends StatelessWidget {
const OrderRequestPage({super.key});
@override
Widget build(BuildContext context) {
// حقن الكنترولر
final OrderRequestController controller = Get.put(OrderRequestController());
return Scaffold(
body: Directionality(
textDirection: TextDirection.rtl,
child: GetBuilder<OrderRequestController>(
builder: (controller) {
// 🔥 التعديل الأهم: التحقق من وجود أي بيانات (List أو Map)
if (controller.myList == null && controller.myMapData == null) {
return const Center(
child:
CircularProgressIndicator()); // شاشة تحميل بدلاً من فراغ
}
// 🔥 استخدام دوال الكنترولر الآمنة لجلب البيانات بدلاً من الوصول المباشر
// قمت بتحويل _safeGet إلى دالة عامة safeGet في الكنترولر (تأكد من جعلها public)
// أو سأقوم بكتابة المنطق هنا مباشرة لضمان العمل:
String getValue(int index) {
if (controller.myList != null &&
index < controller.myList!.length) {
return controller.myList![index].toString();
}
if (controller.myMapData != null &&
controller.myMapData!.containsKey(index.toString())) {
return controller.myMapData![index.toString()].toString();
}
return "";
}
final String passengerName =
getValue(8).isEmpty ? "عميل" : getValue(8);
final String startAddr =
getValue(29).isEmpty ? "موقع الانطلاق" : getValue(29);
final String endAddr =
getValue(30).isEmpty ? "الوجهة" : getValue(30);
final bool isVisa = (getValue(13) == 'true');
// منطق Speed = سعر ثابت
final bool isSpeed =
controller.tripType.toLowerCase().contains('speed');
final String carTypeLabel =
isSpeed ? "سعر ثابت" : controller.tripType;
final Color carTypeColor =
isSpeed ? Colors.red.shade700 : Colors.blue.shade700;
final IconData carIcon =
isSpeed ? Icons.local_offer : Icons.directions_car;
return Stack(
children: [
// 1. الخارطة
Positioned.fill(
bottom: 300,
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,
onMapCreated: (c) {
controller.onMapCreated(c);
controller.update();
},
),
),
// 2. كبسولة الوصول للراكب
Positioned(
top: 50,
left: 0,
right: 0,
child: Center(
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 15, vertical: 8),
decoration: BoxDecoration(
color: Colors.black87,
borderRadius: BorderRadius.circular(30),
boxShadow: [
BoxShadow(color: Colors.black26, blurRadius: 8)
],
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.near_me,
color: Colors.amber, size: 16),
const SizedBox(width: 8),
Text(
"الوصول للراكب: ${controller.timeToPassenger}",
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 13),
),
],
),
),
),
),
// 3. البطاقة السفلية
Align(
alignment: Alignment.bottomCenter,
child: Container(
height: 360,
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(25),
topRight: Radius.circular(25),
),
boxShadow: [
BoxShadow(
color:
Theme.of(context).shadowColor.withOpacity(0.1),
blurRadius: 20,
spreadRadius: 5)
],
),
padding: const EdgeInsets.fromLTRB(20, 10, 20, 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(
child: Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: Theme.of(context).dividerColor,
borderRadius: BorderRadius.circular(2)))),
const SizedBox(height: 15),
// الصف الأول: الراكب والسعر
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
CircleAvatar(
radius: 24,
backgroundColor: Theme.of(context)
.colorScheme
.surfaceVariant,
child: Icon(Icons.person,
color: Theme.of(context).hintColor,
size: 28),
),
const SizedBox(width: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(passengerName,
style: const TextStyle(
fontSize: 17,
fontWeight: FontWeight.bold)),
Row(
children: [
const Icon(Icons.star,
color: Colors.amber, size: 14),
Text(controller.passengerRating,
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.bold)),
],
),
],
),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text("${controller.tripPrice} ل.س",
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: AppColor.primaryColor)),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: isVisa
? Colors.purple.withOpacity(0.1)
: Colors.green.withOpacity(0.1),
borderRadius: BorderRadius.circular(4)),
child: Row(
children: [
Text(isVisa ? "فيزا" : "كاش",
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.bold,
color: isVisa
? Colors.purple
: Colors.green)),
const SizedBox(width: 4),
Icon(
isVisa
? Icons.credit_card
: Icons.money,
size: 14,
color: isVisa
? Colors.purple
: Colors.green),
],
),
),
],
),
],
),
const SizedBox(height: 15),
// الصف الثاني: شريط المعلومات
Container(
padding: const EdgeInsets.symmetric(
vertical: 10, horizontal: 10),
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.surfaceVariant
.withOpacity(0.5),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: Theme.of(context).dividerColor),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildInfoItem(
context, carIcon, carTypeLabel, carTypeColor),
Container(
height: 20,
width: 1,
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: Theme.of(context).dividerColor),
_buildInfoItem(
context,
Icons.access_time_filled,
controller.totalTripDuration,
Theme.of(context)
.textTheme
.bodyLarge
?.color ??
Colors.black87),
],
),
),
const SizedBox(height: 20),
// الصف الثالث: العناوين
Expanded(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
children: [
const Icon(Icons.my_location,
size: 18, color: Colors.green),
Expanded(
child: Container(
width: 2,
color: Colors.grey.shade300,
margin: const EdgeInsets.symmetric(
vertical: 2))),
const Icon(Icons.location_on,
size: 18, color: Colors.red),
],
),
const SizedBox(width: 15),
Expanded(
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
const Text("موقع الانطلاق",
style: TextStyle(
fontSize: 11,
color: Colors.grey)),
Text(startAddr,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600),
maxLines: 1,
overflow: TextOverflow.ellipsis),
],
),
const Spacer(),
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
const Text("الوجهة",
style: TextStyle(
fontSize: 11,
color: Colors.grey)),
Text(endAddr,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600),
maxLines: 1,
overflow: TextOverflow.ellipsis),
],
),
],
),
)
],
),
),
const SizedBox(height: 15),
// الصف الرابع: الأزرار
Row(
children: [
InkWell(
onTap: () => Get.back(),
child: Container(
height: 50,
width: 50,
decoration: BoxDecoration(
color: Colors.red.shade50,
shape: BoxShape.circle,
border:
Border.all(color: Colors.red.shade100)),
child: const Icon(Icons.close,
color: Colors.red, size: 24),
),
),
const SizedBox(width: 15),
Expanded(
child: ElevatedButton(
onPressed: () => controller.acceptOrder(),
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.primaryColor,
foregroundColor: Colors.white,
padding:
const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15)),
elevation: 2,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text("قبول الرحلة",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold)),
const SizedBox(width: 15),
SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
value: controller.progress,
color: Colors.white,
strokeWidth: 2.5,
backgroundColor: Colors.white24),
),
const SizedBox(width: 8),
Text("${controller.remainingTime}",
style: const TextStyle(
fontSize: 14, color: Colors.white)),
],
),
),
),
],
),
],
),
),
),
],
);
},
),
),
);
}
Widget _buildInfoItem(
BuildContext context, IconData icon, String text, Color color) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 16, color: color),
const SizedBox(width: 6),
Text(text,
style: TextStyle(
fontSize: 13, fontWeight: FontWeight.bold, color: color)),
],
);
}
}

View File

@@ -0,0 +1,558 @@
// import 'dart:convert';
// import 'package:flutter/material.dart';
// import 'package:get/get.dart';
// import 'package:intaleq_maps/intaleq_maps.dart';
// import 'package:siro_driver/controller/home/captin/home_captain_controller.dart';
// import 'package:siro_driver/constant/box_name.dart';
// import 'package:siro_driver/controller/firebase/firbase_messge.dart';
// import 'package:siro_driver/main.dart';
// import 'package:siro_driver/print.dart';
// import 'package:siro_driver/views/home/Captin/driver_map_page.dart';
// import '../../../../constant/colors.dart';
// import '../../../../constant/links.dart';
// import '../../../../constant/style.dart';
// import '../../../../controller/firebase/notification_service.dart';
// import '../../../../controller/functions/crud.dart';
// import '../../../../controller/functions/launch.dart';
// import '../../../../controller/home/captin/order_request_controller.dart';
// import '../../../widgets/elevated_btn.dart';
// import '../../../widgets/mydialoug.dart';
// class OrderSpeedRequest extends StatelessWidget {
// OrderSpeedRequest({super.key});
// final OrderRequestController orderRequestController =
// Get.put(OrderRequestController());
// // دالة مساعدة لاستخراج البيانات بأمان (Null Safety)
// String _getData(int index, {String defaultValue = ''}) {
// // if (orderRequestController.myList.length > index &&
// // orderRequestController.myList[index] != null) {
// // return orderRequestController.myList[index].toString();
// // }
// return defaultValue;
// }
// @override
// Widget build(BuildContext context) {
// return GetBuilder<OrderRequestController>(
// builder: (controller) {
// // --- استخراج البيانات بشكل نظيف ---
// final String price =
// double.tryParse(_getData(2))?.toStringAsFixed(2) ?? 'N/A';
// final bool isComfortTrip = _getData(31) == 'Comfort';
// final String carType = _getData(31).tr;
// final String pickupName = _getData(12);
// final String pickupDetails = '(${_getData(11)})';
// final String pickupFullAddress = _getData(29);
// final String dropoffName = _getData(5);
// final String dropoffDetails = '(${_getData(4)})';
// final String dropoffFullAddress = _getData(30);
// final String passengerName = _getData(8);
// final String passengerRating = _getData(33);
// final bool isVisaPayment = _getData(13) == 'true';
// final bool hasSteps = _getData(20) == 'haveSteps';
// final String mapUrl =
// 'https://www.google.com/maps/dir/${_getData(0)}/${_getData(1)}/';
// final String rideId = _getData(16);
// return Scaffold(
// appBar: AppBar(
// title: Text('Speed Order'.tr),
// leading: IconButton(
// icon: const Icon(Icons.arrow_back),
// onPressed: () => Get.back(),
// ),
// backgroundColor: AppColor.primaryColor,
// elevation: 2.0,
// ),
// backgroundColor: AppColor.secondaryColor ?? Colors.grey[100],
// body: SafeArea(
// child: Padding(
// padding:
// const EdgeInsets.symmetric(horizontal: 10.0, vertical: 8.0),
// child: Column(
// children: [
// // 1. قسم الخريطة (ارتفاع ثابت)
// SizedBox(
// height: Get.height * 0.28,
// child: ClipRRect(
// borderRadius: BorderRadius.circular(15.0),
// child: GoogleMap(
// initialCameraPosition: CameraPosition(
// zoom: 12,
// target: Get.find<HomeCaptainController>().myLocation,
// ),
// cameraTargetBounds:
// CameraTargetBounds(controller.bounds),
// myLocationButtonEnabled: false,
// trafficEnabled: false,
// buildingsEnabled: false,
// mapToolbarEnabled: false,
// myLocationEnabled: true,
// markers: {
// Marker(
// markerId: MarkerId('MyLocation'.tr),
// position: LatLng(controller.latPassengerLocation,
// controller.lngPassengerLocation),
// icon: controller.startIcon),
// Marker(
// markerId: MarkerId('Destination'.tr),
// position: LatLng(
// controller.latPassengerDestination,
// controller.lngPassengerDestination),
// icon: controller.endIcon),
// },
// polylines: {
// Polyline(
// zIndex: 1,
// consumeTapEvents: true,
// geodesic: true,
// endCap: Cap.buttCap,
// startCap: Cap.buttCap,
// visible: true,
// polylineId: const PolylineId('routeOrder'),
// points: controller.pointsDirection,
// color: AppColor.primaryColor,
// width: 3,
// ),
// },
// ),
// ),
// ),
// const SizedBox(height: 8),
// // 2. بطاقة السعر
// _buildPriceCard(price, carType, isComfortTrip),
// const SizedBox(height: 8),
// // 3. التفاصيل القابلة للتمرير (تأخذ المساحة المتبقية)
// Expanded(
// child: SingleChildScrollView(
// physics: const BouncingScrollPhysics(),
// child: Column(
// mainAxisSize: MainAxisSize.min,
// children: [
// _buildLocationCard(
// icon: Icons.arrow_circle_up,
// iconColor: AppColor.greenColor,
// title: pickupName,
// subtitle: pickupDetails,
// fullAddress: pickupFullAddress,
// ),
// const SizedBox(height: 8),
// _buildLocationCard(
// icon: Icons.arrow_circle_down,
// iconColor: AppColor.redColor,
// title: dropoffName,
// subtitle: dropoffDetails,
// fullAddress: dropoffFullAddress,
// ),
// const SizedBox(height: 8),
// _buildInfoCard(isVisaPayment, hasSteps, mapUrl),
// const SizedBox(height: 8),
// _buildPassengerCard(passengerName, passengerRating),
// ],
// ),
// ),
// ),
// // 4. الأزرار والمؤقت (مثبتة في الأسفل)
// Padding(
// padding: const EdgeInsets.only(top: 8.0),
// child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// // زر القبول
// Expanded(
// child: MyElevatedButton(
// kolor: AppColor.greenColor,
// title: 'Accept Order'.tr,
// onPressed: () => _handleAcceptOrder(controller),
// ),
// ),
// const SizedBox(width: 10),
// // المؤقت
// GetBuilder<OrderRequestController>(
// id: 'timerUpdate',
// builder: (timerCtrl) {
// final isNearEnd = timerCtrl.remainingTimeSpeed <= 5;
// return SizedBox(
// width: 60,
// height: 60,
// child: Stack(
// alignment: Alignment.center,
// children: [
// CircularProgressIndicator(
// value: timerCtrl.progressSpeed,
// color: isNearEnd
// ? Colors.redAccent
// : AppColor.primaryColor,
// strokeWidth: 5,
// backgroundColor: Colors.grey.shade300,
// ),
// Text(
// '${timerCtrl.remainingTimeSpeed}',
// style: AppStyle.headTitle2.copyWith(
// color: isNearEnd
// ? Colors.redAccent
// : AppColor.writeColor ?? Colors.black,
// ),
// ),
// ],
// ),
// );
// },
// ),
// const SizedBox(width: 10),
// // زر الرفض
// Expanded(
// child: MyElevatedButton(
// title: 'Refuse Order'.tr,
// onPressed: () =>
// _handleRefuseOrder(controller, rideId),
// kolor: AppColor.redColor,
// ),
// ),
// ],
// ),
// ),
// ],
// ),
// ),
// ),
// );
// },
// );
// }
// // --- WIDGET BUILDERS (لبناء الواجهة بشكل نظيف) ---
// // Widget _buildPriceCard(String price, String carType, bool isComfortTrip) {
// // return Card(
// // elevation: 3,
// // shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
// // child: Padding(
// // padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 16.0),
// // child: Row(
// // mainAxisAlignment: MainAxisAlignment.spaceBetween,
// // children: [
// // Text(
// // price,
// // style: AppStyle.headTitle.copyWith(
// // color: AppColor.primaryColor,
// // fontWeight: FontWeight.bold,
// // fontSize: 28),
// // ),
// // Column(
// // crossAxisAlignment: CrossAxisAlignment.end,
// // children: [
// // Text(
// // carType,
// // style: AppStyle.title.copyWith(
// // color: AppColor.greenColor, fontWeight: FontWeight.bold),
// // ),
// // if (isComfortTrip)
// // Row(
// // children: [
// // const Icon(Icons.ac_unit,
// // color: AppColor.blueColor, size: 18),
// // const SizedBox(width: 4),
// // Text('Air condition Trip'.tr,
// // style: AppStyle.subtitle.copyWith(fontSize: 13)),
// // ],
// // ),
// // ],
// // ),
// // ],
// // ),
// // ),
// // );
// // }
// // Widget _buildInfoCard(bool isVisaPayment, bool hasSteps, String mapUrl) {
// // return Card(
// // elevation: 2,
// // shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
// // child: Padding(
// // padding: const EdgeInsets.all(10.0),
// // child: Row(
// // mainAxisAlignment: MainAxisAlignment.spaceBetween,
// // children: [
// // Row(
// // children: [
// // Icon(
// // isVisaPayment ? Icons.credit_card : Icons.payments_outlined,
// // color: isVisaPayment
// // ? AppColor.deepPurpleAccent
// // : AppColor.greenColor,
// // size: 24,
// // ),
// // const SizedBox(width: 8),
// // Text(
// // isVisaPayment ? 'Visa'.tr : 'Cash'.tr,
// // style: AppStyle.title.copyWith(fontWeight: FontWeight.w600),
// // ),
// // ],
// // ),
// // if (hasSteps)
// // Expanded(
// // child: Row(
// // mainAxisAlignment: MainAxisAlignment.center,
// // children: [
// // const Icon(Icons.format_list_numbered_rtl_outlined,
// // color: AppColor.bronze, size: 24),
// // const SizedBox(width: 4),
// // Flexible(
// // child: Text(
// // 'Trip has Steps'.tr,
// // style: AppStyle.title
// // .copyWith(color: AppColor.bronze, fontSize: 13),
// // overflow: TextOverflow.ellipsis,
// // )),
// // ],
// // ),
// // ),
// // TextButton.icon(
// // style: TextButton.styleFrom(
// // padding: EdgeInsets.zero,
// // tapTargetSize: MaterialTapTargetSize.shrinkWrap,
// // alignment: Alignment.centerRight,
// // ),
// // onPressed: () => showInBrowser(mapUrl),
// // icon: const Icon(Icons.directions_outlined,
// // color: AppColor.blueColor, size: 20),
// // label: Text("Directions".tr,
// // style: AppStyle.subtitle
// // .copyWith(color: AppColor.blueColor, fontSize: 13)),
// // ),
// // ],
// // ),
// // ),
// // );
// // }
// // Widget _buildPassengerCard(String name, String rating) {
// // return Card(
// // elevation: 2,
// // shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
// // child: Padding(
// // padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 10.0),
// // child: Row(
// // children: [
// // const Icon(Icons.person_outline,
// // color: AppColor.greyColor, size: 22),
// // const SizedBox(width: 10),
// // Expanded(
// // child: Text(
// // name,
// // style: AppStyle.title,
// // overflow: TextOverflow.ellipsis,
// // ),
// // ),
// // const SizedBox(width: 10),
// // const Icon(Icons.star_rounded, color: Colors.amber, size: 20),
// // Text(
// // rating,
// // style: AppStyle.title.copyWith(fontWeight: FontWeight.bold),
// // ),
// // ],
// // ),
// // ),
// // );
// // }
// // Widget _buildLocationCard(
// {required IconData icon,
// required Color iconColor,
// required String title,
// required String subtitle,
// required String fullAddress}) {
// return Card(
// elevation: 2,
// shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
// margin: const EdgeInsets.symmetric(vertical: 4),
// child: Padding(
// padding: const EdgeInsets.all(10.0),
// child: Row(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Icon(icon, color: iconColor, size: 28),
// const SizedBox(width: 12),
// Expanded(
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Text(
// "$title $subtitle".trim(),
// style: AppStyle.title.copyWith(fontWeight: FontWeight.w600),
// maxLines: 1,
// overflow: TextOverflow.ellipsis,
// ),
// if (fullAddress.isNotEmpty) ...[
// const SizedBox(height: 3),
// Text(
// fullAddress,
// style: AppStyle.subtitle
// .copyWith(fontSize: 13, color: AppColor.greyColor),
// maxLines: 2,
// overflow: TextOverflow.ellipsis,
// ),
// ]
// ],
// ),
// ),
// ],
// ),
// ),
// );
// }
// // // --- منطق التعامل مع الطلبات (Logic Handlers) ---
// // Future<void> _handleAcceptOrder(OrderRequestController controller) async {
// // // 1. محاولة تحديث الحالة في السيرفر
// // // 1. إظهار لودينج وإيقاف التفاعل
// // Get.dialog(const Center(child: CircularProgressIndicator()),
// // barrierDismissible: false);
// // // هذا الرابط يجب أن يكون لملف PHP الآمن الذي يحتوي على rowCount
// // var res = await CRUD().post(link: AppLink.updateStausFromSpeed, payload: {
// // 'id': (controller.myList[16]),
// // 'rideTimeStart': DateTime.now().toString(),
// // 'status': 'Apply',
// // 'driver_id': box.read(BoxName.driverID),
// // });
// // Log.print('oreder response update: ${res}');
// // // 2. إغلاق اللودينج بمجرد وصول الرد
// // Get.back(); // إغلاق اللودينج
// // // 2. معالجة الفشل (Robust Error Handling)
// // // نفحص إذا كانت النتيجة فشل سواء وصلت كنص أو كـ JSON
// // bool isFailed = false;
// // if (res == 'failure') isFailed = true;
// // if (res is Map && res['status'] == 'failure') isFailed = true;
// // if (isFailed) {
// // MyDialog().getDialog(
// // "This ride is already applied by another driver.".tr, '', () {
// // Get.back(); // يغلق نافذة التنبيه (Dialog)
// // Get.back(); // يغلق صفحة الطلب بالكامل (Screen) ويرجع للخريطة
// // });
// // return; // توقف تام للكود هنا، لن يتم تنفيذ أي سطر بالأسفل
// // }
// // // 3. معالجة النجاح (Success Handling)
// // // إيقاف المؤقت وتحديث الواجهة
// // controller.endTimer();
// // controller.changeApplied();
// // // تحديث حالة السائق في التطبيق
// // Get.put(HomeCaptainController()).changeRideId();
// // box.write(BoxName.statusDriverLocation, 'on');
// // // *هام*: تم حذف استدعاء الـ API الثاني المكرر هنا لأنه غير ضروري وقد يسبب مشاكل
// // // تسجيل الطلب في سجل السائقين (Driver History)
// // CRUD().postFromDialogue(link: AppLink.addDriverOrder, payload: {
// // 'driver_id': (controller.myList[6].toString()),
// // 'order_id': (controller.myList[16].toString()),
// // 'status': 'Apply'
// // });
// // // إرسال إشعار للراكب
// // List<String> bodyToPassenger = [
// // controller.myList[6].toString(),
// // controller.myList[8].toString(),
// // controller.myList[9].toString(),
// // ];
// // NotificationService.sendNotification(
// // target: controller.myList[9].toString(),
// // title: "Accepted Ride".tr,
// // body: 'your ride is Accepted'.tr,
// // isTopic: false,
// // tone: 'start',
// // driverList: bodyToPassenger,
// // category: 'Accepted Ride',
// // );
// // // حفظ البيانات في الصندوق (Box) للانتقال للصفحة التالية
// // box.write(BoxName.rideArguments, {
// // 'passengerLocation': controller.myList[0].toString(),
// // 'passengerDestination': controller.myList[1].toString(),
// // 'Duration': controller.myList[4].toString(),
// // 'totalCost': controller.myList[26].toString(),
// // 'Distance': controller.myList[5].toString(),
// // 'name': controller.myList[8].toString(),
// // 'phone': controller.myList[10].toString(),
// // 'email': controller.myList[28].toString(),
// // 'WalletChecked': controller.myList[13].toString(),
// // 'tokenPassenger': controller.myList[9].toString(),
// // 'direction':
// // 'https://www.google.com/maps/dir/${controller.myList[0]}/${controller.myList[1]}/',
// // 'DurationToPassenger': controller.myList[15].toString(),
// // 'rideId': (controller.myList[16].toString()),
// // 'passengerId': (controller.myList[7].toString()),
// // 'driverId': (controller.myList[18].toString()),
// // 'durationOfRideValue': controller.myList[19].toString(),
// // 'paymentAmount': controller.myList[2].toString(),
// // 'paymentMethod':
// // controller.myList[13].toString() == 'true' ? 'visa' : 'cash',
// // 'isHaveSteps': controller.myList[20].toString(),
// // 'step0': controller.myList[21].toString(),
// // 'step1': controller.myList[22].toString(),
// // 'step2': controller.myList[23].toString(),
// // 'step3': controller.myList[24].toString(),
// // 'step4': controller.myList[25].toString(),
// // 'passengerWalletBurc': controller.myList[26].toString(),
// // 'timeOfOrder': DateTime.now().toString(),
// // 'totalPassenger': controller.myList[2].toString(),
// // 'carType': controller.myList[31].toString(),
// // 'kazan': controller.myList[32].toString(),
// // 'startNameLocation': controller.myList[29].toString(),
// // 'endNameLocation': controller.myList[30].toString(),
// // });
// // // الانتقال لصفحة تتبع الراكب
// // Get.back(); // يغلق صفحة الطلب الحالية
// // Get.to(() => PassengerLocationMapPage(),
// // arguments: box.read(BoxName.rideArguments));
// // Log.print(
// // 'box.read(BoxName.rideArguments): ${box.read(BoxName.rideArguments)}');
// // }
// // void _handleRefuseOrder(OrderRequestController controller, String rideId) {
// controller.endTimer();
// // controller.refuseOrder(rideId);
// // تسجيل الرفض في الإشعارات المحلية للسائق
// controller.addRideToNotificationDriverString(
// rideId,
// _getData(29),
// _getData(30),
// '${DateTime.now().year}-${DateTime.now().month}-${DateTime.now().day}',
// '${DateTime.now().hour}:${DateTime.now().minute}',
// _getData(2),
// _getData(7),
// 'wait',
// _getData(31),
// _getData(33),
// _getData(2),
// _getData(5),
// _getData(4));
// // الخروج من الصفحة بعد الرفض
// Get.back();
// }
// // }

View File

@@ -0,0 +1,228 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.dart';
import 'package:siro_driver/constant/api_key.dart';
import 'dart:math' as math;
import '../../../../constant/colors.dart';
import '../../../../controller/home/captin/home_captain_controller.dart';
class OrderRequestPageTest extends StatefulWidget {
const OrderRequestPageTest({super.key});
@override
State<OrderRequestPageTest> createState() => _OrderRequestPageTestState();
}
class _OrderRequestPageTestState extends State<OrderRequestPageTest> {
late OrderRequestController _orderRequestController;
@override
void initState() {
super.initState();
// Initialize the controller and process arguments
_initializeController();
}
void _initializeController() {
// Get the controller or create a new one if not exists
_orderRequestController = Get.put(OrderRequestController());
// Process arguments passed to the page
final arguments = Get.arguments;
final myListString = arguments['myListString'];
var myList =
arguments['DriverList'] == null || arguments['DriverList'].isEmpty
? jsonDecode(myListString)
: arguments['DriverList'];
// Parse coordinates and prepare map data
_orderRequestController.parseCoordinates(myList);
// Start timer and calculate fuel consumption
_orderRequestController.startTimer(
myList[6].toString(),
myList[16].toString(),
);
_orderRequestController.calculateConsumptionFuel();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 6),
child: Container(
color: const Color.fromARGB(255, 241, 238, 238),
child: ListView(
children: [
SizedBox(
height: Get.height * .33,
child: Obx(() => IntaleqMap(
apiKey: AK.mapAPIKEY,
initialCameraPosition: CameraPosition(
zoom: 12,
target:
Get.find<HomeCaptainController>().myLocation,
),
cameraTargetBounds: CameraTargetBounds(
_orderRequestController.mapBounds.value),
myLocationButtonEnabled: true,
// trafficEnabled: false,
// buildingsEnabled: false,
// mapToolbarEnabled: true,
myLocationEnabled: true,
markers: _orderRequestController.markers.value,
polylines: _orderRequestController.polylines.value,
onMapCreated: (IntaleqMapController controller) {
_orderRequestController.mapController.value =
controller;
},
onCameraMove: (CameraPosition position) {
_orderRequestController
.updateCameraPosition(position);
},
)),
),
// Rest of your UI components
],
),
),
),
],
),
);
}
}
class OrderRequestController extends GetxController {
// Reactive variables for map-related data
Rx<LatLngBounds> mapBounds = Rx<LatLngBounds>(LatLngBounds(
southwest: const LatLng(0, 0), northeast: const LatLng(0, 0)));
Rx<Set<Marker>> markers = Rx<Set<Marker>>({});
Rx<Set<Polyline>> polylines = Rx<Set<Polyline>>({});
Rx<IntaleqMapController?> mapController = Rx<IntaleqMapController?>(null);
// Icons for start and end markers
late InlqBitmap startIcon;
late InlqBitmap endIcon;
// Coordinates for passenger location and destination
Rx<LatLng?> passengerLocation = Rx<LatLng?>(null);
Rx<LatLng?> passengerDestination = Rx<LatLng?>(null);
@override
void onInit() {
super.onInit();
// Initialize marker icons
_initializeMarkerIcons();
}
void _initializeMarkerIcons() async {
// Load custom marker icons
startIcon = InlqBitmap.fromAsset('assets/start_marker.png');
endIcon = InlqBitmap.fromAsset('assets/end_marker.png');
}
void parseCoordinates(List myList) {
// Parse coordinates from the input list
var cords = myList[0].split(',');
var cordDestination = myList[1].split(',');
double latPassengerLocation = double.parse(cords[0]);
double lngPassengerLocation = double.parse(cords[1]);
double latPassengerDestination = double.parse(cordDestination[0]);
double lngPassengerDestination = double.parse(cordDestination[1]);
// Update passenger location and destination
passengerLocation.value =
LatLng(latPassengerLocation, lngPassengerLocation);
passengerDestination.value =
LatLng(latPassengerDestination, lngPassengerDestination);
// Create markers
_createMarkers();
// Create polyline
_createPolyline();
// Calculate map bounds
_calculateMapBounds();
}
void _createMarkers() {
if (passengerLocation.value == null || passengerDestination.value == null)
return;
markers.value = {
Marker(
markerId: MarkerId('MyLocation'.tr),
position: passengerLocation.value!,
draggable: true,
icon: startIcon,
),
Marker(
markerId: MarkerId('Destination'.tr),
position: passengerDestination.value!,
draggable: true,
icon: endIcon,
),
};
}
void _createPolyline() {
if (passengerLocation.value == null || passengerDestination.value == null)
return;
polylines.value = {
Polyline(
zIndex: 1,
// consumeTapEvents: true,
geodesic: true,
// endCap: Cap.buttCap,
// startCap: Cap.buttCap,
visible: true,
polylineId: const PolylineId('routeOrder'),
points: [passengerLocation.value!, passengerDestination.value!],
color: AppColor.primaryColor,
width: 2,
),
};
}
void _calculateMapBounds() {
if (passengerLocation.value == null || passengerDestination.value == null)
return;
double minLatitude = math.min(passengerLocation.value!.latitude,
passengerDestination.value!.latitude);
double maxLatitude = math.max(passengerLocation.value!.latitude,
passengerDestination.value!.latitude);
double minLongitude = math.min(passengerLocation.value!.longitude,
passengerDestination.value!.longitude);
double maxLongitude = math.max(passengerLocation.value!.longitude,
passengerDestination.value!.longitude);
mapBounds.value = LatLngBounds(
southwest: LatLng(minLatitude, minLongitude),
northeast: LatLng(maxLatitude, maxLongitude),
);
}
void updateCameraPosition(CameraPosition position) {
// Implement any specific logic for camera position updates
}
void startTimer(String param1, String param2) {
// Implement timer start logic
}
void calculateConsumptionFuel() {
// Implement fuel consumption calculation
}
}

View File

@@ -0,0 +1,237 @@
import 'dart:convert';
import 'package:siro_driver/constant/box_name.dart';
import 'package:siro_driver/constant/links.dart';
import 'package:siro_driver/controller/functions/crud.dart';
import 'package:siro_driver/views/widgets/my_scafold.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../../main.dart';
import '../../../../print.dart';
class VipOrderPage extends StatelessWidget {
const VipOrderPage({super.key});
@override
Widget build(BuildContext context) {
Get.put(VipOrderController());
return MyScafolld(
title: 'VIP Order'.tr,
body: [
GetBuilder<VipOrderController>(builder: (vipOrderController) {
if (vipOrderController.isLoading) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (vipOrderController.tripData.isEmpty) {
return Center(
child: Text('No orders available'.tr),
);
}
final order = vipOrderController.tripData[0];
Log.print('order: ${order}');
return SafeArea(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Passenger Section
_buildSectionTitle('Passenger Information'.tr),
_buildInfoCard(
children: [
_buildDetailRow('Name'.tr,
'${order['passengerName'] ?? 'Unknown'} ${order['passengerLastName'] ?? ''}'),
_buildDetailRow(
'Phone'.tr, order['passengerPhone'] ?? 'Unknown'),
_buildDetailRow(
'Gender'.tr, order['passengergender'] ?? 'Unknown'),
_buildDetailRow('time Selected'.tr,
order['timeSelected'] ?? 'Unknown'),
_buildDetailRow(
'Ride Status'.tr, order['status'] ?? 'Unknown'),
// _buildDetailRow('Ride Status'.tr,
// vipOrderController.myList[4] ?? 'Unknown'),
IconButton(
onPressed: () {
// print(vipOrderController.myList);
},
icon: const Icon(Icons.add),
),
],
),
const SizedBox(height: 24),
// Action Buttons
_buildActionButtons(context),
],
),
),
);
})
],
isleading: true,
);
}
}
Widget _buildSectionTitle(String title) {
return Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
);
}
Widget _buildInfoCard({required List<Widget> children}) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.2),
spreadRadius: 1,
blurRadius: 5,
offset: const Offset(0, 3),
),
],
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: children,
),
),
);
}
Widget _buildDetailRow(String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
flex: 2,
child: Text(
label,
style: const TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
),
Expanded(
flex: 3,
child: Text(
value,
style: const TextStyle(color: Colors.black54),
),
),
],
),
);
}
Widget _buildActionButtons(BuildContext context) {
return Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () => _onReject(context),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text(
'Reject'.tr,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
),
const SizedBox(width: 16),
Expanded(
child: ElevatedButton(
onPressed: () => _onApply(context),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text(
'Apply'.tr,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
),
],
);
}
void _onReject(BuildContext context) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Ride Rejected'),
backgroundColor: Colors.red,
),
);
}
void _onApply(BuildContext context) {
// TODO: Implement application logic
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Ride Applied'),
backgroundColor: Colors.green,
),
);
}
class VipOrderController extends GetxController {
bool isLoading = false;
List tripData = [];
fetchOrder() async {
isLoading = true; // Set loading state
update(); // Notify listeners
var res = await CRUD().get(link: AppLink.getMishwariDriver, payload: {
'driverId': box.read(BoxName.driverID).toString(),
});
if (res != 'failure') {
tripData = jsonDecode(res)['message'];
} else {
tripData = [];
}
isLoading = false; // Loading complete
update(); // Notify listeners
}
@override
void onInit() async {
fetchOrder();
super.onInit();
}
}