295 lines
9.8 KiB
Dart
295 lines
9.8 KiB
Dart
import 'package:flutter/material.dart';
|
||
import '/data/models/contact_model.dart';
|
||
import '/data/repositories/contact_repository.dart';
|
||
import '/domain/services/crypto_service.dart';
|
||
import 'dart:isolate';
|
||
import 'package:cryptography/cryptography.dart';
|
||
import 'package:flutter/foundation.dart';
|
||
import 'package:chepuhagram/data/models/message_model.dart';
|
||
|
||
class ContactProvider extends ChangeNotifier {
|
||
final ContactRepository _repository = ContactRepository();
|
||
final CryptoService _cryptoService;
|
||
|
||
ContactProvider(this._cryptoService);
|
||
final Map<int, SecretKey> _sharedKeysCache = {};
|
||
List<Contact> _contacts = [];
|
||
List<Contact> _allContacts = [];
|
||
bool _isLoading = false;
|
||
bool _isFirstLoad = true;
|
||
String? _error;
|
||
int? _currentUserId;
|
||
|
||
List<Contact> get contacts => _contacts;
|
||
List<Contact> get allContacts => _allContacts;
|
||
bool get isLoading => _isLoading;
|
||
String? get error => _error;
|
||
Map<int, SecretKey> get sharedKeysCache => _sharedKeysCache;
|
||
|
||
void setSharedKey(int contactId, SecretKey key) {
|
||
_sharedKeysCache[contactId] = key;
|
||
}
|
||
|
||
void setCurrentUserId(int? id) {
|
||
_currentUserId = id;
|
||
notifyListeners();
|
||
}
|
||
|
||
int? getCurrentUserId() {
|
||
return _currentUserId;
|
||
}
|
||
|
||
Future<void> loadContacts({bool enrichContacts = true}) async {
|
||
if (_isFirstLoad) {
|
||
_isFirstLoad = false;
|
||
_isLoading = true;
|
||
}
|
||
_error = null;
|
||
notifyListeners();
|
||
|
||
try {
|
||
final allContacts = await _repository.fetchChatContacts();
|
||
final userIdCopy = _currentUserId;
|
||
_contacts = await Isolate.run(() {
|
||
return allContacts
|
||
.where((contact) => contact.id != userIdCopy)
|
||
.toList();
|
||
});
|
||
// Check if user changed during isolate execution
|
||
if (userIdCopy != _currentUserId) {
|
||
return; // Discard stale data
|
||
}
|
||
_allContacts = _contacts;
|
||
_isLoading = false;
|
||
notifyListeners();
|
||
|
||
if (enrichContacts) {
|
||
await _enrichContactsWithLastMessages();
|
||
}
|
||
} catch (e) {
|
||
_error = e.toString();
|
||
print("❌ ОШИБКА ПРИ ЗАГРУЗКЕ КОНТАКТОВ: $_error");
|
||
} finally {
|
||
if (_error != null) {
|
||
_isLoading = false;
|
||
}
|
||
notifyListeners();
|
||
}
|
||
}
|
||
|
||
Future<void> loadAllContactsForNewChat() async {
|
||
_isLoading = true;
|
||
_error = null;
|
||
notifyListeners();
|
||
|
||
try {
|
||
final allContacts = await _repository.fetchAllUsers();
|
||
_allContacts = allContacts
|
||
.where((contact) => contact.id != _currentUserId)
|
||
.toList();
|
||
} catch (e) {
|
||
_error = e.toString();
|
||
} finally {
|
||
_isLoading = false;
|
||
notifyListeners();
|
||
}
|
||
}
|
||
|
||
|
||
String _getMediaPreview(MessageType type) {
|
||
switch (type) {
|
||
case MessageType.videoNote:
|
||
return '[Кружок]';
|
||
case MessageType.voiceNote:
|
||
return '[Голосовое]';
|
||
case MessageType.image:
|
||
return '[Фото]';
|
||
case MessageType.video:
|
||
return '[Видео]';
|
||
case MessageType.file:
|
||
return '[Файл]';
|
||
case MessageType.text:
|
||
default:
|
||
return '';
|
||
}
|
||
}
|
||
|
||
Future<void> _enrichContactsWithLastMessages() async {
|
||
final myPrivKeyBase64 = await _cryptoService.getPrivateKey();
|
||
if (myPrivKeyBase64 == null) return;
|
||
|
||
// Создаем локальные копии для передачи
|
||
final contactsToProcess = List<Contact>.from(_contacts);
|
||
final cacheCopy = Map<int, SecretKey>.from(_sharedKeysCache);
|
||
|
||
print('Avialable cache for contacts: ${cacheCopy.length}');
|
||
|
||
try {
|
||
final updatedContacts = await compute(CryptoService.bulkDecryptContacts, {
|
||
'contacts': contactsToProcess,
|
||
'privKey': myPrivKeyBase64,
|
||
'cache': cacheCopy,
|
||
});
|
||
for (var contact in updatedContacts) {
|
||
print(
|
||
'Decrypted contact: ${contact.name} ${contact.surname}, lastMessage: ${contact.lastMessage}, isDecrypted: ${contact.isLastMsgDecrypted}',
|
||
);
|
||
}
|
||
_contacts = updatedContacts;
|
||
notifyListeners();
|
||
} catch (e) {
|
||
print("Ошибка дешифровки: $e");
|
||
}
|
||
}
|
||
|
||
Future<void> updateContact(
|
||
int userId, {
|
||
String? lastMessage,
|
||
DateTime? lastMessageTime,
|
||
bool? isLastMsgDecrypted,
|
||
int? unreadCount,
|
||
}) async {
|
||
try {
|
||
final updatedContact = await _repository.fetchContactById(userId);
|
||
final index = _contacts.indexWhere((c) => c.id == userId);
|
||
if (index != -1) {
|
||
final existing = _contacts[index];
|
||
_contacts[index] = existing.copyWith(
|
||
username: updatedContact.username,
|
||
name: updatedContact.name,
|
||
surname: updatedContact.surname,
|
||
avatarUrl: updatedContact.avatarUrl,
|
||
avatarFileId: updatedContact.avatarFileId,
|
||
isOnline: updatedContact.isOnline,
|
||
publicKey: updatedContact.publicKey,
|
||
lastMessage: lastMessage ?? existing.lastMessage,
|
||
lastMessageTime: lastMessageTime ?? existing.lastMessageTime,
|
||
isLastMsgDecrypted: isLastMsgDecrypted ?? existing.isLastMsgDecrypted,
|
||
unreadCount: unreadCount ?? existing.unreadCount,
|
||
lastMessageId: updatedContact.lastMessageId,
|
||
);
|
||
print(
|
||
"Контакт ${updatedContact.name} ${updatedContact.surname} ${updatedContact.id} ${updatedContact.avatarFileId} ${updatedContact.avatarUrl} обновлен",
|
||
);
|
||
notifyListeners();
|
||
}
|
||
} catch (e) {
|
||
print("Error updating contact: $e");
|
||
}
|
||
}
|
||
|
||
Future<void> updateContactOnlineStatus(int userId, bool isOnline) async {
|
||
try {
|
||
final index = _contacts.indexWhere((c) => c.id == userId);
|
||
if (index != -1) {
|
||
final existing = _contacts[index];
|
||
_contacts[index] = existing.copyWith(
|
||
isOnline: isOnline,
|
||
username: existing.username,
|
||
name: existing.name,
|
||
surname: existing.surname,
|
||
avatarUrl: existing.avatarUrl,
|
||
avatarFileId: existing.avatarFileId,
|
||
publicKey: existing.publicKey,
|
||
);
|
||
print("Контакт ${existing.name} ${existing.surname} онлайн обновлен");
|
||
notifyListeners();
|
||
}
|
||
} catch (e) {
|
||
print("Error updating contact: $e");
|
||
}
|
||
}
|
||
|
||
Future<void> updateContactLastMessage(int contactId, {String? lastMessage, DateTime? lastMessageTime, bool? isLastMsgDecrypted, int? lastMessageId, bool isEdited = false}) async {
|
||
try {
|
||
final index = _contacts.indexWhere((c) => c.id == contactId);
|
||
if (index != -1) {
|
||
final existing = _contacts[index];
|
||
String displayMessage;
|
||
if (isEdited) {
|
||
final baseMessage = lastMessage ?? existing.lastMessage;
|
||
final rawMessage = baseMessage != null && baseMessage.isNotEmpty
|
||
? baseMessage
|
||
: 'Сообщение изменено';
|
||
displayMessage = rawMessage.endsWith('(изменено)')
|
||
? rawMessage
|
||
: '$rawMessage (изменено)';
|
||
} else {
|
||
displayMessage = lastMessage ?? existing.lastMessage ?? '';
|
||
}
|
||
|
||
_contacts[index] = existing.copyWith(
|
||
lastMessage: displayMessage.isNotEmpty ? displayMessage : null,
|
||
lastMessageTime: lastMessageTime,
|
||
isLastMsgDecrypted: isLastMsgDecrypted ?? existing.isLastMsgDecrypted,
|
||
lastMessageId: lastMessageId,
|
||
);
|
||
print("Последнее сообщение контакта ${existing.name} обновлено: $displayMessage");
|
||
notifyListeners();
|
||
}
|
||
} catch (e) {
|
||
print("Error updating contact last message: $e");
|
||
}
|
||
}
|
||
|
||
Future<void> refreshContactLastMessage(int contactId) async {
|
||
try {
|
||
// Получить предпоследнее сообщение из базы данных
|
||
final lastMessages = await _repository.getLastMessagesForContact(contactId, limit: 2);
|
||
if (lastMessages.isNotEmpty) {
|
||
final lastMsg = lastMessages.first;
|
||
final contact = _contacts.firstWhere((c) => c.id == contactId);
|
||
final messageId = int.tryParse(lastMsg['id'].toString());
|
||
final timestamp = DateTime.tryParse(lastMsg['timestamp']?.toString() ?? '');
|
||
final myPrivKeyBase64 = await _cryptoService.getPrivateKey();
|
||
if (myPrivKeyBase64 != null && contact.publicKey != null) {
|
||
try {
|
||
final sharedSecret = await _cryptoService.deriveSharedSecret(
|
||
myPrivKeyBase64,
|
||
contact.publicKey!,
|
||
);
|
||
final decryptedText = await _cryptoService.decryptMessage(
|
||
lastMsg['content'],
|
||
sharedSecret,
|
||
);
|
||
await updateContactLastMessage(
|
||
contactId,
|
||
lastMessage: decryptedText,
|
||
lastMessageTime: timestamp,
|
||
isLastMsgDecrypted: true,
|
||
lastMessageId: messageId,
|
||
);
|
||
} catch (e) {
|
||
print("Error decrypting last message: $e");
|
||
await updateContactLastMessage(
|
||
contactId,
|
||
lastMessage: lastMsg['content50'] ?? 'Зашифрованное сообщение',
|
||
lastMessageTime: timestamp,
|
||
isLastMsgDecrypted: false,
|
||
lastMessageId: messageId,
|
||
);
|
||
}
|
||
} else {
|
||
await updateContactLastMessage(
|
||
contactId,
|
||
lastMessage: lastMsg['content50'] ?? 'Зашифрованное сообщение',
|
||
lastMessageTime: timestamp,
|
||
isLastMsgDecrypted: false,
|
||
lastMessageId: messageId,
|
||
);
|
||
}
|
||
} else {
|
||
// Нет сообщений
|
||
await updateContactLastMessage(
|
||
contactId,
|
||
lastMessage: null,
|
||
lastMessageTime: null,
|
||
lastMessageId: null,
|
||
);
|
||
}
|
||
} catch (e) {
|
||
print("Error refreshing contact last message: $e");
|
||
}
|
||
}
|
||
}
|