371 lines
13 KiB
Dart
Executable File
371 lines
13 KiB
Dart
Executable File
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:get/get.dart';
|
|
import 'dart:math';
|
|
|
|
import '../../constant/box_name.dart';
|
|
import '../../constant/colors.dart';
|
|
import '../../constant/links.dart';
|
|
import '../../constant/style.dart';
|
|
import '../../controller/firebase/firbase_messge.dart';
|
|
import '../../controller/firebase/notification_service.dart';
|
|
import '../../controller/functions/crud.dart';
|
|
import '../../controller/home/captin/home_captain_controller.dart';
|
|
import '../../controller/notification/ride_available_controller.dart';
|
|
import '../../main.dart';
|
|
import '../home/Captin/driver_map_page.dart';
|
|
import '../widgets/my_scafold.dart';
|
|
import '../widgets/mycircular.dart';
|
|
import '../widgets/mydialoug.dart';
|
|
|
|
// --- Placeholder Classes and Variables (for demonstration) ---
|
|
// These are dummy implementations to make the code runnable.
|
|
// You should use your actual project files.
|
|
|
|
// --- End of Placeholder Classes ---
|
|
|
|
class AvailableRidesPage extends StatelessWidget {
|
|
const AvailableRidesPage({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
// Use findOrPut to avoid re-creating the controller on rebuilds
|
|
Get.lazyPut(() => RideAvailableController());
|
|
Get.lazyPut(() => HomeCaptainController());
|
|
|
|
return GetBuilder<RideAvailableController>(
|
|
builder: (rideAvailableController) {
|
|
// rideAvailableController.sortRidesByDistance(); // Original logic
|
|
return MyScafolld(
|
|
title: 'Available for rides'.tr,
|
|
body: [
|
|
rideAvailableController.isLoading
|
|
? const MyCircularProgressIndicator()
|
|
: Builder(
|
|
builder: (context) {
|
|
// Filtering logic remains the same
|
|
final filteredRides = rideAvailableController
|
|
.rideAvailableMap['message']
|
|
.where((rideInfo) {
|
|
var driverType =
|
|
box.read(BoxName.carTypeOfDriver).toString();
|
|
switch (driverType) {
|
|
case 'Comfort':
|
|
return ['Speed', 'Comfort']
|
|
.contains(rideInfo['carType']);
|
|
case 'Speed':
|
|
case 'Scooter':
|
|
case 'Awfar Car':
|
|
return rideInfo['carType'] == driverType;
|
|
case 'Lady':
|
|
return ['Comfort', 'Speed', 'Lady']
|
|
.contains(rideInfo['carType']);
|
|
default:
|
|
return false;
|
|
}
|
|
}).toList();
|
|
|
|
if (filteredRides.isEmpty) {
|
|
return Center(
|
|
child: Text(
|
|
"No rides available for your vehicle type.".tr,
|
|
style: AppStyle.subtitle,
|
|
),
|
|
);
|
|
}
|
|
|
|
return ListView.builder(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 12, vertical: 16),
|
|
itemCount: filteredRides.length,
|
|
itemBuilder: (context, index) {
|
|
return RideAvailableCard(
|
|
rideInfo: filteredRides[index],
|
|
);
|
|
},
|
|
);
|
|
},
|
|
)
|
|
],
|
|
isleading: true);
|
|
});
|
|
}
|
|
}
|
|
|
|
class RideAvailableCard extends StatelessWidget {
|
|
final Map<String, dynamic> rideInfo;
|
|
|
|
const RideAvailableCard({Key? key, required this.rideInfo}) : super(key: key);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
// The main card with improved styling
|
|
return Card(
|
|
margin: const EdgeInsets.only(bottom: 16.0),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
|
elevation: 5,
|
|
shadowColor: Colors.black.withOpacity(0.1),
|
|
child: InkWell(
|
|
borderRadius: BorderRadius.circular(16),
|
|
onTap: () {
|
|
// You can add an action here, e.g., show ride details on a map
|
|
},
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_buildHeader(),
|
|
const SizedBox(height: 16),
|
|
_buildRouteInfo(),
|
|
const Divider(height: 32),
|
|
_buildRideDetails(),
|
|
const SizedBox(height: 20),
|
|
_buildAcceptButton(),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Header section with Price and Car Type
|
|
Widget _buildHeader() {
|
|
return Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text('Fare'.tr, style: AppStyle.subtitle.copyWith(fontSize: 12)),
|
|
const SizedBox(height: 4),
|
|
Text('${rideInfo['price']} \$',
|
|
style: AppStyle.title
|
|
.copyWith(fontSize: 24, color: AppColor.primaryColor)),
|
|
],
|
|
),
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
|
decoration: BoxDecoration(
|
|
color: AppColor.greenColor.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
child: Text(
|
|
rideInfo['carType'],
|
|
style: AppStyle.title
|
|
.copyWith(color: AppColor.greenColor, fontSize: 12),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
// Visual representation of the pickup and dropoff route
|
|
Widget _buildRouteInfo() {
|
|
return Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Dotted line and icons column
|
|
Column(
|
|
children: [
|
|
const Icon(CupertinoIcons.circle_fill,
|
|
color: AppColor.greenColor, size: 20),
|
|
...List.generate(
|
|
4,
|
|
(index) => Container(
|
|
height: 4,
|
|
width: 2,
|
|
color: AppColor.writeColor,
|
|
margin: const EdgeInsets.symmetric(vertical: 2),
|
|
)),
|
|
const Icon(CupertinoIcons.location_solid,
|
|
color: Colors.red, size: 20),
|
|
],
|
|
),
|
|
const SizedBox(width: 16),
|
|
// Location text column
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_buildLocationText(rideInfo['startName'], 'Pickup'.tr),
|
|
const SizedBox(height: 20),
|
|
_buildLocationText(rideInfo['endName'], 'Dropoff'.tr),
|
|
],
|
|
),
|
|
)
|
|
],
|
|
);
|
|
}
|
|
|
|
// Helper for location text
|
|
Widget _buildLocationText(String location, String label) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(label, style: AppStyle.subtitle.copyWith(fontSize: 12)),
|
|
const SizedBox(height: 2),
|
|
Text(
|
|
location,
|
|
style: AppStyle.title.copyWith(fontWeight: FontWeight.normal),
|
|
maxLines: 2,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
// Ride details section with Distance and Passenger Rating
|
|
Widget _buildRideDetails() {
|
|
return Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
children: [
|
|
_buildInfoChip(
|
|
icon: CupertinoIcons.map_pin_ellipse,
|
|
value: '${rideInfo['distance']} ${'KM'.tr}',
|
|
label: 'Distance'.tr,
|
|
color: AppColor.primaryColor,
|
|
),
|
|
_buildInfoChip(
|
|
icon: CupertinoIcons.star_fill,
|
|
value: '${rideInfo['passengerRate']}',
|
|
label: 'Rating'.tr,
|
|
color: Colors.amber,
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
// A reusable chip for displaying info with an icon
|
|
Widget _buildInfoChip(
|
|
{required IconData icon,
|
|
required String value,
|
|
required String label,
|
|
required Color color}) {
|
|
return Column(
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(icon, color: color, size: 16),
|
|
const SizedBox(width: 8),
|
|
Text(value, style: AppStyle.title),
|
|
],
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(label, style: AppStyle.subtitle.copyWith(fontSize: 12)),
|
|
],
|
|
);
|
|
}
|
|
|
|
// The accept button with improved styling
|
|
Widget _buildAcceptButton() {
|
|
return SizedBox(
|
|
width: double.infinity,
|
|
child: ElevatedButton.icon(
|
|
icon: const Icon(Icons.check_circle_outline, color: Colors.white),
|
|
label: Text('Accept'.tr,
|
|
style: const TextStyle(
|
|
color: Colors.white, fontWeight: FontWeight.bold)),
|
|
onPressed: _acceptRide,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: AppColor.greenColor,
|
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
|
shape:
|
|
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
elevation: 2,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// --- Ride Acceptance Logic ---
|
|
// This logic is copied exactly from your original code.
|
|
void _acceptRide() async {
|
|
var res = await CRUD().post(
|
|
link: '${AppLink.endPoint}rides/updateStausFromSpeed.php',
|
|
payload: {
|
|
'id': rideInfo['id'],
|
|
'rideTimeStart': DateTime.now().toString(),
|
|
'status': 'Apply',
|
|
'driver_id': box.read(BoxName.driverID),
|
|
});
|
|
|
|
if (res != "failure") {
|
|
List<String> bodyToPassenger = [
|
|
box.read(BoxName.driverID).toString(),
|
|
box.read(BoxName.nameDriver).toString(),
|
|
box.read(BoxName.tokenDriver).toString(),
|
|
];
|
|
box.write(BoxName.statusDriverLocation, 'on');
|
|
await CRUD().postFromDialogue(link: AppLink.addDriverOrder, payload: {
|
|
'driver_id': box.read(BoxName.driverID),
|
|
'order_id': rideInfo['id'],
|
|
'status': 'Apply'
|
|
});
|
|
// await CRUD().post(link: AppLink.updateRides, payload: {
|
|
// 'id': rideInfo['id'],
|
|
// 'DriverIsGoingToPassenger': DateTime.now().toString(),
|
|
// 'status': 'Applied'
|
|
// });
|
|
await CRUD().post(
|
|
link: AppLink.updateWaitingRide,
|
|
payload: {'id': rideInfo['id'], 'status': 'Applied'});
|
|
// if (AppLink.endPoint.toString() != AppLink.seferCairoServer) {
|
|
|
|
NotificationService.sendNotification(
|
|
target: rideInfo['passengerToken'].toString(),
|
|
title: 'Accepted Ride'.tr,
|
|
body: 'your ride is Accepted'.tr,
|
|
isTopic: false, // Important: this is a token
|
|
tone: 'start',
|
|
driverList: bodyToPassenger, category: 'Accepted Ride',
|
|
);
|
|
Get.back();
|
|
Get.to(() => PassengerLocationMapPage(), arguments: {
|
|
'passengerLocation': rideInfo['start_location'].toString(),
|
|
'passengerDestination': rideInfo['end_location'].toString(),
|
|
'Duration': rideInfo['duration'].toString(),
|
|
'totalCost': rideInfo['price'].toString(),
|
|
'Distance': rideInfo['distance'].toString(),
|
|
'name': rideInfo['first_name'].toString(),
|
|
'phone': rideInfo['phone'].toString(),
|
|
'email': rideInfo['email'].toString(),
|
|
'WalletChecked': rideInfo['payment_method'].toString(),
|
|
'tokenPassenger': rideInfo['passengerToken'].toString(),
|
|
'direction':
|
|
'https://www.google.com/maps/dir/${rideInfo['start_location']}/${rideInfo['end_location']}/',
|
|
'DurationToPassenger': rideInfo['duration'].toString(),
|
|
'rideId': rideInfo['id'].toString(),
|
|
'passengerId': rideInfo['passenger_id'].toString(),
|
|
'driverId': box.read(BoxName.driverID).toString(),
|
|
'durationOfRideValue': rideInfo['duration'].toString(),
|
|
'paymentAmount': rideInfo['price'].toString(),
|
|
'paymentMethod': 'cash'.toString() == 'true' ? 'visa' : 'cash',
|
|
'isHaveSteps': 'startEnd'.toString(),
|
|
'step0': ''.toString(),
|
|
'step1': ''.toString(),
|
|
'step2': ''.toString(),
|
|
'step3': ''.toString(),
|
|
'step4': ''.toString(),
|
|
'passengerWalletBurc': rideInfo['bruc'].toString(),
|
|
'timeOfOrder': DateTime.now().toString(),
|
|
'totalPassenger': rideInfo['price'].toString(),
|
|
'carType': rideInfo['carType'].toString(),
|
|
'kazan': Get.find<HomeCaptainController>().kazan.toString(),
|
|
'startNameLocation': rideInfo['startName'].toString(),
|
|
'endNameLocation': rideInfo['endName'].toString(),
|
|
});
|
|
} else {
|
|
MyDialog().getDialog(
|
|
"This ride is already taken by another driver.".tr, '', () {
|
|
CRUD().post(
|
|
link: AppLink.deleteAvailableRide, payload: {'id': rideInfo['id']});
|
|
|
|
Get.back();
|
|
});
|
|
}
|
|
}
|
|
}
|