21-06-2026+22-20
This commit is contained in:
parent
ba1ed1032d
commit
4363fdf699
|
|
@ -1,10 +1,6 @@
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:chepuhagram/data/models/message_model.dart';
|
import 'package:chepuhagram/data/models/message_model.dart';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:drift_sqflite/drift_sqflite.dart';
|
import 'package:drift_sqflite/drift_sqflite.dart';
|
||||||
import 'package:path/path.dart' as p;
|
|
||||||
import 'package:path_provider/path_provider.dart';
|
|
||||||
|
|
||||||
part 'local_db_service.g.dart';
|
part 'local_db_service.g.dart';
|
||||||
|
|
||||||
|
|
@ -72,15 +68,6 @@ class LocalDbService extends _$LocalDbService {
|
||||||
Future<void> saveMessages(List<dynamic> messageList) async {
|
Future<void> saveMessages(List<dynamic> messageList) async {
|
||||||
if (messageList.isEmpty) return;
|
if (messageList.isEmpty) return;
|
||||||
|
|
||||||
final List<int> incomingIds = messageList
|
|
||||||
.map<int?>(
|
|
||||||
(msg) => (msg is MessageModel)
|
|
||||||
? msg.id
|
|
||||||
: (msg['id'] == null ? null : int.tryParse(msg['id'].toString())),
|
|
||||||
)
|
|
||||||
.whereType<int>()
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
// Преобразуем входящие данные в компаньоны заранее
|
// Преобразуем входящие данные в компаньоны заранее
|
||||||
final companions = messageList.map<MessagesCompanion>((msg) {
|
final companions = messageList.map<MessagesCompanion>((msg) {
|
||||||
final int? id;
|
final int? id;
|
||||||
|
|
@ -153,21 +140,6 @@ class LocalDbService extends _$LocalDbService {
|
||||||
|
|
||||||
// Выполняем все операции в рамках ОДНОЙ транзакции БД
|
// Выполняем все операции в рамках ОДНОЙ транзакции БД
|
||||||
await transaction(() async {
|
await transaction(() async {
|
||||||
if (incomingIds.isNotEmpty) {
|
|
||||||
// ВНИМАНИЕ: Ограничьте удаление только текущим чатом,
|
|
||||||
// иначе эта строка очистит сообщения из всех остальных диалогов!
|
|
||||||
final first = companions.first;
|
|
||||||
await (delete(messages)..where(
|
|
||||||
(tbl) =>
|
|
||||||
((tbl.senderId.equals(first.senderId.value) &
|
|
||||||
tbl.receiverId.equals(first.receiverId.value)) |
|
|
||||||
(tbl.senderId.equals(first.receiverId.value) &
|
|
||||||
tbl.receiverId.equals(first.senderId.value))) &
|
|
||||||
tbl.id.isNotIn(incomingIds),
|
|
||||||
))
|
|
||||||
.go();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Быстрая пакетная вставка/обновление
|
// Быстрая пакетная вставка/обновление
|
||||||
await batch((b) {
|
await batch((b) {
|
||||||
b.insertAll(
|
b.insertAll(
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ class ApiService extends ChangeNotifier {
|
||||||
try {
|
try {
|
||||||
// Подставляй свой эндпоинт, например: /users/by-username/
|
// Подставляй свой эндпоинт, например: /users/by-username/
|
||||||
final response = await Dio().get(
|
final response = await Dio().get(
|
||||||
'${AppConstants.baseUrl}//users/by-username/$username',
|
'${AppConstants.baseUrl}/users/by-username/$username',
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.statusCode == 200 && response.data != null) {
|
if (response.statusCode == 200 && response.data != null) {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,10 @@ class CryptoService {
|
||||||
_memoryKeysCache[contactId] = key;
|
_memoryKeysCache[contactId] = key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void clearCache() {
|
||||||
|
_memoryKeysCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
Future<Map<String, String>> initAccountSecurity(String masterPassword) async {
|
Future<Map<String, String>> initAccountSecurity(String masterPassword) async {
|
||||||
// Генерируем пару X25519 ключей
|
// Генерируем пару X25519 ключей
|
||||||
final keyPair = await algorithm.newKeyPair();
|
final keyPair = await algorithm.newKeyPair();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:chepuhagram/data/datasources/local_db_service.dart';
|
import 'package:chepuhagram/data/datasources/local_db_service.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
@ -9,12 +10,11 @@ import 'package:http/http.dart' as http;
|
||||||
import 'package:chepuhagram/domain/services/api_service.dart';
|
import 'package:chepuhagram/domain/services/api_service.dart';
|
||||||
import 'package:chepuhagram/data/datasources/ws_client.dart';
|
import 'package:chepuhagram/data/datasources/ws_client.dart';
|
||||||
import 'package:chepuhagram/domain/services/crypto_service.dart';
|
import 'package:chepuhagram/domain/services/crypto_service.dart';
|
||||||
|
import 'package:chepuhagram/logic/contact_provider.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
import 'package:path_provider/path_provider.dart';
|
|
||||||
import 'package:chepuhagram/data/models/session_model.dart';
|
import 'package:chepuhagram/data/models/session_model.dart';
|
||||||
import 'package:chepuhagram/presentation/screens/splash_screen.dart';
|
import 'package:chepuhagram/presentation/screens/splash_screen.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:chepuhagram/main.dart';
|
import 'package:chepuhagram/main.dart';
|
||||||
|
|
||||||
class AuthProvider extends ChangeNotifier {
|
class AuthProvider extends ChangeNotifier {
|
||||||
|
|
@ -126,6 +126,15 @@ class AuthProvider extends ChangeNotifier {
|
||||||
final mode = await _storage.read(key: 'theme_mode');
|
final mode = await _storage.read(key: 'theme_mode');
|
||||||
final color = await _storage.read(key: 'accent_color');
|
final color = await _storage.read(key: 'accent_color');
|
||||||
await _storage.deleteAll();
|
await _storage.deleteAll();
|
||||||
|
CryptoService.clearCache();
|
||||||
|
final context = navigatorKey.currentContext;
|
||||||
|
if (context != null) {
|
||||||
|
try {
|
||||||
|
Provider.of<ContactProvider>(context, listen: false).clearCache();
|
||||||
|
} catch (e) {
|
||||||
|
print("Error clearing contact provider cache: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
await prefs.clear();
|
await prefs.clear();
|
||||||
await LocalDbService().clearDatabase();
|
await LocalDbService().clearDatabase();
|
||||||
|
|
@ -149,13 +158,24 @@ class AuthProvider extends ChangeNotifier {
|
||||||
// Поскольку у вас Drift открывает SqfliteQueryExecutor (local_db_service.dart),
|
// Поскольку у вас Drift открывает SqfliteQueryExecutor (local_db_service.dart),
|
||||||
// самый надежный способ — физически удалить файл базы данных чепухаграма.
|
// самый надежный способ — физически удалить файл базы данных чепухаграма.
|
||||||
try {
|
try {
|
||||||
final docDir =
|
final dbFolder = await databaseFactory.getDatabasesPath();
|
||||||
await getApplicationSupportDirectory(); // Или папка, где лежит БД у вас в local_db_service.dart
|
final dbFile = File(p.join(dbFolder, 'chat_app.db'));
|
||||||
final dbFile = File(p.join(docDir.path, 'chepuhagram.db'));
|
|
||||||
if (await dbFile.exists()) {
|
if (await dbFile.exists()) {
|
||||||
await dbFile.delete();
|
await dbFile.delete();
|
||||||
print("БАЗА ДАННЫХ УСПЕШНО УНИЧТОЖЕНА В ЦЕЛЯХ БЕЗОПАСНОСТИ");
|
print("БАЗА ДАННЫХ УСПЕШНО УНИЧТОЖЕНА В ЦЕЛЯХ БЕЗОПАСНОСТИ");
|
||||||
}
|
}
|
||||||
|
final journalFile = File(p.join(dbFolder, 'chat_app.db-journal'));
|
||||||
|
if (await journalFile.exists()) {
|
||||||
|
await journalFile.delete();
|
||||||
|
}
|
||||||
|
final walFile = File(p.join(dbFolder, 'chat_app.db-wal'));
|
||||||
|
if (await walFile.exists()) {
|
||||||
|
await walFile.delete();
|
||||||
|
}
|
||||||
|
final shmFile = File(p.join(dbFolder, 'chat_app.db-shm'));
|
||||||
|
if (await shmFile.exists()) {
|
||||||
|
await shmFile.delete();
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("Ошибка удаления файла БД: $e");
|
print("Ошибка удаления файла БД: $e");
|
||||||
}
|
}
|
||||||
|
|
@ -470,6 +490,23 @@ class AuthProvider extends ChangeNotifier {
|
||||||
// Метод для начала с чистого листа (новые ключи)
|
// Метод для начала с чистого листа (новые ключи)
|
||||||
Future<void> resetKeys() async {
|
Future<void> resetKeys() async {
|
||||||
await _storage.delete(key: 'private_key');
|
await _storage.delete(key: 'private_key');
|
||||||
|
try {
|
||||||
|
final allKeys = await _storage.readAll();
|
||||||
|
for (final key in allKeys.keys) {
|
||||||
|
if (key.startsWith('contact_shared_key_') || key.startsWith('contact_public_key_')) {
|
||||||
|
await _storage.delete(key: key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("Error clearing cached contact keys in secure storage: $e");
|
||||||
|
}
|
||||||
|
CryptoService.clearCache();
|
||||||
|
final context = navigatorKey.currentContext;
|
||||||
|
if (context != null) {
|
||||||
|
try {
|
||||||
|
Provider.of<ContactProvider>(context, listen: false).clearCache();
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
_needsKeyRecovery = false;
|
_needsKeyRecovery = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,12 @@ class ContactProvider extends ChangeNotifier {
|
||||||
|
|
||||||
void setSharedKey(int contactId, SecretKey key) {
|
void setSharedKey(int contactId, SecretKey key) {
|
||||||
_sharedKeysCache[contactId] = key;
|
_sharedKeysCache[contactId] = key;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearCache() {
|
||||||
|
_sharedKeysCache.clear();
|
||||||
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setCurrentUserId(int? id) {
|
void setCurrentUserId(int? id) {
|
||||||
|
|
@ -184,6 +190,20 @@ class ContactProvider extends ChangeNotifier {
|
||||||
);
|
);
|
||||||
_sortContacts();
|
_sortContacts();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
} else {
|
||||||
|
final newContact = updatedContact.copyWith(
|
||||||
|
lastMessage: lastMessage,
|
||||||
|
lastMessageTime: lastMessageTime,
|
||||||
|
isLastMsgDecrypted: isLastMsgDecrypted ?? false,
|
||||||
|
unreadCount: unreadCount ?? 0,
|
||||||
|
firstUnreadMessageId: firstUnreadMessageId,
|
||||||
|
);
|
||||||
|
_contacts.add(newContact);
|
||||||
|
print(
|
||||||
|
"Новый контакт ${newContact.name} ${newContact.surname} добавлен в список чатов",
|
||||||
|
);
|
||||||
|
_sortContacts();
|
||||||
|
notifyListeners();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("Error updating contact: $e");
|
print("Error updating contact: $e");
|
||||||
|
|
@ -248,6 +268,13 @@ class ContactProvider extends ChangeNotifier {
|
||||||
);
|
);
|
||||||
_sortContacts();
|
_sortContacts();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
} else {
|
||||||
|
await updateContact(
|
||||||
|
contactId,
|
||||||
|
lastMessage: lastMessage,
|
||||||
|
lastMessageTime: lastMessageTime,
|
||||||
|
isLastMsgDecrypted: isLastMsgDecrypted,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("Error updating contact last message: $e");
|
print("Error updating contact last message: $e");
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
@ -533,10 +533,12 @@ class _ChatScreenState extends State<ChatScreen> with RouteAware {
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_isOnline = data['online'] ?? false;
|
_isOnline = data['online'] ?? false;
|
||||||
if (data['last_online'] != null)
|
if (data['last_online'] != null) {
|
||||||
_lastOnline = DateTime.parse(data['last_online']).add(offset);
|
final parsed = DateTime.tryParse(data['last_online']);
|
||||||
else
|
_lastOnline = parsed != null ? parsed.add(offset) : null;
|
||||||
|
} else {
|
||||||
_lastOnline = null;
|
_lastOnline = null;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(e);
|
print(e);
|
||||||
|
|
@ -1583,6 +1585,10 @@ class _ChatScreenState extends State<ChatScreen> with RouteAware {
|
||||||
context,
|
context,
|
||||||
listen: false,
|
listen: false,
|
||||||
).sendMessage({'type': 'delete_message', 'message_id': id});
|
).sendMessage({'type': 'delete_message', 'message_id': id});
|
||||||
|
} else if (msg.tempId != null) {
|
||||||
|
try {
|
||||||
|
await _localDbService.deleteMessage(msg.tempId!);
|
||||||
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3017,9 +3023,10 @@ class _ChatScreenState extends State<ChatScreen> with RouteAware {
|
||||||
replyToId: data['reply_to_id'] != null
|
replyToId: data['reply_to_id'] != null
|
||||||
? int.tryParse(data['reply_to_id'].toString())
|
? int.tryParse(data['reply_to_id'].toString())
|
||||||
: null,
|
: null,
|
||||||
replyToText: decryptedReplyToText ?? null,
|
replyToText: decryptedReplyToText,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
messages.sort((a, b) => a.createdAt.compareTo(b.createdAt));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
@ -3496,7 +3503,9 @@ class _ChatScreenState extends State<ChatScreen> with RouteAware {
|
||||||
} else {
|
} else {
|
||||||
// ЕСЛИ ВСЁ ПРОЧИТАНО:
|
// ЕСЛИ ВСЁ ПРОЧИТАНО:
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (messages.isNotEmpty && _itemScrollController.isAttached) {
|
||||||
_itemScrollController.jumpTo(index: 0);
|
_itemScrollController.jumpTo(index: 0);
|
||||||
|
}
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
final contactProvider = context.read<ContactProvider>();
|
final contactProvider = context.read<ContactProvider>();
|
||||||
contactProvider.updateContact(
|
contactProvider.updateContact(
|
||||||
|
|
@ -3721,7 +3730,9 @@ class _ChatScreenState extends State<ChatScreen> with RouteAware {
|
||||||
// Если пользователь не был в самом низу, удерживаем его текущий видимый индекс
|
// Если пользователь не был в самом низу, удерживаем его текущий видимый индекс
|
||||||
if (firstVisibleIndex != null && firstVisibleIndex > 0) {
|
if (firstVisibleIndex != null && firstVisibleIndex > 0) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (_itemScrollController.isAttached) {
|
||||||
_itemScrollController.jumpTo(index: firstVisibleIndex, alignment: 0);
|
_itemScrollController.jumpTo(index: firstVisibleIndex, alignment: 0);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -4284,11 +4295,12 @@ class _ChatScreenState extends State<ChatScreen> with RouteAware {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _scrollToBottom() async {
|
Future<void> _scrollToBottom() async {
|
||||||
|
if (messages.isEmpty || !_itemScrollController.isAttached) return;
|
||||||
_itemScrollController.jumpTo(index: 0, alignment: 0);
|
_itemScrollController.jumpTo(index: 0, alignment: 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _scrollToMessage(int? messageId) async {
|
Future<void> _scrollToMessage(int? messageId) async {
|
||||||
if (messageId == null) return;
|
if (messageId == null || !_itemScrollController.isAttached) return;
|
||||||
|
|
||||||
// 1. Ищем индекс в массиве
|
// 1. Ищем индекс в массиве
|
||||||
final int msgIndex = messages.indexWhere((m) => m.id == messageId);
|
final int msgIndex = messages.indexWhere((m) => m.id == messageId);
|
||||||
|
|
@ -4448,7 +4460,7 @@ class _ChatScreenState extends State<ChatScreen> with RouteAware {
|
||||||
await Future.delayed(const Duration(milliseconds: 2000));
|
await Future.delayed(const Duration(milliseconds: 2000));
|
||||||
|
|
||||||
final index = messages.indexWhere((m) => m.id == anchorId);
|
final index = messages.indexWhere((m) => m.id == anchorId);
|
||||||
if (index != -1) {
|
if (index != -1 && _itemScrollController.isAttached) {
|
||||||
_itemScrollController.jumpTo(index: index, alignment: 0.5);
|
_itemScrollController.jumpTo(index: index, alignment: 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1066,7 +1066,6 @@ class _ContactsScreenState extends State<ContactsScreen> with RouteAware {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
// На Windows кнопка всегда предлагает "Обновить"
|
|
||||||
_isDownloading ? "Отмена" : "Обновить",
|
_isDownloading ? "Отмена" : "Обновить",
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
|
|
@ -1584,10 +1583,17 @@ class _ContactsScreenState extends State<ContactsScreen> with RouteAware {
|
||||||
|
|
||||||
// Обрабатываем сообщение ТОЛЬКО если оно от другого чата
|
// Обрабатываем сообщение ТОЛЬКО если оно от другого чата
|
||||||
if (senderId != null && senderId != currentActiveChatContactId) {
|
if (senderId != null && senderId != currentActiveChatContactId) {
|
||||||
final contact = contactProvider.contacts
|
var contact = contactProvider.contacts
|
||||||
.where((c) => c.id == senderId)
|
.where((c) => c.id == senderId)
|
||||||
.firstOrNull;
|
.firstOrNull;
|
||||||
|
|
||||||
|
if (contact == null) {
|
||||||
|
await contactProvider.updateContact(senderId);
|
||||||
|
contact = contactProvider.contacts
|
||||||
|
.where((c) => c.id == senderId)
|
||||||
|
.firstOrNull;
|
||||||
|
}
|
||||||
|
|
||||||
if (contact != null) {
|
if (contact != null) {
|
||||||
final currentUnread = contact.unreadCount;
|
final currentUnread = contact.unreadCount;
|
||||||
final msgId = int.tryParse(data['id']?.toString() ?? '');
|
final msgId = int.tryParse(data['id']?.toString() ?? '');
|
||||||
|
|
@ -1742,10 +1748,17 @@ class _ContactsScreenState extends State<ContactsScreen> with RouteAware {
|
||||||
if (data['type'] == 'message_sent') {
|
if (data['type'] == 'message_sent') {
|
||||||
final receiverId = int.tryParse(data['receiver_id']?.toString() ?? '');
|
final receiverId = int.tryParse(data['receiver_id']?.toString() ?? '');
|
||||||
if (receiverId != null) {
|
if (receiverId != null) {
|
||||||
final contact = contactProvider.contacts
|
var contact = contactProvider.contacts
|
||||||
.where((c) => c.id == receiverId)
|
.where((c) => c.id == receiverId)
|
||||||
.firstOrNull;
|
.firstOrNull;
|
||||||
|
|
||||||
|
if (contact == null) {
|
||||||
|
await contactProvider.updateContact(receiverId);
|
||||||
|
contact = contactProvider.contacts
|
||||||
|
.where((c) => c.id == receiverId)
|
||||||
|
.firstOrNull;
|
||||||
|
}
|
||||||
|
|
||||||
if (contact != null) {
|
if (contact != null) {
|
||||||
final messageType = MessageModel.parseMessageType(
|
final messageType = MessageModel.parseMessageType(
|
||||||
data['message_type']?.toString() ?? 'text',
|
data['message_type']?.toString() ?? 'text',
|
||||||
|
|
@ -1853,9 +1866,15 @@ class _ContactsScreenState extends State<ContactsScreen> with RouteAware {
|
||||||
final messageId = int.tryParse(data['message_id']?.toString() ?? '');
|
final messageId = int.tryParse(data['message_id']?.toString() ?? '');
|
||||||
final senderId = int.tryParse(data['sender_id']?.toString() ?? '');
|
final senderId = int.tryParse(data['sender_id']?.toString() ?? '');
|
||||||
if (messageId != null && senderId != null) {
|
if (messageId != null && senderId != null) {
|
||||||
final contact = contactProvider.contacts
|
var contact = contactProvider.contacts
|
||||||
.where((c) => c.id == senderId)
|
.where((c) => c.id == senderId)
|
||||||
.firstOrNull;
|
.firstOrNull;
|
||||||
|
if (contact == null) {
|
||||||
|
await contactProvider.updateContact(senderId);
|
||||||
|
contact = contactProvider.contacts
|
||||||
|
.where((c) => c.id == senderId)
|
||||||
|
.firstOrNull;
|
||||||
|
}
|
||||||
if (contact != null) {
|
if (contact != null) {
|
||||||
final editedAt = DateTime.tryParse(
|
final editedAt = DateTime.tryParse(
|
||||||
data['edited_at']?.toString() ?? '',
|
data['edited_at']?.toString() ?? '',
|
||||||
|
|
|
||||||
|
|
@ -151,13 +151,17 @@ class _SplashScreenState extends State<SplashScreen>
|
||||||
final myPrivKeyBase64 = await cryptoService.getPrivateKey();
|
final myPrivKeyBase64 = await cryptoService.getPrivateKey();
|
||||||
|
|
||||||
if (myPrivKeyBase64 != null) {
|
if (myPrivKeyBase64 != null) {
|
||||||
|
final String privKeyFingerprint = myPrivKeyBase64.replaceAll(RegExp(r'[^a-zA-Z0-9]'), '');
|
||||||
|
final String fingerprint = privKeyFingerprint.substring(
|
||||||
|
0, privKeyFingerprint.length > 16 ? 16 : privKeyFingerprint.length);
|
||||||
|
|
||||||
// Проходим по каждому контакту строго ПО ОЧЕРЕДИ
|
// Проходим по каждому контакту строго ПО ОЧЕРЕДИ
|
||||||
for (var c in contactProvider.contacts) {
|
for (var c in contactProvider.contacts) {
|
||||||
final savedKeyHex = await storage.read(
|
final savedKeyHex = await storage.read(
|
||||||
key: '$_contactSharedKey${c.id}',
|
key: '${_contactSharedKey}${fingerprint}_${c.id}',
|
||||||
);
|
);
|
||||||
final savedPubKey = await storage.read(
|
final savedPubKey = await storage.read(
|
||||||
key: '$_contactPublicKey${c.id}',
|
key: '${_contactPublicKey}${fingerprint}_${c.id}',
|
||||||
);
|
);
|
||||||
|
|
||||||
if (savedKeyHex != null && savedPubKey == c.publicKey) {
|
if (savedKeyHex != null && savedPubKey == c.publicKey) {
|
||||||
|
|
@ -184,11 +188,11 @@ class _SplashScreenState extends State<SplashScreen>
|
||||||
// Опускаем await для записи на диск, чтобы медленная файловая система не тормозила расчет следующего ключа.
|
// Опускаем await для записи на диск, чтобы медленная файловая система не тормозила расчет следующего ключа.
|
||||||
secretKey.extractBytes().then((bytes) {
|
secretKey.extractBytes().then((bytes) {
|
||||||
storage.write(
|
storage.write(
|
||||||
key: '$_contactSharedKey${c.id}',
|
key: '${_contactSharedKey}${fingerprint}_${c.id}',
|
||||||
value: base64Encode(bytes),
|
value: base64Encode(bytes),
|
||||||
);
|
);
|
||||||
storage.write(
|
storage.write(
|
||||||
key: '$_contactPublicKey${c.id}',
|
key: '${_contactPublicKey}${fingerprint}_${c.id}',
|
||||||
value: c.publicKey!,
|
value: c.publicKey!,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -757,8 +757,6 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: widget.message.messageType == MessageType.videoNote
|
color: widget.message.messageType == MessageType.videoNote
|
||||||
? Colors.transparent
|
? Colors.transparent
|
||||||
: (isUnread && kDebugMode)
|
|
||||||
? Colors.red
|
|
||||||
: (isMe
|
: (isMe
|
||||||
? Theme.of(context).colorScheme.brightness ==
|
? Theme.of(context).colorScheme.brightness ==
|
||||||
Brightness.dark
|
Brightness.dark
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,9 @@ def _build_user_search_filter(query: str):
|
||||||
func.lower(models.User.username).like(normalized),
|
func.lower(models.User.username).like(normalized),
|
||||||
func.lower(models.User.first_name).like(normalized),
|
func.lower(models.User.first_name).like(normalized),
|
||||||
func.lower(models.User.last_name).like(normalized),
|
func.lower(models.User.last_name).like(normalized),
|
||||||
|
models.User.phone.like(normalized),
|
||||||
|
func.lower(models.User.first_name + " " + func.coalesce(models.User.last_name, '')).like(normalized),
|
||||||
|
func.lower(func.coalesce(models.User.last_name, '') + " " + models.User.first_name).like(normalized),
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _delete_old_avatar_file(file_id: str, db: AsyncSession):
|
async def _delete_old_avatar_file(file_id: str, db: AsyncSession):
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue