import 'dart:async'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:chepuhagram/logic/auth_provider.dart'; import 'package:chepuhagram/logic/contact_provider.dart'; import 'package:chepuhagram/presentation/screens/login_screen.dart'; import 'package:chepuhagram/presentation/screens/contacts_screen.dart'; import 'package:chepuhagram/presentation/screens/account_setup_screen.dart'; import 'package:chepuhagram/presentation/screens/key_recovery_screen.dart'; import 'package:chepuhagram/presentation/screens/chat_screen.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:chepuhagram/main.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'dart:convert'; import 'package:chepuhagram/domain/services/crypto_service.dart'; import 'package:cryptography/cryptography.dart'; import 'package:flutter/foundation.dart'; import 'package:chepuhagram/core/theme_manager.dart'; import 'dart:ui'; import 'dart:io'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; class SplashScreen extends StatefulWidget { const SplashScreen({super.key}); @override State createState() => _SplashScreenState(); } class _SplashScreenState extends State with TickerProviderStateMixin { int? _targetChatId; String? _statusMessage; late AnimationController _fadeController; late AnimationController _pulseController; late Animation _fadeAnimation; late Animation _pulseAnimation; static const String _notificationLaunchKey = 'notification_launch_data'; static const String _contactPublicKey = 'contact_public_key_'; static const String _contactSharedKey = 'contact_shared_key_'; @override void initState() { super.initState(); _fadeController = AnimationController( vsync: this, duration: const Duration(milliseconds: 800), ); _pulseController = AnimationController( vsync: this, duration: const Duration(milliseconds: 1200), ); _fadeAnimation = Tween( begin: 0.0, end: 1.0, ).animate(CurvedAnimation(parent: _fadeController, curve: Curves.easeIn)); _pulseAnimation = Tween(begin: 1.0, end: 1.15).animate( CurvedAnimation(parent: _pulseController, curve: Curves.easeInOut), ); _pulseController.repeat(reverse: true); _fadeController.forward(); _setupNotificationHandler(); _initializeApp(); } void _setupNotificationHandler() { FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) { if (message.data['type'] == 'enc_message') { final senderId = int.tryParse( message.data['sender_id']?.toString() ?? '', ); if (senderId != null) { setState(() => _targetChatId = senderId); } } }); } Future _initializeApp() async { if (!mounted) return; setState(() => _statusMessage = "Подключение..."); final authProvider = context.read(); bool? isLoggedIn; try { isLoggedIn = await authProvider.tryAutoLogin(); } catch (e) { setState( () => _statusMessage = 'Ошибка входа: ${e.toString().replaceAll('Exception: ', '')}', ); await Future.delayed(const Duration(seconds: 3)); if (mounted) _navigateTo(const LoginScreen()); return; } if (!mounted) return; if (isLoggedIn) { setState(() => _statusMessage = "Аутентификация..."); bool connected = false; int connectAttempt = 1; while (!connected) { try { await authProvider.initRealtime(); connected = true; } catch (e) { setState( () => _statusMessage = 'Соединение... (попытка $connectAttempt)', ); connectAttempt++; await Future.delayed(const Duration(seconds: 2)); } } setState(() => _statusMessage = "Загрузка профиля..."); await authProvider.refreshMe(); if (authProvider.needsSetup) { _navigateTo(const AccountSetupScreen()); } else if (authProvider.needsKeyRecovery) { _navigateTo(const KeyRecoveryScreen()); } else { setState(() => _statusMessage = "Загрузка контактов..."); _loadContactsAndNavigate(authProvider.currentUserId); } } else { _navigateTo(const LoginScreen()); } } Future _loadContactsAndNavigate(int? currentUserId) async { final contactProvider = context.read(); final cryptoService = context.read(); final int? targetId = await _getTargetChatId(); // Мгновенно уходим на экран контактов, показывая лоадеры "Дешифровка..." _navigateTo(ContactsScreen(targetChatId: targetId)); try { contactProvider.setCurrentUserId(currentUserId); await contactProvider.loadContacts(enrichContacts: false); final storage = const FlutterSecureStorage(); final myPrivKeyBase64 = await cryptoService.getPrivateKey(); if (myPrivKeyBase64 != null) { final String privKeyFingerprint = myPrivKeyBase64.replaceAll(RegExp(r'[^a-zA-Z0-9]'), ''); final String fingerprint = privKeyFingerprint.substring( 0, privKeyFingerprint.length > 16 ? 16 : privKeyFingerprint.length); // Проходим по каждому контакту строго ПО ОЧЕРЕДИ for (var c in contactProvider.contacts) { final savedKeyHex = await storage.read( key: '${_contactSharedKey}${fingerprint}_${c.id}', ); final savedPubKey = await storage.read( key: '${_contactPublicKey}${fingerprint}_${c.id}', ); if (savedKeyHex != null && savedPubKey == c.publicKey) { // Состояние 1: Ключ уже был в локальном хранилище устройства. // Применяем его моментально без ожидания. final secretKey = SecretKey(base64Decode(savedKeyHex)); contactProvider.setSharedKey(c.id, secretKey); CryptoService.cacheSharedKey(c.id, secretKey); } else if (c.publicKey != null) { // Состояние 2: Ключа нет на диске. Вычисляем Диффи-Хеллмана для ЭТОГО конкретного контакта. // Цикл приостановится (await), давая UI продышаться и показать анимацию для предыдущих контактов. final secretKey = await cryptoService.deriveSharedSecret( myPrivKeyBase64, c.publicKey!, contactId: c.id, ); // Как только ключ готов — ТУТ ЖЕ обновляем провайдер и RAM-кэш для этой строки. // Виджет AnimatedSwitcher на экране контактов сразу поймает обновление и красиво проявит текст. contactProvider.setSharedKey(c.id, secretKey); CryptoService.cacheSharedKey(c.id, secretKey); // Извлекаем байты и сохраняем в SecureStorage. // Опускаем await для записи на диск, чтобы медленная файловая система не тормозила расчет следующего ключа. secretKey.extractBytes().then((bytes) { storage.write( key: '${_contactSharedKey}${fingerprint}_${c.id}', value: base64Encode(bytes), ); storage.write( key: '${_contactPublicKey}${fingerprint}_${c.id}', value: c.publicKey!, ); }); } } } } catch (e) { print("Ошибка при фоновой загрузке контактов или ключей: $e"); } } Future _getTargetChatId() async { int? targetChatId = _targetChatId; final prefs = await SharedPreferences.getInstance(); if (targetChatId == null) { final savedData = prefs.getString(_notificationLaunchKey); if (savedData != null) { try { final data = jsonDecode(savedData) as Map; targetChatId = int.tryParse(data['sender_id']?.toString() ?? ''); } catch (e) { print('Error parsing saved notification data: $e'); } } } if (targetChatId == null && initialMessage != null) { if (initialMessage!.data['type'] == 'enc_message') { targetChatId = int.tryParse( initialMessage!.data['sender_id']?.toString() ?? '', ); } } await prefs.remove(_notificationLaunchKey); return targetChatId; } void _navigateTo(Widget screen) { if (mounted) { Navigator.pushReplacement( context, MaterialPageRoute(builder: (_) => screen), ); } } @override void dispose() { _fadeController.dispose(); _pulseController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; return Scaffold( body: Stack( children: [ Center( child: FadeTransition( opacity: _fadeAnimation, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Spacer(), const Spacer(), ScaleTransition( scale: _pulseAnimation, child: Icon( Icons.messenger_outline, size: 80, color: colorScheme.primary, ), ), const SizedBox(height: 24), Text( "Chepuhagram", style: TextStyle( color: colorScheme.primary, fontSize: 32, fontWeight: FontWeight.bold, letterSpacing: 1.2, ), ), const SizedBox(height: 80), SizedBox( height: 40, child: AnimatedSwitcher( duration: const Duration(milliseconds: 300), child: _statusMessage != null ? Text( _statusMessage!, key: ValueKey(_statusMessage), style: TextStyle( color: colorScheme.outline, fontSize: 14, ), textAlign: TextAlign.center, ) : const SizedBox.shrink(), ), ), const CircularProgressIndicator(), const Spacer(), Text( 'Made by ArturKarasevich', style: TextStyle(color: colorScheme.outline, fontSize: 12), ), const Spacer(), ], ), ), ), ], ), ); } }