import 'dart:async'; import 'dart:developer'; import 'package:audioplayers/audioplayers.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:geolocator/geolocator.dart'; import 'package:get/get.dart'; import 'package:provider/provider.dart'; import '../Provider/RideNotification.dart'; import '../Provider/RideProvider.dart'; import '../Provider/RideStatusProvider.dart'; import '../Provider/SelectedRideProvider.dart'; import '../Provider/utils_singleton.dart'; import '../config/enum/ride_enum.dart'; import '../models/Ride.dart'; import '../routes/app_routes.dart'; import '../services/PubNubDriverManager.dart'; import '../utils/colors.dart'; import '../widgets/RideCardView.dart'; import '../widgets/RideMapView.dart'; import '../widgets/RideStatusWidget/RideCardShimmer.dart'; class RideRequestQueueScreen extends StatefulWidget { const RideRequestQueueScreen({Key? key}) : super(key: key); @override State createState() => _RideRequestQueueScreenState(); } class _RideRequestQueueScreenState extends State with TickerProviderStateMixin, WidgetsBindingObserver { final _listKey = GlobalKey(); final List _rideIds = []; final Map> _rideDetailsFutures = {}; final Map _rideStartTimes = {}; final Map _rideCache = {}; // Cache for loaded rides // Animation controllers - reduced complexity late AnimationController _fadeController; late Animation _fadeAnimation; // Audio player instance - simplified AudioPlayer? _audioPlayer; bool _isAudioInitialized = false; bool _isDisposing = false; bool _isInitialized = false; late final RideNotification _rideNotifier; Timer? _cleanupTimer; Timer? _audioRetryTimer; Position? _driverLivePosition; StreamSubscription? _positionStreamSubscription; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); log("✅ RideRequestQueueScreen: initState"); // Initialize animation controller - single controller for better performance _fadeController = AnimationController( duration: const Duration(milliseconds: 600), vsync: this, ); _fadeAnimation = Tween( begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: _fadeController, curve: Curves.easeInOut, )); _rideNotifier = context.read(); _rideNotifier.addListener(_onQueueChanged); // Simplified initialization WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted && !_isDisposing) { _performInitialization(); } }); } @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); if (state == AppLifecycleState.paused) { _pauseAudio(); } else if (state == AppLifecycleState.resumed) { _resumeAudio(); } } Future _performInitialization() async { try { // Initialize components in parallel for better performance await Future.wait([ _initializeAudio(), _initializeData(), _startListeningToDriverLocation(), ]); // Initialize PubNub await _initializePubnub(); // Start animations _fadeController.forward(); // Start cleanup timer with longer interval to reduce overhead _startCleanupTimer(); setState(() { _isInitialized = true; }); HapticFeedback.lightImpact(); log("✅ Initialization complete"); } catch (e) { log("❌ Error during initialization: $e"); if (mounted) { Get.back(); } } } Future _initializeAudio() async { try { _audioPlayer = AudioPlayer(); await _audioPlayer!.setReleaseMode(ReleaseMode.loop); _isAudioInitialized = true; await _startAudio(); } catch (e) { _isAudioInitialized = false; log("❌ Error initializing audio: $e"); // Retry audio initialization after a delay _scheduleAudioRetry(); } } void _scheduleAudioRetry() { _audioRetryTimer?.cancel(); _audioRetryTimer = Timer(const Duration(seconds: 2), () { if (mounted && !_isDisposing && !_isAudioInitialized) { _initializeAudio(); } }); } Future _initializeData() async { final rideProvider = context.read(); final selectedProvider = context.read(); final pendingIds = _rideNotifier.pendingRideIds.reversed.toList(); _rideIds.addAll(pendingIds); final now = DateTime.now(); // Pre-fetch ride details and cache them final futures = []; for (var id in _rideIds) { final future = rideProvider.getRideDetails(id).then((ride) { if (ride != null && mounted) { _rideCache[id] = ride; } return ride; }); _rideDetailsFutures[id] = future; _rideStartTimes[id] = now; futures.add(future); } // Wait for initial rides to load (with timeout) try { await Future.wait(futures).timeout(const Duration(seconds: 3)); } catch (e) { log("Some rides failed to load initially: $e"); } if (_rideIds.isNotEmpty) { selectedProvider.selectRide(_rideIds.first); } } void _startCleanupTimer() { _cleanupTimer?.cancel(); _cleanupTimer = Timer.periodic(const Duration(seconds: 5), (timer) { if (!_isDisposing && mounted) { _rideNotifier.cleanupExpiredRides(); _cleanupRideCache(); } }); } void _cleanupRideCache() { // Remove cached rides that are no longer in the pending list final pendingIds = _rideNotifier.pendingRideIds; _rideCache.removeWhere((id, ride) => !pendingIds.contains(id)); } Future _initializePubnub() async { try { final socketManager = context.read(); await socketManager.connect(uuid: utils.user!.username!); log("PubNub initialize completed!"); } catch (e) { log("PubNub initialization failed: $e"); } } @override void dispose() { log("❌ RideRequestQueueScreen: dispose"); _isDisposing = true; WidgetsBinding.instance.removeObserver(this); // Stop audio _pauseAudio(); _audioPlayer?.dispose(); _audioPlayer = null; // Dispose animation controllers _fadeController.dispose(); // Cancel timers and subscriptions _cleanupTimer?.cancel(); _audioRetryTimer?.cancel(); _positionStreamSubscription?.cancel(); // Remove listener safely try { _rideNotifier.removeListener(_onQueueChanged); } catch (e) { log("Error removing listener during dispose: $e"); } // Clear cache _rideCache.clear(); super.dispose(); } Future _startListeningToDriverLocation() async { try { bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); if (!serviceEnabled) { log('Location services are disabled.'); return; } LocationPermission permission = await Geolocator.checkPermission(); if (permission == LocationPermission.denied) { permission = await Geolocator.requestPermission(); if (permission == LocationPermission.denied) { log('Location permissions are denied.'); return; } } if (permission == LocationPermission.deniedForever) { log('Location permissions are permanently denied.'); return; } // Get last known position first try { final lastKnown = await Geolocator.getLastKnownPosition(); if (mounted && lastKnown != null) { _updateDriverPosition(lastKnown); } } catch (e) { log("Error getting last known position: $e"); } // Start location stream with optimized settings const LocationSettings locationSettings = LocationSettings( accuracy: LocationAccuracy.high, // Reduced from bestForNavigation distanceFilter: 20, // Increased from 10 to reduce updates ); _positionStreamSubscription = Geolocator.getPositionStream( locationSettings: locationSettings, ).listen( (Position position) { if (mounted && !_isDisposing) { _updateDriverPosition(position); } }, onError: (e) => log("Error in location stream: $e"), ); } catch (e) { log("Error starting location tracking: $e"); } } void _updateDriverPosition(Position position) { setState(() { _driverLivePosition = position; if (utils.user?.driver != null) { utils.user!.driver!.currentLatitude = position.latitude; utils.user!.driver!.currentLongitude = position.longitude; } }); } Future _startAudio() async { if (!_isAudioInitialized || _isDisposing || _audioPlayer == null) return; try { await _audioPlayer!.play(AssetSource('audio/CardScreenaudio.mpga')); log("🔊 Audio started playing"); } catch (e) { log("❌ Error playing audio: $e"); _scheduleAudioRetry(); } } Future _pauseAudio() async { if (_audioPlayer == null) return; try { await _audioPlayer!.pause(); } catch (e) { log("❌ Error pausing audio: $e"); } } Future _resumeAudio() async { if (!_isAudioInitialized || _audioPlayer == null) return; try { await _audioPlayer!.resume(); } catch (e) { log("❌ Error resuming audio: $e"); } } void _onQueueChanged() { if (!mounted || _isDisposing) { log("🚫 _onQueueChanged called on disposed widget. Ignoring."); return; } final newQueue = _rideNotifier.pendingRideIds; final rideProvider = context.read(); final selectedProvider = context.read(); final bool wasListPopulated = _rideIds.isNotEmpty; // Batch updates for better performance final List toRemove = []; final List toAdd = []; // Find items to remove for (final id in List.from(_rideIds)) { if (!newQueue.contains(id)) { toRemove.add(id); } } // Find items to add for (final id in newQueue) { if (!_rideIds.contains(id)) { toAdd.add(id); } } // Remove items with animation for (final idToRemove in toRemove) { final index = _rideIds.indexOf(idToRemove); if (index != -1) { _rideIds.removeAt(index); _rideDetailsFutures.remove(idToRemove); _rideStartTimes.remove(idToRemove); _rideCache.remove(idToRemove); _listKey.currentState?.removeItem( index, (context, animation) => SlideTransition( position: animation.drive( Tween( begin: Offset.zero, end: const Offset(1.0, 0.0), ).chain(CurveTween(curve: Curves.easeInBack)) ), child: FadeTransition( opacity: animation, child: const RideCardShimmer(), ), ), duration: const Duration(milliseconds: 300), // Reduced duration ); } } // Add new items with animation for (final idToAdd in toAdd.reversed) { _rideIds.insert(0, idToAdd); final future = rideProvider.getRideDetails(idToAdd).then((ride) { if (ride != null && mounted) { _rideCache[idToAdd] = ride; } return ride; }); _rideDetailsFutures[idToAdd] = future; _rideStartTimes[idToAdd] = DateTime.now(); _listKey.currentState?.insertItem( 0, duration: const Duration(milliseconds: 400), // Reduced duration ); } // Update selected ride if (selectedProvider.selectedRideId == null || !newQueue.contains(selectedProvider.selectedRideId)) { if (newQueue.isNotEmpty) { WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { selectedProvider.selectRide(newQueue.last); } }); } } // Handle empty list if (wasListPopulated && _rideIds.isEmpty) { _performExitAnimation(); } } void _performExitAnimation() { log("exist ....... ############################ "); _fadeController.reverse().then((_) { if (mounted && !_isDisposing) { final rideProvider = context.read(); if (rideProvider.currentride == null) { context.read().updatestates(RideEnum.idle); } Get.until((route) => route.settings.name == AppRoutes.main); } }); } @override Widget build(BuildContext context) { final selectedRideId = context.watch().selectedRideId; if (!_isInitialized) { return Scaffold( backgroundColor: Theme.of(context).brightness == Brightness.dark ? AppColors.blackBackgroundColor : AppColors.whiteBackgroundColor, body: const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator(), SizedBox(height: 16), Text('Loading ride requests...'), ], ), ), ); } return Scaffold( backgroundColor: Theme.of(context).brightness == Brightness.dark ? AppColors.blackBackgroundColor : AppColors.whiteBackgroundColor, body: Stack( children: [ // Map with fade animation and selected ride data FadeTransition( opacity: _fadeAnimation, child: RideMapView( selectedRideId: selectedRideId, selectedRide: selectedRideId != null ? _rideCache[selectedRideId] : null, driverPosition: _driverLivePosition, ), ), // Ride list with optimized animations Align( alignment: Alignment.bottomCenter, child: Container( height: MediaQuery.of(context).size.height * 0.45, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.center, colors: [ Colors.transparent, Theme.of(context).scaffoldBackgroundColor.withOpacity(0.1), ], ), ), child: AnimatedList( key: _listKey, padding: const EdgeInsets.only(bottom: 16, top: 8), initialItemCount: _rideIds.length, itemBuilder: (context, index, animation) { if (index >= _rideIds.length) return const SizedBox.shrink(); final rideId = _rideIds[index]; final isSelected = selectedRideId == rideId; final startTime = _rideStartTimes[rideId]; return SlideTransition( position: animation.drive( Tween( begin: const Offset(0.0, 1.0), end: Offset.zero, ).chain(CurveTween(curve: Curves.easeOut)) // Simplified curve ), child: FadeTransition( opacity: animation, child: Container( key: ValueKey(rideId), margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), child: _buildRideCard(rideId, isSelected, startTime), ), ), ); }, ), ), ), // Gradient overlay for better visual hierarchy Positioned( top: 0, left: 0, right: 0, height: 100, child: FadeTransition( opacity: _fadeAnimation, child: Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Theme.of(context).scaffoldBackgroundColor.withOpacity(0.3), Colors.transparent, ], ), ), ), ), ), ], ), ); } Widget _buildRideCard(int rideId, bool isSelected, DateTime? startTime) { // Use cached ride if available, otherwise use FutureBuilder final cachedRide = _rideCache[rideId]; if (cachedRide != null) { return _buildRideCardContent(cachedRide, isSelected, startTime); } return FutureBuilder( future: _rideDetailsFutures[rideId], builder: (context, snapshot) { if (snapshot.hasData) { final ride = snapshot.data!; return _buildRideCardContent(ride, isSelected, startTime); } return const RideCardShimmer(); }, ); } Widget _buildRideCardContent(Ride ride, bool isSelected, DateTime? startTime) { Duration individualDuration = const Duration(seconds: 15); if (startTime != null) { final elapsed = DateTime.now().difference(startTime); final remaining = individualDuration - elapsed; individualDuration = remaining.inSeconds > 0 ? remaining : Duration.zero; } return AnimatedContainer( duration: const Duration(milliseconds: 200), // Reduced duration curve: Curves.easeInOut, transform: Matrix4.identity()..scale(isSelected ? 1.02 : 1.0), child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), boxShadow: isSelected ? [ BoxShadow( color: Theme.of(context).primaryColor.withOpacity(0.3), blurRadius: 8, spreadRadius: 2, ) ] : null, ), child: RideCardView( key: ObjectKey(ride), ride: ride, isSelected: isSelected, animationDuration: individualDuration, driverCurrentPosition: _driverLivePosition, ), ), ); } }