370 lines
14 KiB
Dart
Executable File
370 lines
14 KiB
Dart
Executable File
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 '../../constant/box_name.dart';
|
|
import '../../constant/colors.dart';
|
|
import '../../constant/links.dart';
|
|
import '../../constant/style.dart';
|
|
import '../../controller/functions/crud.dart';
|
|
import '../../controller/home/captin/home_captain_controller.dart';
|
|
import '../../controller/notification/ride_available_controller.dart';
|
|
import '../../main.dart'; // للوصول للـ box
|
|
import '../home/Captin/driver_map_page.dart';
|
|
import '../widgets/my_scafold.dart';
|
|
import '../widgets/mycircular.dart';
|
|
import '../widgets/mydialoug.dart';
|
|
|
|
class AvailableRidesPage extends StatelessWidget {
|
|
const AvailableRidesPage({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
// حقن الكنترولر (تأكد أنك تستخدم الكود الجديد الذي أعطيتك إياه للكنترولر)
|
|
Get.lazyPut(() => RideAvailableController());
|
|
Get.lazyPut(() => HomeCaptainController());
|
|
|
|
return GetBuilder<RideAvailableController>(
|
|
builder: (controller) {
|
|
return MyScafolld(
|
|
title: 'Available for rides'.tr,
|
|
isleading: true,
|
|
body: [
|
|
controller.isLoading
|
|
? const Center(
|
|
child: Padding(
|
|
padding: EdgeInsets.only(top: 50.0),
|
|
child: MyCircularProgressIndicator(),
|
|
))
|
|
: Builder(
|
|
builder: (context) {
|
|
// 1. الفلترة حسب نوع السيارة (تم نقل المنطق للكنترولر، لكن هنا للعرض فقط)
|
|
// الكنترولر الجديد يفلتر عند الإضافة، لكن لا ضرر من التأكيد هنا
|
|
final ridesList = controller.availableRides;
|
|
|
|
if (ridesList.isEmpty) {
|
|
return Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
const SizedBox(height: 100),
|
|
Icon(CupertinoIcons.car_detailed,
|
|
size: 60,
|
|
color:
|
|
AppColor.primaryColor.withOpacity(0.5)),
|
|
const SizedBox(height: 20),
|
|
Text(
|
|
"No rides available right now.".tr,
|
|
style: AppStyle.subtitle,
|
|
),
|
|
const SizedBox(height: 20),
|
|
ElevatedButton.icon(
|
|
onPressed: () => controller.getRideAvailable(
|
|
forceRefresh: true),
|
|
icon: const Icon(Icons.refresh),
|
|
label: Text("Refresh Market".tr),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: AppColor.primaryColor,
|
|
foregroundColor: Colors.white,
|
|
),
|
|
)
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// 2. عرض القائمة
|
|
return RefreshIndicator(
|
|
onRefresh: () async {
|
|
await controller.getRideAvailable(forceRefresh: true);
|
|
},
|
|
child: ListView.builder(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 12, vertical: 16),
|
|
itemCount: ridesList.length,
|
|
itemBuilder: (context, index) {
|
|
return RideAvailableCard(
|
|
rideInfo: ridesList[index],
|
|
);
|
|
},
|
|
),
|
|
);
|
|
},
|
|
)
|
|
],
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// بطاقة الرحلة (The Card)
|
|
// =============================================================================
|
|
class RideAvailableCard extends StatelessWidget {
|
|
final Map<String, dynamic> rideInfo;
|
|
|
|
const RideAvailableCard({Key? key, required this.rideInfo}) : super(key: key);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Card(
|
|
margin: const EdgeInsets.only(bottom: 16.0),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
|
elevation: 4,
|
|
shadowColor: Colors.black.withOpacity(0.1),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_buildHeader(),
|
|
const SizedBox(height: 16),
|
|
_buildRouteInfo(),
|
|
const Divider(height: 24, thickness: 0.5),
|
|
_buildRideDetails(),
|
|
const SizedBox(height: 20),
|
|
_buildAcceptButton(),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// تصميم البطاقة (Header, Route, Details)
|
|
// ---------------------------------------------------------------------------
|
|
Widget _buildHeader() {
|
|
return Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text('Price'.tr, style: AppStyle.subtitle.copyWith(fontSize: 12)),
|
|
Text(
|
|
'${rideInfo['price']} ${'SYP'.tr}', // العملة
|
|
style: AppStyle.title.copyWith(
|
|
fontSize: 20, color: AppColor.primaryColor, height: 1.2),
|
|
),
|
|
],
|
|
),
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
|
decoration: BoxDecoration(
|
|
color: AppColor.greenColor.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(20),
|
|
border: Border.all(color: AppColor.greenColor.withOpacity(0.3)),
|
|
),
|
|
child: Text(
|
|
rideInfo['carType'] ?? 'Fixed Price'.tr,
|
|
style: AppStyle.title
|
|
.copyWith(color: AppColor.greenColor, fontSize: 13),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildRouteInfo() {
|
|
return Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Column(
|
|
children: [
|
|
const Icon(Icons.my_location,
|
|
color: AppColor.primaryColor, size: 18),
|
|
Container(
|
|
height: 30,
|
|
width: 1,
|
|
color: Colors.grey.shade300,
|
|
margin: const EdgeInsets.symmetric(vertical: 4),
|
|
),
|
|
const Icon(Icons.location_on, color: Colors.red, size: 18),
|
|
],
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(rideInfo['startName'] ?? 'Unknown Location'.tr,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: AppStyle.title.copyWith(fontSize: 14)),
|
|
const SizedBox(height: 22),
|
|
Text(rideInfo['endName'] ?? 'Destination'.tr,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: AppStyle.title.copyWith(fontSize: 14)),
|
|
],
|
|
),
|
|
)
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildRideDetails() {
|
|
return Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
children: [
|
|
_infoItem(Icons.social_distance, '${rideInfo['distance']} KM'),
|
|
_infoItem(Icons.access_time, '${rideInfo['duration']} Min'),
|
|
_infoItem(Icons.star, '${rideInfo['passengerRate'] ?? 5.0}',
|
|
iconColor: Colors.amber),
|
|
],
|
|
);
|
|
}
|
|
|
|
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)),
|
|
],
|
|
);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// زر القبول والمنطق الكامل (Accept Logic) 🔥
|
|
// ---------------------------------------------------------------------------
|
|
Widget _buildAcceptButton() {
|
|
return SizedBox(
|
|
width: double.infinity,
|
|
height: 50,
|
|
child: ElevatedButton(
|
|
onPressed: _acceptRideNewLogic,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: AppColor.greenColor,
|
|
shape:
|
|
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
elevation: 2,
|
|
),
|
|
child: Text(
|
|
'Accept Ride'.tr,
|
|
style: const TextStyle(
|
|
color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// 🔥🔥🔥 الوظيفة الأهم: قبول الرحلة وتجهيز البيانات 🔥🔥🔥
|
|
void _acceptRideNewLogic() async {
|
|
// 1. إظهار Loading
|
|
Get.dialog(
|
|
const Center(child: MyCircularProgressIndicator()),
|
|
barrierDismissible: false,
|
|
);
|
|
|
|
try {
|
|
String driverId = box.read(BoxName.driverID).toString();
|
|
|
|
// 2. إرسال الطلب للسيرفر (acceptRide.php الجديد)
|
|
var response = await CRUD().post(
|
|
link: "${AppLink.ride}/rides/acceptRide.php",
|
|
payload: {
|
|
'id': rideInfo['id'].toString(),
|
|
'driver_id': driverId,
|
|
'status': 'Apply', // الحالة المتفق عليها
|
|
'passengerToken': rideInfo['passengerToken'].toString(),
|
|
},
|
|
);
|
|
|
|
// إخفاء الـ Loading
|
|
Get.back();
|
|
|
|
// 3. تحليل الرد
|
|
var jsonResponse = jsonDecode(response);
|
|
|
|
if (jsonResponse['status'] == 'success') {
|
|
// ✅ نجاح: أنت الفائز بالرحلة
|
|
|
|
// تحديث حالة السائق محلياً
|
|
Get.find<HomeCaptainController>().changeRideId();
|
|
box.write(BoxName.statusDriverLocation, 'on');
|
|
|
|
// 🔥 تجهيز الـ Arguments كاملة (Mapping) 📦
|
|
// نأخذ البيانات من rideInfo (القادمة من getRideWaiting) ونمررها للخريطة
|
|
Map<String, dynamic> fullRideArgs = {
|
|
// معرفات الرحلة
|
|
'rideId': rideInfo['id'].toString(),
|
|
'passengerId': rideInfo['passengerId'].toString(),
|
|
'driverId': driverId,
|
|
|
|
// المواقع (يجب أن تكون Strings بصيغة "lat,lng")
|
|
'passengerLocation': rideInfo['start_location'].toString(),
|
|
'passengerDestination': rideInfo['end_location'].toString(),
|
|
|
|
// الأسماء والعناوين
|
|
'startNameLocation': rideInfo['startName'].toString(),
|
|
'endNameLocation': rideInfo['endName'].toString(),
|
|
|
|
// تفاصيل الراكب
|
|
'name': rideInfo['first_name'] ?? 'Passenger',
|
|
'phone': rideInfo['phone'].toString(),
|
|
'email': rideInfo['email'] ?? '',
|
|
'tokenPassenger': rideInfo['passengerToken'].toString(),
|
|
'passengerWalletBurc': rideInfo['bruc'].toString(), // رصيد الراكب
|
|
|
|
// التفاصيل المالية والرحلة
|
|
'totalCost': rideInfo['price'].toString(), // السعر الكلي
|
|
'paymentAmount': rideInfo['price'].toString(), // المبلغ المطلوب
|
|
'Distance': rideInfo['distance'].toString(),
|
|
'Duration': rideInfo['duration'].toString(),
|
|
'durationOfRideValue':
|
|
rideInfo['duration'].toString(), // تكرار للتأكد
|
|
'carType': rideInfo['carType'].toString(),
|
|
|
|
// الدفع والمحفظة
|
|
'paymentMethod': (rideInfo['payment_method'] == 'visa' ||
|
|
rideInfo['payment_method'] == 'wallet')
|
|
? 'visa'
|
|
: 'cash',
|
|
'WalletChecked':
|
|
rideInfo['passenger_wallet'].toString() != '0' ? 'true' : 'false',
|
|
'kazan': Get.find<HomeCaptainController>()
|
|
.kazan
|
|
.toString(), // نسبة الشركة (من الكنترولر)
|
|
|
|
// بيانات إضافية (لتجنب الـ Null Safety errors)
|
|
'direction':
|
|
'http://googleusercontent.com/maps.google.com/maps?saddr=${rideInfo['start_location']}&daddr=${rideInfo['end_location']}',
|
|
'timeOfOrder': DateTime.now().toString(),
|
|
'isHaveSteps': 'false', // لو كان عندك خطوات في الـ waitingRides ضيفها
|
|
'step0': '', 'step1': '', 'step2': '', 'step3': '', 'step4': '',
|
|
};
|
|
|
|
// حفظ البيانات في الصندوق احتياطياً (Crash Recovery)
|
|
box.write(BoxName.rideArguments, fullRideArgs);
|
|
|
|
// الانتقال لصفحة الخريطة ومسح الصفحات السابقة لضمان عدم الرجوع للسوق
|
|
Get.offAll(() => PassengerLocationMapPage(), arguments: fullRideArgs);
|
|
} else {
|
|
// ❌ فشل: الرحلة أخذها سائق آخر
|
|
// نقوم بتحديث القائمة فوراً
|
|
Get.find<RideAvailableController>()
|
|
.getRideAvailable(forceRefresh: true);
|
|
|
|
MyDialog().getDialog(
|
|
"Trip taken".tr,
|
|
"This ride was just accepted by another driver.".tr,
|
|
() => Get.back(), // زر الموافقة
|
|
);
|
|
}
|
|
} catch (e) {
|
|
Get.back(); // إخفاء اللودينج في حال الخطأ
|
|
print("Accept Ride Error: $e");
|
|
MyDialog().getDialog(
|
|
"Error".tr,
|
|
"An unexpected error occurred. Please try again.".tr,
|
|
() => Get.back(),
|
|
);
|
|
}
|
|
}
|
|
}
|