25-7-28-2
This commit is contained in:
25
lib/views/home/Captin/orderCaptin/call.dart
Executable file
25
lib/views/home/Captin/orderCaptin/call.dart
Executable 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(),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
811
lib/views/home/Captin/orderCaptin/order_over_lay.dart
Executable file
811
lib/views/home/Captin/orderCaptin/order_over_lay.dart
Executable file
@@ -0,0 +1,811 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_overlay_window/flutter_overlay_window.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:just_audio/just_audio.dart';
|
||||
import 'package:sefer_driver/constant/api_key.dart';
|
||||
import '../../../../constant/box_name.dart';
|
||||
import '../../../../constant/links.dart';
|
||||
import '../../../../controller/firebase/local_notification.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.updateStausFromSpeed, payload: {
|
||||
'id': orderData!.orderId,
|
||||
'rideTimeStart': DateTime.now().toString(),
|
||||
'status': 'Apply',
|
||||
'driver_id': box.read(BoxName.driverID),
|
||||
});
|
||||
if (AppLink.endPoint != AppLink.seferCairoServer) {
|
||||
CRUD().post(
|
||||
link: "${AppLink.endPoint}/ride/rides/updateStausFromSpeed.php",
|
||||
payload: {
|
||||
'id': orderData!.orderId,
|
||||
'rideTimeStart': DateTime.now().toString(),
|
||||
'status': 'Apply',
|
||||
'driver_id': box.read(BoxName.driverID),
|
||||
});
|
||||
}
|
||||
final payload = {
|
||||
// بيانات أساسية
|
||||
'driver_id': driverId,
|
||||
'status': 'Apply',
|
||||
'passengerLocation': _getData(0),
|
||||
'passengerDestination': _getData(1),
|
||||
'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('myList: ${myList}');
|
||||
Log.print('payload: ${payload}');
|
||||
CRUD().post(
|
||||
link: AppLink.addOverLayStatus,
|
||||
payload: payload,
|
||||
);
|
||||
if (res != "failure") {
|
||||
// Using rideId (_getData(16)) for order_id consistently
|
||||
CRUD().post(link: AppLink.addDriverOrder, payload: {
|
||||
'driver_id': driverId, // Driver ID from the order data
|
||||
'order_id': orderData!.orderId,
|
||||
'status': 'Apply'
|
||||
});
|
||||
|
||||
if (AppLink.endPoint != AppLink.seferCairoServer) {
|
||||
CRUD().post(
|
||||
link: "${AppLink.endPoint}/ride/driver_order/add.php",
|
||||
payload: {
|
||||
'driver_id': driverId,
|
||||
'order_id': orderData!.orderId,
|
||||
'status': 'Apply'
|
||||
});
|
||||
}
|
||||
_log("Server update successful. Writing to storage.");
|
||||
notificationController.showNotification(
|
||||
"Order Accepted".tr,
|
||||
"Open app and go to passenger".tr,
|
||||
'ding',
|
||||
'',
|
||||
);
|
||||
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);
|
||||
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;
|
||||
}
|
||||
await _crud.post(link: AppLink.addDriverOrder, payload: {
|
||||
'driver_id': driverId,
|
||||
'order_id': orderID,
|
||||
'status': 'Refused'
|
||||
});
|
||||
await _crud.post(link: AppLink.updateRides, payload: {
|
||||
'id': orderID,
|
||||
'status': 'Refused',
|
||||
'driver_id': driverId,
|
||||
});
|
||||
_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!;
|
||||
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("\$${order.price}", "السعر".tr,
|
||||
Icons.monetization_on_rounded, AppColors.priceHighlight,
|
||||
isLarge: true),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: _buildHighlightInfo(
|
||||
"${order.tripDistanceKm.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),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
431
lib/views/home/Captin/orderCaptin/order_request_page.dart
Executable file
431
lib/views/home/Captin/orderCaptin/order_request_page.dart
Executable file
@@ -0,0 +1,431 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:sefer_driver/controller/home/captin/home_captain_controller.dart';
|
||||
import 'package:sefer_driver/views/widgets/mydialoug.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:sefer_driver/constant/box_name.dart';
|
||||
import 'package:sefer_driver/controller/firebase/firbase_messge.dart';
|
||||
import 'package:sefer_driver/main.dart';
|
||||
import 'package:sefer_driver/views/home/Captin/driver_map_page.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'dart:math' as math;
|
||||
import '../../../../constant/colors.dart';
|
||||
import '../../../../constant/links.dart';
|
||||
import '../../../../constant/style.dart';
|
||||
import '../../../../controller/functions/crud.dart';
|
||||
import '../../../../controller/functions/encrypt_decrypt.dart';
|
||||
import '../../../../controller/functions/launch.dart';
|
||||
import '../../../../controller/home/captin/order_request_controller.dart';
|
||||
import '../../../widgets/elevated_btn.dart';
|
||||
|
||||
class OrderRequestPage extends StatefulWidget {
|
||||
const OrderRequestPage({super.key});
|
||||
|
||||
@override
|
||||
State<OrderRequestPage> createState() => _OrderRequestPageState();
|
||||
}
|
||||
|
||||
class _OrderRequestPageState extends State<OrderRequestPage> {
|
||||
final OrderRequestController orderRequestController =
|
||||
Get.put(OrderRequestController());
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Order Request'.tr),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: GetBuilder<OrderRequestController>(
|
||||
builder: (controller) {
|
||||
if (controller.myList == null) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
return Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: Get.height * 0.3,
|
||||
child: GoogleMap(
|
||||
mapType: MapType.normal,
|
||||
initialCameraPosition: CameraPosition(
|
||||
target: LatLng(controller.latPassengerLocation,
|
||||
controller.lngPassengerLocation),
|
||||
zoom: 14.0,
|
||||
),
|
||||
myLocationButtonEnabled: true,
|
||||
onMapCreated: controller.onMapCreated,
|
||||
myLocationEnabled: true,
|
||||
markers: {
|
||||
Marker(
|
||||
markerId: const MarkerId('startLocation'),
|
||||
position: LatLng(controller.latPassengerLocation,
|
||||
controller.lngPassengerLocation),
|
||||
icon: controller.startIcon,
|
||||
),
|
||||
Marker(
|
||||
markerId: const MarkerId('destinationLocation'),
|
||||
position: LatLng(controller.latPassengerDestination,
|
||||
controller.lngPassengerDestination),
|
||||
icon: controller.endIcon,
|
||||
),
|
||||
},
|
||||
polylines: {
|
||||
Polyline(
|
||||
polylineId: const PolylineId('route'),
|
||||
color: AppColor.primaryColor,
|
||||
width: 5,
|
||||
points: controller.pointsDirection,
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
Card(
|
||||
elevation: 4,
|
||||
child: ListTile(
|
||||
leading: Icon(
|
||||
controller.myList[13].toString() == 'true'
|
||||
? Icons.credit_card
|
||||
: Icons.money,
|
||||
color: controller.myList[13].toString() == 'true'
|
||||
? AppColor.deepPurpleAccent
|
||||
: AppColor.greenColor,
|
||||
),
|
||||
title: Text(
|
||||
'Payment Method'.tr,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
trailing: Text(
|
||||
controller.myList[13].toString() == 'true'
|
||||
? 'Visa'
|
||||
: 'Cash',
|
||||
style:
|
||||
Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Card(
|
||||
elevation: 4,
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.account_circle,
|
||||
color: AppColor.secondaryColor),
|
||||
title: Text(
|
||||
controller.myList[8],
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
subtitle: Row(
|
||||
children: [
|
||||
const Icon(Icons.star,
|
||||
size: 16, color: Colors.amber),
|
||||
Text(
|
||||
controller.myList[33].toString(),
|
||||
style: const TextStyle(color: Colors.amber),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Card(
|
||||
elevation: 4,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.location_on,
|
||||
color: AppColor.greenColor),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
// Keep Expanded here for layout
|
||||
child: Text(
|
||||
controller.myList[29],
|
||||
style:
|
||||
Theme.of(context).textTheme.titleSmall,
|
||||
maxLines: 2, // Allow up to 2 lines
|
||||
overflow: TextOverflow
|
||||
.ellipsis, // Handle overflow
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Divider(),
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.flag,
|
||||
color: AppColor.redColor),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
// Keep Expanded here for layout
|
||||
child: Text(
|
||||
controller.myList[30],
|
||||
style:
|
||||
Theme.of(context).textTheme.titleSmall,
|
||||
maxLines: 2, // Allow up to 2 lines
|
||||
overflow: TextOverflow
|
||||
.ellipsis, // Handle overflow
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Card(
|
||||
elevation: 4,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
_InfoTile(
|
||||
icon: Icons.timer,
|
||||
label:
|
||||
'${(double.parse(controller.myList[12]) / 60).toStringAsFixed(0)} ${'min'.tr}',
|
||||
),
|
||||
_InfoTile(
|
||||
icon: Icons.directions_car,
|
||||
label:
|
||||
'${(double.parse(controller.myList[11]) / 1000).toStringAsFixed(1)} ${'km'.tr}',
|
||||
),
|
||||
_InfoTile(
|
||||
icon: Icons.monetization_on,
|
||||
label: '${controller.myList[2]}',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
MyElevatedButton(
|
||||
kolor: AppColor.greenColor,
|
||||
title: 'Accept Order'.tr,
|
||||
onPressed: () async {
|
||||
Get.put(HomeCaptainController()).changeRideId();
|
||||
box.write(BoxName.statusDriverLocation, 'on');
|
||||
controller.endTimer();
|
||||
controller.changeApplied();
|
||||
|
||||
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),
|
||||
});
|
||||
if (AppLink.endPoint != AppLink.seferCairoServer) {
|
||||
CRUD().post(
|
||||
link:
|
||||
"${AppLink.endPoint}/ride/rides/updateStausFromSpeed.php",
|
||||
payload: {
|
||||
'id': (controller.myList[16]),
|
||||
'rideTimeStart': DateTime.now().toString(),
|
||||
'status': 'Apply',
|
||||
'driver_id': box.read(BoxName.driverID),
|
||||
});
|
||||
}
|
||||
if (res == 'failure') {
|
||||
MyDialog().getDialog(
|
||||
"This ride is already applied by another driver."
|
||||
.tr,
|
||||
'', () {
|
||||
Get.back();
|
||||
});
|
||||
} else {
|
||||
await CRUD().postFromDialogue(
|
||||
link: AppLink.addDriverOrder,
|
||||
payload: {
|
||||
'driver_id':
|
||||
(controller.myList[6].toString()),
|
||||
'order_id':
|
||||
(controller.myList[16].toString()),
|
||||
'status': 'Apply'
|
||||
});
|
||||
if (AppLink.endPoint !=
|
||||
AppLink.seferCairoServer) {
|
||||
CRUD().postFromDialogue(
|
||||
link:
|
||||
'${AppLink.endPoint}/rides/driver_order/add.php',
|
||||
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(),
|
||||
];
|
||||
FirebaseMessagesController()
|
||||
.sendNotificationToPassengerToken(
|
||||
"Accepted Ride".tr,
|
||||
'your ride is Accepted'.tr,
|
||||
controller.myList[9].toString(),
|
||||
bodyToPassenger,
|
||||
'start.wav');
|
||||
Get.back();
|
||||
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.to(() => PassengerLocationMapPage(),
|
||||
arguments: box.read(BoxName.rideArguments));
|
||||
}
|
||||
},
|
||||
),
|
||||
GetBuilder<OrderRequestController>(
|
||||
builder: (timerController) {
|
||||
final isNearEnd = timerController.remainingTime <=
|
||||
5; // Define a threshold for "near end"
|
||||
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
CircularProgressIndicator(
|
||||
value: timerController.progress,
|
||||
// Set the color based on the "isNearEnd" condition
|
||||
color: isNearEnd ? Colors.red : Colors.blue,
|
||||
),
|
||||
Text(
|
||||
'${timerController.remainingTime}',
|
||||
style: AppStyle.number,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
MyElevatedButton(
|
||||
title: 'Refuse Order'.tr,
|
||||
onPressed: () async {
|
||||
controller.endTimer();
|
||||
List<String> bodyToPassenger = [
|
||||
box.read(BoxName.driverID).toString(),
|
||||
box.read(BoxName.nameDriver).toString(),
|
||||
box.read(BoxName.tokenDriver).toString(),
|
||||
];
|
||||
|
||||
FirebaseMessagesController()
|
||||
.sendNotificationToPassengerToken(
|
||||
'Order Under Review'.tr,
|
||||
'${box.read(BoxName.nameDriver)} ${'is reviewing your order. They may need more information or a higher price.'.tr}',
|
||||
controller.myList[9].toString(),
|
||||
bodyToPassenger,
|
||||
'notification.wav');
|
||||
|
||||
controller.refuseOrder(
|
||||
EncryptionHelper.instance.encryptData(
|
||||
controller.myList[16].toString()),
|
||||
);
|
||||
controller.addRideToNotificationDriverString(
|
||||
controller.myList[16].toString(),
|
||||
controller.myList[29].toString(),
|
||||
controller.myList[30].toString(),
|
||||
'${DateTime.now().year}-${DateTime.now().month}-${DateTime.now().day}',
|
||||
'${DateTime.now().hour}:${DateTime.now().minute}',
|
||||
controller.myList[2].toString(),
|
||||
controller.myList[7].toString(),
|
||||
'wait',
|
||||
controller.myList[31].toString(),
|
||||
controller.myList[33].toString(),
|
||||
controller.myList[2].toString(),
|
||||
controller.myList[5].toString(),
|
||||
controller.myList[4].toString());
|
||||
},
|
||||
kolor: AppColor.redColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _InfoTile extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String label;
|
||||
|
||||
const _InfoTile({required this.icon, required this.label});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Icon(icon, color: AppColor.primaryColor),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
label,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
603
lib/views/home/Captin/orderCaptin/order_speed_request.dart
Executable file
603
lib/views/home/Captin/orderCaptin/order_speed_request.dart
Executable file
@@ -0,0 +1,603 @@
|
||||
import 'dart:convert'; // Though not directly used in this version's UI logic, often kept for model interactions.
|
||||
|
||||
import 'package:sefer_driver/controller/home/captin/home_captain_controller.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:sefer_driver/constant/box_name.dart';
|
||||
import 'package:sefer_driver/controller/firebase/firbase_messge.dart';
|
||||
import 'package:sefer_driver/main.dart'; // For `box`
|
||||
import 'package:sefer_driver/views/home/Captin/driver_map_page.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import '../../../../constant/colors.dart'; // Your AppColor
|
||||
import '../../../../constant/links.dart'; // Your AppLink
|
||||
import '../../../../constant/style.dart'; // Your AppStyle
|
||||
import '../../../../controller/functions/crud.dart';
|
||||
import '../../../../controller/functions/launch.dart';
|
||||
import '../../../../controller/home/captin/order_request_controller.dart';
|
||||
import '../../../widgets/elevated_btn.dart'; // Your MyElevatedButton
|
||||
|
||||
class OrderSpeedRequest extends StatelessWidget {
|
||||
OrderSpeedRequest({super.key});
|
||||
|
||||
final OrderRequestController orderRequestController =
|
||||
Get.put(OrderRequestController());
|
||||
|
||||
// Helper to make myList access more readable and safer
|
||||
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) {
|
||||
// Define AppBar first to get its height for body calculations
|
||||
final appBar = AppBar(
|
||||
title: Text('Speed Order'.tr),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () => Get.back(),
|
||||
),
|
||||
backgroundColor: AppColor.primaryColor, // Example color, adjust as needed
|
||||
elevation: 2.0,
|
||||
);
|
||||
|
||||
final double appBarHeight = appBar.preferredSize.height;
|
||||
final MediaQueryData mediaQueryData = MediaQuery.of(context);
|
||||
final double screenHeight = mediaQueryData.size.height;
|
||||
final double statusBarHeight = mediaQueryData.padding.top;
|
||||
final double bottomSystemPadding = mediaQueryData.padding.bottom;
|
||||
|
||||
// Calculate available height for the Scaffold's body content
|
||||
// Subtracting status bar, app bar, and bottom system padding (like navigation bar)
|
||||
final double availableBodyHeight =
|
||||
screenHeight - appBarHeight - statusBarHeight - bottomSystemPadding;
|
||||
|
||||
// Define overall padding for the body content
|
||||
const EdgeInsets bodyContentPadding =
|
||||
EdgeInsets.symmetric(horizontal: 10.0, vertical: 8.0);
|
||||
|
||||
// Calculate the height for the main content Column, considering the body's own padding
|
||||
double mainColumnHeight = availableBodyHeight - bodyContentPadding.vertical;
|
||||
if (mainColumnHeight < 0) mainColumnHeight = 0;
|
||||
|
||||
return GetBuilder<OrderRequestController>(
|
||||
builder: (controller) {
|
||||
// Pre-extract data for readability and safety
|
||||
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); // Commonly used ID
|
||||
|
||||
return Scaffold(
|
||||
appBar: appBar,
|
||||
backgroundColor: AppColor.secondaryColor ??
|
||||
Colors.grey[100], // Background for the page
|
||||
body: SafeArea(
|
||||
// Ensures content is not obscured by system UI
|
||||
child: Padding(
|
||||
padding:
|
||||
bodyContentPadding, // Apply overall padding to the body's content area
|
||||
child: SizedBox(
|
||||
// Constrain the height of the main layout Column
|
||||
height: mainColumnHeight,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// --- MAP SECTION ---
|
||||
SizedBox(
|
||||
height:
|
||||
Get.height * 0.28, // Relative to total screen height
|
||||
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),
|
||||
|
||||
// --- PRICE & TRIP TYPE SECTION ---
|
||||
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)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// --- EXPANDED SECTION FOR SCROLLABLE (BUT NOT USER-SCROLLABLE) CONTENT ---
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
physics:
|
||||
const NeverScrollableScrollPhysics(), // Prevents user scrolling
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize
|
||||
.min, // Takes minimum vertical space needed
|
||||
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),
|
||||
// --- PAYMENT, STEPS & DIRECTIONS INFO ---
|
||||
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, // Using payments_outlined for cash
|
||||
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)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
// --- PASSENGER INFO ---
|
||||
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(
|
||||
passengerName,
|
||||
style: AppStyle.title,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
const Icon(Icons.star_rounded,
|
||||
color: Colors.amber, size: 20),
|
||||
Text(
|
||||
passengerRating,
|
||||
style: AppStyle.title.copyWith(
|
||||
fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// const SizedBox(height: 8), // Spacer before action buttons if needed
|
||||
|
||||
// --- ACTION BUTTONS & TIMER ---
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0, bottom: 5.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: MyElevatedButton(
|
||||
kolor: AppColor.greenColor,
|
||||
title: 'Accept Order'.tr,
|
||||
onPressed: () async {
|
||||
Get.put(HomeCaptainController()).changeRideId();
|
||||
box.write(BoxName.statusDriverLocation, 'on');
|
||||
var res = await CRUD().post(
|
||||
link: AppLink.updateStausFromSpeed,
|
||||
payload: {
|
||||
'id': rideId,
|
||||
'rideTimeStart':
|
||||
DateTime.now().toString(),
|
||||
'status': 'Apply',
|
||||
'driver_id': box.read(BoxName.driverID),
|
||||
});
|
||||
if (AppLink.endPoint !=
|
||||
AppLink.seferCairoServer) {
|
||||
CRUD().post(
|
||||
link:
|
||||
"${AppLink.endPoint}/ride/rides/updateStausFromSpeed.php",
|
||||
payload: {
|
||||
'id': rideId,
|
||||
'rideTimeStart':
|
||||
DateTime.now().toString(),
|
||||
'status': 'Apply',
|
||||
'driver_id': box.read(BoxName.driverID),
|
||||
});
|
||||
}
|
||||
if (res != "failure") {
|
||||
box.write(BoxName.statusDriverLocation, 'on');
|
||||
controller.changeApplied();
|
||||
List<String> bodyToPassenger = [
|
||||
box.read(BoxName.driverID).toString(),
|
||||
box.read(BoxName.nameDriver).toString(),
|
||||
box.read(BoxName.tokenDriver).toString(),
|
||||
rideId.toString(),
|
||||
];
|
||||
Get.put(FirebaseMessagesController())
|
||||
.sendNotificationToDriverMAP(
|
||||
'Accepted Ride',
|
||||
'your ride is applied'.tr,
|
||||
controller.arguments?['DriverList']
|
||||
?[9]
|
||||
?.toString() ??
|
||||
_getData(9), // Safer access
|
||||
bodyToPassenger,
|
||||
'start.wav');
|
||||
|
||||
// Using rideId (_getData(16)) for order_id consistently
|
||||
CRUD().postFromDialogue(
|
||||
link: AppLink.addDriverOrder,
|
||||
payload: {
|
||||
'driver_id': _getData(
|
||||
6), // Driver ID from the order data
|
||||
'order_id': rideId,
|
||||
'status': 'Apply'
|
||||
});
|
||||
|
||||
if (AppLink.endPoint !=
|
||||
AppLink.seferCairoServer) {
|
||||
CRUD().post(
|
||||
link:
|
||||
"${AppLink.endPoint}/ride/driver_order/add.php",
|
||||
payload: {
|
||||
'driver_id': _getData(6),
|
||||
'order_id': rideId,
|
||||
'status': 'Apply'
|
||||
});
|
||||
}
|
||||
|
||||
Get.back(); // Go back from order request screen
|
||||
box.write(BoxName.rideArguments, {
|
||||
'passengerLocation': _getData(0),
|
||||
'passengerDestination': _getData(1),
|
||||
'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': mapUrl,
|
||||
'DurationToPassenger': _getData(15),
|
||||
'rideId': rideId,
|
||||
'passengerId': _getData(7),
|
||||
'driverId': box
|
||||
.read(BoxName.driverID)
|
||||
.toString(), // Current driver accepting
|
||||
'durationOfRideValue': _getData(19),
|
||||
'paymentAmount': _getData(2),
|
||||
'paymentMethod': _getData(13) == 'true'
|
||||
? 'visa'
|
||||
: 'cash',
|
||||
'isHaveSteps': _getData(20),
|
||||
'step0': _getData(21),
|
||||
'step1': _getData(22),
|
||||
'step2': _getData(23),
|
||||
'step3': _getData(24),
|
||||
'step4': _getData(25),
|
||||
'passengerWalletBurc': _getData(26),
|
||||
'timeOfOrder': DateTime.now().toString(),
|
||||
'totalPassenger': _getData(
|
||||
2), // This is likely trip cost for passenger
|
||||
'carType': _getData(31),
|
||||
'kazan':
|
||||
_getData(32), // Driver's commission/cut
|
||||
'startNameLocation': _getData(29),
|
||||
'endNameLocation': _getData(30),
|
||||
});
|
||||
Get.to(() => PassengerLocationMapPage(),
|
||||
arguments:
|
||||
box.read(BoxName.rideArguments));
|
||||
} else {
|
||||
Get.defaultDialog(
|
||||
title:
|
||||
"This ride is already taken by another driver."
|
||||
.tr,
|
||||
middleText: '',
|
||||
titleStyle: AppStyle.title,
|
||||
middleTextStyle: AppStyle.title,
|
||||
confirm: MyElevatedButton(
|
||||
title: 'Ok'.tr,
|
||||
onPressed: () {
|
||||
Get.back(); // Close dialog
|
||||
Get.back(); // Close order request screen
|
||||
}));
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
// --- TIMER ---
|
||||
GetBuilder<OrderRequestController>(
|
||||
id: 'timerUpdate', // Ensure controller calls update(['timerUpdate']) for this
|
||||
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: () async {
|
||||
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(); // refuseOrder or endTimer should handle navigation if needed.
|
||||
// If not, add Get.back(); here.
|
||||
},
|
||||
kolor: AppColor.redColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Helper widget for location cards to reduce repetition and improve readability
|
||||
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), // Add a little vertical margin between cards
|
||||
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(), // Trim to avoid extra spaces if subtitle is empty
|
||||
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, // Allow up to 2 lines for address
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
230
lib/views/home/Captin/orderCaptin/test_order_page.dart
Executable file
230
lib/views/home/Captin/orderCaptin/test_order_page.dart
Executable file
@@ -0,0 +1,230 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.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(() => GoogleMap(
|
||||
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: (GoogleMapController 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<GoogleMapController?> mapController = Rx<GoogleMapController?>(null);
|
||||
|
||||
// Icons for start and end markers
|
||||
late BitmapDescriptor startIcon;
|
||||
late BitmapDescriptor 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 = await BitmapDescriptor.fromAssetImage(
|
||||
const ImageConfiguration(size: Size(48, 48)),
|
||||
'assets/start_marker.png');
|
||||
|
||||
endIcon = await BitmapDescriptor.fromAssetImage(
|
||||
const ImageConfiguration(size: Size(48, 48)), '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
|
||||
}
|
||||
}
|
||||
237
lib/views/home/Captin/orderCaptin/vip_order_page.dart
Executable file
237
lib/views/home/Captin/orderCaptin/vip_order_page.dart
Executable file
@@ -0,0 +1,237 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:sefer_driver/constant/box_name.dart';
|
||||
import 'package:sefer_driver/constant/links.dart';
|
||||
import 'package:sefer_driver/controller/functions/crud.dart';
|
||||
import 'package:sefer_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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user