import 'dart:convert'; import 'dart:developer'; import 'dart:developer' as dev; import 'package:get_it/get_it.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:uuid/uuid.dart' as uuid_lib; import 'package:polyline_codec/polyline_codec.dart'; import 'package:wanisclient/config/AppSnackbar.dart'; import 'package:wanisclient/providers/public_keys_provider.dart'; import 'package:wanisclient/providers/utils_singleton.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_polyline_points/flutter_polyline_points.dart'; import 'package:http/http.dart' as http; import 'package:latlong2/latlong.dart'; import '../config/api_response.dart'; import '../config/constants/app_constant.dart'; import '../config/enum/orde_type.dart'; import '../config/enum/ride_enum.dart'; import '../config/enum/ride_tracking_state.dart'; import '../config/enum/ride_type.dart'; import '../models/DriverLocationDto.dart'; import '../models/RideUpdateRequest.dart'; import '../models/SocketRequest/ride_request.dart'; import '../models/SocketRequest/ride_stop_request.dart'; import '../models/ride_model.dart'; import '../models/ride_options.dart'; import '../services/RideServices.dart'; import '../services/audio_service.dart'; import '../services/push_notification_service.dart'; import 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart' as gog; class RouteData { final List points; final Duration duration; final double distanceKm; final String geometryHash; RouteData({ required this.points, required this.duration, required this.distanceKm, required this.geometryHash, }); @override String toString() { return 'RouteData{points: $points, duration: $duration, distanceKm: $distanceKm, geometryHash: $geometryHash}'; } } class RidesProvider with ChangeNotifier { static final RidesProvider _instance = RidesProvider._internal(); final RideService _rideService = RideService(); factory RidesProvider() { return _instance; } RidesProvider._internal(); List _rides = []; List _scheduledrides = []; RideEnum _currentstate = RideEnum.idle; order_type _currenttype = order_type.TAXI_BOOKING; RideTracking _currentridetrackstate = RideTracking.track_drive; LatLng? _startlocation; String? _startlocationname; List _stoplocationlocation = []; LatLng? _destinationlocation; String? _destinationlocationname; String? _couponcode; double? _couponvalue; double? _couponpercentage; String? _geohash; RouteData? _routedata; RideOption? _selecteridoption; double _price = 0.0; bool _iskidsride = false; RideDto? _currentride; RideType? _oncreationridetype; DateTime? _ridestartdatetime; int _currentPage = 0; final int _pageSize = 10; bool _hasMore = true; bool _hasMoreschudle = true; bool _isLoadingById = false; bool _isLoadingNotRated = false; bool _isLoadingCancelById = false; bool _isLoadingCreateride = false; bool _isLoading = false; bool _isLoadingRate = false; bool _isLoadingCancel = false; bool _coming = false; // Add this new flag bool _driverArrivedSoundPlayedForCurrentRide = false; bool get driverArrivedSoundPlayedForCurrentRide => _driverArrivedSoundPlayedForCurrentRide; bool get isLoading => _isLoading; bool get isLoadingNotRated => _isLoadingNotRated; bool get isLoadingCancelById => _isLoadingCancelById; bool get isLoadingRate => _isLoadingRate; bool get isLoadingCancel => _isLoadingCancel; bool get coming => _coming; bool get isLoadingById => _isLoadingById; bool get isLoadingCreateride => _isLoadingCreateride; bool get hasMore => _hasMore; bool get hasMoreschudle => _hasMoreschudle; RideDto? get currentride => _currentride; DateTime? get ridestartdatetime => _ridestartdatetime; LatLng? get startlocation => _startlocation; List get rides => _rides; List get scheduledrides => _scheduledrides; RideType? get oncreationridetype => _oncreationridetype; double get price => _price; bool get iskidsride => _iskidsride; RideOption? get selecteridoption => _selecteridoption; List? get stoplocationlocation => _stoplocationlocation; LatLng? get destinationlocation => _destinationlocation; RideEnum get currentstate => _currentstate; order_type get currenttype => _currenttype; RideTracking get currentridetrackstate => _currentridetrackstate; String? get startlocationname => _startlocationname; String? get destinationlocationname => _destinationlocationname; String? get couponcode => _couponcode; double? get couponvalue => _couponvalue; double? get couponpercentage => _couponpercentage; String? get geohash => _geohash; RouteData? get routedata => _routedata; void clearRideProvider(String name) { try { log('start cleaning .... $name'); _currentride = null; _currentstate = RideEnum.idle; _currenttype = order_type.TAXI_BOOKING; _currentridetrackstate = RideTracking.track_drive; notifyListeners(); _driverArrivedSoundPlayedForCurrentRide = false; _stoplocationlocation = []; _rides = []; _scheduledrides = []; _destinationlocation = null; _price = 0.0; _iskidsride = false; _geohash = ""; _isLoadingById = false; _isLoading = false; _hasMore = false; _startlocation = null; _routedata = null; _startlocationname = null; _selecteridoption = null; _oncreationridetype = null; _destinationlocationname = null; _ridestartdatetime = null; _coming = false; log('end cleanning ....'); notifyListeners(); } catch (e) { log("error clear : $e"); } } void clearInTripScreen() { try { log('start cleaning ....'); _currentride = null; _currentstate = RideEnum.fill_places; _currenttype = order_type.TAXI_BOOKING; _currentridetrackstate = RideTracking.track_drive; notifyListeners(); _driverArrivedSoundPlayedForCurrentRide = false; _stoplocationlocation = []; _rides = []; _scheduledrides = []; _destinationlocation = null; _price = 0.0; _iskidsride=false; _geohash = ""; _couponcode = ""; _couponvalue = null; _couponpercentage = null; _isLoadingById = false; _isLoading = false; _hasMore = false; _startlocation = null; _routedata = null; _startlocationname = null; _selecteridoption = null; _oncreationridetype = null; _destinationlocationname = null; _ridestartdatetime = null; _coming = false; log('end cleanning ....'); notifyListeners(); } catch (e) { log("error clear : $e"); } } void updateRide(RideDto ride) { _currentride = ride; notifyListeners(); } // Method to update the flag void setDriverArrivedSoundPlayed(bool played) { if (_driverArrivedSoundPlayedForCurrentRide != played) { _driverArrivedSoundPlayedForCurrentRide = played; notifyListeners(); // Notify listeners if you want UI to react to this flag } } // In RidesProvider class - fix the updateComing method Future updateComing(bool coming) async { if (_coming == coming) return; // Prevent loop if value hasn't changed _coming = coming; notifyListeners(); // Optional: persist the state if needed // final prefs = await SharedPreferences.getInstance(); // await prefs.setBool('driver_coming_ack', coming); } void updateridestartdatetime(DateTime date) { // print("date ------------------------ $date"); _ridestartdatetime = date; // print("_ridestartdatetime ------------------------ $_ridestartdatetime"); notifyListeners(); } void updateRideType(RideType type) { _oncreationridetype = type; notifyListeners(); } void updateGeoHash(String value) { log("updateGeoHash ----- >>>>> $value"); _geohash = value; notifyListeners(); } void Updateroutedata(RouteData value) { log("Updateroutedata ----- >>>>>"); _routedata = value; notifyListeners(); } void updateCoupon(String? value) { _couponcode = value; notifyListeners(); } void updateCouponvalue(double? value,double? percentage) { _couponvalue = value; _couponpercentage = percentage; notifyListeners(); } void updateCurrentstate(RideEnum state) { try { if (_currentstate == state) { return; } _currentstate = state; if (state == RideEnum.idle) { log('is IDLE ************ $state'); clearRideProvider("updateCurrentstate"); //updateCoupon(""); } else { log('is not IDLE ************ $state'); } notifyListeners(); } catch (e) { log("error updateCurrentstate : $e"); } } void updateCurrentType(order_type type) { try { if (_currenttype == type) { return; } _currenttype = type; notifyListeners(); } catch (e) { log("error updateCurrentType : $e"); } } void updateCurrentridetrackstate(RideTracking state) { _currentridetrackstate = state; notifyListeners(); } void updatePrice(double price) { _price = price; notifyListeners(); } void updateSelecteridoption(RideOption data) { _selecteridoption = data; _iskidsride = data.iskidsride??false; notifyListeners(); } void updateStartlocation(LatLng? data) { _startlocation = data; notifyListeners(); } void updateStartlocationName(String? data) { _startlocationname = data; notifyListeners(); } void updatedestinationlocationName(String? data) { _destinationlocationname = data; notifyListeners(); } void updateStoplocationlocation(List data) { _stoplocationlocation = data; notifyListeners(); } void addStoplocationlocation(LatLng data) { var stop = RideStopRequest( stopLocation: "${data.latitude},${data.longitude}", plannedStopDurationMinutes: 1); _stoplocationlocation.add(stop); notifyListeners(); } void removeStoplocationlocation(int index) { if (index < _stoplocationlocation.length) { _stoplocationlocation.removeAt(index); notifyListeners(); } } void updateDestinationlocation(LatLng? data) { _destinationlocation = data; notifyListeners(); } double getLatitude(String location) { // Split the string by comma List coordinates = location.split(','); // Return the first part as latitude, convert it to double return double.tryParse(coordinates[0]) ?? 0.0; // Return 0.0 if the parsing fails } // Function to extract longitude from the string double getLongitude(String location) { // Split the string by comma List coordinates = location.split(','); // Return the second part as longitude, convert it to double return double.tryParse(coordinates[1]) ?? 0.0; // Return 0.0 if the parsing fails } /// Fetches the driving route, plus ETA *and* distance in kilometers. Future fetchRouteInfo(dynamic start, dynamic end) async { const profile = 'driving'; final coordinates = '${start.longitude},${start.latitude};${end.longitude},${end.latitude}'; if (!publickeys.guardKey(publickeys.mapboxToken, 'mapboxToken')) { throw Exception('Mapbox token not configured — contact admin'); } final url = Uri.https( 'api.mapbox.com', '/directions/v5/mapbox/$profile/$coordinates', { 'geometries': 'polyline6', 'overview': 'full', 'access_token': publickeys.mapboxToken, }, ); final response = await http.get(url); if (response.statusCode != 200) { throw Exception('Directions API error: ${response.statusCode}'); } final data = json.decode(response.body) as Map; if ((data['routes'] as List).isEmpty) { throw Exception('No route found'); } final route = (data['routes'] as List).first as Map; final String overview = route['geometry'] as String; final secs = (route['duration'] as num).toInt(); final meters = (route['distance'] as num).toDouble(); final km = meters / 1000.0; // --- THE FIX IS HERE --- // Use `final` instead of `const` to create the codec instance at runtime. final decodedCoordinates = PolylineCodec.decode(overview, precision: 6); final points = decodedCoordinates.map( (coord) => gog.Position(coord[1], coord[0]) ).toList(); final routeData = RouteData( points: points, duration: Duration(seconds: secs), distanceKm: km, geometryHash: overview, ); updateGeoHash(overview); Updateroutedata(routeData); log("test route data : $routeData"); return routeData; } Future getPlaceNameByGoogle(double latitude, double longitude) async { // The function name is updated for clarity log("getPlaceNameByMapbox: Fetching for $latitude, $longitude"); try { if (!publickeys.guardKey(publickeys.mapboxToken, 'mapboxToken')) return ""; final url = Uri.https( 'api.mapbox.com', '/geocoding/v5/mapbox.places/$longitude,$latitude.json', { 'access_token': publickeys.mapboxToken, 'language': 'fr', 'limit': '1', 'types': 'address,place,locality,region,postcode,district', }, ); final response = await http.get(url); if (response.statusCode != 200) { throw Exception('Failed to fetch address: ${response.statusCode}'); } final data = json.decode(utf8.decode(response.bodyBytes)) as Map; final features = data['features'] as List; if (features.isEmpty) { throw Exception('No address found for these coordinates'); } // --- FIX IS HERE --- // 1. Access the first element in the list (which is a Map). // 2. Access the 'place_name' key from that Map. return features.first['place_name'] as String; } catch (e) { // Updated the log message to be more accurate log("Error getting address from Mapbox: $e"); return ("$latitude,$longitude"); } } String _buildSmartReadableAddress(Map result) { final addressComponents = (result['address_components'] as List).cast>(); String getComponent(String type) { for (final component in addressComponents) { if ((component['types'] as List).contains(type)) { return component['long_name'] as String; } } return ''; } final streetNumber = getComponent('street_number'); final streetName = getComponent('route'); final city = getComponent('locality'); final governorate = getComponent('administrative_area_level_1'); final postalCode = getComponent('postal_code'); final country = getComponent('country'); // --- Graceful Degradation Logic --- // Check if the street name is valid and usable. final isStreetNameValid = streetName.isNotEmpty && streetName.toLowerCase() != 'unnamed road' && streetName.toLowerCase() != 'route sans nom'; // If we have a valid street name, build the full street-level address. if (isStreetNameValid) { final streetAddress = '$streetNumber $streetName'.trim(); final cityAndPostalCode = '$city $postalCode'.trim(); final addressParts = [ streetAddress, cityAndPostalCode, governorate, country, ].where((part) => part.isNotEmpty).toList(); return addressParts.join(', '); } // OTHERWISE, if the street name is missing or useless, build a locality-level address. else { final addressParts = [ city, governorate, country, ].where((part) => part.isNotEmpty).toList(); // Only return this if we at least have a city. if (addressParts.isNotEmpty) { return addressParts.join(', '); } } // Absolute last resort: return Google's default formatted address, but cleaned. return _cleanFormattedAddress(result['formatted_address'] as String); } /// Cleans Google's raw formatted address, primarily removing plus codes. String _cleanFormattedAddress(String address) { final plusCodeRegex = RegExp(r'^[A-Z0-9]{4}\+[A-Z0-9]{2,}\s*,?\s*'); return address.replaceFirst(plusCodeRegex, '').trim(); } //STEP 1: Add these two methods to your RidesProvider class // This searches for places when user types Future>> searchPlaces(String query) async { if (query.length < 3) return []; // Don't search for less than 3 characters try { if (!publickeys.guardKey(publickeys.googleMapKey, 'googleMapKey')) return []; final url = Uri.https( 'maps.googleapis.com', '/maps/api/place/autocomplete/json', { 'input': query, 'key': publickeys.googleMapKey, // Make sure this is your actual API key 'language': 'fr', 'components': 'country:tn', // Restrict to Tunisia (TN is Tunisia's country code) }, ); final response = await http.get(url); if (response.statusCode == 200) { final data = json.decode(response.body); if (data['status'] == 'OK') { List> results = []; for (var prediction in data['predictions']) { results.add({ 'description': prediction['description'], 'place_id': prediction['place_id'], }); } return results; } } return []; } catch (e) { print('Search error: $e'); return []; } } // This gets coordinates when user selects a place Future getPlaceCoordinates(String placeId) async { try { if (!publickeys.guardKey(publickeys.googleMapKey, 'googleMapKey')) return null; final url = Uri.https( 'maps.googleapis.com', '/maps/api/place/details/json', { 'place_id': placeId, 'fields': 'geometry', 'key': publickeys.googleMapKey, }, ); final response = await http.get(url); if (response.statusCode == 200) { final data = json.decode(response.body); if (data['status'] == 'OK') { final location = data['result']['geometry']['location']; return LatLng(location['lat'], location['lng']); } } return null; } catch (e) { print('Get coordinates error: $e'); return null; } } Future finbyid(int rideId) async { _isLoadingById = true; notifyListeners(); try { final APIResponse response = await _rideService.fetchbyid(rideId); print('response.isValidResponse findbyid : ${response.isValidResponse}'); print('response.data : ${response.data}'); if (response.isValidResponse) { print('current ride : ${_currentride}'); _currentride = response.data!; print('current ride after : ${_currentride}'); } else { print("Error: ${response.errorMessage}"); } } catch (e) { print("Exception: $e"); } finally { _isLoadingById = false; notifyListeners(); } } Future getdriverlocaiton(int rideId) async { _isLoadingById = true; notifyListeners(); try { log("Start getting driver locaiton ############################################"); final APIResponse response = await _rideService.finddriverlocationby(rideId); if (response.isValidResponse) { log("done getting driver locaiton ############################################"); return response.data!; } else { print("Error: ${response.errorMessage}"); return null; } } catch (e) { print("Exception: $e"); } finally { _isLoadingById = false; notifyListeners(); } } Future cancelride(int rideId) async { _isLoadingCancelById = true; notifyListeners(); try { print( 'start canceling ride ********************************************************************************************************'); final APIResponse response = await _rideService.RideCancelbyuser(rideId); print('response.isValidResponse : ${response.isValidResponse}'); print('response.data : ${response.data}'); if (response.isValidResponse && response.data == true) { print('ride canceled successfully'); } else { print("Error: ${response.errorMessage}"); } } catch (e) { print("Exception: $e"); } finally { _isLoadingCancelById = false; notifyListeners(); } } Future finbyidWithUpdate(int rideId) async { _isLoadingById = true; notifyListeners(); try { final APIResponse response = await _rideService.fetchbyid(rideId); print( 'response.isValidResponse finbyidWithUpdate: ${response.isValidResponse}'); print('response.data : ${response.data}'); if (response.isValidResponse) { print('current ride : ${_currentride}'); _currentride = response.data!; print('current ride after : ${_currentride}'); updatestatusbycurrentride(); } else { print("Error: ${response.errorMessage}"); } } catch (e) { print("Exception: $e"); } finally { _isLoadingById = false; notifyListeners(); } } void updatestatusbycurrentride() { if (currentride == null) { _isLoadingById = false; notifyListeners(); return; } fillProvider(_currentride!); if (currentride?.currentridetypesocket == "DRIVER_ASSIGNED" && currentride!.driverId != null) { updateCurrentstate(RideEnum.tracking_ride); } else if (currentride?.currentridetypesocket == "RIDE_END") { updateCurrentstate(RideEnum.rate_driver); updateCurrentridetrackstate(RideTracking.rating_driver); } else if (currentride?.currentridetypesocket == "RIDE_CANCEL_USER" || currentride?.rideStatus == "COMPLETED") { updateCurrentstate(RideEnum.idle); AppSnackbar.showInfo("Info", "the ride is already cancel by the client"); } else if (currentride?.currentridetypesocket == "RIDE_CANCEL_DRIVER") { log("detect here !!!!!!!!!!!!!!!!!!!"); GetIt.I().playSound(SoundEvent.driverCancelled); updateCurrentstate(RideEnum.idle); AppSnackbar.showInfo("Info", "the ride is already cancel by the driver"); } else if (currentride?.currentridetypesocket == "DRIVER_ARRIVED" || currentride?.currentridetypesocket == "USER_COMING") { if (!_driverArrivedSoundPlayedForCurrentRide) { GetIt.I().playSound(SoundEvent.driverarriver); setDriverArrivedSoundPlayed(true); } updateCurrentstate(RideEnum.driver_arrived); updateCurrentridetrackstate(RideTracking.driver_arrived); } else if (currentride?.currentridetypesocket == "RIDE_STOP") { updateCurrentstate(RideEnum.start_ride); updateCurrentridetrackstate(RideTracking.track_ride); } else if (currentride?.currentridetypesocket == "RIDE_START") { updateCurrentstate(RideEnum.start_ride); updateCurrentridetrackstate(RideTracking.track_ride); } notifyListeners(); } Future get_my_latest_ride_ongoing(String name) async { log(" ---- get_my_latest_ride_ongoing -- $name"); _isLoadingById = true; notifyListeners(); try { final APIResponse response = await _rideService.get_my_latest_ride_by_status('ONGOING'); print( 'response.isValidResponse get_my_latest_ride_ongoing: ${response.isValidResponse}'); print('response.data : ${response.data}'); log(" ---- get_my_latest_ride_ongoing -- 01 $name"); if (response.isValidResponse) { print('current ride my_latest_ride_by_status : ${_currentride}'); _currentride = response.data!; log(" ---- get_my_latest_ride_ongoing -- 02 $name"); updatestatusbycurrentride(); print('current ride after : ${_currentride}'); } else { print("Error: ${response.errorMessage}"); } } catch (e) { print("Exception: $e"); } finally { _isLoadingById = false; notifyListeners(); } } Future get_my_latest_ride_not_rated(String name) async { log("-- get_my_latest_ride_not_rated $name"); _isLoadingNotRated = true; notifyListeners(); try { final APIResponse response = await _rideService.get_my_latest_ride_not_rated(); print( 'get_my_latest_ride_not_rated response.isValidResponse : ${response.isValidResponse}'); print('get_my_latest_ride_not_rated response.data : ${response.data}'); log("-- get_my_latest_ride_not_rated 01 $name"); if (response.isValidResponse) { _isLoadingNotRated = false; log("-- get_my_latest_ride_not_rated 02 $name"); return response.data; } else { print("Error: ${response.errorMessage}"); log("-- get_my_latest_ride_not_rated 03 $name"); } } catch (e) { print("Exception: $e"); } finally { _isLoadingNotRated = false; notifyListeners(); } } Future get_my_latest_ride_request() async { log("get latest ride ... pull in 10s"); _isLoadingById = true; notifyListeners(); try { final APIResponse response = await _rideService.get_my_latest_ride(); print( 'response.isValidResponse get_my_latest_ride_request empty status: ${response.isValidResponse}'); print('response.data : ${response.data}'); if (response.isValidResponse) { print('current ride : ${_currentride}'); _currentride = response.data!; // fillProvider(_currentride!); print('current ride after : ${_currentride}'); } else { print("Error: ${response.errorMessage}"); } } catch (e) { print("Exception: $e"); } finally { _isLoadingById = false; notifyListeners(); } } Future get_nbDriver(int ride_id) async { log("get_nbDriver ... "); _isLoadingById = true; notifyListeners(); try { final APIResponse response =await _rideService.get_nbDriver(ride_id); print('response.isValidResponse get_nbDriver empty status: ${response.isValidResponse}'); print('response.data : ${response.data}'); if (response.isValidResponse) { return response.data!; } else { print("Error: ${response.errorMessage}"); return -1; } } catch (e) { print("Exception: $e"); return -1; } finally { _isLoadingById = false; notifyListeners(); } } Future createride(RideRequestModel request) async { _isLoadingCreateride = true; notifyListeners(); try { final APIResponse response = await _rideService.createride(request); print('response.isValidResponse : ${response.isValidResponse}'); print('response.data : ${response.data}'); if (response.isValidResponse) { print('current ride : $_currentride'); _currentride = response.data!; print('current ride after : $_currentride'); return response.data!; } else { print("Error: ${response.errorMessage}"); return null; } } catch (e) { print("Exception: $e"); } finally { _isLoadingCreateride = false; notifyListeners(); } } Future updateride(int rideId, String cancelreason, String type) async { _isLoadingCreateride = true; notifyListeners(); try { final APIResponse response = await _rideService.updateride(rideId, cancelreason, type); print('response.isValidResponse : ${response.isValidResponse}'); print('response.data : ${response.data}'); if (response.isValidResponse) { print('current ride : ${_currentride}'); _currentride = response.data!; print('current ride after : ${_currentride}'); } else { print("Error: ${response.errorMessage}"); } } catch (e) { print("Exception: $e"); } finally { _isLoadingCreateride = false; notifyListeners(); } } Future rate_driver_current_ride(int rating) async { log("Rating ride ******* rate_driver_current_ride"); if (_currentride == null) { AppSnackbar.showError("Error", "Ride not exist !!"); return; } _isLoadingRate = true; notifyListeners(); try { final response = await _rideService.rate_driver(rating, _currentride!.driverId!); print('response.data rate_driver_current_ride: ${response.data}'); } catch (e) { print("Exception: $e"); } finally { _isLoadingRate = false; notifyListeners(); } } Future rate_driver(int rating, int driverid) async { _isLoadingRate = true; notifyListeners(); try { final response = await _rideService.rate_driver(rating, driverid); print('response.data : ${response.data}'); } catch (e) { print("Exception: $e"); } finally { _isLoadingRate = false; notifyListeners(); } } Future fetchMyRides({bool loadMore = false,String? status}) async { if (loadMore && !_hasMoreschudle) { log("init rides schuled ... 01"); return; } _isLoading = true; notifyListeners(); if (!loadMore) { _currentPage = 0; _rides.clear(); _hasMore = true; } try { final response = await _rideService.fetchRides( page: _currentPage, size: _pageSize, type: 'INSTANT',status: status); if (response.isValidResponse) { final newRides = response.data!; if (loadMore) { _rides.addAll(newRides); } else { _rides = newRides; } _hasMore = newRides.length == _pageSize; _currentPage++; } else { debugPrint('Error: ${response.errorMessage}'); } } catch (e) { debugPrint('Exception: $e'); } finally { _isLoading = false; notifyListeners(); } } Future submitRideScheduledData(RideRequestModel req) async { _isLoadingCreateride = true; try { log("req data : ' **** $req"); final response = await _rideService.postRideScheduledData(req.toJson()); log("response data : ' **** $response"); if (response.statusCode == 200) { // Success print('Metadata submitted successfully'); AppSnackbar.showSuccess("Success", "Ride scheduled successfully"); updateCurrentstate(RideEnum.idle); } else { print('Failed to submit metadata: ${response.body}'); AppSnackbar.showError("Error", "Error happen please try again later"); } } catch (e) { print('Error submitting metadata: $e'); AppSnackbar.showError("Error", "error happen when trying to create schedule ride"); } finally { _isLoadingCreateride = false; } } Future updateRideScheduledData(RideRequestModel req) async { try { if (_currentride == null) { AppSnackbar.showError("Error", "You need to select ride before"); return; } final response = await _rideService.updateRideScheduledData( req.toJson(), _currentride!.id); if (response.statusCode == 200) { // Success print('Metadata submitted successfully'); AppSnackbar.showSuccess("Success", "Your data is updated successfully"); clearRideProvider("updateRideScheduledData"); updateCoupon(""); } else { print('Failed to submit metadata: ${response.body}'); AppSnackbar.showError("Error", "error happen please try again later"); } } catch (e) { print('Error submitting metadata: $e'); AppSnackbar.showError("Error", "error happen when trying to update schedule ride"); } } Future cancelRideScheduled(int id) async { try { _isLoadingCancel = true; notifyListeners(); final response = await _rideService.CancelRideScheduled(id); if (response.statusCode == 200) { // Success print('Metadata submitted successfully'); AppSnackbar.showSuccess("Success", "Your data is updated successfully"); // scheduledrides.removeWhere((ride) => ride.id == id); // ✅ Update ride status instead of removing final index = scheduledrides.indexWhere((ride) => ride.id == id); if (index != -1) { scheduledrides[index].rideStatus = "CANCELLED"; } notifyListeners(); } else { print('Failed to submit metadata: ${response.body}'); AppSnackbar.showError("Error", "error happen please try again later"); } } catch (e) { print('Error submitting metadata: $e'); AppSnackbar.showError("Error", "Error happen when cancel the schedule ride"); } finally { _isLoadingCancel = false; notifyListeners(); } } Future fetchSCHEDULEMyRides({bool loadMore = false}) async { if (loadMore && !_hasMoreschudle) { log("init rides schuled ... 01"); return; } _isLoading = true; notifyListeners(); if (!loadMore) { _currentPage = 0; _scheduledrides.clear(); _hasMoreschudle = true; } try { final response = await _rideService.fetchRides( page: _currentPage, size: _pageSize, type: 'SCHEDULE'); if (response.isValidResponse) { final newRides = response.data!; if (loadMore) { _scheduledrides.addAll(newRides); } else { _scheduledrides = newRides; } _hasMoreschudle = newRides.length == _pageSize; _currentPage++; } else { debugPrint('Error: ${response.errorMessage}'); } } catch (e) { debugPrint('Exception: $e'); } finally { _isLoading = false; notifyListeners(); } } Future fetchSCHEDULEMyRidesStatus({bool? history,bool loadMore = false,required List status}) async { if (loadMore && !_hasMoreschudle) { log("init rides schuled ... 01"); return; } _isLoading = true; notifyListeners(); if (!loadMore) { _currentPage = 0; _scheduledrides.clear(); _hasMoreschudle = true; } try { final response = await _rideService.fetchRidesStatus(page: _currentPage,history:history, size: _pageSize, type: 'SCHEDULE', status: status); if (response.isValidResponse) { final newRides = response.data!; if (loadMore) { _scheduledrides.addAll(newRides); } else { _scheduledrides = newRides; } _hasMoreschudle = newRides.length == _pageSize; _currentPage++; } else { debugPrint('Error: ${response.errorMessage}'); } } catch (e) { debugPrint('Exception: $e'); } finally { _isLoading = false; notifyListeners(); } } Future request_ride(RideRequestModel request) async { try { final APIResponse response = await _rideService.request_ride(request); if (response.isValidResponse) { return response.data!; } return false; // Return null on failure } catch (e) { log("Exception in request_ride for #${request.uuid}: $e"); return false; } } Future _getOrFetchFCMToken(SharedPreferences prefs) async { await PushNotificationService().getFCMToken(); String fcmToken = prefs.getString("FCM token") ?? "InvalidToken"; return fcmToken; } Future convertToReq(RideType type) async { try { final prefs = await SharedPreferences.getInstance(); final ridestarttime = type == RideType.INSTANT ? DateTime.now() : _ridestartdatetime; print(">> _ridestartdatetime = $_ridestartdatetime"); // make sure _stopLocationList is your actual list of stops final stopsList = _stoplocationlocation.map((stop) { return RideStopRequest( stopLocation: stop.stopLocation, plannedStopDurationMinutes: stop.plannedStopDurationMinutes, actualStopDurationMinutes: null, additionalCharges: 0.0, stopStartTime: DateTime.now(), stopEndTime: null, ); }).toList(); print(">> stopsList.length = ${stopsList.length}"); for (var i = 0; i < stopsList.length; i++) { print(" stop #$i → ${stopsList[i].stopLocation}"); } String fcmToken = await _getOrFetchFCMToken(prefs); return RideRequestModel( userId: utils.user!.id!.toString(), startLocation: '${_startlocation!.latitude},${_startlocation!.longitude}', endLocation: '${_destinationlocation!.latitude},${_destinationlocation!.longitude}', endLocationname: _destinationlocationname!, startLocationname: _startlocationname!, stops: stopsList, rideStartTime: ridestarttime, rideType: type.name, totalPrice: _price, iskidsride: _iskidsride, couponcode: _couponcode, couponvalue: _couponvalue, couponpercentage: _couponpercentage, uuid: "${const uuid_lib.Uuid().v4()}-${DateTime.now().toUtc().toIso8601String()}-${utils.user!.username}", fcmtoken:fcmToken ); } catch (e) { log('Error putting variable : $e'); return null; } } Future fillProvider(RideDto ride) async { // Update start location if valid if (ride.startLocation.isNotEmpty && ride.startLocation.contains(',')) { List parts = ride.startLocation.split(','); if (parts.length == 2) { double? lat = double.tryParse(parts[0]); double? lng = double.tryParse(parts[1]); if (lat != null && lng != null) { updateStartlocation(LatLng(lat, lng)); log("updateStartlocation ** $_startlocation"); } } } // Update end location if valid if (ride.endLocation.isNotEmpty && ride.endLocation.contains(',')) { List parts = ride.endLocation.split(','); if (parts.length == 2) { double? lat = double.tryParse(parts[0]); double? lng = double.tryParse(parts[1]); if (lat != null && lng != null) { updateDestinationlocation(LatLng(lat, lng)); log("updateDestinationlocation ** $_destinationlocation"); } } } // Update stop locations if not null or empty if (ride.stops.isNotEmpty) { List convertedStops = ride.stops.map((stop) { return RideStopRequest( stopLocation: stop.stopLocation, stopStartTime: stop.stopStartTime, stopEndTime: stop.stopEndTime, additionalCharges: stop.additionalCharges, actualStopDurationMinutes: stop.actualStopDurationMinutes, plannedStopDurationMinutes: stop.plannedStopDurationMinutes, ); }).toList(); updateStoplocationlocation(convertedStops); } if (ride.rideStartTime != null) { updateridestartdatetime(ride.rideStartTime!); } if (ride.totalPrice != null) { updatePrice(ride.totalPrice!); } if (ride.couponcode != null) { updateCoupon(ride.couponcode!); } if (ride.couponvalue != null||ride.couponpercentage != null) { updateCouponvalue(ride.couponvalue,ride.couponpercentage); } // Update location names if not null or empty if (ride.startLocationName != null && ride.startLocationName.trim().isNotEmpty) { updateStartlocationName(ride.startLocationName); } if (ride.endLocationName != null && ride.endLocationName.trim().isNotEmpty) { updatedestinationlocationName(ride.endLocationName); } if (_startlocation != null && _destinationlocation != null) { updateGeoHash(ride.geometryhash); //await fetchRouteInfo(_startlocation!, _destinationlocation!); } updateRide(ride); } Future send_update_ride(RideUpdateRequest request) async { try { final APIResponse response = await _rideService.send_update_ride(request); if (response.isValidResponse) { return response.data!; } return false; // Return null on failure } catch (e) { log("Exception in getRideDetails for #${request.rideId}: $e"); return false; } } /// Decode polyline string to list of LatLng List decodePolyline(String polyline) { PolylinePoints polylinePoints = PolylinePoints(); List result = polylinePoints.decodePolyline(polyline); return result .map((point) => LatLng(point.latitude, point.longitude)) .toList(); } /// Get distance from the start of a polyline to a target location double getDistanceFromPolylineToLatLng(String polyline, LatLng target) { if (polyline.isEmpty) { return 0.0; } try { final points = decodePolyline(polyline); if (points.length < 2) { // Handle cases where the polyline is too short return 0.0; } // Use the last point of the polyline final endPoint = points.last; return const Distance().as(LengthUnit.Kilometer, endPoint, target); } catch (e) { log("Error decoding polyline or calculating distance: $e"); return 0.0; } } /// Get estimated duration from polyline to target double getDurationFromPolylineToLatLng(String polyline, LatLng target, {double speedKmh = 40}) { final distance = getDistanceFromPolylineToLatLng(polyline, target); return (distance / speedKmh) * 60; } } final ridesprovidersingliton = RidesProvider();