Files
intaleq_driver/lib/views/notification/available_rides_page.dart
Hamza-Ayed 3c0ae4cf2f 26-1-20/1
2026-01-20 10:11:10 +03:00

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(),
);
}
}
}