Files
Fondex/lib/controllers/book_parcel_controller.dart

341 lines
14 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:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:geocoding/geocoding.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
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");
}
}
}