import 'dart:async'; import 'dart:developer'; import 'dart:math' as math; import 'dart:convert'; import 'package:carousel_slider/carousel_slider.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:wanisdriver/Provider/driver_provider.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_lucide/flutter_lucide.dart'; import 'package:geolocator/geolocator.dart' as geolocator; import 'package:latlong2/latlong.dart'; import 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:sliding_up_panel/sliding_up_panel.dart'; import '../Provider/RideNotification.dart'; import '../Provider/RideStatusProvider.dart'; import '../Provider/authentification_provider.dart'; import '../Provider/public_provider.dart'; import '../Provider/utils_singleton.dart'; import '../config/AppSnackbar.dart'; import '../config/constants/consts.dart'; import '../config/custom_loader.dart'; import '../config/enum/ride_enum.dart'; import '../network/kafkahandler.dart'; import '../utils/colors.dart'; import '../utils/sized.dart'; import '../widgets/DriverInfoCard.dart'; import '../widgets/PermissionErrorWidget.dart'; import 'package:flutter/services.dart'; import '../Provider/RideProvider.dart'; import '../widgets/custom_button.dart'; import 'MainScreen.dart'; class HomeScreen extends StatefulWidget { @override _HomeScreenState createState() => _HomeScreenState(); } class _HomeScreenState extends State with WidgetsBindingObserver { double currentZoomLevel = 12.0; double driverInfoCardHeight = 0; MapboxMap? mapController; bool _isMapInitialized = false; bool _isDisposing = false; bool isLoading = true; bool permissionsGranted = false; bool _styleLoaded = false; bool _availabilityLoading = false; LatLng? currentLocation; Color enlignecolor = const Color(0xFF58BE3F); Color horlinecolor = const Color(0xFFF03D3D); static const double _minRadius = 1000; static const double _maxRadius = 7000; static const _rideHeatSourceId = 'ride_heat_source'; static const _rideHeatLayerId = 'ride_heat_layer'; double circleRadius = 3000; bool _disposed = false; StreamSubscription? _posSub; Timer? _ridePollTimer; late final AuthProvider authProvider; late final PublicProvider publicprovider; PointAnnotationManager? _locationAnnotationManager; CircleAnnotationManager? _circleAnnotationManager; int _annotationRetryCount = 0; static const int _maxAnnotationRetries = 5; bool _managersInitialized = false; Uint8List? _markerImageBytes; final Completer _mapReadyCompleter = Completer(); bool _isInitializingAnnotations = false; Completer _annotationManagerCompleter = Completer(); StreamSubscription>? _heatSub; Timer? _mapInitializationTimer; int _mapInitRetryCount = 0; static const int _maxMapInitRetries = 5; bool _isMapCreationInProgress = false; PointAnnotation? _locationAnnotation; static const _driverCircleSourceId = 'driver_circle_source'; static const _driverCircleLayerId = 'driver_circle_layer'; Utils? _utils_listened; bool _driver_available = false; final kafkahandler = KafkaHandler.instance; Timer? _radiusUpdateTimer; @override void initState() { super.initState(); authProvider = context.read(); publicprovider = context.read(); WidgetsBinding.instance.addObserver(this); _loadMarkerImage(); WidgetsBinding.instance.addPostFrameCallback((_) { Future.delayed(const Duration(milliseconds: 500), () { if (mounted) { _initialize(); kafkahandler.connect(); _startHeatSubscription(); } }); }); } void _startHeatSubscription() { _heatSub?.cancel(); _heatSub = kafkahandler.rideHeatStream.listen((fc) async { await _ensureHeatLayer(); await _updateHeatData(fc); }); } void _startPollingTimer() { _ridePollTimer?.cancel(); log("start pulling every 5sec ####################################"); _ridePollTimer = Timer.periodic(const Duration(seconds: 5), (timer) { _pollRideStatus(); }); } void _pollRideStatus() async { if (!mounted) { log("Polling stopped because widget is no longer mounted."); _ridePollTimer?.cancel(); return; } if (context.read().currentride == null) { log("Polling for ride status update..."); try { await context.read().get_my_latest_ride_ongoing(); } catch (e) { log("Error during ride polling: $e"); } } } Future _ensureHeatLayer() async { if (mapController == null) return; final style = mapController!.style; final hasSource = await style.styleSourceExists(_rideHeatSourceId); if (!hasSource) { await style.addSource(GeoJsonSource( id: _rideHeatSourceId, data: jsonEncode({'type': 'FeatureCollection', 'features': []}), )); } final hasLayer = await style.styleLayerExists(_rideHeatLayerId); if (!hasLayer) { // add a real 'heatmap' layer using raw style json (works on all versions) final heatLayerJson = jsonEncode({ 'id': _rideHeatLayerId, 'type': 'heatmap', 'source': _rideHeatSourceId, 'paint': { // weight by "count" property from backend 'heatmap-weight': [ 'interpolate', ['linear'], ['get', 'count'], 1, 0.2, 20, 0.6, 50, 1.0 ], // vary radius by zoom 'heatmap-radius': [ 'interpolate', ['linear'], ['zoom'], 10, 15, 12, 22, 14, 32 ], 'heatmap-intensity': 1.0, 'heatmap-opacity': 0.8 } }); // insert above symbols if you prefer; belowId can be null await style.addStyleLayer(heatLayerJson, null); } } Future _updateHeatData(Map fc) async { if (mapController == null) return; await mapController!.style.setStyleSourceProperty( _rideHeatSourceId, 'data', jsonEncode(fc), ); } Future _ensureLocationReady() async { if (!await geolocator.Geolocator.isLocationServiceEnabled()) { log("๐Ÿ“ก Location services are disabled"); await geolocator.Geolocator.openLocationSettings(); return false; } var perm = await geolocator.Geolocator.checkPermission(); log("๐Ÿ” Permission status: $perm"); if (perm == geolocator.LocationPermission.deniedForever) { log("๐Ÿšซ Permission denied forever"); await geolocator.Geolocator.openAppSettings(); return false; } if (perm == geolocator.LocationPermission.denied) { perm = await geolocator.Geolocator.requestPermission(); log("๐Ÿ”„ Permission requested, result: $perm"); if (perm != geolocator.LocationPermission.whileInUse && perm != geolocator.LocationPermission.always) { log("โŒ Permission denied again"); return false; } } log("โœ… Permissions okay"); return true; } Future _setCircleVisibility(bool visible) async { if (mapController == null) return; final style = mapController!.style; // final hasLayer = await style.styleLayerExists(_driverCircleLayerId); // if (!hasLayer) return; // // // drive a paint property โ€” this guarantees a repaint // await style.setStyleLayerProperty( // _driverCircleLayerId, // 'fill-opacity', // jsonEncode(visible ? 0.20 : 0.0), // ); // // if (!visible) { // // clear geometry so hit-testing etc. is empty // await style.setStyleSourceProperty( // _driverCircleSourceId, // 'data', // jsonEncode({'type': 'FeatureCollection', 'features': []}), // ); // } // force a redraw on engines that cache the last frame await _pokeRender(); await _ensureLocationMarker(); } Future _getCurrentLocation() async { if (_disposed || !mounted) return; if (!await _ensureLocationReady()) { if (!_disposed && mounted) { setState(() { isLoading = false; permissionsGranted = false; }); } return; } final last = await geolocator.Geolocator.getLastKnownPosition(); if (last != null && !_disposed && mounted) { log("๐Ÿ“ฆ Last known position: ${last.latitude}, ${last.longitude}"); if (!_disposed && mounted) { setState(() => currentLocation = LatLng(last.latitude, last.longitude)); // ADDED: if style is already loaded, render immediately (no wait) if (_styleLoaded && _isMapInitialized && mapController != null) { // try to create marker/circle โ€” managers may not exist yet, so guard if (_managersInitialized) { await _addLocationMarker(); await _updateCirclePolygon(currentLocation!, circleRadius); } } } } geolocator.LocationSettings settings = const geolocator.LocationSettings( accuracy: geolocator.LocationAccuracy.high, distanceFilter: 10, ); if (defaultTargetPlatform == TargetPlatform.android) { settings = geolocator.AndroidSettings( accuracy: geolocator.LocationAccuracy.bestForNavigation, distanceFilter: 5, forceLocationManager: true, ); } _posSub?.cancel(); _posSub = geolocator.Geolocator.getPositionStream(locationSettings: settings).listen((pos) { if (_disposed) return; final newLocation = LatLng(pos.latitude, pos.longitude); if (!_disposed && mounted) { setState(() { currentLocation = newLocation; }); } _updateLocationMarker(newLocation, pos.heading); // FIXED: Check if map is properly initialized before flying to location if (_isMapInitialized && mapController != null) { mapController?.flyTo( CameraOptions( center: Point(coordinates: Position(newLocation.longitude, newLocation.latitude)), // no zoom here on purpose ), MapAnimationOptions(duration: 800), ); } }, onError: (e) { log('โ— Position stream error: $e'); }); if (!_disposed && mounted) { setState(() { permissionsGranted = true; isLoading = false; }); } } Future _initialize() async { if (!mounted) return; setState(() => isLoading = true); await _getCurrentLocation(); await publicprovider.fetchOffer(); if (!mounted) return; initialazeDriverConnection(); if (!mounted) return; setState(() => isLoading = false); final prefs = await SharedPreferences.getInstance(); double? driverraduice = prefs.getDouble("driverraduice"); if (driverraduice == null) { updateraduice(); } else { circleRadius = driverraduice; } final utilsInit = context.read(); _driver_available = utilsInit.user?.driver?.available ?? false; // seed cache _startPollingTimer(); } Future zoomInRadius() async { if (_isDisposing || _disposed) return; // Update UI immediately for smooth interaction setState(() { circleRadius = (circleRadius + 500).clamp(_minRadius, _maxRadius); }); // Update map circle immediately if available and map is ready if (_isMapInitialized && currentLocation != null) { final availableNow = utils.user?.driver?.available ?? false; if (availableNow) { try { await _updateCirclePolygon(currentLocation!, circleRadius); } catch (e) { log('Error updating circle polygon: $e'); } } } // Debounce only the SharedPreferences and API calls _radiusUpdateTimer?.cancel(); _radiusUpdateTimer = Timer(const Duration(milliseconds: 800), () async { if (!mounted || _isDisposing || _disposed) return; try { await updateraduice(); // This saves to SharedPreferences and updates API } catch (e) { log('Error saving radius: $e'); } }); } // REPLACE your existing zoomOutRadius function with this: Future zoomOutRadius() async { if (_isDisposing || _disposed) return; // Update UI immediately for smooth interaction setState(() { circleRadius = (circleRadius - 500).clamp(_minRadius, _maxRadius); }); // Update map circle immediately if available and map is ready if (_isMapInitialized && currentLocation != null) { final availableNow = utils.user?.driver?.available ?? false; if (availableNow) { try { await _updateCirclePolygon(currentLocation!, circleRadius); } catch (e) { log('Error updating circle polygon: $e'); } } } // Debounce only the SharedPreferences and API calls _radiusUpdateTimer?.cancel(); _radiusUpdateTimer = Timer(const Duration(milliseconds: 800), () async { if (!mounted || _isDisposing || _disposed) return; try { await updateraduice(); // This saves to SharedPreferences and updates API } catch (e) { log('Error saving radius: $e'); } }); } Future updateraduice() async { final prefs = await SharedPreferences.getInstance(); final rideprovider = context.read(); rideprovider.updateraduice(circleRadius); await prefs.setDouble("driverraduice", circleRadius); } Future _wait_for_style_ready(MapboxMap map, {Duration timeout = const Duration(seconds: 3), Duration interval = const Duration(milliseconds: 60)}) async { final style = map.style; final start = DateTime.now(); while (true) { try { final ok = await style.styleSourceExists('composite'); if (ok) return; } catch (_) { // ignore transient channel errors while style is booting } if (DateTime.now().difference(start) > timeout) { log('โš ๏ธ style readiness wait timed out; proceeding anyway'); return; } await Future.delayed(interval); } } void _onMapCreated(MapboxMap map) async { if (_isDisposing || _disposed || _isMapCreationInProgress) return; _isMapCreationInProgress = true; log('๐ŸŸข Map creation started'); try { mapController = map; log('๐ŸŸข Map controller assigned'); // wait for style to actually be usable (fast loop, no arbitrary sleep) await _wait_for_style_ready(map); _styleLoaded = true; _isMapInitialized = true; log('๐ŸŸข Style ready โ†’ init layers/managers'); // create geojson source/layer for the meters circle await _ensureCircleLayer(); await _ensureHeatLayer(); // managers (marker/circle annotations) โ€” marker bytes already preloaded await _initializeAnnotationManagers(); if (!_mapReadyCompleter.isCompleted) { _mapReadyCompleter.complete(); } // if we already know a location, draw immediately if (currentLocation != null) { await _addLocationMarker(); await _updateCirclePolygon(currentLocation!, circleRadius); await _ensureLocationMarker(); } } catch (e, st) { log('โŒ Error in _onMapCreated: $e\n$st'); if (_mapInitRetryCount < _maxMapInitRetries) { _mapInitRetryCount++; _scheduleMapInitRetry(); } } finally { _isMapCreationInProgress = false; } } // NEW: Schedule map initialization retry void _scheduleMapInitRetry() { _mapInitializationTimer?.cancel(); _mapInitializationTimer = Timer(Duration(milliseconds: 1000), () { if (mounted && !_disposed && mapController != null) { log('๐Ÿ”„ Retrying map initialization (attempt $_mapInitRetryCount)'); _onMapCreated(mapController!); } }); } // ENHANCED: More robust annotation manager initialization Future _initializeAnnotationManagers() async { if (_isDisposing || _disposed || mapController == null || _isInitializingAnnotations) { log('๐ŸŸก Skipping manager initialization - already initializing or not ready'); return; } _isInitializingAnnotations = true; try { log('๐Ÿ”„ Initializing annotation managers...'); // Reset the completer if needed if (_annotationManagerCompleter.isCompleted) { _annotationManagerCompleter = Completer(); } // CRITICAL FIX: Never call deleteAll on null or invalid managers // Simply set them to null without trying to clear them _locationAnnotationManager = null; _circleAnnotationManager = null; // FIXED: Add delay before creating new managers on Android if (defaultTargetPlatform == TargetPlatform.android) { await Future.delayed(const Duration(milliseconds: 1000)); } if (_isDisposing || _disposed) return; // Create new managers with retry logic _locationAnnotationManager = await _createManagerWithRetry( () => mapController!.annotations.createPointAnnotationManager(), 'Point annotation manager' ); _circleAnnotationManager = await _createManagerWithRetry( () => mapController!.annotations.createCircleAnnotationManager(), 'Circle annotation manager' ); // Load marker image if not loaded if (_markerImageBytes == null) { await _loadMarkerImage(); } _managersInitialized = true; _annotationManagerCompleter.complete(); log('๐ŸŸข Annotation managers initialized successfully'); } catch (e, stackTrace) { log('โŒ Error initializing annotation managers: $e'); log('Stack trace: $stackTrace'); _managersInitialized = false; // Clear references on error _locationAnnotationManager = null; _circleAnnotationManager = null; // Complete with error if (!_annotationManagerCompleter.isCompleted) { _annotationManagerCompleter.completeError(e); } // Retry if within limits if (_annotationRetryCount < _maxAnnotationRetries) { _annotationRetryCount++; log('๐Ÿ”„ Scheduling annotation manager retry (attempt $_annotationRetryCount)'); Future.delayed(Duration(milliseconds: 2000 * _annotationRetryCount), () { if (!_disposed && mounted) { _initializeAnnotationManagers(); } }); } } finally { _isInitializingAnnotations = false; } } // NEW: Load marker image with error handling Future _loadMarkerImage() async { try { final ByteData imageData = await rootBundle.load('assets/icons/taxi_car_marker.png'); _markerImageBytes = imageData.buffer.asUint8List(); log('๐ŸŸข Marker image loaded'); } catch (e) { log('โŒ Error loading marker image: $e'); // Create a simple fallback marker if image fails to load _markerImageBytes = Uint8List(0); } } // ENHANCED: Helper method to create managers with retry logic Future _createManagerWithRetry(Future Function() createFunction, String managerName) async { int attempts = 0; const maxAttempts = 3; while (attempts < maxAttempts) { try { final manager = await createFunction(); log('๐ŸŸข $managerName created successfully'); return manager; } catch (e) { attempts++; log('โŒ Failed to create $managerName (attempt $attempts/$maxAttempts): $e'); if (attempts >= maxAttempts) { rethrow; } // Wait longer between each attempt await Future.delayed(Duration(milliseconds: 200 * attempts)); } } throw Exception('Failed to create $managerName after $maxAttempts attempts'); } Future _ensureCircleLayer() async { if (mapController == null) return; final style = mapController!.style; // ensure source final hasSource = await style.styleSourceExists(_driverCircleSourceId); if (!hasSource) { await style.addSource(GeoJsonSource( id: _driverCircleSourceId, data: jsonEncode({ 'type': 'FeatureCollection', 'features': [], }), )); } // ensure layer final hasLayer = await style.styleLayerExists(_driverCircleLayerId); if (!hasLayer) { await style.addLayer( FillLayer( id: _driverCircleLayerId, sourceId: _driverCircleSourceId, fillColor: Colors.green.value, // opaque color fillOutlineColor: Colors.green.value, ), ); // NEW: set initial opacity (visible state) here await style.setStyleLayerProperty( _driverCircleLayerId, 'fill-opacity', jsonEncode(0.20), ); } } Future _updateCirclePolygon(LatLng center, double radiusMeters) async { if (mapController == null) return; final featureCollection = { 'type': 'FeatureCollection', 'features': [ { 'type': 'Feature', // 'geometry': _circlePolygon(center.latitude, center.longitude, radiusMeters), 'properties': {}, } ], }; await mapController!.style.setStyleSourceProperty( _driverCircleSourceId, 'data', jsonEncode(featureCollection), // MUST be a String ); } /// returns a GeoJSON Polygon { "type":"Polygon", "coordinates":[ [lon,lat], ... ] } Map _circlePolygon(double lat, double lon, double radiusMeters, {int steps = 128}) { final coords = >[]; for (int i = 0; i <= steps; i++) { final bearing = (i * 360.0) / steps; final p = _destinationPoint(lat, lon, radiusMeters, bearing); coords.add([p.longitude, p.latitude]); } return { 'type': 'Polygon', 'coordinates': [coords], }; } LatLng _destinationPoint(double lat, double lon, double distanceMeters, double bearingDegrees) { const earthRadius = 6371000.0; // meters final br = bearingDegrees * math.pi / 180.0; final dr = distanceMeters / earthRadius; final lat1 = lat * math.pi / 180.0; final lon1 = lon * math.pi / 180.0; final lat2 = math.asin(math.sin(lat1) * math.cos(dr) + math.cos(lat1) * math.sin(dr) * math.cos(br)); final lon2 = lon1 + math.atan2( math.sin(br) * math.sin(dr) * math.cos(lat1), math.cos(dr) - math.sin(lat1) * math.sin(lat2), ); return LatLng(lat2 * 180.0 / math.pi, (lon2 * 180.0 / math.pi)); } // ENHANCED: Much safer location marker update void _updateLocationMarker(LatLng newLocation, double heading) async { if (!mounted || _isDisposing || _disposed) return; if (!_managersInitialized || _locationAnnotationManager == null) return; if (_markerImageBytes == null || _markerImageBytes!.isEmpty) return; final options = PointAnnotationOptions( geometry: Point(coordinates: Position(newLocation.longitude, newLocation.latitude)), image: _markerImageBytes!, iconSize: 2.1, iconRotate: heading, ); try { if (_locationAnnotation == null) { // First time: create the marker _locationAnnotation = await _locationAnnotationManager!.create(options); } else { // Update existing marker _locationAnnotation = PointAnnotation( id: _locationAnnotation!.id, geometry: options.geometry, iconSize: options.iconSize, iconRotate: options.iconRotate, iconImage: _locationAnnotation!.iconImage, // Keep original image ); await _locationAnnotationManager!.update(_locationAnnotation!); } // final availableNow = context.read().user?.driver?.available ?? false; // if (availableNow) { // await _updateCirclePolygon(newLocation, circleRadius); // } else { // await _setCircleVisibility(false); // } } catch (e) { log('updateLocationMarker error: $e'); } } Future _updateDriverRadiusCircle({bool? overrideAvailable}) async { if (!mounted || _isDisposing || _disposed || currentLocation == null) return; // // final available = overrideAvailable ?? (context.read().user?.driver?.available ?? false); // // try { // if (!available) { // // offline โ†’ hide immediately // await _setCircleVisibility(false); // log('๐ŸŸก Driver offline โ†’ radius hidden'); // return; // } // // // online โ†’ ensure visible and update polygon // await _setCircleVisibility(true); // await _updateCirclePolygon(currentLocation!, circleRadius); // log('๐ŸŸข Driver online โ†’ radius updated'); // } catch (e) { // log('โŒ Error updating driver radius circle: $e'); // } } // ENHANCED: Safer initial marker addition Future _addLocationMarker() async { if (_isDisposing || _disposed || currentLocation == null) return; if (!_isMapInitialized || !_managersInitialized) return; if (_locationAnnotationManager == null || _markerImageBytes == null || _markerImageBytes!.isEmpty) return; final options = PointAnnotationOptions( geometry: Point(coordinates: Position(currentLocation!.longitude, currentLocation!.latitude)), image: _markerImageBytes!, iconSize: 2.0, ); try { _locationAnnotation ??= await _locationAnnotationManager!.create(options); final availableNow = context.read().user?.driver?.available ?? false; if (availableNow) { await _updateCirclePolygon(currentLocation!, circleRadius); } else { await _setCircleVisibility(false); } } catch (e) { log('addLocationMarker error: $e'); } } @override void didChangeDependencies() { super.didChangeDependencies(); // watch ensures this runs again if the provider instance is swapped final utils = context.watch(); if (!identical(_utils_listened, utils)) { // detach old _utils_listened?.removeListener(_onUtilsChanged); // attach new _utils_listened = utils; _utils_listened?.addListener(_onUtilsChanged); // sync once immediately _syncAvailabilityFromUtils(); } } void _onUtilsChanged() { // called whenever Utils.notifyListeners() fires _syncAvailabilityFromUtils(); } void _syncAvailabilityFromUtils() { final now = _utils_listened?.user?.driver?.available ?? false; if (now != _driver_available) { _driver_available = now; // show/hide the radius immediately without waiting for a rebuild if (_isMapInitialized && _managersInitialized && mounted) { _updateDriverRadiusCircle(overrideAvailable: _driver_available); } } } // ENHANCED: Improved disposal with proper cleanup sequence @override void dispose() { _heatSub?.cancel(); log('HomeScreen dispose() called. Canceling timers...'); // Set flags first to prevent new async work from starting. _isDisposing = true; _disposed = true; WidgetsBinding.instance.removeObserver(this); _utils_listened?.removeListener(_onUtilsChanged); _ridePollTimer?.cancel(); _posSub?.cancel(); _mapInitializationTimer?.cancel(); _radiusUpdateTimer?.cancel(); // Safely dispose Mapbox-related resources. _disposeAnnotationManagers(); log('HomeScreen disposal completed.'); super.dispose(); } // CRITICAL FIX: Much safer disposal that prevents crashes Future _disposeAnnotationManagers() async { try { log('๐Ÿ”„ Starting annotation manager disposal...'); // Simply nullify references without calling deleteAll // The Mapbox SDK will handle cleanup when the map is disposed if (_locationAnnotationManager != null) { log('๐ŸŸข Location annotation manager reference cleared'); _locationAnnotationManager = null; } if (_circleAnnotationManager != null) { log('๐ŸŸข Circle annotation manager reference cleared'); _circleAnnotationManager = null; } _managersInitialized = false; log('๐ŸŸข Annotation managers disposed safely'); } catch (e) { log('๐ŸŸก Error during annotation manager disposal: $e'); } } @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.resumed) { _onAppResume(); } } Future _onAppResume() async { if (!_disposed) { await _getCurrentLocation(); // FIXED: Reinitialize map components if needed after app resume if (_isMapInitialized && !_managersInitialized) { log('๐Ÿ”„ Reinitializing annotation managers after app resume'); await _initializeAnnotationManagers(); } } } Future initialazeDriverConnection() async { final rideprovider = context.read(); if (rideprovider.currentride == null) { await Provider.of(context, listen: false) .update_availabilty_always_online(); } await rideconnectionsocket(); } void handleStatusChange(RideEnum newStatus) { if (!mounted) return; try { final mainState = MainScreen.globalKey.currentState; if (mainState == null) { throw Exception('MainScreen state not available'); } final currentStatus = context.read().ridestatus; log('Changing status from $currentStatus to $newStatus'); context.read().updatestates(newStatus); rideconnectionsocket(); switch (newStatus) { case RideEnum.idle: mainState.navigateToScreen(0); break; case RideEnum.to_client: case RideEnum.driver_arrived: case RideEnum.start_ride: mainState.navigateToScreen(4); break; case RideEnum.rate_client: case RideEnum.collect_cash: break; case RideEnum.check_availabilty_after_complete: mainState.navigateToScreen(0); break; default: log('Unhandled status: $newStatus'); } } catch (e) { log('Status change error: $e'); AppSnackbar.showError("Error", "Failed to update ride status"); if (mounted) { MainScreen.globalKey.currentState?.navigateToScreen(0); } } } void handleHeightChange(double newHeight) { if (newHeight != driverInfoCardHeight) { setState(() => driverInfoCardHeight = newHeight); } } double _getResponsiveBottom(BuildContext context) { final screenHeight = getResponsiveScreenHeight(context); switch (screenHeight.toInt()) { case 812: return screenHeight * .22; case 1024: return screenHeight * .76; case 896: return screenHeight * .2; case 720: return screenHeight * .25; case 640: return screenHeight * .2; default: return screenHeight * .10; } } double _getResponsive2Bottom(BuildContext context) { double screenHeight = getResponsiveScreenHeight(context); switch (screenHeight) { case 812: return screenHeight * 0.31; case 1024: return screenHeight * 0.76; case 896: return screenHeight * 0.28; case 720: return screenHeight * 0.345; case 640: return screenHeight * 0.31; default: return screenHeight * 0.10; } } // NEW: Get appropriate map style with fallback String _getMapStyle() { try { final brightness = Theme.of(context).brightness; if (brightness == Brightness.dark) { return MapboxStyles.DARK; } else { return MapboxStyles.MAPBOX_STREETS; } } catch (e) { log('Error getting theme brightness: $e'); return MapboxStyles.MAPBOX_STREETS; // Fallback } } Future _launchURL(String url) async { try { // Add protocol if missing String formattedUrl = url; if (!url.startsWith('http://') && !url.startsWith('https://')) { formattedUrl = 'https://$url'; } final Uri uri = Uri.parse(formattedUrl); if (await canLaunchUrl(uri)) { await launchUrl(uri, mode: LaunchMode.externalApplication); } else { print('Could not launch $formattedUrl'); // Optionally show a snackbar to the user AppSnackbar.showError("Error", "Cannot open link"); } } catch (e) { print('Error launching URL: $e'); AppSnackbar.showError("Error", "Invalid link"); } } Future _ensureLocationMarker() async { if (!_managersInitialized || _locationAnnotationManager == null || currentLocation == null) { return; } // If we never created it, create it if (_locationAnnotation == null) { await _addLocationMarker(); return; } // Try to touch/update the existing one; if Mapbox dropped the bitmap, // update will fail and we recreate from scratch. try { await _locationAnnotationManager!.update(_locationAnnotation!); } catch (_) { try { await _locationAnnotationManager!.delete(_locationAnnotation!); } catch (_) {} _locationAnnotation = null; await _addLocationMarker(); } } @override Widget build(BuildContext context) { final utils = context.watch(); final available = utils.user!.driver!.available; if (isLoading || currentLocation == null) { print('๐ŸŸข _HomeScreenState๐ŸŸข currentLocation $currentLocation'); print('๐ŸŸข _HomeScreenState๐ŸŸข isLoading $isLoading'); return Container( color: Theme.of(context).brightness == Brightness.dark ? AppColors.blackBackgroundColor : AppColors.whiteBackgroundColor, height: MediaQuery.sizeOf(context).height, width: MediaQuery.sizeOf(context).width, child: const Center( child: CustomLoader( logoSize: 60.0, dotSize: 10.0, ) ), ); } if (!permissionsGranted) { return PermissionErrorWidget(onRetry: _initialize); } print('๐ŸŸข _HomeScreenState๐ŸŸข context.watch().loader ${context.watch().loader}'); print('๐ŸŸข utils.user!.driver!.available๐ŸŸข utils.user!.driver!.available 1111111 ${utils.user!.driver!.available}'); return SlidingUpPanel( renderPanelSheet: true, panelSnapping: true, parallaxEnabled: true, backdropTapClosesPanel: true, backdropColor: Colors.black, parallaxOffset: 1, snapPoint: 0.1, borderRadius: const BorderRadius.only( topLeft: Radius.circular(15), topRight: Radius.circular(15), ), minHeight: 55.0, maxHeight: 500.0, panel: const Center( child: DriverInfoCard(), ), collapsed: const Center(child: SizedBox()), body: Padding( padding: EdgeInsets.only( top: MediaQuery.of(context).padding.top, bottom: MediaQuery.of(context).padding.bottom + 16, ), child: Stack( alignment: Alignment.bottomCenter, children: [ // ENHANCED: MapWidget with improved configuration MapWidget( // key: ValueKey("mapWidget_${currentLocation?.latitude}_${currentLocation?.longitude}"), cameraOptions: CameraOptions( center: Point( coordinates: Position( currentLocation!.longitude, currentLocation!.latitude ) ), zoom: 12.0, ), styleUri: _getMapStyle(), onMapCreated: _onMapCreated, gestureRecognizers: >{ Factory( () => EagerGestureRecognizer(), ), }, // FIXED: Add map options for better Android compatibility mapOptions: MapOptions( pixelRatio: MediaQuery.of(context).devicePixelRatio, ), ), if (publicprovider.offers.isNotEmpty) Positioned( top: MediaQuery.of(context).size.height * 0.05, child: Container( height: 60, width: MediaQuery.of(context).size.width, decoration: BoxDecoration( boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: CarouselSlider.builder( itemCount: publicprovider.offers.length, options: CarouselOptions( height: 60, viewportFraction: 1.0, autoPlay: true, autoPlayInterval: const Duration(seconds: 3), autoPlayAnimationDuration: const Duration(milliseconds: 800), autoPlayCurve: Curves.fastOutSlowIn, pauseAutoPlayOnTouch: true, enlargeCenterPage: false, ), itemBuilder: (context, index, realIndex) { final offer = publicprovider.offers[index]; return GestureDetector( onTap: () { // Open the link when tapped _launchURL(offer.link); }, child: ClipRRect( child: Image.network( '${AppString.imageLink}${offer.image.url}', fit: BoxFit.cover, width: MediaQuery.of(context).size.width, errorBuilder: (context, error, stackTrace) { return Image.asset( 'assets/images/bannerAds.jpg', fit: BoxFit.cover, ); }, loadingBuilder: (context, child, loadingProgress) { if (loadingProgress == null) return child; return Center( child: CircularProgressIndicator( value: loadingProgress.expectedTotalBytes != null ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes! : null, ), ); }, ), ), ); }, ), ), ), Positioned( bottom: _getResponsiveBottom(context), child: context.watch().loader ? const SizedBox( height: 50, child: Center( child: CustomLoader( logoSize: 30.0, dotSize: 5.0, ), ), ) : Column( children: [ CustomButton( title: utils.user!.driver!.available ? "Go offline" : "Go online", onClick: () async { _toggleAvailability(); await rideconnectionsocket(); }, backgroundColor: utils.user!.driver!.available ? horlinecolor : enlignecolor, height: 50, borderRadius: 10, width: MediaQuery.sizeOf(context).width * .6, txtColor: Colors.white, borderColor: utils.user!.driver!.available ? horlinecolor : enlignecolor, icon: utils.user!.driver!.available ? LucideIcons.power : LucideIcons.power_off, loadercolor:Colors.white, isloading: _availabilityLoading || context.watch().loader, ), ], ), ), // if (available) // Positioned( // bottom: _getResponsive2Bottom(context), // left: 0, // right: 0, // child: Align( // alignment: Alignment.center, // child: Container( // padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1), // decoration: BoxDecoration( // color: Theme.of(context).brightness == Brightness.dark // ? AppColors.darkPrimaryColor // : AppColors.primaryColor, // borderRadius: BorderRadius.circular(30), // boxShadow: [ // BoxShadow( // color: Colors.grey.withOpacity(0.1), // spreadRadius: 1, // blurRadius: 10, // offset: const Offset(0, 2), // ), // ], // ), // child: Row( // mainAxisSize: MainAxisSize.min, // mainAxisAlignment: MainAxisAlignment.center, // children: [ // SizedBox( // width: 35, // child: FloatingActionButton( // shape: const CircleBorder(), // heroTag: 'decRadius', // mini: true, // backgroundColor: Colors.white, // onPressed: zoomOutRadius, // child: const Icon( // Icons.remove, // color: AppColors.black, // size: 25, // ), // ), // ), // const SizedBox(width: 12), // Text( // '${(circleRadius / 1000).toStringAsFixed(1)} km', // style: const TextStyle( // fontSize: 14, // color: AppColors.white, // fontWeight: FontWeight.bold, // ), // ), // const SizedBox(width: 12), // SizedBox( // width: 35, // child: FloatingActionButton( // shape: const CircleBorder(), // heroTag: 'incRadius', // mini: true, // backgroundColor: Colors.white, // onPressed: zoomInRadius, // child: const Icon( // Icons.add, // color: AppColors.black, // size: 25, // ), // ), // ), // ], // ), // ), // ) // ), ], ), ), ); } Future _pokeRender() async { if (mapController == null) return; try { final cam = await mapController!.getCameraState(); // tiny, invisible bearing wiggle to force a redraw await mapController!.setCamera(CameraOptions(bearing: (cam.bearing ?? 0) + 0.000001)); await mapController!.setCamera(CameraOptions(bearing: cam.bearing)); } catch (_) { // ignore } } // ENHANCED: Safer availability toggle with better async handling void _toggleAvailability() async { if (!mounted || _isDisposing || _disposed) return; try { setState(() => _availabilityLoading = true); // local spinner only await context.read().update_availabilty(); final utilsNow = context.read(); final availableNow = utilsNow.user?.driver?.available ?? false; await rideconnectionsocket(); if (_isMapInitialized && _managersInitialized) { await _updateDriverRadiusCircle(overrideAvailable: availableNow); await _ensureLocationMarker(); } final rideStatus = context.read().ridestatus; final hasPendingRide = rideStatus != RideEnum.idle && rideStatus != RideEnum.check_availabilty_after_complete; final utils = context.read(); if (utils.user!.driver!.available && hasPendingRide) { WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted && !_isDisposing) { MainScreen.globalKey.currentState?.navigateToScreen(4); } }); } } catch (e) { AppSnackbar.showError("Error", "Failed to update availability"); log('Toggle availability error: $e'); } finally { if (mounted && !_isDisposing) { setState(() => _availabilityLoading = false); } } } rideconnectionsocket() async { if (!mounted) return; final rideProvider = Provider.of(context, listen: false); rideProvider.rideconnectionsocket(); } }