import 'dart:async'; import 'dart:convert'; import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:wanisdriver/Provider/public_keys_provider.dart'; import 'package:wanisdriver/models/Ride.dart'; import 'package:flutter_polyline_points/flutter_polyline_points.dart'; import 'package:geolocator/geolocator.dart'; import 'package:get/get.dart'; import 'package:get_it/get_it.dart'; import 'package:latlong2/latlong.dart'; import '../Screen/MainScreen.dart'; import '../config/AppSnackbar.dart'; import '../config/api_response.dart'; import '../config/constants/app_constant.dart'; import '../config/enum/ride_enum.dart'; import '../main.dart'; import '../models/RideUpdateRequest.dart'; import '../routes/app_routes.dart'; import '../services/RideNotificationHelper.dart'; import '../services/RideServices.dart'; import 'package:http/http.dart' as http; import '../services/audio_service.dart'; import '../utils/DistanceCalculation.dart'; import 'RideNotification.dart'; import 'RideNotificationProvider.dart'; import 'RideRequestProvider.dart'; import 'RideStatusProvider.dart'; class RouteData { final List points; final Duration duration; final double distanceKm; RouteData({ required this.points, required this.duration, required this.distanceKm, }); } class RideProvider with ChangeNotifier { static final RideProvider _instance = RideProvider._internal(); factory RideProvider() { return _instance; } void bindDeps( RideStatusProvider rideStatusProvider, RideNotification rideNotification, ) { _rideStatusProvider = rideStatusProvider; _ridenotification = rideNotification; } RideProvider._internal(); final RideService _rideService = RideService(); late RideStatusProvider _rideStatusProvider; late RideNotification _ridenotification; final RideNotificationService _notificationService = GetIt.I(); List _rideshistory = []; bool _isLoading = false; bool _isLoadingPaymentCash = false; bool _isLoadingPaymentTpe = false; bool _isLoadingById = false; bool _isRefreshing = false; bool _isLoadingRate = false; bool _isChangeStatusLoading = false; bool _isAcceptLoading = false; bool _isdiclineLoading = false; bool _hasMore = true; int _currentPage = 0; int _totalPages = 1; Ride? _currentride; final Map _isLoadingAcceptMap = {}; final Map _isLoadingDeclineMap = {}; bool isLoadingAccept(int rideId) => _isLoadingAcceptMap[rideId] ?? false; bool isLoadingDecline(int rideId) => _isLoadingDeclineMap[rideId] ?? false; List get ridehistory => _rideshistory; Ride? get currentride => _currentride; bool get isLoading => _isLoading; bool get isLoadingPaymentCash => _isLoadingPaymentCash; bool get isLoadingPaymentTpe => _isLoadingPaymentTpe; bool get isLoadingRate => _isLoadingRate; bool get isChangeStatusLoading => _isChangeStatusLoading; bool get isAcceptLoading => _isAcceptLoading; bool get isdiclineLoading => _isdiclineLoading; bool get isLoadingById => _isLoadingById; bool get isRefreshing => _isRefreshing; // Add this at the top with other dependencies late RideNotificationProvider _rideNotificationProvider; final Map _rideCache = {}; Ride? getCachedRide(int rideId) => _rideCache[rideId]; void clear() { log('Cleaning Cleaning Cleaning Cleaning Cleaning Cleaning Cleaning Cleaning Cleaning Cleaning Cleaning Cleaning'); _rideshistory = []; _isLoading = false; _isLoadingById = false; _isRefreshing = false; _isLoadingRate = false; _isChangeStatusLoading = false; _isAcceptLoading = false; _isdiclineLoading = false; _isLoadingPaymentCash = false; _isLoadingPaymentTpe = false; _hasMore = true; _currentPage = 0; _totalPages = 1; _currentride = null; _isLoadingAcceptMap.clear(); _isLoadingDeclineMap.clear(); notifyListeners(); } Future refresh({String? date}) async { _rideshistory=[]; await fetchridebystatus(page: 0, date: date,status: ["REQUESTED"],isCorporate: false,type: 'SCHEDULE'); // Normal } void bindNotificationDeps(RideNotificationProvider notificationProvider) { _rideNotificationProvider = notificationProvider; } void setcurrentride(Ride ride){ _currentride = ride; notifyListeners(); } List getRoutePoints({Ride? ride}) { // Use the passed ride object if it exists, otherwise fall back to the global _currentride. // This maintains compatibility with other parts of your app. final aRide = ride ?? _currentride; final poly = aRide?.encodedpolyline; if (poly == null || poly.isEmpty) return []; if (poly.trimLeft().startsWith('[')) { try { final List coords = json.decode(poly); return coords.map((c) { return LatLng((c[1] as num).toDouble(), (c[0] as num).toDouble()); }).toList(); } catch (e) { log('Failed to parse geojson coords: $e'); return []; } } final pts = PolylinePoints().decodePolyline(poly); return pts.map((p) => LatLng(p.latitude, p.longitude)).toList(); } Future get_my_latest_ride_not_rated() async { notifyListeners(); try { final APIResponse response = await _rideService.get_my_latest_ride_not_rated(); print('response.isValidResponse : ${response.isValidResponse}'); print('response.data : ${response.data}'); if (response.isValidResponse) { return response.data; } else { print("Error: ${response.errorMessage}"); } } catch (e) { print("Exception: $e"); } finally { notifyListeners(); } } Future fetchRouteInfoMapbox(LatLng start, LatLng end) async { if (!publickeys.guardKey(publickeys.mapboxToken, 'mapboxToken')) { throw Exception('Mapbox token not configured — contact admin'); } final url = Uri.https( 'api.mapbox.com', '/directions/v5/mapbox/driving/${start.longitude},${start.latitude};${end.longitude},${end.latitude}', { 'geometries': 'polyline', 'overview': 'full', 'steps': 'false', 'access_token': publickeys.mapboxToken!, }, ); final response = await http.get(url); if (response.statusCode != 200) { throw Exception('Mapbox Directions API error: ${response.statusCode}'); } final data = json.decode(response.body) as Map; if (data['code'] != 'Ok' || (data['routes'] as List).isEmpty) { throw Exception('No route found (code=${data['code']})'); } final route = (data['routes'] as List).first as Map; final geometry = route['geometry'] as String; final secs = (route['duration'] as num).toInt(); final meters = (route['distance'] as num).toDouble(); final km = meters / 1000.0; final decoded = PolylinePoints().decodePolyline(geometry); final points = decoded.map((p) => LatLng(p.latitude, p.longitude)).toList(); return RouteData( points: points, duration: Duration(seconds: secs), distanceKm: km, ); } // Paste this entire function inside your RideProvider class List decodePolyline(String encodedPolyline) { if (encodedPolyline.isEmpty) { return []; } // This part handles a different format you had before, making it robust. if (encodedPolyline.trimLeft().startsWith('[')) { try { final List coords = json.decode(encodedPolyline); return coords.map((c) { return LatLng((c[1] as num).toDouble(), (c[0] as num).toDouble()); }).toList(); } catch (e) { log('Failed to parse geojson coords: $e'); return []; } } // This uses the standard library to decode the polyline. try { final List points = PolylinePoints().decodePolyline(encodedPolyline); return points.map((point) => LatLng(point.latitude, point.longitude)).toList(); } catch (e) { log('--- ERROR DECODING POLYLINE --- : $e'); return []; } } String getCalculatedDistanceString(String currentlocation,String targetlocaiton) { final double? distance = calculateDistanceBetweenDriverAndUser( currentlocation, targetlocaiton, ); if (distance != null) { return "${distance.toStringAsFixed(1)} Km"; } else { return "Error Km"; // Or handle based on your logic for invalid user location } } Future acceptRide(int rideId) async { _setAcceptLoading(rideId, true); try { final response = await _rideService.acceptRide(rideId); if (response.isValidResponse) { AppSnackbar.showSuccess("Success", "Ride accepted"); // Remove ride from available pickups _rideshistory.removeWhere((ride) => ride.id == rideId); notifyListeners(); print("Ride accepted: ${response.data}"); } else { AppSnackbar.showError("Error", "Error accepting ride"); print("Error accepting ride: ${response.errorMessage}"); } } catch (e) { print("Exception while accepting ride: $e"); } finally { _setAcceptLoading(rideId, false); } } Future fetchPendingRides({int page = 0}) async { final bool isInitialPage = page == 0; if (isInitialPage) { _isRefreshing = true; } else { _isLoading = true; } notifyListeners(); try { final APIResponse> response = await _rideService.fetchPendingRides( page: page, size: 5, ); if (response.isValidResponse) { final data = response.data ?? []; if (isInitialPage) { _rideshistory = data; } else { _rideshistory.addAll(data); } _totalPages = response.totalPages!; _hasMore = _currentPage < _totalPages - 1; } else { print("Error: ${response.errorMessage}"); } } catch (e) { print("Exception: $e"); } finally { _isLoading = false; _isRefreshing = false; notifyListeners(); } } Future confirmPayment({ required int rideId, required String paymentType, }) async { paymentType == "CASH" ? _isLoadingPaymentCash = true: _isLoadingPaymentTpe = true; notifyListeners(); try { final rideService = RideService(); final result = await rideService.confirmPayment( rideId: rideId, paymentType: paymentType, ); if(result.isValidResponse){ return true; }else{return false;} } catch (e) { print("Exception while accepting ride: $e"); return false; } finally { paymentType == "CASH" ? _isLoadingPaymentCash = false: _isLoadingPaymentTpe = false; notifyListeners(); } } Future declineRide(int rideId) async { _setDeclineLoading(rideId, true); try { final response = await _rideService.declineRide(rideId); if (response.isValidResponse) { AppSnackbar.showSuccess("Success", "Ride declined"); // Remove ride from available pickups _rideshistory.removeWhere((ride) => ride.id == rideId); notifyListeners(); print("Ride declined: ${response.data}"); } else { AppSnackbar.showError("Error", "Error declining ride"); print("Error declining ride: ${response.errorMessage}"); } } catch (e) { print("Exception while declining ride: $e"); } finally { _setDeclineLoading(rideId, false); } } void _setAcceptLoading(int rideId, bool value) { _isLoadingAcceptMap[rideId] = value; notifyListeners(); } void _setDeclineLoading(int rideId, bool value) { _isLoadingDeclineMap[rideId] = value; notifyListeners(); } Future fetchridebystatus({int page = 0, String? date,required List status,required bool isCorporate,required String type}) async { if (page == 0) { _isRefreshing = true; notifyListeners(); } else { _isLoading = true; notifyListeners(); } try { final APIResponse> response = await _rideService.fetchridebystatus(page, 5,isCorporate, date: date,status: status,type:type); if (response.isValidResponse) { if (page == 0) { _rideshistory = response.data!; } else { _rideshistory.addAll(response.data!); } _totalPages = response.totalPages!; _hasMore = _currentPage < _totalPages - 1; } else { _hasMore = false; print("Error: ${response.errorMessage}"); } } catch (e) { print("Exception: $e"); } finally { _isLoading = false; _isRefreshing = false; notifyListeners(); } } Future findbyid(int rideId) async { log("Orchestrating find by id for ride #$rideId"); _isLoadingById = true; notifyListeners(); try { // ✅ Call the safe, cached, and debounced method instead of the raw service. final Ride? rideData = await getRideDetails(rideId); if (rideData != null) { log('Current ride was: ${_currentride?.id}'); log('Current ride was: ===> $_currentride'); _currentride = rideData; log('Current ride is now: ${_currentride?.id}'); } else { // getRideDetails already logs errors, but you can add more handling here if needed. print("Error: Failed to get details for ride #$rideId"); } } catch (e) { // This catch block is for unexpected errors in this method's logic. print("Exception in finbyid orchestration: $e"); } finally { _isLoadingById = false; notifyListeners(); } } Future getRideDetails(int rideId, {bool forceRefresh = false}) async { if (!forceRefresh && _rideCache.containsKey(rideId)) { return _rideCache[rideId]; } try { final resp = await _rideService.fetchbyid(rideId); if (resp.isValidResponse) { _rideCache[rideId] = resp.data!; return resp.data!; } return null; } catch (e) { log('getRideDetails($rideId) failed: $e'); return null; } } Future accept_ride_driver(int rideId) async { _isAcceptLoading = true; notifyListeners(); try { final res = await _rideService.accept_ride_driver(rideId); if (res.isValidResponse && res.data == true) { _rideCache.remove(rideId); // 🟢 force a refetch next time return true; } return false; } finally { _isAcceptLoading = false; notifyListeners(); } } Future accept_ride_driver_manual(int rideId) async { _isAcceptLoading = true; notifyListeners(); try { final res = await _rideService.accept_ride_driver_manual(rideId); if (res.isValidResponse && res.data == true) { _rideCache.remove(rideId); // 🟢 force a refetch next time return true; } return false; } finally { _isAcceptLoading = false; notifyListeners(); } } // Future accept_ride_driver(int rideId) async { // _isAcceptLoading =true; // notifyListeners(); // try { // final APIResponse response = await _rideService.accept_ride_driver(rideId); // if (response.isValidResponse) { // // return response.data!; // } // return false; // Return null on failure // } catch (e) { // log("Exception in getRideDetails for #$rideId: $e"); // return false; // }finally { // _isAcceptLoading = false; // notifyListeners(); // } // } Future send_update_ride(RideUpdateRequest request) async { log("response send_update_ride 01: $request"); _isChangeStatusLoading =true; notifyListeners(); try { final APIResponse response = await _rideService.send_update_ride(request); log("response send_update_ride: $response"); if (response.isValidResponse) { log("response send_update_ride 002: ${response.data}"); return response.data!; } return false; // Return null on failure } catch (e) { log("Exception in getRideDetails for #${request.rideId}: $e"); return false; } finally { _isChangeStatusLoading = false; notifyListeners(); } } Future cancel_ride_driver(int rideId) async { _isdiclineLoading =true; notifyListeners(); try { final APIResponse response = await _rideService.cancel_ride_driver(rideId); if (response.isValidResponse) { // ✅ Use the service to cancel the specific notification await _notificationService.cancelRideNotification(rideId); return response.data!; } return false; // Return null on failure } catch (e) { log("❌ Exception cancelling ride #$rideId: $e"); return false; } finally { _isdiclineLoading = false; notifyListeners(); } } Future cancelAllRideNotifications() async { try { // Get the MyAppState instance using navigatorKey final context = navigatorKey.currentContext; if (context != null) { await _notificationService.cancelAllRideNotifications(); } } catch (e) { log('Error cancelling all ride notifications: $e'); } } Future updateraduice(double raduice) async { _isLoadingById = true; notifyListeners(); try { final APIResponse response = await _rideService.updateraduice(raduice); print('response. : ${response.isValidResponse}'); print('response.daisValidResponse ta : ${response.data}'); if (response.isValidResponse) { bool updated = response.data!; } else { print("Error: ${response.errorMessage}"); } } catch (e) { print("Exception: $e"); } finally { _isLoadingById = false; notifyListeners(); } } Future finbyidWithUpdate(int rideId) async { _isLoadingById = true; notifyListeners(); try { final APIResponse response = await _rideService.fetchbyid(rideId); 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}'); updatestatusbycurrentride(); } else { print("Error: ${response.errorMessage}"); } } catch (e) { print("Exception: $e"); } finally { _isLoadingById = false; notifyListeners(); } } Future get_my_latest_ride_ongoing() async { print('---------------------------------------------------------------------------------------get_my_latest_ride_ongoing'); if(_currentride!=null){ log("already you have ride"); return; } _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 get_my_latest_ride_ongoing: ${response.data}'); if (response.isValidResponse) { _currentride = response.data!; print('current ride : ${_currentride}'); updatestatusbycurrentride(); print('current ride after : $_currentride'); } else { log("no ride found for the last session: ${response.errorMessage}"); } } catch (e) { print("Exception: $e"); } finally { _isLoadingById = false; notifyListeners(); } } updatestatusbycurrentride(){ if (currentride == null) { _isLoadingById = false; notifyListeners(); return; } final mainState = MainScreen.globalKey.currentState; log('current socket state : ${currentride?.currentridetypesocket}'); log('current currentride?.currentridetypesocket=="DRIVER_ARRIVED" : ${currentride?.currentridetypesocket=="DRIVER_ARRIVED"}'); // fillProvider(_currentride!); if(currentride?.currentridetypesocket=="DRIVER_ASSIGNED"){ _rideStatusProvider.updatestates(RideEnum.to_client); _ridenotification.clearAllPendingRides(); }else if(currentride?.currentridetypesocket=="RIDE_END"){ _rideStatusProvider.updatestates(RideEnum.ride_end); _ridenotification.clearAllPendingRides(); mainState?.navigateToScreen(0); }else if(currentride?.currentridetypesocket=="RIDE_CANCEL_USER" ){ GetIt.I().playSound(SoundEvent.riderCancelled); _rideStatusProvider.updatestates(RideEnum.cancel_ride); _ridenotification.clearAllPendingRides(); AppSnackbar.showInfo("Ride Update", "the ride is already cancel by the client"); mainState?.navigateToScreen(0); }else if(currentride?.currentridetypesocket=="RIDE_CANCEL_DRIVER"){ _rideStatusProvider.updatestates(RideEnum.cancel_ride); _ridenotification.clearAllPendingRides(); AppSnackbar.showInfo("Ride Update", "the ride is already cancel by the driver"); mainState?.navigateToScreen(0); }else if(currentride?.currentridetypesocket=="DRIVER_ARRIVED"||currentride?.currentridetypesocket == "USER_COMING"){ _rideStatusProvider.updatestates(RideEnum.driver_arrived); _ridenotification.clearAllPendingRides(); mainState?.navigateToScreen(4); }else if(currentride?.currentridetypesocket=="RIDE_STOP"){ _rideStatusProvider.updatestates(RideEnum.stop); _ridenotification.clearAllPendingRides(); mainState?.navigateToScreen(4); } else if(currentride?.currentridetypesocket=="RIDE_START"){ _rideStatusProvider.updatestates(RideEnum.start_ride); _ridenotification.clearAllPendingRides(); mainState?.navigateToScreen(4); } notifyListeners(); } double getLatitude(String location) { List coordinates = location.split(','); return double.tryParse(coordinates[0]) ?? 0.0; } double getLongitude(String location) { List coordinates = location.split(','); return double.tryParse(coordinates[1]) ?? 0.0; } Future rate_user_current_ride(int rating) async { if (_currentride == null) { AppSnackbar.showError("Error", "Ride not exist !!"); return; } _isLoadingRate = true; notifyListeners(); try { final response = await _rideService.rate_user(rating, _currentride!.userId!); print('response.data : ${response.data}'); } catch (e) { print("Exception: $e"); } finally { _isLoadingRate = false; notifyListeners(); } } Future rate_user(int rating,int userid) async { _isLoadingRate = true; notifyListeners(); try { final response = await _rideService.rate_user(rating,userid); print('response.data : ${response.data}'); } catch (e) { print("Exception: $e"); } finally { _isLoadingRate = false; notifyListeners(); } } } final rideSingleton = RideProvider();