import 'dart:async'; import 'dart:developer'; import 'package:app_settings/app_settings.dart'; import 'package:wanisdriver/Provider/authentification_provider.dart'; import 'package:wanisdriver/Screen/NavigationSecreenToClient.dart'; import 'package:wanisdriver/Screen/NotificationScreen.dart'; import 'package:wanisdriver/Screen/UpdateProfileScreen.dart'; import 'package:wanisdriver/config/enum/ride_enum.dart'; import 'package:wanisdriver/widgets/custom_text.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:geolocator/geolocator.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../Provider/ConnectionStatusProvider.dart'; import '../Provider/RideProvider.dart'; import '../Provider/RideStatusProvider.dart'; import '../Provider/language_provider.dart'; import '../Provider/utils_singleton.dart'; import '../config/AppSnackbar.dart'; import '../config/FloatingActionHandler.dart'; import '../Provider/RideNotification.dart'; import '../config/constants/app_constant.dart'; import '../config/custom_loader.dart'; import '../models/Ride.dart'; import '../utils/RatingPopup.dart'; import '../utils/colors.dart'; import '../widgets/ConnectionBanner.dart'; import '../widgets/custom_bottom_navigation_bar.dart'; import '../widgets/islandDialog.dart'; import 'ProfileScreen.dart'; import 'HistoryScreen.dart'; import 'HomeScreen.dart'; class MainScreen extends StatefulWidget { static final GlobalKey<_MainScreenState> globalKey = GlobalKey<_MainScreenState>(); MainScreen({Key? key}) : super(key: key); @override _MainScreenState createState() => _MainScreenState(); } class _MainScreenState extends State with WidgetsBindingObserver { late StreamSubscription _netSub; StreamSubscription? _gpsSub; bool _hasGps = true; bool _hasLocationPermission = true; bool _gpsDialogShowing = false; bool _connectionDialogShowing = false; bool _initializing = false; bool userblocked = false; static const platformNavigation = MethodChannel('com.camelsoft.wanisdriver/Ride'); // static const _connectionChannel = // EventChannel('com.camelsoft.wanisdriver/connection_status'); StreamSubscription? _connectionStatusSubscription; int _currentIndex = 0; final FloatingActionHandler _floatingHandler = FloatingActionHandler(); bool _disposed = false; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); // FIX: Defer initialization until after the first frame is built. // This prevents the "setState() called during build" error by ensuring // widgets are ready to be updated by the provider. WidgetsBinding.instance.addPostFrameCallback((_) { initialize(); }); // _listenToConnectionStatus(); _listenToFlutterConnectivity(); _listenToGpsStatus(); _checkLocationPermission(); } void _showConnectionLostDialog() { if (_connectionDialogShowing) return; // already showing _connectionDialogShowing = true; showDialog( context: context, barrierDismissible: false, // can’t dismiss manually builder: (_) => SimpleDynamicIslandDialog( title: languageSingleton.translate('No Internet'), content: languageSingleton.translate('Please check your connection. The app will resume automatically once you are back online.'), barrierDismissible: false, // keep parity with GPS dialog actionText: languageSingleton.translate('Open Settings'), onAction: () { AppSettings.openAppSettings(type: AppSettingsType.wifi); }, ), ); } void _dismissConnectionLostDialog() { if (_connectionDialogShowing) { Navigator.of(context, rootNavigator: true).pop(); _connectionDialogShowing = false; } } Future _checkLocationPermission() async { final status = await Geolocator.checkPermission(); final granted = status == LocationPermission.always || status == LocationPermission.whileInUse; if (granted != _hasLocationPermission) { setState(() => _hasLocationPermission = granted); if (!granted) { _onLocationPermissionDenied(); } else { _onLocationPermissionGranted(); } } } void _onLocationPermissionDenied() { AppSnackbar.showWarning("Permission Denied", languageSingleton.translate("Allow location permission in Settings to continue.")); } void _onLocationPermissionGranted() { AppSnackbar.showSuccess( languageSingleton.translate("Permission Granted"), languageSingleton.translate("Thank you! Location permission granted.")); servicecheckup(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.resumed) { _checkLocationPermission(); _isUserTokenValid(); } } Future _isUserTokenValid() async { log("-- _isUserTokenValid main Screen "); final prefs = await SharedPreferences.getInstance(); var tokenData = prefs.getString("token"); bool tokenverif = tokenData != null && tokenData.isNotEmpty && tokenData != "InvalidToken"; bool fetchprofile =await context.read().fetchProfile(); return fetchprofile && tokenverif; } void _listenToGpsStatus() { Geolocator.isLocationServiceEnabled().then((enabled) { // βœ… FIX: Check if the widget is still in the tree before calling setState. if (mounted) { setState(() => _hasGps = enabled); if (!enabled) _onGpsOff(); } }); _gpsSub = Geolocator.getServiceStatusStream().listen((status) { final enabled = status == ServiceStatus.enabled; if (enabled != _hasGps) { // βœ… FIX: Also add the check here for the stream listener. if (mounted) { setState(() => _hasGps = enabled); if (enabled) { _onGpsBackOn(); } else { _onGpsOff(); } } } }); } void _onGpsOff() { if (_gpsDialogShowing) return; _gpsDialogShowing = true; showDialog( context: context, barrierDismissible: false, builder: (_) => SimpleDynamicIslandDialog( title: languageSingleton.translate('GPS Disabled'), content: languageSingleton.translate('Please enable GPS to continue using location features.'), actionText: languageSingleton.translate('Open Settings'), barrierDismissible: false, onAction: () { Navigator.of(context, rootNavigator: true).pop(); // CLOSE dialog _gpsDialogShowing = false; Geolocator.openLocationSettings(); // THEN open GPS settings }, ), ); } void _onGpsBackOn() { if (_gpsDialogShowing) { Navigator.of(context, rootNavigator: true).pop(); _gpsDialogShowing = false; } AppSnackbar.showSuccess( languageSingleton.translate("GPS Enabled"), languageSingleton.translate("Location services have been restored."), ); servicecheckup(); } // βœ… REPLACE your old function with this new, upgraded one. void _listenToFlutterConnectivity() { // Get the provider once, outside the listener. final connectionProvider = context.read(); _netSub = Connectivity().onConnectivityChanged.listen((results) { // The new connectivity_plus gives a list. We just need to know if it's empty or not. final bool isNowOnline = results.contains(ConnectivityResult.mobile) || results.contains(ConnectivityResult.wifi) || results.contains(ConnectivityResult.ethernet); // πŸ‘‰ THIS IS THE IMPORTANT NEW PART // We tell the provider the new status. This will update the banner! connectionProvider.updateStatus(isNowOnline); // This is your old logic for the pop-up dialog, which we keep. if (isNowOnline) { _dismissConnectionLostDialog(); _onBackOnline(); } else { _showConnectionLostDialog(); } }); } void _onBackOnline() { AppSnackbar.showSuccess( "Back Online", "You’ve reconnected to the internet!"); servicecheckup(); } // void _listenToConnectionStatus() { // _connectionStatusSubscription = _connectionChannel.receiveBroadcastStream().listen( // (status) { // log("Received native connection status: $status"); // if (mounted) { // context.read().updateStatus(status as bool); // } // }, // onError: (error) { // log("Error listening to native connection status: $error"); // /*if (mounted) { // context.read().updateStatus(false); // }*/ // }, // ); // } void _setupMethodChannel() { platformNavigation.setMethodCallHandler((call) async { if (call.method == 'navigateToRide') { final int rideId = call.arguments; log("πŸš€ Received rideId $rideId from native. Enqueueing..."); context.read().enqueue(rideId); } }); } Future servicecheckup() async { final rideProvider = Provider.of(context, listen: false); await rideProvider.rideconnectionsocket(); await rideProvider.locationconnectionservice(); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); _floatingHandler.dispose(); _disposed = true; _connectionStatusSubscription?.cancel(); _gpsSub?.cancel(); _netSub.cancel(); super.dispose(); } final List _pages = [ HomeScreen(), NotificationScreen(), HistoryScreen(), ProfileScreen(), NavigationSecreenToClientsecreen(), UpdateProfileScreen(), ]; void _onTap(int index) { final rideprovider = context.read(); if (rideprovider.currentride != null) { return; } setState(() => _currentIndex = index); } void navigateToScreen(int index, {bool force = false}) { if (_disposed) return; if (index < 0 || index >= _pages.length) { log('Invalid navigation index: $index'); return; } setState(() => _currentIndex = index); if (index == 0) { _resetHomeState(); } } void _resetHomeState() { WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { rideSingleton.clear(); context.read().clear(); initialize(); } }); } Future initialize() async { log("*************** _initializing : $_initializing" ); if (!mounted) return; try{ if (_initializing) return; setState(() { _initializing = true; }); rideSingleton.clear(); _floatingHandler; servicecheckup(); _setupMethodChannel(); await context.read().fetchProfile(); await context.read().get_my_latest_ride_ongoing(); bool haveride = context.read().currentride != null; if (haveride) { if (!mounted) return; setState(() { _initializing = false; }); return; } checkrating(); if (!mounted) return; setState(() { _initializing = false; }); }catch(e){ log("*************** error init : $e"); } } Future checkrating() async { if (!mounted) return; final rideProvider = context.read(); Ride? ride = await rideProvider.get_my_latest_ride_not_rated(); if (ride == null || ride.userId == null) return; RatingPopup.show( context: context, userImageUrl: '$mediaUrl${ride.userimageurl}', userName: '${ride.ridername}', onRatingChanged: (rating) { print('User rated: $rating stars'); rideProvider.rate_user(rating, ride.userId); }, ); } @override Widget build(BuildContext context) { if (_initializing) { return Container( color:Theme.of(context).brightness == Brightness.dark ?AppColors.blackBackgroundColor:AppColors.whiteBackgroundColor, child: const Center(child: CustomLoader())); } final connectedNative = context.watch().isNativeConnected; // βœ… ADD THIS LOG STATEMENT log('MAIN_SCREEN BUILD: Building with connectedNative = $connectedNative'); final ridestatus = context.watch(); if (ridestatus.ridestatus == RideEnum.to_client && _currentIndex != 4) { WidgetsBinding.instance.addPostFrameCallback((_) { navigateToScreen(4); }); } if (context.watch().user!.blocked == true) { return Scaffold( body: Container( color: Colors.white, // Or any background color you prefer padding: const EdgeInsets.all(16.0), child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ const Icon( Icons.block, color: Colors.red, size: 80, ), const SizedBox(height: 20), const Text( 'Your account has been temporarily blocked.', textAlign: TextAlign.center, style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: Colors.black87, ), ), const SizedBox(height: 10), Txt( text: context.watch().user!.blockReason??"No raison available", textAlign: TextAlign.center, style: const TextStyle( fontSize: 16, color: Colors.black54, ), ), const SizedBox(height: 20), const Txt( text: 'Instructions:', textAlign: TextAlign.center, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.blueAccent, ), ), const SizedBox(height: 5), const Txt( text: 'contact client support for instruction', textAlign: TextAlign.center, style: TextStyle( fontSize: 14, color: Colors.black54, ), ), ], ), ), ), ); } return WillPopScope( onWillPop: () async { return false; }, child: Scaffold( body: Stack( children: [ _pages[_currentIndex], Positioned( top: 50, left: 0, right: 0, child: DynamicIslandConnectionBanner(connected: connectedNative), ), ], ), bottomNavigationBar: CustomBottomNavigationBar( currentIndex: _currentIndex, onTap: _onTap, ), ), ); } }