import 'dart:convert'; import 'dart:math'; import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; // Import for WidgetsBinding import 'package:geolocator/geolocator.dart'; import 'package:get/get.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import '../../constant/links.dart'; import '../functions/crud.dart'; import '../functions/location_controller.dart'; class RideAvailableController extends GetxController { bool isLoading = false; // FIX 1: Initialize the map with a default structure. // This prevents `rideAvailableMap['message']` from ever being null in the UI. Map rideAvailableMap = {'message': []}; late LatLng southwest; late LatLng northeast; LatLngBounds calculateBounds(double lat, double lng, double radiusInMeters) { const double earthRadius = 6378137.0; // Earth's radius in meters double latDelta = (radiusInMeters / earthRadius) * (180 / pi); double lngDelta = (radiusInMeters / (earthRadius * cos(pi * lat / 180))) * (180 / pi); double minLat = lat - latDelta; double maxLat = lat + latDelta; double minLng = lng - lngDelta; double maxLng = lng + lngDelta; minLat = max(-90.0, minLat); maxLat = min(90.0, maxLat); minLng = (minLng + 180) % 360 - 180; maxLng = (maxLng + 180) % 360 - 180; if (minLng > maxLng) { double temp = minLng; minLng = maxLng; maxLng = temp; } return LatLngBounds( southwest: LatLng(minLat, minLng), northeast: LatLng(maxLat, maxLng), ); } double calculateDistance(String startLocation) { List startLocationParts = startLocation.split(','); double startLatitude = double.parse(startLocationParts[0]); double startLongitude = double.parse(startLocationParts[1]); double currentLatitude = Get.find().myLocation.latitude; double currentLongitude = Get.find().myLocation.longitude; return Geolocator.distanceBetween( currentLatitude, currentLongitude, startLatitude, startLongitude, ); } // A helper function to safely show dialogs after the build cycle is complete. void _showDialogAfterBuild(Widget dialog) { // FIX 2: Use addPostFrameCallback to ensure dialogs are shown after the build process. // This resolves the "visitChildElements() called during build" error. WidgetsBinding.instance.addPostFrameCallback((_) { Get.dialog( dialog, barrierDismissible: true, transitionCurve: Curves.easeOutBack, transitionDuration: const Duration(milliseconds: 200), ); }); } Future getRideAvailable() async { try { isLoading = true; update(); LatLngBounds bounds = calculateBounds( Get.find().myLocation.latitude, Get.find().myLocation.longitude, 4000); var payload = { 'southwestLat': bounds.southwest.latitude.toString(), 'southwestLon': bounds.southwest.longitude.toString(), 'northeastLat': bounds.northeast.latitude.toString(), 'northeastLon': bounds.northeast.longitude.toString(), }; var res = await CRUD().get(link: AppLink.getRideWaiting, payload: payload); isLoading = false; // Request is complete, stop loading indicator. if (res != 'failure') { final decodedResponse = jsonDecode(res); // Check for valid response structure if (decodedResponse is Map && decodedResponse.containsKey('message') && decodedResponse['message'] is List) { rideAvailableMap = decodedResponse; // If the list of rides is empty, show the "No Rides" dialog if ((rideAvailableMap['message'] as List).isEmpty) { _showDialogAfterBuild(_buildNoRidesDialog()); } } else { // If response format is unexpected, treat as no rides and show dialog rideAvailableMap = {'message': []}; _showDialogAfterBuild(_buildNoRidesDialog()); } update(); // Update the UI with new data (or empty list) } else { // This block now handles network/server errors correctly HapticFeedback.lightImpact(); update(); // Update UI to turn off loader // Show a proper error dialog instead of "No Rides" _showDialogAfterBuild( _buildErrorDialog("Failed to fetch rides. Please try again.".tr)); } } catch (e) { isLoading = false; update(); // This catches other exceptions like JSON parsing errors _showDialogAfterBuild( _buildErrorDialog("An unexpected error occurred.".tr)); } } // Extracted dialogs into builder methods for cleanliness. Widget _buildNoRidesDialog() { return CupertinoAlertDialog( title: Column( mainAxisSize: MainAxisSize.min, children: [ const Icon( CupertinoIcons.car, size: 44, color: CupertinoColors.systemGrey, ), const SizedBox(height: 12), Text( "No Rides Available".tr, style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w600), ), ], ), content: Padding( padding: const EdgeInsets.only(top: 8), child: Text( "Please check back later for available rides.".tr, style: const TextStyle(fontSize: 13, color: CupertinoColors.systemGrey), ), ), actions: [ CupertinoDialogAction( onPressed: () { Get.back(); // Close dialog Get.back(); // Go back from AvailableRidesPage }, child: Text('OK'.tr), ), ], ); } Widget _buildErrorDialog(String error) { // You can log the error here for debugging. // print("Error fetching rides: $error"); return CupertinoAlertDialog( title: const Icon( CupertinoIcons.exclamationmark_triangle_fill, color: CupertinoColors.systemRed, size: 44, ), content: Text( error, // Display the specific error message passed to the function style: const TextStyle(fontSize: 14), ), actions: [ CupertinoDialogAction( onPressed: () { Get.back(); // Close dialog Get.back(); // Go back from AvailableRidesPage }, child: Text('OK'.tr), ), ], ); } @override void onInit() { super.onInit(); getRideAvailable(); } }