import 'dart:async'; import 'dart:developer'; import 'package:beemwidget/text_styles/beem_text_styles.dart'; import 'package:app_settings/app_settings.dart'; import 'package:wanisclient/models/ride_model.dart'; import 'package:wanisclient/providers/utils_singleton.dart'; import 'package:wanisclient/screens/ride_tracking_screen.dart'; import 'package:wanisclient/utils/RatingPopup.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:flutter_typeahead/flutter_typeahead.dart'; import 'package:geolocator/geolocator.dart'; import 'package:provider/provider.dart'; import 'package:sidebarx/sidebarx.dart'; import '../config/AppSnackbar.dart'; import '../config/constants/app_constant.dart'; import '../config/constants/arabe.dart' as Language; import '../config/custom_text.dart'; import '../config/enum/ride_enum.dart'; import '../config/enum/ride_type.dart'; import '../models/RideUpdateRequest.dart'; import '../providers/language_provider.dart'; import '../providers/public_provider.dart'; import '../providers/ride_providers.dart'; import '../routes/ScreensIndexs.dart'; import '../services/Sockets/PubNubManager.dart'; import '../services/SystemInsetsService.dart'; import '../services/map_service.dart'; import '../utils/colors.dart'; import '../widgets/image_slider_popup.dart'; import '../widgets/islandDialog.dart'; import 'NewUiScreen/PermissionModals/LocationPermissionModal.dart'; import 'Sidebar.dart'; import 'finding_ride_screen.dart'; class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); @override State createState() => _HomeScreenState(); } class _HomeScreenState extends State with WidgetsBindingObserver { StreamSubscription? _netSub; bool _hasInternet = true; StreamSubscription? _gpsSub; bool _hasGps = true; bool _hasLocationPermission = true; bool _gpsDialogShowing = false; bool _isprocessing = false; late final SidebarXController _controller; final GlobalKey _key = GlobalKey(); double _systembottompadding = 0; late PubNubManager _pubnubManager; bool _connectionDialogShowing = false; final MapService mapService = MapService(); Timer? _ridePollTimer; final FocusNode _typeAheadFocusNode = FocusNode(); final TextEditingController _typeAheadController = TextEditingController(); @override void initState() { super.initState(); _controller = SidebarXController(selectedIndex: 0, extended: true); WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addPostFrameCallback((_) { initialize(); }); _listenToGpsStatus(); _checkLocationPermission(); _listenToFlutterConnectivity(); log("initState Home Screen 000"); _startPollingTimer(); } void _startPollingTimer() { _ridePollTimer?.cancel(); log("start pulling every 5sec ####################################"); _ridePollTimer = Timer.periodic(const Duration(seconds: 5), (timer) { _pollRideStatus(); }); } void _pollRideStatus() async { if (!mounted) { _ridePollTimer?.cancel(); return; } final rideprovider = context.read(); if (rideprovider.currentride != null && rideprovider.currentride!.rideStatus != "ONGOING" && rideprovider.currentride!.rideStatus != "REQUESTED" && rideprovider.currentstate != RideEnum.rate_driver) { try { rideprovider.clearRideProvider("Home screen clear"); } catch (e) { log("Error during home clear polling: $e"); } } if (rideprovider.currentride == null || (rideprovider.currentride != null && rideprovider.currentride!.rideStatus != "ONGOING")) { log("Polling for ride status update..."); try { await rideprovider.get_my_latest_ride_ongoing("Home _startPollingTimer"); } catch (e) { log("Error during ride polling: $e"); } } } Future _checkLocationPermission() async { final status = await Geolocator.checkPermission(); final granted = status == LocationPermission.always || status == LocationPermission.whileInUse; if (granted != _hasLocationPermission) { setState(() => _hasLocationPermission = granted); if (!granted) { _showCustomLocationPermissionDialog(); } else { _onLocationPermissionGranted(); } } } void _showCustomLocationPermissionDialog() { showDialog( context: context, barrierDismissible: false, builder: (context) => Dialog( backgroundColor: Colors.transparent, child: LocationPermissionModal( isArabic: false, onAllow: () async { Navigator.of(context).pop(); final permission = await Geolocator.requestPermission(); if (permission == LocationPermission.always || permission == LocationPermission.whileInUse) { setState(() => _hasLocationPermission = true); _onLocationPermissionGranted(); } else if (permission == LocationPermission.deniedForever) { // await Geolocator.openAppSettings(); } }, onMaybeLater: () { Navigator.of(context).pop(); AppSnackbar.showWarning("Warning", "Some features may not work without location access."); }, ), ), ); } void _onLocationPermissionGranted() { AppSnackbar.showSuccess( "Success", "Thank you! Location permission granted."); } @override void didChangeAppLifecycleState(AppLifecycleState state) { log('App lifecycle state changed to: $state'); if (state == AppLifecycleState.resumed) { // Re-check GPS status when app resumes _recheckGpsStatus(); _checkLocationPermission(); _refreshRideStatusOnResume(); log("didChangeAppLifecycleState Home Screen resumed"); } } Future _recheckGpsStatus() async { try { final isEnabled = await Geolocator.isLocationServiceEnabled(); log('Rechecking GPS on resume - enabled: $isEnabled, dialog showing: $_gpsDialogShowing'); if (!mounted) return; // If GPS is now enabled and dialog was showing, dismiss it if (isEnabled && _gpsDialogShowing) { try { Navigator.of(context, rootNavigator: true).pop(); _gpsDialogShowing = false; initializepubnub(); AppSnackbar.showSuccess( "Success", "Location services have been restored.", ); } catch (e) { log('Error dismissing GPS dialog on resume: $e'); _gpsDialogShowing = false; } } // Update the GPS state if (isEnabled != _hasGps) { setState(() => _hasGps = isEnabled); // If GPS is disabled and no dialog is showing, show it if (!isEnabled && !_gpsDialogShowing) { _onGpsOff(); } } } catch (e) { log('Error rechecking GPS status: $e'); } } Future _refreshRideStatusOnResume() async { final rideProvider = context.read(); print('→ Resumed app: Checking latest ongoing ride...'); await rideProvider.get_my_latest_ride_ongoing("Home _refreshRideStatusOnResume"); print('→ Ride after refresh: ${rideProvider.currentride}'); if (rideProvider.currentride == null) { print('→ No current ride found. Checking rating...'); await checkrating(); } if (!mounted) return; setState(() { _isprocessing = false; }); } void _listenToGpsStatus() async { // Initial GPS status check try { final initialStatus = await Geolocator.isLocationServiceEnabled(); if (mounted) { setState(() => _hasGps = initialStatus); if (!initialStatus && !_gpsDialogShowing) { _onGpsOff(); } } } catch (e) { log('Error checking initial GPS status: $e'); } // Listen to GPS status changes _gpsSub = Geolocator.getServiceStatusStream().listen( (ServiceStatus status) { if (!mounted) return; final enabled = status == ServiceStatus.enabled; log('GPS Status changed: $status, enabled: $enabled, current _hasGps: $_hasGps'); // Only update if status actually changed if (enabled != _hasGps) { setState(() => _hasGps = enabled); if (enabled) { _onGpsBackOn(); } else { _onGpsOff(); } } }, onError: (error) { log('Error in GPS status stream: $error'); }, ); } void _onGpsOff() { if (_gpsDialogShowing || !mounted) return; log('GPS turned off, showing dialog'); _gpsDialogShowing = true; showDialog( context: context, barrierDismissible: false, builder: (_) => WillPopScope( onWillPop: () async => false, child: AlertDialog( title: const Text('GPS Disabled'), content: const Text( 'Please enable GPS to continue using location features.', ), actions: [ TextButton( onPressed: () async { try { await Geolocator.openLocationSettings(); } catch (e) { log('Error opening location settings: $e'); } }, child: const Text('Open Settings'), ), ], ), ), ).whenComplete(() { // Ensure dialog state is reset if dialog is dismissed unexpectedly if (mounted) { _gpsDialogShowing = false; } }); } void _onGpsBackOn() { log('GPS turned back on, _gpsDialogShowing: $_gpsDialogShowing'); if (_gpsDialogShowing && mounted) { try { Navigator.of(context, rootNavigator: true).pop(); log('GPS dialog dismissed successfully'); // Initialize PubNub and show success message initializepubnub(); AppSnackbar.showSuccess( "Success", "Location services have been restored.", ); } catch (e) { log('Error dismissing GPS dialog: $e'); } finally { _gpsDialogShowing = false; } } } void initializepubnub() { final userContext = UserContext(username: utils.user!.username!); _pubnubManager.init(userContext); } void _listenToFlutterConnectivity() { _netSub = Connectivity().onConnectivityChanged.listen((dynamic event) { List results; if (event is List) { results = event; } else if (event is ConnectivityResult) { results = [event]; } else { results = [ConnectivityResult.none]; } final nowOnline = results.any((r) => r != ConnectivityResult.none); if (nowOnline != _hasInternet) { setState(() => _hasInternet = nowOnline); if (nowOnline) { _dismissConnectionLostDialog(); _onBackOnline(); } else { _showConnectionLostDialog(); } } }); } void _dismissConnectionLostDialog() { if (_connectionDialogShowing) { Navigator.of(context, rootNavigator: true).pop(); _connectionDialogShowing = false; } } void _showConnectionLostDialog() { if (_connectionDialogShowing) return; _connectionDialogShowing = true; showDialog( context: context, barrierDismissible: false, builder: (_) => SimpleDynamicIslandDialog( title: 'No Internet', content: 'Please check your connection. The app will resume automatically once you are back online.', barrierDismissible: false, actionText: 'Open Settings', onAction: () async { AppSettings.openAppSettings(type: AppSettingsType.wifi); }, ), ); } void _onBackOnline() { AppSnackbar.showSuccess( "Success", "You've reconnected to the internet!"); initializepubnub(); } Future initialize() async { _pubnubManager = Provider.of(context, listen: false); initializepubnub(); final rideProvider = context.read(); await rideProvider.get_my_latest_ride_ongoing("Home initialize"); if (rideProvider.currentride != null) return; checkrating(); checkOffer(); if (!mounted) return; final pad = await SystemInsetsService.instance.bottomPadding(context); log("pad : $pad"); setState(() { if (!mounted) return; _isprocessing = false; _systembottompadding = pad; }); } @override void dispose() { _controller.dispose(); WidgetsBinding.instance.removeObserver(this); _typeAheadController.dispose(); _typeAheadFocusNode.dispose(); _gpsSub?.cancel(); _netSub?.cancel(); _ridePollTimer?.cancel(); super.dispose(); } Future checkrating() async { final rideProvider = context.read(); RideDto? ride = await rideProvider.get_my_latest_ride_not_rated("Home checkrating"); if (ride == null || ride.driverId == null) return; RatingPopup.show( context: context, userImageUrl: '$mediaUrl${ride.driverimageurl}', userName: '${ride.drivername}', onRatingChanged: (rating) { print('User rated: $rating stars'); rideProvider.rate_driver(rating, ride.driverId!); }, ); } Future checkOffer() async { final publicprovider = context.read(); await publicprovider.fetchOffer(); if (!context.mounted || publicprovider.offers.isEmpty) return; log("checkOffer =====>>> ${publicprovider.offers}"); ImageSliderPopup.show( context: context, offers: publicprovider.offers, ); } @override Widget build(BuildContext context) { final rideProvider = context.watch(); final bool canGoBack = rideProvider.currentstate == RideEnum.idle; // Use a variable to hold the content that will change. Widget screenContent; if (rideProvider.currentstate == RideEnum.searching_ride) { // The content for the searching state. screenContent = FindingRideScreen( key: const ValueKey('FindingRideScreen'), title: rideProvider.startlocationname ?? 'Start Location name', endLocation: rideProvider.destinationlocationname ?? "", time: (rideProvider.routedata?.duration.inMinutes ?? 0).toDouble(), distance: rideProvider.routedata?.distanceKm ?? 0.0, price: rideProvider.price.toString(), paymentMethod: rideProvider.selecteridoption != null ? rideProvider.selecteridoption!.name : "", onCancel: () async { if (_isprocessing) return; if (!mounted) return; setState(() => _isprocessing = true); log("Ride canceled for reason: NO reason the cancel happen in the search screen"); RideUpdateRequest request = RideUpdateRequest( rideId: rideProvider.currentride!.id, type: 'RIDE_CANCEL_USER', message: 'NO reason the cancel happen in the search screen', riderusername: rideProvider.currentride!.driverusername, stoplocation: 'NOT_EXIST'); bool result = await rideProvider.send_update_ride(request); if (!result) { AppSnackbar.showError("Error", "Error occurred please try again later"); if (!mounted) return; setState(() => _isprocessing = false); return; } rideProvider.updateCurrentstate(RideEnum.idle); if (!mounted) return; setState(() => _isprocessing = false); }, ); } else if (rideProvider.currentstate == RideEnum.tracking_ride || rideProvider.currentstate == RideEnum.start_ride || rideProvider.currentstate == RideEnum.driver_arrived || rideProvider.currentstate == RideEnum.rate_driver) { // The content for the tracking/in-ride state. screenContent = const RideTrackingScreen( key: ValueKey('RideTrackingScreen'), ); } else { // The content for the idle state. screenContent = AnimatedBuilder( key: const ValueKey('MainAnimatedBuilder'), animation: _controller, builder: (context, child) { return Stack( children: [ Positioned.fill( child: ScreensIndexs( controller: _controller, ), ), Positioned( top: 0, left: 0, right: 0, child: buildWaveAppBar(context, rideProvider, mapService), ), ], ); }, ); } return PopScope( canPop: canGoBack, child: Scaffold( key: _key, drawer: rideProvider.currentstate == RideEnum.idle ? Sidebar(systembottompadding:_systembottompadding,controller: _controller) : null, body: WillPopScope(onWillPop: () async => false, child: screenContent), bottomNavigationBar: rideProvider.currentstate == RideEnum.idle ? _buildBottomNavigationBar() : const SizedBox(), ), ); } Widget _buildBottomNavigationBar() { // Ensure currentIndex is within valid range for bottom navigation (0-4) int bottomNavIndex = _controller.selectedIndex; if (bottomNavIndex > 4) { bottomNavIndex = 0; // Default to Home if index is from sidebar } final languageProvider = context.watch(); return Container( decoration: BoxDecoration( color: Colors.white, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 10, offset: const Offset(0, -2), ), ], ), child: BottomNavigationBar( currentIndex: bottomNavIndex, onTap: (index) { _controller.selectIndex(index); }, type: BottomNavigationBarType.fixed, backgroundColor: Colors.white, selectedItemColor: AppNewColors.black, unselectedItemColor: AppNewColors.grayTxtColor, iconSize: 28, selectedLabelStyle: const TextStyle( fontSize: 15, fontWeight: FontWeight.w500, color: AppNewColors.black, ), unselectedLabelStyle: const TextStyle( fontSize: 14, fontWeight: FontWeight.w400, color: AppNewColors.grayTxtColor, ), items: [ BottomNavigationBarItem( icon: Padding( padding: const EdgeInsets.all(4.0), child: SvgPicture.asset('assets/images/New/inbox.svg'), ), label: languageProvider.translate("Home"), ), BottomNavigationBarItem( icon: Padding( padding: const EdgeInsets.all(4.0), child: SvgPicture.asset('assets/images/New/wallet.svg'), ), label: languageProvider.translate("Wallet"), ), BottomNavigationBarItem( icon: Padding( padding: const EdgeInsets.all(4.0), child: SvgPicture.asset('assets/images/New/car-front.svg'), ), label: languageProvider.translate("Rides"), ), BottomNavigationBarItem( icon: Padding( padding: const EdgeInsets.all(4.0), child: SvgPicture.asset('assets/images/New/bell.svg'), ), label: languageProvider.translate("Alerts"), ), BottomNavigationBarItem( icon: Padding( padding: const EdgeInsets.all(4.0), child: SvgPicture.asset('assets/images/New/user.svg'), ), label: languageProvider.translate("Profile"), ), ], ), ); } Widget buildWaveAppBar( BuildContext context, RidesProvider rideprovider, MapService mapService) { final double bar_h = 150 + MediaQuery.of(context).padding.top; return SizedBox( height: bar_h, child: Stack( clipBehavior: Clip.none, children: [ if (rideprovider.currentstate == RideEnum.idle && _controller.selectedIndex == 0) Positioned( top: MediaQuery.sizeOf(context).width * .17, left: 16, right: 16, child: Container( width: MediaQuery.sizeOf(context).width * .9, padding: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), color: AppNewColors.white, ), child: Row( children: [ IconButton( onPressed: () { _key.currentState?.openDrawer(); }, icon: const Icon( Icons.menu, size: 26, color: AppNewColors.black, ), ), Container( width: 18, height: 18, decoration: ShapeDecoration( color: const Color(0xFFDAEDE7), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(99), ), ), child: Center( // Use a Center widget to center the child child: Container( width: 10, height: 10, decoration: const ShapeDecoration( color: Color(0xFF08875D), shape: OvalBorder(), ), ), ), ), Expanded( child: TypeAheadField( suggestionsCallback: (String pattern) async { return await mapService.fetchSuggestions( query: pattern, provider: MapProvider.googleMaps, ); }, itemBuilder: (context, suggestion) { return Container( padding: const EdgeInsets.symmetric( vertical: 10, horizontal: 10), decoration: BoxDecoration( color: Colors.white, border: Border( bottom: BorderSide( color: Colors.green.shade100, ), ), ), child: Row( children: [ const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Txt( text: suggestion['name'] ?? 'Unknown Location', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w500), overflow: TextOverflow.ellipsis, maxLines: 1, ), Txt( text: suggestion['address']?['city'] ?? 'Unknown City', style: const TextStyle( color: Colors.grey, fontSize: 14), overflow: TextOverflow.ellipsis, maxLines: 1, ), ], ), ), ], ), ); }, onSelected: (value) { print('Selected Location: ${value['name']}'); rideprovider.updateRideType(RideType.INSTANT); rideprovider.updateCurrentstate(RideEnum.fill_places); }, decorationBuilder: (context, child) { return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(10), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 8, offset: const Offset(3, 3), ), ], ), child: ClipRRect( borderRadius: BorderRadius.circular(10), child: child, ), ); }, builder: (BuildContext context, TextEditingController controller, FocusNode focusNode) { focusNode.addListener(() { if (focusNode.hasFocus) { //rideprovider.clear("choose your destination"); rideprovider.updateRideType(RideType.INSTANT); rideprovider .updateCurrentstate(RideEnum.fill_places); } }); return TextField( controller: controller, focusNode: focusNode, decoration: InputDecoration( label: Txt( text: 'choose your destination', style: BeemTextStyles.poppinsMedium14 .copyWith(color: const Color(0xff656565)), ), hintStyle: TextStyle(color: Colors.grey.shade500), contentPadding: const EdgeInsets.symmetric( vertical: 10, horizontal: 15), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), borderSide: const BorderSide(color: Colors.transparent), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), borderSide: BorderSide(color: Colors.blue.shade400), ), ), ); }, ), ), InkWell( onTap: () { print("666666666666666666666666666666666666666666666666666666666666"); _controller.selectIndex(6); }, child: Container( decoration: BoxDecoration( color: AppNewColors.primaryBtnColor, borderRadius: BorderRadius.circular(16), ), child: Padding( padding: const EdgeInsets.only(left: 8.0,right: 8.0,top: 5.0,bottom: 5.0), child: Row( children: [ SvgPicture.asset( 'assets/images/calendar-clock.svg', width: 20, height: 20, color: Colors.white, ), const SizedBox(width: 6,), Txt(text: "Later", style: TextStyle( fontSize: 14, fontFamily: languageSingleton.currentLangCode == Language.ar ? "Readex_Pro" : 'Inter', fontWeight: FontWeight.w600, color: Colors.white,), ), ], ), ), ), ), ], ), ), ), if (rideprovider.currentstate == RideEnum.choose_ride || rideprovider.currentstate == RideEnum.order_summary ) Positioned( top: 70, // Moved down to avoid clipping into the wave left: 16, right: 16, child: Container( height: 55, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(15), boxShadow: [ BoxShadow( color: AppColors.primaryColor.withOpacity(0.5), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ IconButton( icon: const Icon(Icons.arrow_back, color: Colors.black), onPressed: () { rideprovider.updateCurrentstate(RideEnum.idle); rideprovider.updateCoupon(""); rideprovider.clearRideProvider("Home Screen"); }, ), Container( width: 18, height: 18, decoration: ShapeDecoration( color: const Color(0xFFDAEDE7), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(99), ), ), child: Center( // Use a Center widget to center the child child: Container( width: 10, height: 10, decoration: const ShapeDecoration( color: Color(0xFF08875D), shape: OvalBorder(), ), ), ), ), SizedBox( width: MediaQuery.of(context).size.width * 0.6, child: Text( rideprovider.startlocationname ?? 'Start Location', overflow: TextOverflow.ellipsis, maxLines: 1, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.normal, color: Colors.black, ), ), ), IconButton( icon: const Icon(Icons.add_circle_outline, color: Colors.black), onPressed: () { rideprovider.updateCurrentstate(RideEnum.fill_places); }, ), ], ), ), ), ], ), ); } }