import 'dart:async'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../../logic/auth_provider.dart'; import '../../logic/contact_provider.dart'; import 'login_screen.dart'; import 'contacts_screen.dart'; import 'account_setup_screen.dart'; import 'key_recovery_screen.dart'; import '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'; class SplashScreen extends StatefulWidget { const SplashScreen({super.key}); @override State createState() => _SplashScreenState(); } class _SplashScreenState extends State { int? _targetChatId; String? connectError; // Ключ для SharedPreferences 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(); print('SplashScreen initState'); _setupNotificationHandler(); _initializeApp(); } void _setupNotificationHandler() { print('Setting up notification handler'); // Обработка открытия приложения из уведомления FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) { print('App opened from notification: ${message.data}'); if (message.data['type'] == 'enc_message') { final senderId = int.tryParse( message.data['sender_id']?.toString() ?? '', ); if (senderId != null) { setState(() { _targetChatId = senderId; }); print('Set target chat from opened app: $senderId'); } } }); } Future _initializeApp() async { // 1. Искусственная задержка в 2 секунды для демонстрации splash await Future.delayed(const Duration(seconds: 2)); if (!mounted) return; // 2. Пытаемся выполнить автологин final authProvider = context.read(); final isLoggedIn = await authProvider.tryAutoLogin(); if (!mounted) return; bool connected = false; int connectAttempt = 0; // 3. Навигация в зависимости от результата и статуса аккаунта if (isLoggedIn) { while (!connected) { try { await authProvider.initRealtime(); connected = true; } catch (e) { connectAttempt++; if (e.toString().contains('timeout')) { setState(() { connectError = 'Превышено время ожидания.\n Проверьте интернет соеденение.\n Попытка соеденения: $connectAttempt'; }); } else if (e.toString().contains('Failed host lookup')) { setState(() { connectError = 'Сервер недоступен. Проверьте интернет соеденение.\n Попытка соеденения: $connectAttempt'; }); } else { setState(() { connectError = e.toString().replaceAll('Exception: ', ''); }); } await Future.delayed(Duration(seconds: 2)); } } await authProvider.refreshMe(); // Определяем путь пользователя if (authProvider.needsSetup) { // Путь А: Первичная настройка Navigator.pushReplacement( context, MaterialPageRoute(builder: (_) => const AccountSetupScreen()), ); } else if (authProvider.needsKeyRecovery) { // Путь В: Восстановление ключей Navigator.pushReplacement( context, MaterialPageRoute(builder: (_) => const KeyRecoveryScreen()), ); } else { // Путь Б: Нормальный вход в контакты // Проверяем, было ли приложение запущено из уведомления int? targetChatId = _targetChatId; // Сначала проверяем из onMessageOpenedApp if (targetChatId == null) { final prefs = await SharedPreferences.getInstance(); final savedData = prefs.getString(_notificationLaunchKey); try { final contactProvider = context.read(); contactProvider.setCurrentUserId(authProvider.currentUserId); await contactProvider.loadContacts(enrichContacts: false); final myPrivKeyBase64 = await context .read() .getPrivateKey(); if (myPrivKeyBase64 != null) { final Map keysToCompute = {}; for (var c in contactProvider.contacts) { final savedKeyHex = prefs.getString( '$_contactSharedKey${c.id}', ); final savedPubKey = prefs.getString( '$_contactPublicKey${c.id}', ); if (savedKeyHex != null && savedPubKey == c.publicKey) { final bytes = base64Decode(savedKeyHex); contactProvider.setSharedKey(c.id, SecretKey(bytes)); } else if (c.publicKey != null) { keysToCompute[c.id] = c.publicKey!; } } print( 'Contacts with keys for isolate: ${keysToCompute.keys.toList()}', ); final String privKey = myPrivKeyBase64; final computedKeys = await compute( CryptoService.computeSharedKeysTask, {'keysMap': keysToCompute, 'privKey': privKey}, ); computedKeys.forEach((id, bytes) { contactProvider.setSharedKey(id, SecretKey(bytes)); prefs.setString('$_contactSharedKey$id', base64Encode(bytes)); prefs.setString('$_contactPublicKey$id', keysToCompute[id]!); }); } } catch (e) { print("Ошибка при загрузке контактов или вычислении ключей: $e"); } // Если не установлено, проверяем SharedPreferences if (savedData != null) { try { final data = jsonDecode(savedData) as Map; print('Found saved notification data: $data'); final senderId = int.tryParse( data['sender_id']?.toString() ?? '', ); final type = data['type']?.toString(); // Поддерживаем старый payload (только sender_id) и новый (type+sender_id) if (senderId != null && (type == null || type == 'enc_message')) { targetChatId = senderId; print( 'App launched from saved notification, target chat: $targetChatId', ); } // Очищаем сохраненные данные после использования await prefs.remove(_notificationLaunchKey); } catch (e) { print('Error parsing saved notification data: $e'); await prefs.remove(_notificationLaunchKey); } } // Также проверяем initialMessage как fallback if (targetChatId == null) { print('Checking initialMessage: $initialMessage'); if (initialMessage != null) { print('Initial message data: ${initialMessage!.data}'); if (initialMessage!.data['type'] == 'enc_message') { targetChatId = int.tryParse( initialMessage!.data['sender_id']?.toString() ?? '', ); print('Set target chat from initialMessage: $targetChatId'); } else { print( 'Initial message type is not enc_message: ${initialMessage!.data['type']}', ); } } else { print('No initial message found'); } } } else { print('Using targetChatId from onMessageOpenedApp: $targetChatId'); } if (targetChatId != null) { print( 'Notification targetChatId resolved: $targetChatId, trying to open chat directly', ); try { final contactProvider = context.read(); contactProvider.setCurrentUserId(authProvider.currentUserId); await contactProvider.loadContacts(enrichContacts: false); final contact = contactProvider.contacts.firstWhere( (c) => c.id == targetChatId, ); currentActiveChatContactId = targetChatId; print( 'Directly navigating to ChatScreen for contact: ${contact.username}', ); final prefs = await SharedPreferences.getInstance(); await prefs.remove(_notificationLaunchKey); Navigator.pushReplacement( context, MaterialPageRoute(builder: (_) => ChatScreen(contact: contact)), ); return; } catch (e) { print( 'Failed to open chat directly, falling back to ContactsScreen: $e', ); } } print('Navigating to ContactsScreen, targetChatId: $targetChatId'); final prefs = await SharedPreferences.getInstance(); await prefs.remove(_notificationLaunchKey); Navigator.pushReplacement( context, MaterialPageRoute( builder: (_) => ContactsScreen(targetChatId: targetChatId), ), ); } } else { // Нет токена - переходим на экран входа Navigator.pushReplacement( context, MaterialPageRoute(builder: (_) => const LoginScreen()), ); } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Theme.of(context).colorScheme.surface, body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Spacer(), Icon( Icons.messenger_outline, size: 80, color: Theme.of(context).colorScheme.primary, ), const SizedBox(height: 24), Text( "Chepuhagram", style: TextStyle( color: Theme.of(context).colorScheme.primary, fontSize: 32, fontWeight: FontWeight.bold, letterSpacing: 1.2, ), ), const SizedBox(height: 40), // Мягкий индикатор загрузки снизу CircularProgressIndicator( color: Theme.of(context).colorScheme.primary, ), const SizedBox(height: 40), Text( connectError ?? '', style: TextStyle( color: Theme.of(context).colorScheme.error, fontSize: 14, ), textAlign: TextAlign.center, ), const Spacer(), Text( 'Made by ArturKarasevich', style: TextStyle( color: Theme.of(context).colorScheme.primary, fontSize: 12, ), ), const SizedBox(height: 80), ], ), ), ); } }