Chepuhagram/lib/logic/contact_provider.dart

295 lines
9.8 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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");
}
}
}