Кэширование ID пользователей из чатов (на 24 часа)

This commit is contained in:
needle10
2025-11-22 21:38:48 +03:00
parent bf995d8358
commit 321720cd0a
15 changed files with 669 additions and 238 deletions

View File

@@ -190,7 +190,6 @@ extension ApiServiceAuth on ApiService {
authToken = currentAccount.token; authToken = currentAccount.token;
userId = currentAccount.userId; userId = currentAccount.userId;
_messageCache.clear();
_messageQueue.clear(); _messageQueue.clear();
_lastChatsPayload = null; _lastChatsPayload = null;
_chatsFetchedInThisSession = false; _chatsFetchedInThisSession = false;
@@ -257,4 +256,3 @@ extension ApiServiceAuth on ApiService {
} }
} }
} }

View File

@@ -1,6 +1,119 @@
part of 'api_service.dart'; part of 'api_service.dart';
extension ApiServiceChats on ApiService { 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) { void createGroup(String name, List<int> participantIds) {
final payload = {"name": name, "participantIds": participantIds}; final payload = {"name": name, "participantIds": participantIds};
_sendMessage(48, payload); _sendMessage(48, payload);
@@ -357,22 +470,29 @@ extension ApiServiceChats on ApiService {
return result; return result;
} }
final contactIds = <int>{}; List<dynamic> contactListJson =
for (var chatJson in chatListJson) { chatResponse['payload']?['contacts'] ?? [];
final participants = chatJson['participants'] as Map<String, dynamic>;
contactIds.addAll(participants.keys.map((id) => int.parse(id))); if (contactListJson.isEmpty) {
final contactIds = <int>{};
for (var chatJson in chatListJson) {
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(),
});
final contactResponse = await messages.firstWhere(
(msg) => msg['seq'] == contactSeq,
);
contactListJson = contactResponse['payload']?['contacts'] ?? [];
}
} }
final int contactSeq = _sendMessage(32, {
"contactIds": contactIds.toList(),
});
final contactResponse = await messages.firstWhere(
(msg) => msg['seq'] == contactSeq,
);
final List<dynamic> contactListJson =
contactResponse['payload']?['contacts'] ?? [];
if (presence != null) { if (presence != null) {
updatePresenceData(presence); updatePresenceData(presence);
} }

View File

