Files
Fondex/lib/controllers/book_parcel_controller.dart

432 lines
15 KiB
Dart

import 'dart:convert';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:customer/models/vendor_model.dart';
import 'package:customer/widget/geoflutterfire/src/geoflutterfire.dart';
import 'package:dropdown_textfield/dropdown_textfield.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:geocoding/geocoding.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart' hide Trans;
import 'package:google_maps_flutter/google_maps_flutter.dart' as latlong;
import 'package:http/http.dart' as http;
import 'package:image_picker/image_picker.dart';
import '../constant/constant.dart';
import '../models/parcel_category.dart';
import '../models/parcel_order_model.dart';
import '../models/parcel_weight_model.dart';
import '../models/user_model.dart';
import '../screen_ui/parcel_service/parcel_order_confirmation.dart';
import '../service/fire_store_utils.dart';
import '../themes/show_toast_dialog.dart';
class BookParcelController extends GetxController {
// Sender details
final Rx<TextEditingController> senderLocationController =
TextEditingController().obs;
final Rx<TextEditingController> senderNameController =
TextEditingController().obs;
final Rx<TextEditingController> senderMobileController =
TextEditingController().obs;
final Rx<SingleValueDropDownController> senderWeightController =
SingleValueDropDownController().obs;
final Rx<TextEditingController> senderNoteController =
TextEditingController().obs;
final Rx<TextEditingController> senderCountryCodeController =
TextEditingController(text: Constant.defaultCountryCode).obs;
// Receiver details
final Rx<TextEditingController> receiverLocationController =
TextEditingController().obs;
final Rx<TextEditingController> receiverNameController =
TextEditingController().obs;
final Rx<TextEditingController> receiverMobileController =
TextEditingController().obs;
final Rx<TextEditingController> receiverNoteController =
TextEditingController().obs;
final Rx<TextEditingController> receiverCountryCodeController =
TextEditingController(text: Constant.defaultCountryCode).obs;
// Delivery type
final RxString selectedDeliveryType = 'now'.obs;
// Scheduled delivery fields
final Rx<TextEditingController> scheduledDateController =
TextEditingController().obs;
final Rx<TextEditingController> scheduledTimeController =
TextEditingController().obs;
final RxString scheduledDate = ''.obs;
final RxString scheduledTime = ''.obs;
// Parcel weight list
final RxList<ParcelWeightModel> parcelWeight = <ParcelWeightModel>[].obs;
final RxList<XFile> images = <XFile>[].obs;
final ImagePicker _picker = ImagePicker();
Rx<UserLocation?> senderLocation = Rx<UserLocation?>(null);
Rx<UserLocation?> receiverLocation = Rx<UserLocation?>(null);
ParcelWeightModel? selectedWeight;
ParcelCategory? selectedCategory;
// UI observables
RxBool isScheduled = false.obs;
RxDouble distance = 0.0.obs;
RxDouble duration = 0.0.obs;
RxDouble subTotal = 0.0.obs;
@override
void onInit() {
super.onInit();
setArguments();
getParcelWeight();
setCurrentLocationForSenderAndReceiver();
}
void setArguments() {
if (Get.arguments != null && Get.arguments['parcelCategory'] != null) {
selectedCategory = Get.arguments['parcelCategory'];
}
}
Future<void> getParcelWeight() async {
parcelWeight.value = await FireStoreUtils.getParcelWeight();
}
Future<void> pickScheduledDate(BuildContext context) async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime.now(),
lastDate: DateTime.now().add(const Duration(days: 365)),
);
if (picked != null) {
final formattedDate = "${picked.day}/${picked.month}/${picked.year}";
scheduledDate.value = formattedDate;
scheduledDateController.value.text = formattedDate;
}
}
Future<void> pickScheduledTime(BuildContext context) async {
final TimeOfDay? picked = await showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
);
if (picked != null) {
final formattedTime = picked.format(context);
scheduledTime.value = formattedTime;
scheduledTimeController.value.text = formattedTime;
}
}
void onCameraClick(BuildContext context) {
final action = CupertinoActionSheet(
message: Text(
'Add your parcel image.'.tr(),
style: const TextStyle(fontSize: 15.0),
),
actions: <Widget>[
CupertinoActionSheetAction(
child: Text('Choose image from gallery'.tr()),
onPressed: () async {
Navigator.pop(context);
final imageList = await _picker.pickMultiImage();
if (imageList.isNotEmpty) {
images.addAll(imageList);
}
},
),
CupertinoActionSheetAction(
child: Text('Take a picture'.tr()),
onPressed: () async {
Navigator.pop(context);
final XFile? photo = await _picker.pickImage(
source: ImageSource.camera,
);
if (photo != null) {
images.add(photo);
}
},
),
],
cancelButton: CupertinoActionSheetAction(
child: Text('Cancel'.tr()),
onPressed: () => Navigator.pop(context),
),
);
showCupertinoModalPopup(context: context, builder: (context) => action);
}
Future<void> setCurrentLocationForSenderAndReceiver() async {
try {
await Geolocator.requestPermission();
final position = await Geolocator.getCurrentPosition();
final placemarks = await placemarkFromCoordinates(
position.latitude,
position.longitude,
);
final place = placemarks.first;
final address =
"${place.name}, ${place.subLocality}, ${place.locality}, ${place.administrativeArea}, ${place.postalCode}, ${place.country}";
final userLocation = UserLocation(
latitude: position.latitude,
longitude: position.longitude,
);
senderLocation.value = userLocation;
senderLocationController.value.text = address;
} catch (e) {
debugPrint("Failed to fetch current location: $e");
}
}
bool validateFields() {
if (senderNameController.value.text.isEmpty) {
ShowToastDialog.showToast("Please enter sender name".tr());
return false;
} else if (senderMobileController.value.text.isEmpty) {
ShowToastDialog.showToast("Please enter sender mobile".tr());
return false;
} else if (senderLocationController.value.text.isEmpty) {
ShowToastDialog.showToast("Please enter sender address".tr());
return false;
} else if (receiverNameController.value.text.isEmpty) {
ShowToastDialog.showToast("Please enter receiver name".tr());
return false;
} else if (receiverMobileController.value.text.isEmpty) {
ShowToastDialog.showToast("Please enter receiver mobile".tr());
return false;
} else if (receiverLocationController.value.text.isEmpty) {
ShowToastDialog.showToast("Please enter receiver address".tr());
return false;
} else if (isScheduled.value) {
if (scheduledDate.value.isEmpty) {
ShowToastDialog.showToast("Please select scheduled date".tr());
return false;
} else if (scheduledTime.value.isEmpty) {
ShowToastDialog.showToast("Please select scheduled time".tr());
return false;
}
}
if (selectedWeight == null) {
ShowToastDialog.showToast("Please select parcel weight".tr());
return false;
} else if (senderLocation.value == null || receiverLocation.value == null) {
ShowToastDialog.showToast(
"Please select both sender and receiver locations".tr(),
);
return false;
}
return true;
}
Future<void> bookNow() async {
if (!validateFields()) return;
try {
distance.value = 0.0;
if (Constant.selectedMapType == 'osm') {
print("Fetching route using OSM");
print(
"Sender Location: ${senderLocation.value?.latitude}, ${senderLocation.value?.longitude}",
);
print(
"Receiver Location: ${receiverLocation.value?.latitude}, ${receiverLocation.value?.longitude}",
);
await fetchRouteWithWaypoints([
latlong.LatLng(
senderLocation.value?.latitude ?? 0.0,
senderLocation.value?.longitude ?? 0.0,
),
latlong.LatLng(
receiverLocation.value?.latitude ?? 0.0,
receiverLocation.value?.longitude ?? 0.0,
),
]);
} else {
await fetchGoogleRouteWithWaypoints();
}
if (distance.value < 0.5) {
ShowToastDialog.showToast(
"Sender's location to receiver's location should be more than 1 km."
.tr(),
);
return;
}
subTotal.value =
(distance.value *
double.parse(selectedWeight!.deliveryCharge.toString()));
goToCart();
} catch (e) {
ShowToastDialog.showToast("Something went wrong while booking.".tr());
debugPrint("bookNow error: $e");
}
}
void goToCart() {
DateTime senderPickup =
isScheduled.value
? parseScheduledDateTime(scheduledDate.value, scheduledTime.value)
: DateTime.now();
print("Sender Pickup: $distance");
ParcelOrderModel order = ParcelOrderModel(
id: Constant.getUuid(),
subTotal: subTotal.value.toString(),
parcelType: selectedCategory?.title ?? '',
parcelCategoryID: selectedCategory?.id ?? '',
note: senderNoteController.value.text,
receiverNote: receiverNoteController.value.text,
distance: distance.value.toStringAsFixed(4),
parcelWeight: selectedWeight?.title ?? '',
parcelWeightCharge: selectedWeight?.deliveryCharge,
sendToDriver: isScheduled.value == true ? false : true,
senderPickupDateTime: Timestamp.fromDate(senderPickup),
receiverPickupDateTime: Timestamp.fromDate(DateTime.now()),
taxSetting: Constant.taxList,
isSchedule: isScheduled.value,
sourcePoint: G(
geopoint: GeoPoint(
senderLocation.value!.latitude ?? 0.0,
senderLocation.value!.longitude ?? 0.0,
),
geohash:
Geoflutterfire()
.point(
latitude: senderLocation.value!.latitude ?? 0.0,
longitude: senderLocation.value!.longitude ?? 0.0,
)
.hash,
),
destinationPoint: G(
geopoint: GeoPoint(
receiverLocation.value!.latitude ?? 0.0,
receiverLocation.value!.longitude ?? 0.0,
),
geohash:
Geoflutterfire()
.point(
latitude: receiverLocation.value!.latitude ?? 0.0,
longitude: receiverLocation.value!.longitude ?? 0.0,
)
.hash,
),
sender: LocationInformation(
address: senderLocationController.value.text,
name: senderNameController.value.text,
phone:
"(${senderCountryCodeController.value.text}) ${senderMobileController.value.text}",
),
receiver: LocationInformation(
address: receiverLocationController.value.text,
name: receiverNameController.value.text,
phone:
"(${receiverCountryCodeController.value.text}) ${receiverMobileController.value.text}",
),
receiverLatLong: receiverLocation.value,
senderLatLong: senderLocation.value,
sectionId: Constant.sectionConstantModel?.id ?? '',
);
debugPrint("Order Distance: ${distance.value}");
debugPrint("Subtotal: ${subTotal.value}");
debugPrint("Order JSON: ${order.toJson()}");
Get.to(
() => ParcelOrderConfirmationScreen(),
arguments: {'parcelOrder': order, 'images': images},
);
}
DateTime parseScheduledDateTime(String dateStr, String timeStr) {
try {
final dateParts = dateStr.split('/');
final day = int.parse(dateParts[0]);
final month = int.parse(dateParts[1]);
final year = int.parse(dateParts[2]);
final time = TimeOfDay(
hour: int.parse(timeStr.split(':')[0]),
minute: int.parse(timeStr.split(':')[1].split(' ')[0]),
);
final isPM = timeStr.toLowerCase().contains('pm');
final hour24 = isPM && time.hour < 12 ? time.hour + 12 : time.hour;
return DateTime(year, month, day, hour24, time.minute);
} catch (e) {
debugPrint("Failed to parse scheduled date/time: $e");
return DateTime.now();
}
}
Future<void> fetchGoogleRouteWithWaypoints() async {
final origin =
'${senderLocation.value!.latitude},${senderLocation.value!.longitude}';
final destination =
'${receiverLocation.value!.latitude},${receiverLocation.value!.longitude}';
final url = Uri.parse(
'https://maps.googleapis.com/maps/api/directions/json?origin=$origin&destination=$destination&mode=driving&key=${Constant.mapAPIKey}',
);
try {
final response = await http.get(url);
final data = json.decode(response.body);
if (data['status'] == 'OK') {
final route = data['routes'][0];
final legs = route['legs'] as List;
num totalDistance = 0;
num totalDuration = 0;
for (var leg in legs) {
totalDistance += leg['distance']['value'];
totalDuration += leg['duration']['value'];
}
if (Constant.distanceType.toLowerCase() == "KM".toLowerCase()) {
distance.value = totalDistance / 1000.0;
} else {
distance.value = totalDistance / 1609.34;
}
duration.value = (totalDuration / 60).round().toDouble();
} else {
debugPrint('Google Directions API Error: ${data['status']}');
}
} catch (e) {
debugPrint("Google route fetch error: $e");
}
}
Future<void> fetchRouteWithWaypoints(List<latlong.LatLng> points) async {
final coordinates = points
.map((p) => '${p.longitude},${p.latitude}')
.join(';');
final url = Uri.parse(
'https://router.project-osrm.org/route/v1/driving/$coordinates?overview=full&geometries=geojson',
);
try {
final response = await http.get(url);
if (response.statusCode == 200) {
final decoded = json.decode(response.body);
final dist = decoded['routes'][0]['distance'];
final dur = decoded['routes'][0]['duration'];
if (Constant.distanceType.toLowerCase() == "KM".toLowerCase()) {
distance.value = dist / 1000.00;
} else {
distance.value = dist / 1609.34;
}
duration.value = (dur / 60).round().toDouble();
} else {
debugPrint("Failed to get route: ${response.body}");
}
} catch (e) {
debugPrint("Route fetch error: $e");
}
}
}