import 'dart:async'; import 'dart:io'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:pulse/Notifier/ProfileProvider.dart'; import 'package:pulse/Screens/authScreen/LoginScreen.dart'; import 'package:pulse/Screens/profileScreen.dart'; import 'package:pulse/utils/AppColors.dart'; import 'package:pulse/utils/customSnackBar.dart'; import 'package:flutter_svg/svg.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; class HomeScreen extends StatefulWidget { final String sessionId; final String staffId; const HomeScreen({super.key, required this.sessionId, required this.staffId}); @override State createState() => _HomeScreenState(); } class _HomeScreenState extends State { final GlobalKey webViewKey = GlobalKey(); InAppWebViewController? _webViewController; final Completer _controller = Completer(); final csrfToken = "2fb317d960000fe130fb0e99cee97aa9"; Timer? _connectivityTimer; bool _progressCheckCompleted = false; bool _hasInternet = true; bool _isLoading = false; bool _isRefreshing = false; PullToRefreshController? _pullToRefreshController; // Responsive sizing variables late double _screenHeight; late double _screenWidth; late bool _isPortrait; late bool _isTablet; late double _appBarHeight; late double _safeAreaTop; late double _safeAreaBottom; @override void initState() { super.initState(); _initConnectivity(); _initializePullToRefresh(); WidgetsBinding.instance.addPostFrameCallback((_) { final provider = Provider.of(context, listen: false); if (provider.sessionExists != 0) { provider.fetchProfile( context: context, csrfToken: csrfToken, sessionId: widget.sessionId, staffId: widget.staffId, ); } else { Navigator.pushReplacement( context, MaterialPageRoute(builder: (_) => const LoginScreen()), ); } }); } void _initializePullToRefresh() { _pullToRefreshController = PullToRefreshController( options: PullToRefreshOptions( color: AppColors.accentColor, backgroundColor: AppColors.primaryColor, ), onRefresh: () async { if (Platform.isAndroid) { _webViewController?.reload(); } else if (Platform.isIOS) { _webViewController?.loadUrl( urlRequest: URLRequest( url: await _webViewController?.getUrl(), ), ); } }, ); } Future _injectResponsiveCSS() async { try { // Wait for page to be fully interactive await Future.delayed(const Duration(milliseconds: 1000)); final responsiveCSS = """ """; // Minimal JavaScript to handle edge cases const minimalJS = """ // Only fix dialogs that are actually overflowing function fixOverflowingDialogs() { const dialogs = document.querySelectorAll('.modal-dialog, .ui-dialog, [role="dialog"]'); dialogs.forEach(dialog => { const rect = dialog.getBoundingClientRect(); const windowHeight = window.innerHeight; // Only apply max-height if dialog is taller than 80% of screen if (rect.height > windowHeight * 0.8) { dialog.style.maxHeight = '80vh'; dialog.style.overflowY = 'auto'; } // Check if dialog content needs scrolling const content = dialog.querySelector('.modal-content, .ui-dialog-content'); if (content) { const contentRect = content.getBoundingClientRect(); if (contentRect.height > windowHeight * 0.7) { content.style.maxHeight = '70vh'; content.style.overflowY = 'auto'; } } }); } // Run initially setTimeout(fixOverflowingDialogs, 100); // Run after any DOM changes const observer = new MutationObserver(function(mutations) { let shouldCheck = false; mutations.forEach(function(mutation) { if (mutation.type === 'childList') { mutation.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.classList && ( node.classList.contains('modal-dialog') || node.classList.contains('ui-dialog') || (node.hasAttribute && node.hasAttribute('role') && node.getAttribute('role') === 'dialog') )) { shouldCheck = true; } } }); } }); if (shouldCheck) { setTimeout(fixOverflowingDialogs, 50); } }); observer.observe(document.body, { childList: true, subtree: true }); // Also check on resize window.addEventListener('resize', fixOverflowingDialogs); """; // Inject minimal CSS await _webViewController?.injectCSSCode(source: responsiveCSS); // Inject minimal JavaScript await _webViewController?.evaluateJavascript(source: minimalJS); print("Minimal responsive CSS injected successfully"); } catch (e) { print("Error injecting responsive CSS: $e"); } } // Alternative: Only inject CSS when we detect overflow issues Future _injectCSSOnlyWhenNeeded() async { try { await Future.delayed(const Duration(milliseconds: 1500)); const safeCSS = """ """; await _webViewController?.injectCSSCode(source: safeCSS); print("Safe CSS injected"); } catch (e) { print("Error in safe CSS injection: $e"); } } Future _onRefresh() async { setState(() => _isRefreshing = true); await _webViewController?.reload(); } Future _initConnectivity() async { try { await _checkConnectivity(); _connectivityTimer = Timer.periodic(const Duration(seconds: 3), (timer) => _checkConnectivity()); } catch (_) { _updateConnectionStatus(false); } } Future _checkConnectivity() async { try { final connectivity = Connectivity(); final results = await connectivity.checkConnectivity(); final hasInternet = results.any((r) => r != ConnectivityResult.none); if (hasInternet) { try { final result = await InternetAddress.lookup('google.com'); final socketCheck = result.isNotEmpty && result[0].rawAddress.isNotEmpty; _updateConnectionStatus(socketCheck); } catch (_) { _updateConnectionStatus(false); } } else { _updateConnectionStatus(false); } } catch (_) { _updateConnectionStatus(false); } } bool _wasDisconnected = false; void _updateConnectionStatus(bool hasInternet) { if (mounted) setState(() => _hasInternet = hasInternet); if (!hasInternet) { _wasDisconnected = true; _showNoInternetSnackbar(); } else { ScaffoldMessenger.of(context).hideCurrentSnackBar(); if (_wasDisconnected) { _wasDisconnected = false; _webViewController?.reload(); } } } void _showNoInternetSnackbar() { if (mounted) { WidgetsBinding.instance.addPostFrameCallback((_) { ScaffoldMessenger.of(context).hideCurrentSnackBar(); CustomSnackBar.show( context: context, title: "Connection Error", message: "No internet connection! Please connect to the internet.", icon: Icons.wifi_off, backgroundColor: Colors.redAccent, ); }); } } @override void dispose() { _connectivityTimer?.cancel(); _pullToRefreshController?.dispose(); super.dispose(); } double _getResponsiveSize(double size) { if (_isTablet) { return size * 1.3; } else if (!_isPortrait) { return size * 0.9; } return size; } @override Widget build(BuildContext context) { final mediaQuery = MediaQuery.of(context); // Calculate responsive values _screenHeight = mediaQuery.size.height; _screenWidth = mediaQuery.size.width; _isPortrait = mediaQuery.orientation == Orientation.portrait; _isTablet = _screenWidth >= 600; _safeAreaTop = mediaQuery.padding.top; _safeAreaBottom = mediaQuery.padding.bottom; _appBarHeight = _getResponsiveSize(kToolbarHeight); return WillPopScope( onWillPop: () async { if (await _webViewController?.canGoBack() ?? false) { _webViewController?.goBack(); return false; } return true; }, child: Scaffold( appBar: AppBar( backgroundColor: AppColors.primaryColor, elevation: 0, systemOverlayStyle: SystemUiOverlayStyle.light.copyWith( statusBarColor: AppColors.primaryColor, ), title: SvgPicture.asset( "assets/svg/pulse_logo_ic.svg", height: _getResponsiveSize(68), ), actions: [ Consumer( builder: (context, profileProvider, child) { return InkWell( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => ProfileScreen( sessionId: widget.sessionId, staffId: widget.staffId, csrfToken: csrfToken, userName: profileProvider.staffDetails?.firstname != null ? "${profileProvider.staffDetails!.firstname} ${profileProvider.staffDetails!.lastname ?? ""}" : "User Name", userEmail: profileProvider.staffDetails?.email ?? "user@example.com", profileImageUrl: profileProvider.staffDetails?.smallProfilePic ?? "", ), ), ); }, borderRadius: BorderRadius.circular(_getResponsiveSize(25)), child: Container( margin: EdgeInsets.only(right: _getResponsiveSize(12)), padding: EdgeInsets.symmetric( horizontal: _getResponsiveSize(3), vertical: _getResponsiveSize(3), ), decoration: BoxDecoration( color: Colors.white.withOpacity(0.3), borderRadius: BorderRadius.circular(_getResponsiveSize(25)), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Container( width: _getResponsiveSize(38), height: _getResponsiveSize(38), decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( color: Colors.white.withOpacity(0.3), width: _getResponsiveSize(2), ), ), child: ClipOval( child: CachedNetworkImage( imageUrl: profileProvider.staffDetails?.smallProfilePic ?? "", fit: BoxFit.cover, placeholder: (context, url) => Center( child: SizedBox( width: _getResponsiveSize(16), height: _getResponsiveSize(16), child: CircularProgressIndicator( strokeWidth: _getResponsiveSize(2), valueColor: AlwaysStoppedAnimation(AppColors.accentColor), ), ), ), errorWidget: (context, url, error) => Icon( Icons.person, color: Colors.white, size: _getResponsiveSize(20), ), ), ), ), ], ), ), ); }, ), ], bottom: PreferredSize( preferredSize: Size.fromHeight(_getResponsiveSize(3)), child: _isLoading || _isRefreshing ? LinearProgressIndicator( color: AppColors.accentColor, backgroundColor: Colors.white24, minHeight: _getResponsiveSize(3), ) : const SizedBox.shrink(), ), ), backgroundColor: AppColors.primaryColor, body: AnnotatedRegion( value: SystemUiOverlayStyle.light.copyWith( statusBarColor: AppColors.primaryColor, ), child: SafeArea( top: false, bottom: false, left: false, right: false, child: LayoutBuilder( builder: (context, constraints) { return Column( children: [ Expanded( child: Container( width: double.infinity, height: double.infinity, color: Colors.white, child: Stack( fit: StackFit.expand, children: [ // InAppWebView with built-in pull-to-refresh InAppWebView( key: webViewKey, initialUrlRequest: URLRequest( url: WebUri( "https://pulse.webgrid.in/app/authentication/web_erp?staff_id=${widget.staffId}&session_id=${widget.sessionId}", ), headers: {"Cookie": "session_id=${widget.sessionId}"}, allowsCellularAccess: true, allowsConstrainedNetworkAccess: true, allowsExpensiveNetworkAccess: true, ), initialOptions: InAppWebViewGroupOptions( android: AndroidInAppWebViewOptions( useWideViewPort: true, loadWithOverviewMode: true, allowContentAccess: true, geolocationEnabled: true, allowFileAccess: true, databaseEnabled: true, domStorageEnabled: true, builtInZoomControls: true, displayZoomControls: false, safeBrowsingEnabled: true, clearSessionCache: true, loadsImagesAutomatically: true, thirdPartyCookiesEnabled: true, blockNetworkImage: false, supportMultipleWindows: true, blockNetworkLoads: false, networkAvailable: true, useShouldInterceptRequest: true, hardwareAcceleration: true, ), ios: IOSInAppWebViewOptions( allowsInlineMediaPlayback: true, allowsLinkPreview: true, allowsBackForwardNavigationGestures: true, disallowOverScroll: true, ), crossPlatform: InAppWebViewOptions( javaScriptEnabled: true, useOnDownloadStart: true, allowFileAccessFromFileURLs: true, allowUniversalAccessFromFileURLs: true, mediaPlaybackRequiresUserGesture: false, transparentBackground: true, supportZoom: true, verticalScrollBarEnabled: true, horizontalScrollBarEnabled: false, ), ), pullToRefreshController: _pullToRefreshController, onWebViewCreated: (controller) { _webViewController = controller; _controller.complete(controller); }, onLoadStart: (controller, url) { setState(() { _isLoading = true; _isRefreshing = true; }); }, onLoadStop: (controller, url) async { setState(() { _isLoading = false; _isRefreshing = false; }); _pullToRefreshController?.endRefreshing(); // Try minimal CSS injection first await _injectCSSOnlyWhenNeeded(); }, onLoadError: (controller, url, code, message) { setState(() { _isLoading = false; _isRefreshing = false; }); _pullToRefreshController?.endRefreshing(); }, onProgressChanged: (controller, progress) { if (progress == 100) { _pullToRefreshController?.endRefreshing(); setState(() { _isRefreshing = false; }); } }, onReceivedError: (controller, request, error) { setState(() { _isLoading = false; _isRefreshing = false; }); _pullToRefreshController?.endRefreshing(); }, ), // Loading indicator if (_isLoading) Container( color: Colors.white.withOpacity(0.7), child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox( width: _getResponsiveSize(40), height: _getResponsiveSize(40), child: CircularProgressIndicator( strokeWidth: _getResponsiveSize(3), valueColor: AlwaysStoppedAnimation(AppColors.accentColor), ), ), SizedBox(height: _getResponsiveSize(16)), Text( "Loading...", style: TextStyle( color: AppColors.accentColor, fontSize: _getResponsiveSize(16), fontWeight: FontWeight.w500, ), ), ], ), ), ), // Custom Refresh Indicator overlay if (_isRefreshing) Positioned( top: _getResponsiveSize(10), left: 0, right: 0, child: Container( height: _getResponsiveSize(60), color: Colors.transparent, child: Center( child: Container( padding: EdgeInsets.symmetric( horizontal: _getResponsiveSize(20), vertical: _getResponsiveSize(8), ), decoration: BoxDecoration( color: AppColors.primaryColor.withOpacity(0.9), borderRadius: BorderRadius.circular(_getResponsiveSize(20)), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.2), blurRadius: _getResponsiveSize(8), offset: const Offset(0, 2), ), ], ), child: Row( mainAxisSize: MainAxisSize.min, children: [ SizedBox( width: _getResponsiveSize(20), height: _getResponsiveSize(20), child: CircularProgressIndicator( strokeWidth: _getResponsiveSize(2), valueColor: AlwaysStoppedAnimation(AppColors.accentColor), ), ), SizedBox(width: _getResponsiveSize(12)), Text( "Refreshing...", style: TextStyle( color: Colors.white, fontSize: _getResponsiveSize(14), fontWeight: FontWeight.w500, ), ), ], ), ), ), ), ), ], ), ), ), ], ); }, ), ), ), ), ); } }