@@ -363,11 +363,15 @@ extension ApiServiceConnection on ApiService {
); );
_startHealthMonitoring(); _startHealthMonitoring();
if (_onlineCompleter != null && !_onlineCompleter!.isCompleted) {
_onlineCompleter!.complete();
}
_startPinging(); _startPinging();
_processMessageQueue(); _processMessageQueue();
if (authToken != null && !_chatsFetchedInThisSession) {
print(
"Токен найден, автоматически запускаем авторизацию (opcode 19)...",
);
unawaited(_sendAuthRequestAfterHandshake());
}
} }
if (decodedMessage is Map && decodedMessage['cmd'] == 3) { if (decodedMessage is Map && decodedMessage['cmd'] == 3) {
@@ -670,8 +674,6 @@ extension ApiServiceConnection on ApiService {
_onlineCompleter = Completer<void>(); _onlineCompleter = Completer<void>();
_chatsFetchedInThisSession = false; _chatsFetchedInThisSession = false;
clearAllCaches();
_currentUrlIndex = 0; _currentUrlIndex = 0;
_reconnectDelaySeconds = (_reconnectDelaySeconds * 2).clamp(1, 30); _reconnectDelaySeconds = (_reconnectDelaySeconds * 2).clamp(1, 30);
@@ -721,7 +723,6 @@ extension ApiServiceConnection on ApiService {
_currentUrlIndex = 0; _currentUrlIndex = 0;
_onlineCompleter = Completer<void>(); _onlineCompleter = Completer<void>();
clearAllCaches();
_messageQueue.clear(); _messageQueue.clear();
_presenceData.clear(); _presenceData.clear();

View File

@@ -105,6 +105,20 @@ extension ApiServiceContacts on ApiService {
return; return;
} }
if (!_isSessionOnline || !_isSessionReady) {
print(
'ApiService: сессия еще не готова для запроса заблокированных контактов, ждем...',
);
await waitUntilOnline();
if (!_isSessionReady) {
print(
'ApiService: сессия все еще не готова после ожидания, отменяем запрос',
);
return;
}
}
_isLoadingBlockedContacts = true; _isLoadingBlockedContacts = true;
print('ApiService: запрашиваем заблокированные контакты'); print('ApiService: запрашиваем заблокированные контакты');
_sendMessage(36, {'status': 'BLOCKED', 'count': 100, 'from': 0}); _sendMessage(36, {'status': 'BLOCKED', 'count': 100, 'from': 0});
@@ -278,12 +292,23 @@ extension ApiServiceContacts on ApiService {
Future<List<Contact>> fetchContactsByIds(List<int> contactIds) async { Future<List<Contact>> fetchContactsByIds(List<int> contactIds) async {
if (contactIds.isEmpty) { if (contactIds.isEmpty) {
print(
'⚠️ [fetchContactsByIds] Пустой список contactIds - пропускаем запрос',
);
return []; return [];
} }
print('Запрашиваем данные для ${contactIds.length} контактов...'); print(
'📡 [fetchContactsByIds] Запрашиваем данные для ${contactIds.length} контактов...',
);
print(
'📡 [fetchContactsByIds] IDs: ${contactIds.take(10).join(', ')}${contactIds.length > 10 ? '...' : ''}',
);
try { try {
final int contactSeq = _sendMessage(32, {"contactIds": contactIds}); final int contactSeq = _sendMessage(32, {"contactIds": contactIds});
print(
'📤 [fetchContactsByIds] Отправлен опкод 32 с seq=$contactSeq и ${contactIds.length} ID',
);
final contactResponse = await messages final contactResponse = await messages
.firstWhere((msg) => msg['seq'] == contactSeq) .firstWhere((msg) => msg['seq'] == contactSeq)
@@ -291,7 +316,7 @@ extension ApiServiceContacts on ApiService {
if (contactResponse['cmd'] == 3) { if (contactResponse['cmd'] == 3) {
print( print(
"Ошибка при получении контактов по ID: ${contactResponse['payload']}", "❌ [fetchContactsByIds] Ошибка при получении контактов: ${contactResponse['payload']}",
); );
return []; return [];
} }
@@ -302,13 +327,29 @@ extension ApiServiceContacts on ApiService {
.map((json) => Contact.fromJson(json)) .map((json) => Contact.fromJson(json))
.toList(); .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) { for (final contact in contacts) {
_contactCache[contact.id] = contact; _contactCache[contact.id] = contact;
} }
print("Получены и закэшированы данные для ${contacts.length} контактов."); print(
"✅ [fetchContactsByIds] Закэшированы данные для ${contacts.length} контактов",
);
return contacts; return contacts;
} catch (e) { } catch (e) {
print('Исключение при получении контактов по ID: $e'); print('❌ [fetchContactsByIds] Исключение при получении контактов: $e');
return []; return [];
} }
} }

View File

@@ -164,6 +164,13 @@ class _ChatScreenState extends State<ChatScreen> {
if (contacts.isNotEmpty && mounted) { if (contacts.isNotEmpty && mounted) {
final contact = contacts.first; final contact = contacts.first;
_contactDetailsCache[contact.id] = contact; _contactDetailsCache[contact.id] = contact;
final allChatContacts = _contactDetailsCache.values.toList();
await ChatCacheService().cacheChatContacts(
widget.chatId,
allChatContacts,
);
setState(() {}); setState(() {});
} }
} catch (e) { } 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 @override
void initState() { void initState() {
super.initState(); super.initState();
@@ -185,6 +338,13 @@ class _ChatScreenState extends State<ChatScreen> {
Future<void> _initializeChat() async { Future<void> _initializeChat() async {
await _loadCachedContacts(); 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 profileData = ApiService.instance.lastChatsPayload?['profile'];
final contactProfile = profileData?['contact'] as Map<String, dynamic>?; final contactProfile = profileData?['contact'] as Map<String, dynamic>?;
@@ -193,11 +353,34 @@ class _ChatScreenState extends State<ChatScreen> {
contactProfile['id'] != 0) { contactProfile['id'] != 0) {
_actualMyId = contactProfile['id']; _actualMyId = contactProfile['id'];
print('✅ ID пользователя успешно получен из ApiService: $_actualMyId'); 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 { } else {
_actualMyId = widget.myId; _actualMyId = widget.myId;
print('ПРЕДУПРЕЖДЕНИЕ: Используется ID из виджета: $_actualMyId'); 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) { if (mounted) {
setState(() { setState(() {
_isIdReady = true; _isIdReady = true;
@@ -395,6 +578,11 @@ class _ChatScreenState extends State<ChatScreen> {
if (!mounted) return; if (!mounted) return;
_messages.clear(); _messages.clear();
_messages.addAll(cachedMessages); _messages.addAll(cachedMessages);
if (widget.isGroupChat) {
await _loadGroupParticipants();
}
_buildChatItems(); _buildChatItems();
setState(() { setState(() {
_isLoadingHistory = false; _isLoadingHistory = false;
@@ -427,7 +615,17 @@ class _ChatScreenState extends State<ChatScreen> {
final idsToFetch = senderIds final idsToFetch = senderIds
.where((id) => !_contactDetailsCache.containsKey(id)) .where((id) => !_contactDetailsCache.containsKey(id))
.toList(); .toList();
if (idsToFetch.isNotEmpty) { 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( final newContacts = await ApiService.instance.fetchContactsByIds(
idsToFetch, idsToFetch,
); );
@@ -435,10 +633,29 @@ class _ChatScreenState extends State<ChatScreen> {
for (final contact in newContacts) { for (final contact in newContacts) {
_contactDetailsCache[contact.id] = contact; _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); await chatCacheService.cacheChatMessages(widget.chatId, allMessages);
if (widget.isGroupChat) {
await _loadGroupParticipants();
}
final page = _anyOptimize ? _optPage : _pageSize; final page = _anyOptimize ? _optPage : _pageSize;
final slice = allMessages.length > page final slice = allMessages.length > page
? allMessages.sublist(allMessages.length - page) ? allMessages.sublist(allMessages.length - page)
@@ -1587,14 +1804,30 @@ class _ChatScreenState extends State<ChatScreen> {
} }
Future<void> _loadCachedContacts() async { 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(); final cachedContacts = await ChatCacheService().getCachedContacts();
if (cachedContacts != null && cachedContacts.isNotEmpty) { if (cachedContacts != null && cachedContacts.isNotEmpty) {
for (final contact in cachedContacts) { for (final contact in cachedContacts) {
_contactDetailsCache[contact.id] = contact; _contactDetailsCache[contact.id] = contact;
} }
print( print(
'Кэш контактов для экрана чата заполнен из ChatCacheService: ${_contactDetailsCache.length} контактов.', 'Загружено ${_contactDetailsCache.length} контактов из глобального кэша',
); );
} else {
print('⚠️ Кэш контактов пуст, будет загружено с сервера');
} }
} }
@@ -1746,9 +1979,12 @@ class _ChatScreenState extends State<ChatScreen> {
if (shouldShowName) { if (shouldShowName) {
final senderContact = final senderContact =
_contactDetailsCache[message.senderId]; _contactDetailsCache[message.senderId];
senderName = if (senderContact != null) {
senderContact?.name ?? senderName = senderContact.name;
'Участник ${message.senderId}'; } else {
senderName = 'ID ${message.senderId}';
_loadContactIfNeeded(message.senderId);
}
} }
} }
final hasPhoto = item.message.attaches.any( final hasPhoto = item.message.attaches.any(
@@ -4144,7 +4380,8 @@ class _AddMemberDialogState extends State<_AddMemberDialog> {
itemBuilder: (context, index) { itemBuilder: (context, index) {
final contact = widget.contacts[index]; final contact = widget.contacts[index];
final contactId = contact['id'] as int; 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); final isSelected = _selectedContacts.contains(contactId);
return CheckboxListTile( return CheckboxListTile(
@@ -4316,7 +4553,8 @@ class _ControlMessageChip extends StatelessWidget {
); );
final eventType = controlAttach['event']; 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 isMe = message.senderId == myId;
final senderDisplayName = isMe ? 'Вы' : senderName; final senderDisplayName = isMe ? 'Вы' : senderName;
@@ -4506,18 +4744,41 @@ class _ControlMessageChip extends StatelessWidget {
} }
} }
void openUserProfileById(BuildContext context, int userId) { Future<void> openUserProfileById(BuildContext context, int userId) async {
final contact = ApiService.instance.getCachedContact(userId); 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) { if (contact != null) {
final isGroup = contact.id < 0; // Groups have negative IDs final contactData = contact;
final isGroup = contactData.id < 0;
if (isGroup) { if (isGroup) {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
builder: (context) => GroupProfileDraggableDialog(contact: contact), builder: (context) => GroupProfileDraggableDialog(contact: contactData),
); );
} else { } else {
Navigator.of(context).push( Navigator.of(context).push(
@@ -4525,7 +4786,7 @@ void openUserProfileById(BuildContext context, int userId) {
opaque: false, opaque: false,
barrierColor: Colors.transparent, barrierColor: Colors.transparent,
pageBuilder: (context, animation, secondaryAnimation) { pageBuilder: (context, animation, secondaryAnimation) {
return ContactProfileDialog(contact: contact); return ContactProfileDialog(contact: contactData);
}, },
transitionsBuilder: (context, animation, secondaryAnimation, child) { transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(opacity: animation, child: child); return FadeTransition(opacity: animation, child: child);
@@ -4538,12 +4799,12 @@ void openUserProfileById(BuildContext context, int userId) {
showDialog( showDialog(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: Text('Профиль пользователя $userId'), title: const Text('Ошибка'),
content: Text('Информация о пользователе не найдена в кэше'), content: Text('Не удалось загрузить информацию о пользователе $userId'),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.pop(context),
child: Text('OK'), child: const Text('OK'),
), ),
], ],
), ),

View File

@@ -80,6 +80,7 @@ class _ChatsScreenState extends State<ChatsScreen>
List<SearchResult> _searchResults = []; List<SearchResult> _searchResults = [];
String _searchFilter = 'all'; String _searchFilter = 'all';
bool _hasRequestedBlockedContacts = false; bool _hasRequestedBlockedContacts = false;
final Set<int> _loadingContactIds = {};
List<ChatFolder> _folders = []; List<ChatFolder> _folders = [];
String? _selectedFolderId; 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) { String _formatTimestamp(int timestamp) {
final dt = DateTime.fromMillisecondsSinceEpoch(timestamp); final dt = DateTime.fromMillisecondsSinceEpoch(timestamp);
final now = DateTime.now(); final now = DateTime.now();
@@ -2343,12 +2368,13 @@ class _ChatsScreenState extends State<ChatsScreen>
final bool isSavedMessages = _isSavedMessages(chat); final bool isSavedMessages = _isSavedMessages(chat);
final Contact? contact; final Contact? contact;
int? otherParticipantId;
if (isSavedMessages) { if (isSavedMessages) {
contact = _contacts[chat.ownerId]; contact = _contacts[chat.ownerId];
} else if (isGroupChat) { } else if (isGroupChat) {
contact = null; contact = null;
} else { } else {
final otherParticipantId = chat.participantIds.firstWhere( otherParticipantId = chat.participantIds.firstWhere(
(id) => id != chat.ownerId, (id) => id != chat.ownerId,
orElse: () => 0, orElse: () => 0,
); );
@@ -2360,11 +2386,23 @@ class _ChatsScreenState extends State<ChatsScreen>
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: () {
final bool isChannel = chat.type == 'CHANNEL'; final bool isChannel = chat.type == 'CHANNEL';
final String title = isGroupChat String title;
? (chat.title?.isNotEmpty == true ? chat.title! : "Группа") if (isGroupChat) {
: (isSavedMessages title = chat.title?.isNotEmpty == true
? "Избранное" ? chat.title!
: contact?.name ?? "Unknown"); : "Группа";
} 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 final String? avatarUrl = isGroupChat
? chat.baseIconUrl ? chat.baseIconUrl
: (isSavedMessages ? null : contact?.photoBaseUrl); : (isSavedMessages ? null : contact?.photoBaseUrl);
@@ -2512,7 +2550,12 @@ class _ChatsScreenState extends State<ChatsScreen>
: Text( : Text(
isSavedMessages isSavedMessages
? "Избранное" ? "Избранное"
: (contact?.name ?? 'Unknown'), : (contact?.name ??
(chat.title?.isNotEmpty == true
? chat.title!
: (otherParticipantId != null
? 'ID $otherParticipantId'
: 'ID 0'))),
style: TextStyle( style: TextStyle(
fontSize: 11, fontSize: 11,
color: colors.onSurface, color: colors.onSurface,
@@ -3779,7 +3822,14 @@ class _ChatsScreenState extends State<ChatsScreen>
); );
contact = _contacts[otherParticipantId]; 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; avatarUrl = contact?.photoBaseUrl;
leadingIcon = Icons.person; leadingIcon = Icons.person;
} }
@@ -4479,7 +4529,14 @@ class _AddChatsToFolderDialogState extends State<_AddChatsToFolderDialog> {
orElse: () => myId, orElse: () => myId,
); );
contact = widget.contacts[otherParticipantId]; 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; avatarUrl = contact?.photoBaseUrl;
leadingIcon = Icons.person; leadingIcon = Icons.person;
} }

View File

@@ -22,10 +22,11 @@ class Channel {
factory Channel.fromJson(Map<String, dynamic> json) { factory Channel.fromJson(Map<String, dynamic> json) {
final names = json['names'] as List<dynamic>?; final names = json['names'] as List<dynamic>?;
final nameData = names?.isNotEmpty == true ? names![0] : null; final nameData = names?.isNotEmpty == true ? names![0] : null;
final channelId = json['id'] as int;
return Channel( return Channel(
id: json['id'] as int, id: channelId,
name: nameData?['name'] as String? ?? 'Неизвестный канал', name: nameData?['name'] as String? ?? 'ID $channelId',
description: nameData?['description'] as String?, description: nameData?['description'] as String?,
photoBaseUrl: json['baseUrl'] as String?, photoBaseUrl: json['baseUrl'] as String?,
link: json['link'] as String?, link: json['link'] as String?,

View File

@@ -31,10 +31,11 @@ class Contact {
factory Contact.fromJson(Map<String, dynamic> json) { factory Contact.fromJson(Map<String, dynamic> json) {
final nameData = json['names']?[0]; final nameData = json['names']?[0];
final userId = json['id'] as int;
String finalFirstName = ''; String finalFirstName = '';
String finalLastName = ''; String finalLastName = '';
String finalName = 'Unknown'; String finalName = 'ID $userId';
if (nameData != null) { if (nameData != null) {
finalFirstName = nameData['firstName'] ?? ''; finalFirstName = nameData['firstName'] ?? '';
@@ -42,10 +43,9 @@ class Contact {
final fullName = '$finalFirstName $finalLastName'.trim(); final fullName = '$finalFirstName $finalLastName'.trim();
finalName = fullName.isNotEmpty finalName = fullName.isNotEmpty
? fullName ? fullName
: (nameData['name'] ?? 'Unknown'); : (nameData['name'] ?? 'ID $userId');
} }
final status = json['status']; final status = json['status'];
final isBlocked = status == 'BLOCKED'; final isBlocked = status == 'BLOCKED';

View File

@@ -40,7 +40,6 @@ class _GroupSettingsScreenState extends State<GroupSettingsScreen> {
super.initState(); super.initState();
_currentContact = widget.initialContact; _currentContact = widget.initialContact;
_contactSubscription = ApiService.instance.contactUpdates.listen((contact) { _contactSubscription = ApiService.instance.contactUpdates.listen((contact) {
if (contact.id == _currentContact.id && mounted) { if (contact.id == _currentContact.id && mounted) {
ApiService.instance.updateCachedContact(contact); ApiService.instance.updateCachedContact(contact);
@@ -50,17 +49,14 @@ class _GroupSettingsScreenState extends State<GroupSettingsScreen> {
} }
}); });
_membersSubscription = ApiService.instance.messages.listen((message) { _membersSubscription = ApiService.instance.messages.listen((message) {
if (message['type'] == 'group_members' && mounted) { if (message['type'] == 'group_members' && mounted) {
_handleGroupMembersResponse(message['payload']); _handleGroupMembersResponse(message['payload']);
} }
}); });
_loadMembersFromCache(); _loadMembersFromCache();
if (_loadedMembers.length < 50) { if (_loadedMembers.length < 50) {
_loadedMembers.clear(); _loadedMembers.clear();
_loadedMemberIds.clear(); _loadedMemberIds.clear();
@@ -69,7 +65,6 @@ class _GroupSettingsScreenState extends State<GroupSettingsScreen> {
ApiService.instance.getGroupMembers(widget.chatId, marker: 0, count: 50); ApiService.instance.getGroupMembers(widget.chatId, marker: 0, count: 50);
_isLoadingMembers = true; _isLoadingMembers = true;
} else { } else {
_lastMarker = _loadedMembers.isNotEmpty _lastMarker = _loadedMembers.isNotEmpty
? _loadedMembers.last['id'] as int? ? _loadedMembers.last['id'] as int?
: null; : null;
@@ -81,7 +76,6 @@ class _GroupSettingsScreenState extends State<GroupSettingsScreen> {
); );
} }
_scrollController.addListener(_onScroll); _scrollController.addListener(_onScroll);
} }
@@ -109,7 +103,6 @@ class _GroupSettingsScreenState extends State<GroupSettingsScreen> {
return; return;
} }
List<dynamic> membersRaw = []; List<dynamic> membersRaw = [];
if (currentChat['members'] is List) { if (currentChat['members'] is List) {
membersRaw = currentChat['members'] as List<dynamic>; membersRaw = currentChat['members'] as List<dynamic>;
@@ -163,31 +156,6 @@ class _GroupSettingsScreenState extends State<GroupSettingsScreen> {
); );
} }
void _handleGroupMembersResponse(Map<String, dynamic> payload) { void _handleGroupMembersResponse(Map<String, dynamic> payload) {
print( print(
'DEBUG: _handleGroupMembersResponse вызван с payload: ${payload.keys}', '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) { if (contact != null) {
removableMembers.add({ removableMembers.add({
'id': id, 'id': id,
'name': contact['names']?[0]?['name'] ?? 'Неизвестный', 'name': contact['names']?[0]?['name'] ?? 'ID $id',
'contact': contact, 'contact': contact,
}); });
} }
@@ -456,7 +423,6 @@ class _GroupSettingsScreenState extends State<GroupSettingsScreen> {
content: Text('Удалено ${selectedMembers.length} участников'), content: Text('Удалено ${selectedMembers.length} участников'),
), ),
); );
} }
}, },
), ),
@@ -499,7 +465,7 @@ class _GroupSettingsScreenState extends State<GroupSettingsScreen> {
if (contact != null) { if (contact != null) {
promotableMembers.add({ promotableMembers.add({
'id': id, 'id': id,
'name': contact['names']?[0]?['name'] ?? 'Неизвестный', 'name': contact['names']?[0]?['name'] ?? 'ID $id',
'contact': contact, 'contact': contact,
}); });
} }
@@ -553,7 +519,6 @@ class _GroupSettingsScreenState extends State<GroupSettingsScreen> {
ApiService.instance.leaveGroup(widget.chatId); ApiService.instance.leaveGroup(widget.chatId);
if (mounted) { if (mounted) {
Navigator.of(context) Navigator.of(context)
..pop() ..pop()
..pop(); ..pop();
@@ -708,7 +673,6 @@ class _GroupSettingsScreenState extends State<GroupSettingsScreen> {
Widget _buildGroupManagementButtons() { Widget _buildGroupManagementButtons() {
final colorScheme = Theme.of(context).colorScheme; final colorScheme = Theme.of(context).colorScheme;
bool amIAdmin = false; bool amIAdmin = false;
final currentChat = _getCurrentGroupChat(); final currentChat = _getCurrentGroupChat();
if (currentChat != null) { if (currentChat != null) {
@@ -777,7 +741,6 @@ class _GroupSettingsScreenState extends State<GroupSettingsScreen> {
const SizedBox(height: 8), const SizedBox(height: 8),
], ],
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: FilledButton.icon( child: FilledButton.icon(
@@ -837,12 +800,12 @@ class _GroupSettingsScreenState extends State<GroupSettingsScreen> {
final fullName = '$firstName $lastName'.trim(); final fullName = '$firstName $lastName'.trim();
name = fullName.isNotEmpty name = fullName.isNotEmpty
? fullName ? fullName
: (nameData['name'] as String? ?? 'Неизвестный'); : (nameData['name'] as String? ?? 'ID $id');
} }
} }
} }
if (name == null || name.isEmpty) { if (name == null || name.isEmpty) {
name = 'Неизвестный'; name = 'ID $id';
} }
avatarUrl = avatarUrl =
contact?['baseUrl'] as String? ?? contact?['baseRawUrl'] as String?; contact?['baseUrl'] as String? ?? contact?['baseRawUrl'] as String?;
@@ -1050,7 +1013,8 @@ class _AddMemberDialogState extends State<_AddMemberDialog> {
itemBuilder: (context, index) { itemBuilder: (context, index) {
final contact = widget.contacts[index]; final contact = widget.contacts[index];
final contactId = contact['id'] as int; 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); final isSelected = _selectedContacts.contains(contactId);
return CheckboxListTile( return CheckboxListTile(

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'dart:io'; import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'package:gwid/api/api_service.dart';
class StorageScreen extends StatefulWidget { class StorageScreen extends StatefulWidget {
final bool isModal; final bool isModal;
@@ -114,12 +115,39 @@ class _StorageScreenState extends State<StorageScreen>
} }
Future<void> _clearCache() async { 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 { try {
ApiService.instance.clearAllCaches();
final cacheDir = await getTemporaryDirectory(); final cacheDir = await getTemporaryDirectory();
if (await cacheDir.exists()) { if (await cacheDir.exists()) {
await cacheDir.delete(recursive: true); await cacheDir.delete(recursive: true);
await cacheDir.create(); await cacheDir.create();
} }
await _loadStorageInfo(); await _loadStorageInfo();
if (mounted) { if (mounted) {

View File

@@ -21,7 +21,7 @@ class ChatCacheService {
static const String _chatMessagesKey = 'cached_chat_messages'; static const String _chatMessagesKey = 'cached_chat_messages';
static const Duration _chatsTTL = Duration(hours: 1); 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); static const Duration _messagesTTL = Duration(hours: 2);
Future<void> cacheChats(List<Map<String, dynamic>> chats) async { Future<void> cacheChats(List<Map<String, dynamic>> chats) async {
@@ -54,22 +54,32 @@ class ChatCacheService {
.map( .map(
(contact) => { (contact) => {
'id': contact.id, 'id': contact.id,
'name': contact.name, 'names': [
'firstName': contact.firstName, {
'lastName': contact.lastName, 'name': contact.name,
'firstName': contact.firstName,
'lastName': contact.lastName,
'type': 'ONEME',
},
],
'photoBaseUrl': contact.photoBaseUrl, 'photoBaseUrl': contact.photoBaseUrl,
'baseUrl': contact.photoBaseUrl,
'isBlocked': contact.isBlocked, 'isBlocked': contact.isBlocked,
'isBlockedByMe': contact.isBlockedByMe, 'isBlockedByMe': contact.isBlockedByMe,
'accountStatus': contact.accountStatus, 'accountStatus': contact.accountStatus,
'status': contact.status, 'status': contact.status,
'options': contact.options,
'description': contact.description,
}, },
) )
.toList(); .toList();
await _cacheService.set(_contactsKey, contactsData, ttl: _contactsTTL); await _cacheService.set(_contactsKey, contactsData, ttl: _contactsTTL);
print('Кэшировано ${contacts.length} контактов'); print(
'✅ Кэшировано ${contacts.length} контактов (глобально) с описаниями',
);
} catch (e) { } catch (e) {
print('Ошибка кэширования контактов: $e'); print('Ошибка кэширования контактов: $e');
} }
} }
@@ -80,10 +90,65 @@ class ChatCacheService {
ttl: _contactsTTL, ttl: _contactsTTL,
); );
if (cached != null) { 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) { } 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; return null;
} }
@@ -248,13 +313,14 @@ class ChatCacheService {
'$_chatMessagesKey$chatId', '$_chatMessagesKey$chatId',
'chat_info_$chatId', 'chat_info_$chatId',
'last_message_$chatId', 'last_message_$chatId',
'chat_contacts_$chatId',
]; ];
for (final key in keys) { for (final key in keys) {
await _cacheService.remove(key); await _cacheService.remove(key);
} }
print('Кэш для чата $chatId очищен'); print('Кэш для чата $chatId очищен (включая контакты)');
} catch (e) { } catch (e) {
print('Ошибка очистки кэша для чата $chatId: $e'); print('Ошибка очистки кэша для чата $chatId: $e');
} }

View File

@@ -283,10 +283,12 @@ class ChatMessageBubble extends StatelessWidget {
if (originalSenderId != null && cache != null) { if (originalSenderId != null && cache != null) {
final originalSenderContact = cache[originalSenderId]; final originalSenderContact = cache[originalSenderId];
forwardedSenderName = forwardedSenderName =
originalSenderContact?.name ?? 'Участник $originalSenderId'; originalSenderContact?.name ?? 'ID $originalSenderId';
forwardedSenderAvatarUrl ??= originalSenderContact?.photoBaseUrl; forwardedSenderAvatarUrl ??= originalSenderContact?.photoBaseUrl;
} else if (originalSenderId != null) {
forwardedSenderName = 'ID $originalSenderId';
} else { } else {
forwardedSenderName = 'Неизвестный'; forwardedSenderName = 'Пользователь';
} }
} }
} }
@@ -613,8 +615,8 @@ class ChatMessageBubble extends StatelessWidget {
child: Text( child: Text(
replySenderId != null replySenderId != null
? (contactDetailsCache?[replySenderId]?.name ?? ? (contactDetailsCache?[replySenderId]?.name ??
'Участник $replySenderId') 'ID $replySenderId')
: 'Неизвестный', : 'Пользователь',
style: TextStyle( style: TextStyle(
fontSize: 10, fontSize: 10,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@@ -1294,7 +1296,7 @@ class ChatMessageBubble extends StatelessWidget {
Padding( Padding(
padding: const EdgeInsets.only(left: 2.0, bottom: 2.0), padding: const EdgeInsets.only(left: 2.0, bottom: 2.0),
child: Text( child: Text(
senderName ?? 'Неизвестный', senderName!,
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: _getUserColor( color: _getUserColor(
@@ -3623,7 +3625,7 @@ class ChatMessageBubble extends StatelessWidget {
child: Padding( child: Padding(
padding: const EdgeInsets.only(left: 2.0, bottom: 2.0), padding: const EdgeInsets.only(left: 2.0, bottom: 2.0),
child: Text( child: Text(
senderName ?? 'Неизвестный', senderName!,
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: _getUserColor( color: _getUserColor(

View File

@@ -26,7 +26,8 @@ class ControlMessageChip extends StatelessWidget {
); );
final eventType = controlAttach['event']; 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 isMe = message.senderId == myId;
final senderDisplayName = isMe ? 'Вы' : senderName; final senderDisplayName = isMe ? 'Вы' : senderName;
@@ -297,7 +298,14 @@ class MessagePreviewDialog {
orElse: () => myId, orElse: () => myId,
); );
final contact = contacts[otherParticipantId]; 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]; contacts[message.senderId];
final senderName = isMe final senderName = isMe
? 'Вы' ? 'Вы'
: (senderContact?.name ?? 'Неизвестный'); : (senderContact?.name ??
'ID ${message.senderId}');
String? forwardedFrom; String? forwardedFrom;
String? forwardedFromAvatarUrl; String? forwardedFromAvatarUrl;

View File

@@ -23,7 +23,9 @@ class PinnedMessageWidget extends StatelessWidget {
final colors = Theme.of(context).colorScheme; final colors = Theme.of(context).colorScheme;
final senderName = final senderName =
contacts[pinnedMessage.senderId]?.name ?? contacts[pinnedMessage.senderId]?.name ??
(pinnedMessage.senderId == myId ? 'Вы' : 'Неизвестный'); (pinnedMessage.senderId == myId
? 'Вы'
: 'ID ${pinnedMessage.senderId}');
return Container( return Container(
margin: const EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 0), margin: const EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 0),

View File

@@ -1,9 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gwid/services/avatar_cache_service.dart'; import 'package:gwid/services/avatar_cache_service.dart';
class UserProfilePanel extends StatefulWidget { class UserProfilePanel extends StatefulWidget {
final int userId; final int userId;
final String? name; final String? name;
@@ -37,16 +34,16 @@ class UserProfilePanel extends StatefulWidget {
class _UserProfilePanelState extends State<UserProfilePanel> { class _UserProfilePanelState extends State<UserProfilePanel> {
final ScrollController _nameScrollController = ScrollController(); final ScrollController _nameScrollController = ScrollController();
String get _displayName { String get _displayName {
if (widget.firstName != null || widget.lastName != null) { if (widget.firstName != null || widget.lastName != null) {
final firstName = widget.firstName ?? ''; final firstName = widget.firstName ?? '';
final lastName = widget.lastName ?? ''; final lastName = widget.lastName ?? '';
final fullName = '$firstName $lastName'.trim(); 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 @override
@@ -108,122 +105,6 @@ class _UserProfilePanelState extends State<UserProfilePanel> {
super.dispose(); super.dispose();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colors = Theme.of(context).colorScheme; final colors = Theme.of(context).colorScheme;