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,404 @@
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intaleq_maps/intaleq_maps.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: Theme.of(context).shadowColor.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),
),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
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),
),
),
if (rideInfo['has_steps']?.toString() == 'true') ...[
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.orange.shade100,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.orange),
),
child: Row(
children: [
Icon(Icons.alt_route, color: Colors.orange.shade800, size: 14),
const SizedBox(width: 4),
Text(
'متعددة التوقفات',
style: AppStyle.subtitle.copyWith(
color: Colors.orange.shade800,
fontSize: 11,
fontWeight: FontWeight.bold),
),
],
),
),
]
],
),
],
);
}
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: Theme.of(Get.context!).dividerColor,
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}) {
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)),
],
);
});
}
// ---------------------------------------------------------------------------
// زر القبول والمنطق الكامل (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 = 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': rideInfo['has_steps']?.toString() ?? 'false',
'step0': rideInfo['step0'] ?? '',
'step1': rideInfo['step1'] ?? '',
'step2': rideInfo['step2'] ?? '',
'step3': rideInfo['step3'] ?? '',
'step4': rideInfo['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(),
);
}
}
}

View File

@@ -0,0 +1,87 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_driver/controller/notification/notification_captain_controller.dart';
import 'package:flutter/cupertino.dart';
class NotificationCaptain extends StatelessWidget {
const NotificationCaptain({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
Get.put(NotificationCaptainController());
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('Notifications'.tr),
leading: CupertinoNavigationBarBackButton(
onPressed: () => Get.back(),
),
),
child: SafeArea(
child: GetBuilder<NotificationCaptainController>(
builder: (notificationCaptainController) =>
notificationCaptainController.isLoading
? const Center(child: CupertinoActivityIndicator())
: ListView.builder(
itemCount: notificationCaptainController
.notificationData['message'].length,
itemBuilder: (BuildContext context, int index) {
if (notificationCaptainController
.notificationData['message'] ==
"No notification data found") {
_showCupertinoDialog(context, 'No Notifications',
'There are no notifications at this time.');
return const SizedBox.shrink();
}
var res = notificationCaptainController
.notificationData['message'][index];
return CupertinoListTile(
leading: const Icon(CupertinoIcons.bell_fill),
title: Text(
res['title'],
style:
CupertinoTheme.of(context).textTheme.textStyle,
),
subtitle: Text(
res['body'],
style: CupertinoTheme.of(context)
.textTheme
.tabLabelTextStyle,
),
onTap: () {
_showCupertinoDialog(
context,
res['title'],
res['body'],
onConfirm: () {
notificationCaptainController
.updateNotification(res['id'].toString());
Navigator.of(context).pop();
},
);
},
);
},
),
),
),
);
}
void _showCupertinoDialog(BuildContext context, String title, String content,
{VoidCallback? onConfirm}) {
showCupertinoDialog(
context: context,
builder: (BuildContext context) => CupertinoAlertDialog(
title: Text(title),
content: Text(content),
actions: <CupertinoDialogAction>[
CupertinoDialogAction(
child: const Text('OK'),
onPressed: onConfirm ?? () => Navigator.of(context).pop(),
),
],
),
);
}
}

View File

@@ -0,0 +1,83 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_driver/constant/colors.dart';
import 'package:siro_driver/constant/style.dart';
import '../../controller/notification/passenger_notification_controller.dart';
import '../widgets/elevated_btn.dart';
import '../widgets/my_scafold.dart';
import '../widgets/mycircular.dart';
class NotificationPage extends StatelessWidget {
const NotificationPage({super.key});
@override
Widget build(BuildContext context) {
Get.put(PassengerNotificationController());
return MyScafolld(
isleading: true,
title: 'Notifications',
body: [
GetBuilder<PassengerNotificationController>(
builder: (notificationCaptainController) =>
notificationCaptainController.isloading
? const MyCircularProgressIndicator()
: SafeArea(
child: ListView.builder(
itemCount: notificationCaptainController
.notificationData['message'].length,
itemBuilder: (BuildContext context, int index) {
if (notificationCaptainController
.notificationData['message'] ==
"No notification data found") {
Get.defaultDialog();
}
var res = notificationCaptainController
.notificationData['message'][index];
return Card(
elevation: 4,
color: res['isShown'] == 'true'
? AppColor.secondaryColor.withOpacity(.5)
: AppColor.secondaryColor.withOpacity(.9),
child: ListTile(
onTap: () {
Get.defaultDialog(
title: res['title'],
titleStyle: AppStyle.title,
content: SizedBox(
width: Get.width * .8,
// height: Get.height * .4,
child: Text(
res['body'],
style: AppStyle.title,
),
),
confirm: MyElevatedButton(
title: 'Ok',
onPressed: () {
notificationCaptainController
.updateNotification(
res['id'].toString());
}));
},
leading: res['isShown'] == 'true'
? const Icon(
Icons.notifications_off_outlined)
: const Icon(Icons.notifications_active),
title: Text(
res['title'],
style: AppStyle.title,
),
subtitle: Text(
res['body'],
style: AppStyle.subtitle,
),
),
);
},
),
))
],
);
}
}