import '../../core/utils/logger.dart'; import 'chat_preview.dart'; import 'chats.dart'; Map> buildContactsMap(dynamic contacts) { if (contacts is! List) return {}; final result = >{}; for (final c in contacts.whereType()) { final id = c['id']; if (id is int) result[id] = c.cast(); } return result; } CachedChat? parseChatRow( Map chat, int accountId, int currentUserId, Map> contactsMap, Map chatsConfig, Map presenceMap, Map existing, int cachedAt, ) { try { final id = chat['id']; if (id is! int) return null; final type = (chat['type'] as String?) ?? 'DIALOG'; final otherId = type == 'DIALOG' ? _otherParticipantId(chat['participants'], currentUserId) : null; final titleIcon = _resolveTitleAndIcon( chat, id, type, otherId, contactsMap, existing, ); final lastMessage = _resolveLastMessage(chat['lastMessage']); final previous = existing[id]; final sameLastMessage = previous != null && lastMessage.id != null && previous.lastMsgId == lastMessage.id; final muteFav = _resolveMuteAndFavorite(chatsConfig, id, existing); final presence = _resolvePresence(type, otherId, presenceMap); final adminsOwner = _resolveAdmins(chat); final pinned = _resolvePinnedMessage(chat['pinnedMessage']); final mentionId = int.tryParse( chat['lastMentionMessageId']?.toString() ?? '', ); return CachedChat( id: id, accountId: accountId, type: type, title: titleIcon.title, iconUrl: titleIcon.iconUrl, lastMsgId: lastMessage.id, lastMsgTime: lastMessage.time, lastMsgText: lastMessage.text, lastMsgElements: lastMessage.elements, lastMsgPreview: lastMessage.preview, lastMsgSenderId: lastMessage.senderId ?? (sameLastMessage ? previous.lastMsgSenderId : null), lastMsgStatus: sameLastMessage ? previous.lastMsgStatus : null, unreadCount: (chat['newMessages'] as int?) ?? 0, lastEventTime: (chat['lastEventTime'] as int?) ?? 0, cachedAt: cachedAt, favIndex: muteFav.favIndex, dontDisturbUntil: muteFav.dontDisturbUntil, isOnline: presence.isOnline, seenTime: presence.seenTime, participants: parseParticipants(chat['participants']), options: titleIcon.options, owner: adminsOwner.owner, admins: adminsOwner.admins, pinnedMsgId: pinned.id, pinnedMsgText: pinned.text, pinnedMsgTime: pinned.time, pinnedMsgIsPreview: pinned.isPreview, lastMentionMsgId: mentionId ?? existing[id]?.lastMentionMsgId, ); } catch (e) { logger.e("Ошибка при парсинге чата: $e"); return null; } } ({String? title, String? iconUrl, Set options}) _resolveTitleAndIcon( Map chat, int id, String type, int? otherId, Map> contactsMap, Map existing, ) { if (type == 'DIALOG') { final contact = otherId != null ? contactsMap[otherId] : null; if (contact != null) { Set options = const {}; final contactOpts = contact['options']; if (contactOpts is List) { options = contactOpts.whereType().toSet(); } return ( title: _nameFromContact(contact), iconUrl: contact['baseUrl'] as String?, options: options, ); } return ( title: existing[id]?.title, iconUrl: existing[id]?.iconUrl, options: existing[id]?.options ?? const {}, ); } Set options = const {}; final chatOpts = chat['options']; if (chatOpts is Map) { options = { for (final entry in chatOpts.entries) if (entry.value == true && entry.key is String) entry.key as String, }; } return ( title: chat['title'] as String?, iconUrl: chat['baseIconUrl'] as String?, options: options, ); } ({ int? id, int? time, String? text, String? elements, String? preview, int? senderId, }) _resolveLastMessage(dynamic lastMsg) { if (lastMsg is! Map) { return ( id: null, time: null, text: null, elements: null, preview: null, senderId: null, ); } return ( id: _asIntOrNull(lastMsg['id']), time: _asIntOrNull(lastMsg['time']), text: messagePreviewText(lastMsg), elements: messagePreviewElements(lastMsg), preview: messagePreviewMedia(lastMsg), senderId: _asIntOrNull(lastMsg['sender']), ); } int? _asIntOrNull(Object? value) { if (value is int) return value; if (value is num) return value.toInt(); if (value is String) return int.tryParse(value); return null; } ({int? id, String? text, int? time, bool isPreview}) _resolvePinnedMessage( dynamic pinned, ) { if (pinned is! Map) { return (id: null, text: null, time: null, isPreview: false); } final rawId = pinned['id']; final id = rawId is int ? rawId : int.tryParse(rawId?.toString() ?? ''); if (id == null) return (id: null, text: null, time: null, isPreview: false); final preview = pinnedMessagePreview(pinned); return ( id: id, text: preview.text, time: pinned['time'] as int?, isPreview: preview.isPreview, ); } ({int? favIndex, int dontDisturbUntil}) _resolveMuteAndFavorite( Map chatsConfig, int id, Map existing, ) { final config = chatsConfig[id.toString()] ?? chatsConfig[id]; final ex = existing[id]; if (config is Map) { final configFav = config['favIndex'] as int?; return ( favIndex: (configFav != null && configFav > 0) ? configFav : ex?.favIndex, dontDisturbUntil: (config['dontDisturbUntil'] as int?) ?? 0, ); } if (ex != null) { return (favIndex: ex.favIndex, dontDisturbUntil: ex.dontDisturbUntil); } return (favIndex: null, dontDisturbUntil: 0); } ({int seenTime, bool isOnline}) _resolvePresence( String type, int? otherId, Map presenceMap, ) { if (type != 'DIALOG' || otherId == null) { return (seenTime: 0, isOnline: false); } final presence = presenceMap[otherId.toString()] ?? presenceMap[otherId]; if (presence is Map) { return ( seenTime: (presence['seen'] as int?) ?? 0, isOnline: (presence['status'] as int?) == 1, ); } return (seenTime: 0, isOnline: false); } ({int? owner, Set admins}) _resolveAdmins(Map chat) { int? owner; final ownerRaw = chat['owner']; if (ownerRaw is int) { owner = ownerRaw; } else if (ownerRaw is String) { owner = int.tryParse(ownerRaw); } Set admins = const {}; final adminsRaw = chat['admins']; if (adminsRaw is List) { admins = adminsRaw .map((e) => e is int ? e : int.tryParse(e.toString())) .whereType() .toSet(); } else { final adminParticipants = chat['adminParticipants']; if (adminParticipants is Map) { admins = adminParticipants.keys .map((k) => k is int ? k : int.tryParse(k.toString())) .whereType() .toSet(); } } return (owner: owner, admins: admins); } int? _otherParticipantId(dynamic participants, int currentUserId) { if (participants is! Map) return null; for (final key in participants.keys) { final id = key is int ? key : int.tryParse(key.toString()); if (id != null && id != currentUserId) return id; } return null; } String? _nameFromContact(Map contact) { final names = contact['names']; if (names is! List || names.isEmpty) return null; Map? fallback; for (final entry in names) { if (entry is! Map) continue; final name = entry.cast(); fallback ??= name; if (name['type'] == 'ONEME') return name['name'] as String?; } return fallback?['name'] as String?; } List parseSearchResult(dynamic payload) { final result = (payload as Map?)?['result']; if (result is! List) return const []; final hits = []; for (final item in result) { if (item is! Map) continue; final chat = item['chat']; if (chat is! Map) continue; final id = chat['id']; if (id is! int) continue; final last = chat['lastMessage']; final link = chat['link']; hits.add( ChatSearchHit( id: id, type: (chat['type'] as String?) ?? 'CHAT', title: chat['title'] as String?, avatarUrl: chat['baseIconUrl'] as String?, subtitle: link is String && link.isNotEmpty ? '@$link' : (last is Map ? last['text'] as String? : null), ), ); } return hits; } List parseMessageResult(dynamic payload) { final result = (payload as Map?)?['result']; if (result is! List) return const []; final hits = []; for (final item in result) { if (item is! Map) continue; final message = item['message']; if (message is! Map) continue; final chatId = item['chatId']; if (chatId is! int || chatId == 0) continue; hits.add( MessageSearchHit( chatId: chatId, messageId: message['id']?.toString(), text: message['text'] as String?, time: (message['time'] as int?) ?? 0, senderId: (message['sender'] as int?) ?? 0, ), ); } return hits; } bool sameChatContent(CachedChat a, CachedChat b) { if (a.title != b.title) return false; if (a.iconUrl != b.iconUrl) return false; if (a.owner != b.owner) return false; if (a.pinnedMsgId != b.pinnedMsgId) return false; if (a.pinnedMsgText != b.pinnedMsgText) return false; if (a.pinnedMsgTime != b.pinnedMsgTime) return false; if (a.pinnedMsgIsPreview != b.pinnedMsgIsPreview) return false; if (a.dontDisturbUntil != b.dontDisturbUntil) return false; if (a.favIndex != b.favIndex) return false; if (a.lastMsgId != b.lastMsgId) return false; if (a.lastMsgTime != b.lastMsgTime) return false; if (a.lastMsgText != b.lastMsgText) return false; if (a.lastMsgElements != b.lastMsgElements) return false; if (a.lastMsgPreview != b.lastMsgPreview) return false; if (a.lastMsgSenderId != b.lastMsgSenderId) return false; if (a.unreadCount != b.unreadCount) return false; if (a.lastEventTime != b.lastEventTime) return false; if (a.isOnline != b.isOnline) return false; if (a.seenTime != b.seenTime) return false; if (a.admins.length != b.admins.length) return false; if (!a.admins.containsAll(b.admins)) return false; if (a.options.length != b.options.length) return false; if (!a.options.containsAll(b.options)) return false; if (a.participants.length != b.participants.length) return false; for (final e in a.participants.entries) { if (b.participants[e.key] != e.value) return false; } return true; }