import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import '/data/models/message_model.dart'; import '/data/models/contact_model.dart'; import 'package:chepuhagram/presentation/widgets/message_bubble.dart'; import 'package:chepuhagram/data/repositories/contact_repository.dart'; import 'package:chepuhagram/domain/services/crypto_service.dart'; import 'package:chepuhagram/data/datasources/ws_client.dart'; import 'package:provider/provider.dart'; import '/logic/contact_provider.dart'; import '../../domain/services/api_service.dart'; import 'package:chepuhagram/data/datasources/local_db_service.dart'; import 'package:chepuhagram/main.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'contacts_screen.dart'; class ChatScreen extends StatefulWidget { final Contact contact; const ChatScreen({super.key, required this.contact}); @override State createState() => _ChatScreenState(); } class _ChatScreenState extends State { static const String _notificationLaunchKey = 'notification_launch_data'; int myId = 0; late Contact _currentContact; bool _isKeyLoading = false; final TextEditingController _controller = TextEditingController(); final ContactRepository _contactRepository = ContactRepository(); final apiService = ApiService(); final CryptoService _cryptoService = CryptoService(); List messages = []; StreamSubscription? _socketSubscription; @override void initState() { super.initState(); _currentContact = widget.contact; currentActiveChatContactId = _currentContact.id; // Устанавливаем активный чат final contactProvider = context.read(); myId = contactProvider.getCurrentUserId() ?? 0; // Если ключа нет, загружаем его при входе if (_currentContact.publicKey == null) { _loadContactKey(); } _loadHistory(); final socketService = Provider.of(context, listen: false); _socketSubscription = socketService.messages.listen(_handleIncomingMessage); } Future _loadContactKey() async { if (!mounted) return; setState(() => _isKeyLoading = true); try { final updatedContact = await _contactRepository.fetchContactById( _currentContact.id, ); if (!mounted) return; setState(() { _currentContact = updatedContact; _isKeyLoading = false; }); print(updatedContact.publicKey); } catch (e) { if (!mounted) return; setState(() => _isKeyLoading = false); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text("Не удалось получить ключ шифрования собеседника"), ), ); } } @override void dispose() { currentActiveChatContactId = null; // Сбрасываем активный чат _socketSubscription?.cancel(); _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () { if (Navigator.of(context).canPop()) { Navigator.of(context).pop(); } else { Navigator.of(context).pushReplacement( MaterialPageRoute(builder: (_) => const ContactsScreen()), ); } }, ), title: Text(_currentContact.name), ), body: Column( children: [ Expanded( child: ListView.builder( reverse: true, // Сообщения растут снизу вверх itemCount: messages.length, itemBuilder: (context, index) { final msg = messages[messages.length - 1 - index]; return MessageBubble( message: msg.text, time: msg.createdAt, isMe: msg.isMe, ); }, ), ), _buildMessageInput(), ], ), ); } Widget _buildMessageInput() { return SafeArea( // Добавляем SafeArea здесь child: Padding( padding: const EdgeInsets.all(8.0), child: Row( children: [ Expanded( child: TextField( controller: _controller, decoration: const InputDecoration( hintText: "Напиши сообщение...", ), ), ), IconButton( icon: const Icon(Icons.send), onPressed: () { _sendMessage(); }, ), ], ), ), ); } Future _sendMessage() async { final rawText = _controller.text.trim(); if (rawText.isEmpty) return; _controller.clear(); if (_currentContact.publicKey == null) { await _loadContactKey(); if (_currentContact.publicKey == null) return; } try { final myPrivKey = await _cryptoService.getPrivateKey(); final sharedSecret = await _cryptoService.deriveSharedSecret( myPrivKey!, _currentContact.publicKey!, ); final encryptedText = await _cryptoService.encryptMessage( rawText, sharedSecret, ); final encryptedText50 = await _cryptoService.encryptMessage( rawText.length > 50 ? rawText.substring(0, 50) : rawText, sharedSecret, ); // Формируем payload для сервера final payload = { "type": "private_message", "receiver_id": _currentContact.id, "content": encryptedText, "content50": encryptedText50, }; // Отправляем print("ОТПРАВКА: $payload"); Provider.of(context, listen: false).sendMessage(payload); // Обновляем UI (себе показываем расшифрованный текст) setState(() { messages.add( MessageModel( text: rawText, isMe: true, senderId: myId, receiverId: _currentContact.id, createdAt: DateTime.now(), ), ); }); _controller.clear(); } catch (e) { _controller.text = rawText; ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text("Ошибка шифрования: $e"))); } } void _handleIncomingMessage(Map data) async { if (data['type'] == 'private_message') { final int senderId = int.parse(data['sender_id'].toString()); // 1. Проверяем, что сообщение именно от того, с кем мы сейчас общаемся if (senderId == widget.contact.id) { try { final myPrivKey = await _cryptoService.getPrivateKey(); // 2. Вычисляем общий секрет для расшифровки final sharedSecret = await _cryptoService.deriveSharedSecret( myPrivKey!, widget.contact.publicKey!, ); // 3. Расшифровываем контент final decryptedText = await _cryptoService.decryptMessage( data['content'], sharedSecret, ); // 4. Добавляем в список и обновляем экран await LocalDbService().saveMessages([data]); if (!mounted) return; setState(() { messages.add( MessageModel( text: decryptedText, isMe: false, senderId: senderId, receiverId: myId, createdAt: DateTime.parse(data['timestamp']), ), ); }); } catch (e) { print("Ошибка расшифровки входящего сообщения: $e"); } } else { print( "Сообщение от другого пользователя (ID: $senderId), игнорируем в этом чате", ); // Тут можно добавить логику уведомления для списка чатов } } } Future _loadHistory() async { initialMessage = null; // Сбрасываем данные уведомления при загрузке ключа final prefs = await SharedPreferences.getInstance(); await prefs.remove(_notificationLaunchKey); await prefs.setString(_notificationLaunchKey, ''); // Очищаем данные уведомления при загрузке ключа try { final myPrivKey = await _cryptoService.getPrivateKey(); final sharedSecret = await _cryptoService.deriveSharedSecret( myPrivKey!, widget.contact.publicKey!, ); final localDb = LocalDbService(); final cached = await localDb.getChatHistory(widget.contact.id, myId); try { List loadedLocalMessages = []; for (var msg in cached) { final decrypted = await _cryptoService.decryptMessage( msg['content'], sharedSecret, ); loadedLocalMessages.add( MessageModel( text: decrypted, isMe: msg['sender_id'] == myId, senderId: msg['sender_id'], receiverId: msg['receiver_id'], createdAt: DateTime.parse(msg['timestamp']), ), ); } if (cached.isNotEmpty) { if (!mounted) return; setState(() { messages = loadedLocalMessages; _isKeyLoading = false; }); } } catch (e) { print(e); } final history = await apiService.getChatHistory(widget.contact.id); print(history); List loadedMessages = []; for (var msg in history) { final decrypted = await _cryptoService.decryptMessage( msg['content'], sharedSecret, ); loadedMessages.insert( 0, MessageModel( text: decrypted, isMe: msg['sender_id'] == myId, senderId: msg['sender_id'], receiverId: msg['receiver_id'], createdAt: DateTime.parse(msg['timestamp']), ), ); } try { await localDb.saveMessages(history); } catch (e) { print("Ошибка сохранения истории в локальную базу: $e"); } if (!mounted) return; setState(() { messages = loadedMessages; _isKeyLoading = false; }); } catch (e) { print("Ошибка загрузки истории: $e"); if (!mounted) return; setState(() => _isKeyLoading = false); } } }