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 'dart:convert'; 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'; class ChatScreen extends StatefulWidget { final Contact contact; const ChatScreen({super.key, required this.contact}); @override State createState() => _ChatScreenState(); } class _ChatScreenState extends State { 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 = []; @override void initState() { super.initState(); _currentContact = widget.contact; final contactProvider = context.read(); myId = contactProvider.getCurrentUserId() ?? 0; _loadHistory(); // Если ключа нет, загружаем его при входе if (_currentContact.publicKey == null) { _loadContactKey(); } } Future _loadContactKey() async { setState(() => _isKeyLoading = true); try { final updatedContact = await _contactRepository.fetchContactById( _currentContact.id, ); setState(() { _currentContact = updatedContact; _isKeyLoading = false; }); print(updatedContact.publicKey); } catch (e) { setState(() => _isKeyLoading = false); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text("Не удалось получить ключ шифрования собеседника"), ), ); } } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(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, ); // Формируем payload для сервера final payload = { "type": "private_message", "receiver_id": _currentContact.id, "content": encryptedText, }; // Отправляем 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"))); } } @override void didChangeDependencies() { super.didChangeDependencies(); // Подписываемся на поток сообщений из сокета final socketService = Provider.of(context, listen: false); socketService.messages.listen((rawData) { _handleIncomingMessage(rawData); }); } 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]); 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 { 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) { setState(() { messages = loadedLocalMessages; _isKeyLoading = false; }); } } catch (e) { print(e); } final history = await apiService.getChatHistory(widget.contact.id); List loadedMessages = []; for (var msg in history) { final decrypted = await _cryptoService.decryptMessage( msg['content'], sharedSecret, ); loadedMessages.add( MessageModel( text: decrypted, isMe: msg['sender_id'] == myId, senderId: msg['sender_id'], receiverId: msg['receiver_id'], createdAt: DateTime.parse(msg['timestamp']), ), ); } await localDb.saveMessages(history); setState(() { messages = loadedMessages; _isKeyLoading = false; }); } catch (e) { print("Ошибка загрузки истории: $e"); setState(() => _isKeyLoading = false); } } }