// ignore_for_file: unused_field, deprecated_member_use import 'dart:io'; import 'package:didvan/constants/assets.dart'; import 'package:didvan/main.dart'; import 'package:didvan/providers/media.dart'; import 'package:didvan/providers/server_data.dart'; import 'package:didvan/providers/theme.dart'; import 'package:didvan/providers/user.dart'; import 'package:didvan/routes/routes.dart'; import 'package:didvan/services/app_home_widget/home_widget_repository.dart'; import 'package:didvan/services/app_initalizer.dart'; import 'package:didvan/services/network/request.dart'; import 'package:didvan/services/network/request_helper.dart'; import 'package:didvan/services/storage/storage.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:in_app_update/in_app_update.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:provider/provider.dart'; import 'package:universal_html/html.dart' as html; import 'package:url_launcher/url_launcher.dart'; class Splash extends StatefulWidget { const Splash({Key? key}) : super(key: key); @override State createState() => _SplashState(); } class _SplashState extends State with SingleTickerProviderStateMixin { bool _errorOccured = false; late AnimationController _pulseController; late Animation _pulseAnimation; @override void initState() { super.initState(); if (kIsWeb) { final loader = html.document.getElementById('loading_indicator'); if (loader != null) { loader.remove(); } } _setupAnimations(); WidgetsBinding.instance.addPostFrameCallback((_) { _startInitialization(); }); } void _setupAnimations() { _pulseController = AnimationController( duration: const Duration(milliseconds: 1500), vsync: this, ); _pulseAnimation = Tween( begin: 1.0, end: 1.1, ).animate(CurvedAnimation( parent: _pulseController, curve: Curves.easeInOut, )); _pulseController.repeat(reverse: true); } @override void dispose() { _pulseController.dispose(); super.dispose(); } void _startInitialization() { final themeProvider = context.read(); final userProvider = context.read(); final mediaProvider = context.read(); _initialize(themeProvider, userProvider, mediaProvider); } @override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; final colorScheme = Theme.of(context).colorScheme; return AnnotatedRegion( value: SystemUiOverlayStyle( statusBarColor: Colors.transparent, statusBarIconBrightness: isDark ? Brightness.light : Brightness.dark, systemNavigationBarColor: colorScheme.background, ), child: Scaffold( resizeToAvoidBottomInset: true, backgroundColor: colorScheme.background, body: SafeArea( child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Spacer(flex: 2), _buildLogo(), const SizedBox(height: 60), if (_errorOccured) _buildErrorView(colorScheme) else _buildLoadingView(colorScheme), const Spacer(flex: 3), ], ), ), ), ), ); } Widget _buildLogo() { return SvgPicture.asset( Assets.horizontalLogoWithText, height: 80, ); } Widget _buildLoadingView(ColorScheme colorScheme) { return Column( children: [ SizedBox( width: 60, height: 60, child: Image.asset( Assets.loadingAnimation, fit: BoxFit.contain, ), ), const SizedBox(height: 24), AnimatedBuilder( animation: _pulseController, builder: (context, child) { return Opacity( opacity: 0.4 + (0.4 * _pulseController.value), child: Text( 'در حال بارگذاری...', style: TextStyle( fontSize: 14, color: colorScheme.onBackground.withOpacity(0.6), fontWeight: FontWeight.w400, ), ), ); }, ), ], ); } Widget _buildErrorView(ColorScheme colorScheme) { return Column( children: [ Container( width: 56, height: 56, decoration: BoxDecoration( color: colorScheme.error.withOpacity(0.1), shape: BoxShape.circle, ), child: Icon( Icons.wifi_off_rounded, size: 24, color: colorScheme.error, ), ), const SizedBox(height: 16), Text( 'مشکل در اتصال', style: TextStyle( fontSize: 16, color: colorScheme.onBackground, fontWeight: FontWeight.w500, ), ), const SizedBox(height: 8), Text( 'لطفاً اتصال اینترنت خود را بررسی کنید', style: TextStyle( fontSize: 13, color: colorScheme.onBackground.withOpacity(0.6), ), ), const SizedBox(height: 24), ElevatedButton( onPressed: () { setState(() { _errorOccured = false; }); _startInitialization(); }, style: ElevatedButton.styleFrom( backgroundColor: colorScheme.primary, foregroundColor: Colors.white, elevation: 0, padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: const Text('تلاش مجدد'), ), ], ); } Future _initialize(ThemeProvider themeProvider, UserProvider userProvider, MediaProvider mediaProvider) async { try { if (!mounted) return; final settingsData = await AppInitializer.initilizeSettings(); themeProvider.themeMode = settingsData.themeMode; themeProvider.fontFamily = settingsData.fontFamily; themeProvider.fontScale = settingsData.fontScale; if (mounted) { await AppInitializer.setupServices(context); } final String? token = await userProvider.setAndGetToken(); if (token != null) { RequestService.token = token; } if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) { bool stopForUpdate = await _checkForUpdate(); if (stopForUpdate) return; } if (kIsWeb) { html.window.onBeforeUnload.listen((event) { StorageService.webStorage .removeWhere((key, value) => key == 'image-cache'); }); } bool isMobileApp = !kIsWeb && (Platform.isAndroid || Platform.isIOS); if (isMobileApp) { final String? hasSeenOnboarding = await StorageService.getValue(key: 'hasSeenOnboarding'); if (hasSeenOnboarding != 'true') { if (mounted) { Navigator.of(context).pushReplacementNamed(Routes.onboarding); } return; } } if (token != null) { if (!kIsWeb) { await mediaProvider.getDownloadsList(); } // Add timeout for getUserInfo to prevent blocking on slow networks final result = await userProvider.getUserInfo().timeout( const Duration(seconds: 15), onTimeout: () { debugPrint("getUserInfo timed out - treating as failure"); return false; }, ); if (!result) { await StorageService.delete(key: 'token'); if (mounted) { Navigator.of(context).pushNamedAndRemoveUntil( Routes.splash, (_) => false, ); } return; } // Add timeout for ServerDataProvider.getData() try { await ServerDataProvider.getData().timeout( const Duration(seconds: 10), onTimeout: () { debugPrint("ServerDataProvider.getData timed out - continuing without server data"); }, ); } catch (e) { debugPrint("ServerDataProvider.getData failed: $e - continuing"); // Continue even if server data fetch fails } } _navigateToNextScreen(token); } catch (e) { debugPrint("Error in initialize: $e"); if (mounted) { setState(() { _errorOccured = true; }); } } } void _navigateToNextScreen(String? token) async { if (!mounted) return; String extractedPath = initialURI?.path == '/' ? Routes.home : (initialURI?.path ?? Routes.home); final String destinationRoute = token == null ? Routes.authenticaion : extractedPath; dynamic routeArguments = token == null ? {'isResetPassword': false} : {'showDialogs': false}; if (destinationRoute == Routes.home) { initialURI = null; } if (destinationRoute == Routes.authenticaion) { routeArguments = false; } if (token != null && HomeWidgetRepository.data != null) { Navigator.of(context).pushReplacementNamed( Routes.home, arguments: {'showDialogs': false}, ); await HomeWidgetRepository.decideWhereToGoNotif(); if (HomeWidgetRepository.data != null) { await StorageService.delete( key: 'notification${AppInitializer.createNotificationId(HomeWidgetRepository.data!)}'); } return; } Navigator.of(context).pushReplacementNamed( destinationRoute, arguments: routeArguments, ); } Future _checkForUpdate() async { try { PackageInfo packageInfo = await PackageInfo.fromPlatform(); bool isGooglePlay = packageInfo.installerStore == 'com.android.vending'; if (isGooglePlay) { try { AppUpdateInfo updateInfo = await InAppUpdate.checkForUpdate(); if (updateInfo.updateAvailability == UpdateAvailability.updateAvailable) { if (updateInfo.immediateUpdateAllowed) { await InAppUpdate.performImmediateUpdate(); return true; } else if (updateInfo.flexibleUpdateAllowed) { await InAppUpdate.startFlexibleUpdate(); await InAppUpdate.completeFlexibleUpdate(); return false; } } } catch (e) { return await _checkCustomApiUpdate(packageInfo); } } else { return await _checkCustomApiUpdate(packageInfo); } } catch (e) { debugPrint("Update check failed: $e"); } return false; } Future _checkCustomApiUpdate(PackageInfo packageInfo) async { final req = RequestService( RequestHelper.checkVersion, useAutherization: true, ); await req.httpGet(); if (req.isSuccess && mounted) { final result = req.result; final apiVersion = result['version']?.toString(); final bool isMandatory = result['isMandatory'] ?? false; final String downloadUrl = result['appName'] ?? 'https://api.didvan.app/uploads/apk'; if (apiVersion != null && _isUpdateAvailable(packageInfo.version, apiVersion)) { _showUpdateDialog(downloadUrl, isMandatory); return isMandatory; } } return false; } bool _isUpdateAvailable(String currentVersion, String newVersion) { try { List currentParts = currentVersion.split('.').map(int.parse).toList(); List newParts = newVersion.split('.').map(int.parse).toList(); for (int i = 0; i < newParts.length; i++) { if (i >= currentParts.length) return true; if (newParts[i] > currentParts[i]) return true; if (newParts[i] < currentParts[i]) return false; } return false; } catch (e) { debugPrint("Version parsing error: $e"); return false; } } void _showUpdateDialog(String url, bool isMandatory) { if (!mounted) return; showDialog( context: context, barrierDismissible: !isMandatory, builder: (context) { return PopScope( canPop: !isMandatory, child: AlertDialog( title: const Text( 'به‌روزرسانی دیدوان', textAlign: TextAlign.center, style: TextStyle(fontWeight: FontWeight.bold), ), content: Text( isMandatory ? 'نسخه جدیدی از دیدوان در دسترس است. برای ادامه استفاده از برنامه باید آن را به‌روزرسانی کنید.' : 'نسخه جدیدی از برنامه در دسترس است. آیا مایل به به‌روزرسانی هستید؟', textAlign: TextAlign.center, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), actionsAlignment: MainAxisAlignment.center, actions: [ if (!isMandatory) TextButton( onPressed: () => Navigator.pop(context), child: const Text('بعداً', style: TextStyle(color: Colors.grey)), ), ElevatedButton( onPressed: () async { final Uri uri = Uri.parse(url); if (await canLaunchUrl(uri)) { await launchUrl(uri, mode: LaunchMode.externalApplication); } }, style: ElevatedButton.styleFrom( backgroundColor: Theme.of(context).primaryColor, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10)), ), child: const Text('دانلود و نصب', style: TextStyle(color: Colors.white)), ), ], ), ); }, ); } }