Кэширование ID пользователей из чатов (на 24 часа)
This commit is contained in:
@@ -190,7 +190,6 @@ extension ApiServiceAuth on ApiService {
|
||||
authToken = currentAccount.token;
|
||||
userId = currentAccount.userId;
|
||||
|
||||
_messageCache.clear();
|
||||
_messageQueue.clear();
|
||||
_lastChatsPayload = null;
|
||||
_chatsFetchedInThisSession = false;
|
||||
@@ -257,4 +256,3 @@ extension ApiServiceAuth on ApiService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,119 @@
|
||||
part of 'api_service.dart';
|
||||
|
||||
extension ApiServiceChats on ApiService {
|
||||
Future<void> _sendAuthRequestAfterHandshake() async {
|
||||
if (authToken == null) {
|
||||
print("Токен не найден, пропускаем автоматическую авторизацию");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_chatsFetchedInThisSession) {
|
||||
print("Авторизация уже выполнена в этой сессии, пропускаем");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await _ensureCacheServicesInitialized();
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final deviceId =
|
||||
prefs.getString('spoof_deviceid') ?? generateRandomDeviceId();
|
||||
|
||||
if (prefs.getString('spoof_deviceid') == null) {
|
||||
await prefs.setString('spoof_deviceid', deviceId);
|
||||
}
|
||||
|
||||
final payload = {
|
||||
"chatsCount": 100,
|
||||
"chatsSync": 0,
|
||||
"contactsSync": 0,
|
||||
"draftsSync": 0,
|
||||
"interactive": true,
|
||||
"presenceSync": 0,
|
||||
"token": authToken,
|
||||
};
|
||||
|
||||
if (userId != null) {
|
||||
payload["userId"] = userId;
|
||||
}
|
||||
|
||||
print("Автоматически отправляем opcode 19 для авторизации...");
|
||||
final int chatSeq = _sendMessage(19, payload);
|
||||
final chatResponse = await messages.firstWhere(
|
||||
(msg) => msg['seq'] == chatSeq,
|
||||
);
|
||||
|
||||
if (chatResponse['cmd'] == 1) {
|
||||
print("✅ Авторизация (opcode 19) успешна. Сессия ГОТОВА.");
|
||||
_isSessionReady = true;
|
||||
|
||||
_connectionStatusController.add("ready");
|
||||
_updateConnectionState(
|
||||
conn_state.ConnectionState.ready,
|
||||
message: 'Авторизация успешна',
|
||||
);
|
||||
|
||||
final profile = chatResponse['payload']?['profile'];
|
||||
final contactProfile = profile?['contact'];
|
||||
|
||||
if (contactProfile != null && contactProfile['id'] != null) {
|
||||
print(
|
||||
"[_sendAuthRequestAfterHandshake] ✅ Профиль и ID пользователя найдены. ID: ${contactProfile['id']}. ЗАПУСКАЕМ АНАЛИТИКУ.",
|
||||
);
|
||||
_userId = contactProfile['id'];
|
||||
_sessionId = DateTime.now().millisecondsSinceEpoch;
|
||||
_lastActionTime = _sessionId;
|
||||
|
||||
sendNavEvent('COLD_START');
|
||||
|
||||
_sendInitialSetupRequests();
|
||||
}
|
||||
|
||||
if (_onlineCompleter != null && !_onlineCompleter!.isCompleted) {
|
||||
_onlineCompleter!.complete();
|
||||
}
|
||||
|
||||
final chatListJson = chatResponse['payload']?['chats'] ?? [];
|
||||
final contactListJson = chatResponse['payload']?['contacts'] ?? [];
|
||||
final presence = chatResponse['payload']?['presence'];
|
||||
final config = chatResponse['payload']?['config'];
|
||||
|
||||
if (presence != null) {
|
||||
updatePresenceData(presence);
|
||||
}
|
||||
|
||||
if (config != null) {
|
||||
_processServerPrivacyConfig(config);
|
||||
}
|
||||
|
||||
final result = {
|
||||
'chats': chatListJson,
|
||||
'contacts': contactListJson,
|
||||
'profile': profile,
|
||||
'presence': presence,
|
||||
'config': config,
|
||||
};
|
||||
_lastChatsPayload = result;
|
||||
|
||||
final contacts = contactListJson
|
||||
.map((json) => Contact.fromJson(json))
|
||||
.toList();
|
||||
updateContactCache(contacts);
|
||||
_lastChatsAt = DateTime.now();
|
||||
_preloadContactAvatars(contacts);
|
||||
unawaited(
|
||||
_chatCacheService.cacheChats(
|
||||
chatListJson.cast<Map<String, dynamic>>(),
|
||||
),
|
||||
);
|
||||
unawaited(_chatCacheService.cacheContacts(contacts));
|
||||
_chatsFetchedInThisSession = true;
|
||||
}
|
||||
} catch (e) {
|
||||
print("Ошибка при автоматической авторизации: $e");
|
||||
}
|
||||
}
|
||||
|
||||
void createGroup(String name, List<int> participantIds) {
|
||||
final payload = {"name": name, "participantIds": participantIds};
|
||||
_sendMessage(48, payload);
|
||||
@@ -357,12 +470,18 @@ extension ApiServiceChats on ApiService {
|
||||
return result;
|
||||
}
|
||||
|
||||
List<dynamic> contactListJson =
|
||||
chatResponse['payload']?['contacts'] ?? [];
|
||||
|
||||
if (contactListJson.isEmpty) {
|
||||
final contactIds = <int>{};
|
||||
for (var chatJson in chatListJson) {
|
||||
final participants = chatJson['participants'] as Map<String, dynamic>;
|
||||
final participants =
|
||||
chatJson['participants'] as Map<String, dynamic>? ?? {};
|
||||
contactIds.addAll(participants.keys.map((id) => int.parse(id)));
|
||||
}
|
||||
|
||||
if (contactIds.isNotEmpty) {
|
||||
final int contactSeq = _sendMessage(32, {
|
||||
"contactIds": contactIds.toList(),
|
||||
});
|
||||
@@ -370,8 +489,9 @@ extension ApiServiceChats on ApiService {
|
||||
(msg) => msg['seq'] == contactSeq,
|
||||
);
|
||||
|
||||
final List<dynamic> contactListJson =
|
||||
contactResponse['payload']?['contacts'] ?? [];
|
||||
contactListJson = contactResponse['payload']?['contacts'] ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
if (presence != null) {
|
||||
updatePresenceData(presence);
|
||||
|
||||
@@ -363,11 +363,15 @@ extension ApiServiceConnection on ApiService {
|
||||
);
|
||||
_startHealthMonitoring();
|
||||
|
||||
if (_onlineCompleter != null && !_onlineCompleter!.isCompleted) {
|
||||
_onlineCompleter!.complete();
|
||||
}
|
||||
_startPinging();
|
||||
_processMessageQueue();
|
||||
|
||||
if (authToken != null && !_chatsFetchedInThisSession) {
|
||||
print(
|
||||
"Токен найден, автоматически запускаем авторизацию (opcode 19)...",
|
||||
);
|
||||
unawaited(_sendAuthRequestAfterHandshake());
|
||||
}
|
||||
}
|
||||
|
||||
if (decodedMessage is Map && decodedMessage['cmd'] == 3) {
|
||||
@@ -670,8 +674,6 @@ extension ApiServiceConnection on ApiService {
|
||||
_onlineCompleter = Completer<void>();
|
||||
_chatsFetchedInThisSession = false;
|
||||
|
||||
clearAllCaches();
|
||||
|
||||
_currentUrlIndex = 0;
|
||||
|
||||
_reconnectDelaySeconds = (_reconnectDelaySeconds * 2).clamp(1, 30);
|
||||
@@ -721,7 +723,6 @@ extension ApiServiceConnection on ApiService {
|
||||
_currentUrlIndex = 0;
|
||||
_onlineCompleter = Completer<void>();
|
||||
|
||||
clearAllCaches();
|
||||
_messageQueue.clear();
|
||||
_presenceData.clear();
|
||||
|
||||
|
||||
@@ -105,6 +105,20 @@ extension ApiServiceContacts on ApiService {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_isSessionOnline || !_isSessionReady) {
|
||||
print(
|
||||
'ApiService: сессия еще не готова для запроса заблокированных контактов, ждем...',
|
||||
);
|
||||
await waitUntilOnline();
|
||||
|
||||
if (!_isSessionReady) {
|
||||
print(
|
||||
'ApiService: сессия все еще не готова после ожидания, отменяем запрос',
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_isLoadingBlockedContacts = true;
|
||||
print('ApiService: запрашиваем заблокированные контакты');
|
||||
_sendMessage(36, {'status': 'BLOCKED', 'count': 100, 'from': 0});
|
||||
@@ -278,12 +292,23 @@ extension ApiServiceContacts on ApiService {
|
||||
|
||||
Future<List<Contact>> fetchContactsByIds(List<int> contactIds) async {
|
||||
if (contactIds.isEmpty) {
|
||||
print(
|
||||
'⚠️ [fetchContactsByIds] Пустой список contactIds - пропускаем запрос',
|
||||
);
|
||||
return [];
|
||||
}
|
||||
|
||||
print('Запрашиваем данные для ${contactIds.length} контактов...');
|
||||
print(
|
||||
'📡 [fetchContactsByIds] Запрашиваем данные для ${contactIds.length} контактов...',
|
||||
);
|
||||
print(
|
||||
'📡 [fetchContactsByIds] IDs: ${contactIds.take(10).join(', ')}${contactIds.length > 10 ? '...' : ''}',
|
||||
);
|
||||
try {
|
||||
final int contactSeq = _sendMessage(32, {"contactIds": contactIds});
|
||||
print(
|
||||
'📤 [fetchContactsByIds] Отправлен опкод 32 с seq=$contactSeq и ${contactIds.length} ID',
|
||||
);
|
||||
|
||||
final contactResponse = await messages
|
||||
.firstWhere((msg) => msg['seq'] == contactSeq)
|
||||
@@ -291,7 +316,7 @@ extension ApiServiceContacts on ApiService {
|
||||
|
||||
if (contactResponse['cmd'] == 3) {
|
||||
print(
|
||||
"Ошибка при получении контактов по ID: ${contactResponse['payload']}",
|
||||
"❌ [fetchContactsByIds] Ошибка при получении контактов: ${contactResponse['payload']}",
|
||||
);
|
||||
return [];
|
||||
}
|
||||
@@ -302,13 +327,29 @@ extension ApiServiceContacts on ApiService {
|
||||
.map((json) => Contact.fromJson(json))
|
||||
.toList();
|
||||
|
||||
print(
|
||||
'📦 [fetchContactsByIds] Получено ${contacts.length} контактов из ${contactIds.length} запрошенных',
|
||||
);
|
||||
|
||||
if (contacts.length < contactIds.length) {
|
||||
final receivedIds = contacts.map((c) => c.id).toSet();
|
||||
final missingIds = contactIds
|
||||
.where((id) => !receivedIds.contains(id))
|
||||
.toList();
|
||||
print(
|
||||
'⚠️ [fetchContactsByIds] Отсутствуют ${missingIds.length} контактов: ${missingIds.take(5).join(', ')}${missingIds.length > 5 ? '...' : ''}',
|
||||
);
|
||||
}
|
||||
|
||||
for (final contact in contacts) {
|
||||
_contactCache[contact.id] = contact;
|
||||
}
|
||||
print("Получены и закэшированы данные для ${contacts.length} контактов.");
|
||||
print(
|
||||
"✅ [fetchContactsByIds] Закэшированы данные для ${contacts.length} контактов",
|
||||
);
|
||||
return contacts;
|
||||
} catch (e) {
|
||||
print('Исключение при получении контактов по ID: $e');
|
||||
print('❌ [fetchContactsByIds] Исключение при получении контактов: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,6 +164,13 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
if (contacts.isNotEmpty && mounted) {
|
||||
final contact = contacts.first;
|
||||
_contactDetailsCache[contact.id] = contact;
|
||||
|
||||
final allChatContacts = _contactDetailsCache.values.toList();
|
||||
await ChatCacheService().cacheChatContacts(
|
||||
widget.chatId,
|
||||
allChatContacts,
|
||||
);
|
||||
|
||||
setState(() {});
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -173,6 +180,152 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadGroupParticipants() async {
|
||||
try {
|
||||
print(
|
||||
'🔍 [_loadGroupParticipants] Начинаем загрузку участников группы...',
|
||||
);
|
||||
|
||||
final chatData = ApiService.instance.lastChatsPayload;
|
||||
if (chatData == null) {
|
||||
print('❌ [_loadGroupParticipants] chatData == null');
|
||||
return;
|
||||
}
|
||||
|
||||
final chats = chatData['chats'] as List<dynamic>?;
|
||||
if (chats == null) {
|
||||
print('❌ [_loadGroupParticipants] chats == null');
|
||||
return;
|
||||
}
|
||||
|
||||
print(
|
||||
'🔍 [_loadGroupParticipants] Ищем чат с ID ${widget.chatId} среди ${chats.length} чатов...',
|
||||
);
|
||||
|
||||
final currentChat = chats.firstWhere(
|
||||
(chat) => chat['id'] == widget.chatId,
|
||||
orElse: () => null,
|
||||
);
|
||||
|
||||
if (currentChat == null) {
|
||||
print('❌ [_loadGroupParticipants] Чат с ID ${widget.chatId} не найден');
|
||||
return;
|
||||
}
|
||||
|
||||
print(
|
||||
'✅ [_loadGroupParticipants] Чат найден: ${currentChat['title'] ?? 'Без названия'}',
|
||||
);
|
||||
|
||||
final participants = currentChat['participants'] as Map<String, dynamic>?;
|
||||
if (participants == null || participants.isEmpty) {
|
||||
print('❌ [_loadGroupParticipants] Список участников пуст');
|
||||
return;
|
||||
}
|
||||
|
||||
print(
|
||||
'🔍 [_loadGroupParticipants] Найдено ${participants.length} участников в чате',
|
||||
);
|
||||
|
||||
final participantIds = participants.keys
|
||||
.map((id) => int.tryParse(id))
|
||||
.where((id) => id != null)
|
||||
.cast<int>()
|
||||
.toList();
|
||||
|
||||
if (participantIds.isEmpty) {
|
||||
print('❌ [_loadGroupParticipants] participantIds пуст после парсинга');
|
||||
return;
|
||||
}
|
||||
|
||||
print(
|
||||
'🔍 [_loadGroupParticipants] Обрабатываем ${participantIds.length} ID участников...',
|
||||
);
|
||||
print(
|
||||
'🔍 [_loadGroupParticipants] IDs: ${participantIds.take(10).join(', ')}${participantIds.length > 10 ? '...' : ''}',
|
||||
);
|
||||
|
||||
final idsToFetch = participantIds
|
||||
.where((id) => !_contactDetailsCache.containsKey(id))
|
||||
.toList();
|
||||
|
||||
print(
|
||||
'🔍 [_loadGroupParticipants] В кэше уже есть: ${participantIds.length - idsToFetch.length} контактов',
|
||||
);
|
||||
print(
|
||||
'🔍 [_loadGroupParticipants] Нужно загрузить: ${idsToFetch.length} контактов',
|
||||
);
|
||||
|
||||
if (idsToFetch.isEmpty) {
|
||||
print('✅ [_loadGroupParticipants] Все участники уже в кэше');
|
||||
return;
|
||||
}
|
||||
|
||||
print(
|
||||
'📡 [_loadGroupParticipants] Загружаем информацию о ${idsToFetch.length} участниках...',
|
||||
);
|
||||
print(
|
||||
'📡 [_loadGroupParticipants] IDs для загрузки: ${idsToFetch.take(10).join(', ')}${idsToFetch.length > 10 ? '...' : ''}',
|
||||
);
|
||||
|
||||
final contacts = await ApiService.instance.fetchContactsByIds(idsToFetch);
|
||||
|
||||
print(
|
||||
'📦 [_loadGroupParticipants] Получено ${contacts.length} контактов от API из ${idsToFetch.length} запрошенных',
|
||||
);
|
||||
|
||||
if (contacts.isNotEmpty) {
|
||||
for (final contact in contacts) {
|
||||
print(' 📇 Контакт: ${contact.name} (ID: ${contact.id})');
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
for (final contact in contacts) {
|
||||
_contactDetailsCache[contact.id] = contact;
|
||||
}
|
||||
});
|
||||
|
||||
await ChatCacheService().cacheChatContacts(widget.chatId, contacts);
|
||||
|
||||
print(
|
||||
'✅ [_loadGroupParticipants] Загружено и сохранено ${contacts.length} контактов',
|
||||
);
|
||||
print(
|
||||
'✅ [_loadGroupParticipants] Всего в кэше теперь: ${_contactDetailsCache.length} контактов',
|
||||
);
|
||||
|
||||
if (contacts.length < idsToFetch.length) {
|
||||
final receivedIds = contacts.map((c) => c.id).toSet();
|
||||
final missingIds = idsToFetch
|
||||
.where((id) => !receivedIds.contains(id))
|
||||
.toList();
|
||||
print(
|
||||
'⚠️ [_loadGroupParticipants] Не получены данные для ${missingIds.length} контактов из ${idsToFetch.length} запрошенных',
|
||||
);
|
||||
print(
|
||||
'⚠️ [_loadGroupParticipants] Отсутствующие ID: ${missingIds.take(10).join(', ')}${missingIds.length > 10 ? '...' : ''}',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
print(
|
||||
'⚠️ [_loadGroupParticipants] Widget не mounted, контакты не сохранены',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
print('❌ [_loadGroupParticipants] API вернул ПУСТОЙ список контактов!');
|
||||
print(
|
||||
'❌ [_loadGroupParticipants] Было запрошено ${idsToFetch.length} ID',
|
||||
);
|
||||
print(
|
||||
'❌ [_loadGroupParticipants] Запрошенные ID: ${idsToFetch.take(10).join(', ')}${idsToFetch.length > 10 ? '...' : ''}',
|
||||
);
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
print('❌ [_loadGroupParticipants] Ошибка загрузки участников группы: $e');
|
||||
print('❌ [_loadGroupParticipants] StackTrace: $stackTrace');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -185,6 +338,13 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
Future<void> _initializeChat() async {
|
||||
await _loadCachedContacts();
|
||||
|
||||
if (!widget.isGroupChat && !widget.isChannel) {
|
||||
_contactDetailsCache[widget.contact.id] = widget.contact;
|
||||
print(
|
||||
'✅ [_initializeChat] Собеседник добавлен в кэш: ${widget.contact.name} (ID: ${widget.contact.id})',
|
||||
);
|
||||
}
|
||||
|
||||
final profileData = ApiService.instance.lastChatsPayload?['profile'];
|
||||
final contactProfile = profileData?['contact'] as Map<String, dynamic>?;
|
||||
|
||||
@@ -193,11 +353,34 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
contactProfile['id'] != 0) {
|
||||
_actualMyId = contactProfile['id'];
|
||||
print('✅ ID пользователя успешно получен из ApiService: $_actualMyId');
|
||||
|
||||
try {
|
||||
final myContact = Contact.fromJson(contactProfile);
|
||||
_contactDetailsCache[_actualMyId!] = myContact;
|
||||
print(
|
||||
'✅ [_initializeChat] Собственный профиль добавлен в кэш: ${myContact.name} (ID: $_actualMyId)',
|
||||
);
|
||||
} catch (e) {
|
||||
print(
|
||||
'⚠️ [_initializeChat] Не удалось добавить собственный профиль в кэш: $e',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
_actualMyId = widget.myId;
|
||||
print('ПРЕДУПРЕЖДЕНИЕ: Используется ID из виджета: $_actualMyId');
|
||||
}
|
||||
|
||||
if (!widget.isGroupChat && !widget.isChannel) {
|
||||
final contactsToCache = _contactDetailsCache.values.toList();
|
||||
await ChatCacheService().cacheChatContacts(
|
||||
widget.chatId,
|
||||
contactsToCache,
|
||||
);
|
||||
print(
|
||||
'✅ [_initializeChat] Сохранено ${contactsToCache.length} контактов в кэш чата (включая собственный профиль)',
|
||||
);
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isIdReady = true;
|
||||
@@ -395,6 +578,11 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
if (!mounted) return;
|
||||
_messages.clear();
|
||||
_messages.addAll(cachedMessages);
|
||||
|
||||
if (widget.isGroupChat) {
|
||||
await _loadGroupParticipants();
|
||||
}
|
||||
|
||||
_buildChatItems();
|
||||
setState(() {
|
||||
_isLoadingHistory = false;
|
||||
@@ -427,7 +615,17 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
final idsToFetch = senderIds
|
||||
.where((id) => !_contactDetailsCache.containsKey(id))
|
||||
.toList();
|
||||
|
||||
if (idsToFetch.isNotEmpty) {
|
||||
print(
|
||||
'📡 [_paginateInitialLoad] Загружаем ${idsToFetch.length} отсутствующих контактов из ${senderIds.length} отправителей...',
|
||||
);
|
||||
print(
|
||||
'📡 [_paginateInitialLoad] В кэше: ${senderIds.length - idsToFetch.length}, нужно загрузить: ${idsToFetch.length}',
|
||||
);
|
||||
print(
|
||||
'📡 [_paginateInitialLoad] IDs для загрузки: ${idsToFetch.take(10).join(', ')}${idsToFetch.length > 10 ? '...' : ''}',
|
||||
);
|
||||
final newContacts = await ApiService.instance.fetchContactsByIds(
|
||||
idsToFetch,
|
||||
);
|
||||
@@ -435,10 +633,29 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
for (final contact in newContacts) {
|
||||
_contactDetailsCache[contact.id] = contact;
|
||||
}
|
||||
|
||||
if (newContacts.isNotEmpty) {
|
||||
final allChatContacts = _contactDetailsCache.values.toList();
|
||||
await ChatCacheService().cacheChatContacts(
|
||||
widget.chatId,
|
||||
allChatContacts,
|
||||
);
|
||||
print(
|
||||
'✅ [_paginateInitialLoad] Обновлен кэш: ${allChatContacts.length} контактов для чата ${widget.chatId}',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
print(
|
||||
'✅ [_paginateInitialLoad] Все ${senderIds.length} отправителей уже в кэше',
|
||||
);
|
||||
}
|
||||
|
||||
await chatCacheService.cacheChatMessages(widget.chatId, allMessages);
|
||||
|
||||
if (widget.isGroupChat) {
|
||||
await _loadGroupParticipants();
|
||||
}
|
||||
|
||||
final page = _anyOptimize ? _optPage : _pageSize;
|
||||
final slice = allMessages.length > page
|
||||
? allMessages.sublist(allMessages.length - page)
|
||||
@@ -1587,14 +1804,30 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
}
|
||||
|
||||
Future<void> _loadCachedContacts() async {
|
||||
final chatContacts = await ChatCacheService().getCachedChatContacts(
|
||||
widget.chatId,
|
||||
);
|
||||
if (chatContacts != null && chatContacts.isNotEmpty) {
|
||||
for (final contact in chatContacts) {
|
||||
_contactDetailsCache[contact.id] = contact;
|
||||
}
|
||||
print(
|
||||
'✅ Загружено ${_contactDetailsCache.length} контактов из кэша чата ${widget.chatId}',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Если нет кэша чата, загружаем глобальный кэш
|
||||
final cachedContacts = await ChatCacheService().getCachedContacts();
|
||||
if (cachedContacts != null && cachedContacts.isNotEmpty) {
|
||||
for (final contact in cachedContacts) {
|
||||
_contactDetailsCache[contact.id] = contact;
|
||||
}
|
||||
print(
|
||||
'✅ Кэш контактов для экрана чата заполнен из ChatCacheService: ${_contactDetailsCache.length} контактов.',
|
||||
'✅ Загружено ${_contactDetailsCache.length} контактов из глобального кэша',
|
||||
);
|
||||
} else {
|
||||
print('⚠️ Кэш контактов пуст, будет загружено с сервера');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1746,9 +1979,12 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
if (shouldShowName) {
|
||||
final senderContact =
|
||||
_contactDetailsCache[message.senderId];
|
||||
senderName =
|
||||
senderContact?.name ??
|
||||
'Участник ${message.senderId}';
|
||||
if (senderContact != null) {
|
||||
senderName = senderContact.name;
|
||||
} else {
|
||||
senderName = 'ID ${message.senderId}';
|
||||
_loadContactIfNeeded(message.senderId);
|
||||
}
|
||||
}
|
||||
}
|
||||
final hasPhoto = item.message.attaches.any(
|
||||
@@ -4144,7 +4380,8 @@ class _AddMemberDialogState extends State<_AddMemberDialog> {
|
||||
itemBuilder: (context, index) {
|
||||
final contact = widget.contacts[index];
|
||||
final contactId = contact['id'] as int;
|
||||
final contactName = contact['names']?[0]?['name'] ?? 'Неизвестный';
|
||||
final contactName =
|
||||
contact['names']?[0]?['name'] ?? 'ID $contactId';
|
||||
final isSelected = _selectedContacts.contains(contactId);
|
||||
|
||||
return CheckboxListTile(
|
||||
@@ -4316,7 +4553,8 @@ class _ControlMessageChip extends StatelessWidget {
|
||||
);
|
||||
|
||||
final eventType = controlAttach['event'];
|
||||
final senderName = contacts[message.senderId]?.name ?? 'Неизвестный';
|
||||
final senderName =
|
||||
contacts[message.senderId]?.name ?? 'ID ${message.senderId}';
|
||||
final isMe = message.senderId == myId;
|
||||
final senderDisplayName = isMe ? 'Вы' : senderName;
|
||||
|
||||
@@ -4506,18 +4744,41 @@ class _ControlMessageChip extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
void openUserProfileById(BuildContext context, int userId) {
|
||||
final contact = ApiService.instance.getCachedContact(userId);
|
||||
Future<void> openUserProfileById(BuildContext context, int userId) async {
|
||||
var contact = ApiService.instance.getCachedContact(userId);
|
||||
|
||||
if (contact == null) {
|
||||
print(
|
||||
'⚠️ [openUserProfileById] Контакт $userId не найден в кэше, загружаем с сервера...',
|
||||
);
|
||||
|
||||
try {
|
||||
final contacts = await ApiService.instance.fetchContactsByIds([userId]);
|
||||
if (contacts.isNotEmpty) {
|
||||
contact = contacts.first;
|
||||
print(
|
||||
'✅ [openUserProfileById] Контакт $userId загружен: ${contact.name}',
|
||||
);
|
||||
} else {
|
||||
print(
|
||||
'❌ [openUserProfileById] Сервер не вернул данные для контакта $userId',
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
print('❌ [openUserProfileById] Ошибка загрузки контакта $userId: $e');
|
||||
}
|
||||
}
|
||||
|
||||
if (contact != null) {
|
||||
final isGroup = contact.id < 0; // Groups have negative IDs
|
||||
final contactData = contact;
|
||||
final isGroup = contactData.id < 0;
|
||||
|
||||
if (isGroup) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (context) => GroupProfileDraggableDialog(contact: contact),
|
||||
builder: (context) => GroupProfileDraggableDialog(contact: contactData),
|
||||
);
|
||||
} else {
|
||||
Navigator.of(context).push(
|
||||
@@ -4525,7 +4786,7 @@ void openUserProfileById(BuildContext context, int userId) {
|
||||
opaque: false,
|
||||
barrierColor: Colors.transparent,
|
||||
pageBuilder: (context, animation, secondaryAnimation) {
|
||||
return ContactProfileDialog(contact: contact);
|
||||
return ContactProfileDialog(contact: contactData);
|
||||
},
|
||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||
return FadeTransition(opacity: animation, child: child);
|
||||
@@ -4538,12 +4799,12 @@ void openUserProfileById(BuildContext context, int userId) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text('Профиль пользователя $userId'),
|
||||
content: Text('Информация о пользователе не найдена в кэше'),
|
||||
title: const Text('Ошибка'),
|
||||
content: Text('Не удалось загрузить информацию о пользователе $userId'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text('OK'),
|
||||
child: const Text('OK'),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -80,6 +80,7 @@ class _ChatsScreenState extends State<ChatsScreen>
|
||||
List<SearchResult> _searchResults = [];
|
||||
String _searchFilter = 'all';
|
||||
bool _hasRequestedBlockedContacts = false;
|
||||
final Set<int> _loadingContactIds = {};
|
||||
|
||||
List<ChatFolder> _folders = [];
|
||||
String? _selectedFolderId;
|
||||
@@ -1503,6 +1504,30 @@ class _ChatsScreenState extends State<ChatsScreen>
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadMissingContact(int contactId) async {
|
||||
if (_loadingContactIds.contains(contactId) ||
|
||||
_contacts.containsKey(contactId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
_loadingContactIds.add(contactId);
|
||||
|
||||
try {
|
||||
final contacts = await ApiService.instance.fetchContactsByIds([
|
||||
contactId,
|
||||
]);
|
||||
if (contacts.isNotEmpty && mounted) {
|
||||
setState(() {
|
||||
_contacts[contactId] = contacts.first;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
print('Ошибка загрузки контакта $contactId: $e');
|
||||
} finally {
|
||||
_loadingContactIds.remove(contactId);
|
||||
}
|
||||
}
|
||||
|
||||
String _formatTimestamp(int timestamp) {
|
||||
final dt = DateTime.fromMillisecondsSinceEpoch(timestamp);
|
||||
final now = DateTime.now();
|
||||
@@ -2343,12 +2368,13 @@ class _ChatsScreenState extends State<ChatsScreen>
|
||||
final bool isSavedMessages = _isSavedMessages(chat);
|
||||
|
||||
final Contact? contact;
|
||||
int? otherParticipantId;
|
||||
if (isSavedMessages) {
|
||||
contact = _contacts[chat.ownerId];
|
||||
} else if (isGroupChat) {
|
||||
contact = null;
|
||||
} else {
|
||||
final otherParticipantId = chat.participantIds.firstWhere(
|
||||
otherParticipantId = chat.participantIds.firstWhere(
|
||||
(id) => id != chat.ownerId,
|
||||
orElse: () => 0,
|
||||
);
|
||||
@@ -2360,11 +2386,23 @@ class _ChatsScreenState extends State<ChatsScreen>
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
final bool isChannel = chat.type == 'CHANNEL';
|
||||
final String title = isGroupChat
|
||||
? (chat.title?.isNotEmpty == true ? chat.title! : "Группа")
|
||||
: (isSavedMessages
|
||||
? "Избранное"
|
||||
: contact?.name ?? "Unknown");
|
||||
String title;
|
||||
if (isGroupChat) {
|
||||
title = chat.title?.isNotEmpty == true
|
||||
? chat.title!
|
||||
: "Группа";
|
||||
} else if (isSavedMessages) {
|
||||
title = "Избранное";
|
||||
} else if (contact != null) {
|
||||
title = contact.name;
|
||||
} else if (chat.title?.isNotEmpty == true) {
|
||||
title = chat.title!;
|
||||
} else {
|
||||
title = "ID ${otherParticipantId ?? 0}";
|
||||
if (otherParticipantId != null && otherParticipantId != 0) {
|
||||
_loadMissingContact(otherParticipantId);
|
||||
}
|
||||
}
|
||||
final String? avatarUrl = isGroupChat
|
||||
? chat.baseIconUrl
|
||||
: (isSavedMessages ? null : contact?.photoBaseUrl);
|
||||
@@ -2512,7 +2550,12 @@ class _ChatsScreenState extends State<ChatsScreen>
|
||||
: Text(
|
||||
isSavedMessages
|
||||
? "Избранное"
|
||||
: (contact?.name ?? 'Unknown'),
|
||||
: (contact?.name ??
|
||||
(chat.title?.isNotEmpty == true
|
||||
? chat.title!
|
||||
: (otherParticipantId != null
|
||||
? 'ID $otherParticipantId'
|
||||
: 'ID 0'))),
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: colors.onSurface,
|
||||
@@ -3779,7 +3822,14 @@ class _ChatsScreenState extends State<ChatsScreen>
|
||||
);
|
||||
contact = _contacts[otherParticipantId];
|
||||
|
||||
title = contact?.name ?? "Неизвестный чат";
|
||||
if (contact != null) {
|
||||
title = contact.name;
|
||||
} else if (chat.title?.isNotEmpty == true) {
|
||||
title = chat.title!;
|
||||
} else {
|
||||
title = "ID $otherParticipantId";
|
||||
_loadMissingContact(otherParticipantId);
|
||||
}
|
||||
avatarUrl = contact?.photoBaseUrl;
|
||||
leadingIcon = Icons.person;
|
||||
}
|
||||
@@ -4479,7 +4529,14 @@ class _AddChatsToFolderDialogState extends State<_AddChatsToFolderDialog> {
|
||||
orElse: () => myId,
|
||||
);
|
||||
contact = widget.contacts[otherParticipantId];
|
||||
title = contact?.name ?? "Неизвестный";
|
||||
|
||||
if (contact != null) {
|
||||
title = contact.name;
|
||||
} else if (chat.title?.isNotEmpty == true) {
|
||||
title = chat.title!;
|
||||
} else {
|
||||
title = "ID $otherParticipantId";
|
||||
}
|
||||
avatarUrl = contact?.photoBaseUrl;
|
||||
leadingIcon = Icons.person;
|
||||
}
|
||||
|
||||
@@ -22,10 +22,11 @@ class Channel {
|
||||
factory Channel.fromJson(Map<String, dynamic> json) {
|
||||
final names = json['names'] as List<dynamic>?;
|
||||
final nameData = names?.isNotEmpty == true ? names![0] : null;
|
||||
final channelId = json['id'] as int;
|
||||
|
||||
return Channel(
|
||||
id: json['id'] as int,
|
||||
name: nameData?['name'] as String? ?? 'Неизвестный канал',
|
||||
id: channelId,
|
||||
name: nameData?['name'] as String? ?? 'ID $channelId',
|
||||
description: nameData?['description'] as String?,
|
||||
photoBaseUrl: json['baseUrl'] as String?,
|
||||
link: json['link'] as String?,
|
||||
|
||||
@@ -31,10 +31,11 @@ class Contact {
|
||||
|
||||
factory Contact.fromJson(Map<String, dynamic> json) {
|
||||
final nameData = json['names']?[0];
|
||||
final userId = json['id'] as int;
|
||||
|
||||
String finalFirstName = '';
|
||||
String finalLastName = '';
|
||||
String finalName = 'Unknown';
|
||||
String finalName = 'ID $userId';
|
||||
|
||||
if (nameData != null) {
|
||||
finalFirstName = nameData['firstName'] ?? '';
|
||||
@@ -42,10 +43,9 @@ class Contact {
|
||||
final fullName = '$finalFirstName $finalLastName'.trim();
|
||||
finalName = fullName.isNotEmpty
|
||||
? fullName
|
||||
: (nameData['name'] ?? 'Unknown');
|
||||
: (nameData['name'] ?? 'ID $userId');
|
||||
}
|
||||
|
||||
|
||||
final status = json['status'];
|
||||
final isBlocked = status == 'BLOCKED';
|
||||
|
||||
|
||||
@@ -40,7 +40,6 @@ class _GroupSettingsScreenState extends State<GroupSettingsScreen> {
|
||||
super.initState();
|
||||
_currentContact = widget.initialContact;
|
||||
|
||||
|
||||
_contactSubscription = ApiService.instance.contactUpdates.listen((contact) {
|
||||
if (contact.id == _currentContact.id && mounted) {
|
||||
ApiService.instance.updateCachedContact(contact);
|
||||
@@ -50,17 +49,14 @@ class _GroupSettingsScreenState extends State<GroupSettingsScreen> {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
_membersSubscription = ApiService.instance.messages.listen((message) {
|
||||
if (message['type'] == 'group_members' && mounted) {
|
||||
_handleGroupMembersResponse(message['payload']);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
_loadMembersFromCache();
|
||||
|
||||
|
||||
if (_loadedMembers.length < 50) {
|
||||
_loadedMembers.clear();
|
||||
_loadedMemberIds.clear();
|
||||
@@ -69,7 +65,6 @@ class _GroupSettingsScreenState extends State<GroupSettingsScreen> {
|
||||
ApiService.instance.getGroupMembers(widget.chatId, marker: 0, count: 50);
|
||||
_isLoadingMembers = true;
|
||||
} else {
|
||||
|
||||
_lastMarker = _loadedMembers.isNotEmpty
|
||||
? _loadedMembers.last['id'] as int?
|
||||
: null;
|
||||
@@ -81,7 +76,6 @@ class _GroupSettingsScreenState extends State<GroupSettingsScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
_scrollController.addListener(_onScroll);
|
||||
}
|
||||
|
||||
@@ -109,7 +103,6 @@ class _GroupSettingsScreenState extends State<GroupSettingsScreen> {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
List<dynamic> membersRaw = [];
|
||||
if (currentChat['members'] is List) {
|
||||
membersRaw = currentChat['members'] as List<dynamic>;
|
||||
@@ -163,31 +156,6 @@ class _GroupSettingsScreenState extends State<GroupSettingsScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void _handleGroupMembersResponse(Map<String, dynamic> payload) {
|
||||
print(
|
||||
'DEBUG: _handleGroupMembersResponse вызван с payload: ${payload.keys}',
|
||||
@@ -383,7 +351,6 @@ class _GroupSettingsScreenState extends State<GroupSettingsScreen> {
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
}
|
||||
},
|
||||
),
|
||||
@@ -426,7 +393,7 @@ class _GroupSettingsScreenState extends State<GroupSettingsScreen> {
|
||||
if (contact != null) {
|
||||
removableMembers.add({
|
||||
'id': id,
|
||||
'name': contact['names']?[0]?['name'] ?? 'Неизвестный',
|
||||
'name': contact['names']?[0]?['name'] ?? 'ID $id',
|
||||
'contact': contact,
|
||||
});
|
||||
}
|
||||
@@ -456,7 +423,6 @@ class _GroupSettingsScreenState extends State<GroupSettingsScreen> {
|
||||
content: Text('Удалено ${selectedMembers.length} участников'),
|
||||
),
|
||||
);
|
||||
|
||||
}
|
||||
},
|
||||
),
|
||||
@@ -499,7 +465,7 @@ class _GroupSettingsScreenState extends State<GroupSettingsScreen> {
|
||||
if (contact != null) {
|
||||
promotableMembers.add({
|
||||
'id': id,
|
||||
'name': contact['names']?[0]?['name'] ?? 'Неизвестный',
|
||||
'name': contact['names']?[0]?['name'] ?? 'ID $id',
|
||||
'contact': contact,
|
||||
});
|
||||
}
|
||||
@@ -553,7 +519,6 @@ class _GroupSettingsScreenState extends State<GroupSettingsScreen> {
|
||||
ApiService.instance.leaveGroup(widget.chatId);
|
||||
|
||||
if (mounted) {
|
||||
|
||||
Navigator.of(context)
|
||||
..pop()
|
||||
..pop();
|
||||
@@ -708,7 +673,6 @@ class _GroupSettingsScreenState extends State<GroupSettingsScreen> {
|
||||
Widget _buildGroupManagementButtons() {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
|
||||
|
||||
bool amIAdmin = false;
|
||||
final currentChat = _getCurrentGroupChat();
|
||||
if (currentChat != null) {
|
||||
@@ -777,7 +741,6 @@ class _GroupSettingsScreenState extends State<GroupSettingsScreen> {
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
|
||||
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: FilledButton.icon(
|
||||
@@ -837,12 +800,12 @@ class _GroupSettingsScreenState extends State<GroupSettingsScreen> {
|
||||
final fullName = '$firstName $lastName'.trim();
|
||||
name = fullName.isNotEmpty
|
||||
? fullName
|
||||
: (nameData['name'] as String? ?? 'Неизвестный');
|
||||
: (nameData['name'] as String? ?? 'ID $id');
|
||||
}
|
||||
}
|
||||
}
|
||||
if (name == null || name.isEmpty) {
|
||||
name = 'Неизвестный';
|
||||
name = 'ID $id';
|
||||
}
|
||||
avatarUrl =
|
||||
contact?['baseUrl'] as String? ?? contact?['baseRawUrl'] as String?;
|
||||
@@ -1050,7 +1013,8 @@ class _AddMemberDialogState extends State<_AddMemberDialog> {
|
||||
itemBuilder: (context, index) {
|
||||
final contact = widget.contacts[index];
|
||||
final contactId = contact['id'] as int;
|
||||
final contactName = contact['names']?[0]?['name'] ?? 'Неизвестный';
|
||||
final contactName =
|
||||
contact['names']?[0]?['name'] ?? 'ID $contactId';
|
||||
final isSelected = _selectedContacts.contains(contactId);
|
||||
|
||||
return CheckboxListTile(
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'package:gwid/api/api_service.dart';
|
||||
|
||||
class StorageScreen extends StatefulWidget {
|
||||
final bool isModal;
|
||||
@@ -114,12 +115,39 @@ class _StorageScreenState extends State<StorageScreen>
|
||||
}
|
||||
|
||||
Future<void> _clearCache() async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Очистить кэш'),
|
||||
content: const Text(
|
||||
'Это действие очистит весь кэш приложения, включая кэш сообщений, медиафайлов и аватаров. '
|
||||
'Это действие нельзя отменить.',
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: const Text('Отмена'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
style: TextButton.styleFrom(foregroundColor: Colors.orange),
|
||||
child: const Text('Очистить'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (confirmed != true) return;
|
||||
|
||||
try {
|
||||
ApiService.instance.clearAllCaches();
|
||||
|
||||
final cacheDir = await getTemporaryDirectory();
|
||||
if (await cacheDir.exists()) {
|
||||
await cacheDir.delete(recursive: true);
|
||||
await cacheDir.create();
|
||||
}
|
||||
|
||||
await _loadStorageInfo();
|
||||
|
||||
if (mounted) {
|
||||
|
||||
@@ -21,7 +21,7 @@ class ChatCacheService {
|
||||
static const String _chatMessagesKey = 'cached_chat_messages';
|
||||
|
||||
static const Duration _chatsTTL = Duration(hours: 1);
|
||||
static const Duration _contactsTTL = Duration(hours: 6);
|
||||
static const Duration _contactsTTL = Duration(hours: 24);
|
||||
static const Duration _messagesTTL = Duration(hours: 2);
|
||||
|
||||
Future<void> cacheChats(List<Map<String, dynamic>> chats) async {
|
||||
@@ -54,22 +54,32 @@ class ChatCacheService {
|
||||
.map(
|
||||
(contact) => {
|
||||
'id': contact.id,
|
||||
'names': [
|
||||
{
|
||||
'name': contact.name,
|
||||
'firstName': contact.firstName,
|
||||
'lastName': contact.lastName,
|
||||
'type': 'ONEME',
|
||||
},
|
||||
],
|
||||
'photoBaseUrl': contact.photoBaseUrl,
|
||||
'baseUrl': contact.photoBaseUrl,
|
||||
'isBlocked': contact.isBlocked,
|
||||
'isBlockedByMe': contact.isBlockedByMe,
|
||||
'accountStatus': contact.accountStatus,
|
||||
'status': contact.status,
|
||||
'options': contact.options,
|
||||
'description': contact.description,
|
||||
},
|
||||
)
|
||||
.toList();
|
||||
|
||||
await _cacheService.set(_contactsKey, contactsData, ttl: _contactsTTL);
|
||||
print('Кэшировано ${contacts.length} контактов');
|
||||
print(
|
||||
'✅ Кэшировано ${contacts.length} контактов (глобально) с описаниями',
|
||||
);
|
||||
} catch (e) {
|
||||
print('Ошибка кэширования контактов: $e');
|
||||
print('❌ Ошибка кэширования контактов: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,10 +90,65 @@ class ChatCacheService {
|
||||
ttl: _contactsTTL,
|
||||
);
|
||||
if (cached != null) {
|
||||
return cached.map((data) => Contact.fromJson(data)).toList();
|
||||
final contacts = cached.map((data) => Contact.fromJson(data)).toList();
|
||||
print('✅ Загружено ${contacts.length} контактов из глобального кэша');
|
||||
return contacts;
|
||||
}
|
||||
} catch (e) {
|
||||
print('Ошибка получения кэшированных контактов: $e');
|
||||
print('❌ Ошибка получения кэшированных контактов: $e');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Кэширование контактов для конкретного чата
|
||||
Future<void> cacheChatContacts(int chatId, List<Contact> contacts) async {
|
||||
try {
|
||||
final key = 'chat_contacts_$chatId';
|
||||
final contactsData = contacts
|
||||
.map(
|
||||
(contact) => {
|
||||
'id': contact.id,
|
||||
'names': [
|
||||
{
|
||||
'name': contact.name,
|
||||
'firstName': contact.firstName,
|
||||
'lastName': contact.lastName,
|
||||
'type': 'ONEME',
|
||||
},
|
||||
],
|
||||
'photoBaseUrl': contact.photoBaseUrl,
|
||||
'baseUrl': contact.photoBaseUrl,
|
||||
'isBlocked': contact.isBlocked,
|
||||
'isBlockedByMe': contact.isBlockedByMe,
|
||||
'accountStatus': contact.accountStatus,
|
||||
'status': contact.status,
|
||||
'options': contact.options,
|
||||
'description': contact.description,
|
||||
},
|
||||
)
|
||||
.toList();
|
||||
|
||||
await _cacheService.set(key, contactsData, ttl: _contactsTTL);
|
||||
print('✅ Кэшировано ${contacts.length} контактов для чата $chatId');
|
||||
} catch (e) {
|
||||
print('❌ Ошибка кэширования контактов для чата $chatId: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<Contact>?> getCachedChatContacts(int chatId) async {
|
||||
try {
|
||||
final key = 'chat_contacts_$chatId';
|
||||
final cached = await _cacheService.get<List<dynamic>>(
|
||||
key,
|
||||
ttl: _contactsTTL,
|
||||
);
|
||||
if (cached != null) {
|
||||
final contacts = cached.map((data) => Contact.fromJson(data)).toList();
|
||||
print('✅ Загружено ${contacts.length} контактов из кэша чата $chatId');
|
||||
return contacts;
|
||||
}
|
||||
} catch (e) {
|
||||
print('❌ Ошибка получения кэшированных контактов для чата $chatId: $e');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -248,13 +313,14 @@ class ChatCacheService {
|
||||
'$_chatMessagesKey$chatId',
|
||||
'chat_info_$chatId',
|
||||
'last_message_$chatId',
|
||||
'chat_contacts_$chatId',
|
||||
];
|
||||
|
||||
for (final key in keys) {
|
||||
await _cacheService.remove(key);
|
||||
}
|
||||
|
||||
print('Кэш для чата $chatId очищен');
|
||||
print('Кэш для чата $chatId очищен (включая контакты)');
|
||||
} catch (e) {
|
||||
print('Ошибка очистки кэша для чата $chatId: $e');
|
||||
}
|
||||
|
||||
@@ -283,10 +283,12 @@ class ChatMessageBubble extends StatelessWidget {
|
||||
if (originalSenderId != null && cache != null) {
|
||||
final originalSenderContact = cache[originalSenderId];
|
||||
forwardedSenderName =
|
||||
originalSenderContact?.name ?? 'Участник $originalSenderId';
|
||||
originalSenderContact?.name ?? 'ID $originalSenderId';
|
||||
forwardedSenderAvatarUrl ??= originalSenderContact?.photoBaseUrl;
|
||||
} else if (originalSenderId != null) {
|
||||
forwardedSenderName = 'ID $originalSenderId';
|
||||
} else {
|
||||
forwardedSenderName = 'Неизвестный';
|
||||
forwardedSenderName = 'Пользователь';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -613,8 +615,8 @@ class ChatMessageBubble extends StatelessWidget {
|
||||
child: Text(
|
||||
replySenderId != null
|
||||
? (contactDetailsCache?[replySenderId]?.name ??
|
||||
'Участник $replySenderId')
|
||||
: 'Неизвестный',
|
||||
'ID $replySenderId')
|
||||
: 'Пользователь',
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -1294,7 +1296,7 @@ class ChatMessageBubble extends StatelessWidget {
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 2.0, bottom: 2.0),
|
||||
child: Text(
|
||||
senderName ?? 'Неизвестный',
|
||||
senderName!,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: _getUserColor(
|
||||
@@ -3623,7 +3625,7 @@ class ChatMessageBubble extends StatelessWidget {
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 2.0, bottom: 2.0),
|
||||
child: Text(
|
||||
senderName ?? 'Неизвестный',
|
||||
senderName!,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: _getUserColor(
|
||||
|
||||
@@ -26,7 +26,8 @@ class ControlMessageChip extends StatelessWidget {
|
||||
);
|
||||
|
||||
final eventType = controlAttach['event'];
|
||||
final senderName = contacts[message.senderId]?.name ?? 'Неизвестный';
|
||||
final senderName =
|
||||
contacts[message.senderId]?.name ?? 'ID ${message.senderId}';
|
||||
final isMe = message.senderId == myId;
|
||||
final senderDisplayName = isMe ? 'Вы' : senderName;
|
||||
|
||||
@@ -297,7 +298,14 @@ class MessagePreviewDialog {
|
||||
orElse: () => myId,
|
||||
);
|
||||
final contact = contacts[otherParticipantId];
|
||||
return contact?.name ?? "Неизвестный чат";
|
||||
|
||||
if (contact != null) {
|
||||
return contact.name;
|
||||
} else if (chat.title?.isNotEmpty == true) {
|
||||
return chat.title!;
|
||||
} else {
|
||||
return "ID $otherParticipantId";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -544,7 +552,8 @@ class MessagePreviewDialog {
|
||||
contacts[message.senderId];
|
||||
final senderName = isMe
|
||||
? 'Вы'
|
||||
: (senderContact?.name ?? 'Неизвестный');
|
||||
: (senderContact?.name ??
|
||||
'ID ${message.senderId}');
|
||||
|
||||
String? forwardedFrom;
|
||||
String? forwardedFromAvatarUrl;
|
||||
|
||||
@@ -23,7 +23,9 @@ class PinnedMessageWidget extends StatelessWidget {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
final senderName =
|
||||
contacts[pinnedMessage.senderId]?.name ??
|
||||
(pinnedMessage.senderId == myId ? 'Вы' : 'Неизвестный');
|
||||
(pinnedMessage.senderId == myId
|
||||
? 'Вы'
|
||||
: 'ID ${pinnedMessage.senderId}');
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 0),
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gwid/services/avatar_cache_service.dart';
|
||||
|
||||
|
||||
|
||||
|
||||
class UserProfilePanel extends StatefulWidget {
|
||||
final int userId;
|
||||
final String? name;
|
||||
@@ -37,16 +34,16 @@ class UserProfilePanel extends StatefulWidget {
|
||||
class _UserProfilePanelState extends State<UserProfilePanel> {
|
||||
final ScrollController _nameScrollController = ScrollController();
|
||||
|
||||
|
||||
|
||||
String get _displayName {
|
||||
if (widget.firstName != null || widget.lastName != null) {
|
||||
final firstName = widget.firstName ?? '';
|
||||
final lastName = widget.lastName ?? '';
|
||||
final fullName = '$firstName $lastName'.trim();
|
||||
return fullName.isNotEmpty ? fullName : (widget.name ?? 'Неизвестный');
|
||||
return fullName.isNotEmpty
|
||||
? fullName
|
||||
: (widget.name ?? 'ID ${widget.userId}');
|
||||
}
|
||||
return widget.name ?? 'Неизвестный';
|
||||
return widget.name ?? 'ID ${widget.userId}';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -108,122 +105,6 @@ class _UserProfilePanelState extends State<UserProfilePanel> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
|
||||
Reference in New Issue
Block a user