From f093e7c802417fe1aa6d1e1c95e090abb9c4169d Mon Sep 17 00:00:00 2001 From: Jganenok Date: Fri, 15 May 2026 17:02:22 +0700 Subject: [PATCH] =?UTF-8?q?=D0=BF=D0=BE=D1=87=D0=B8=D0=BD=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=B3=D0=B0=D0=BB=D0=BE=D1=87=D0=BA=D0=B8,=20=D0=B7=D0=B0?= =?UTF-8?q?=D0=BA=D1=80=D0=B5=D0=BF=D0=BB=D0=B5=D0=BD=D0=BD=D1=8B=D0=B5=20?= =?UTF-8?q?=D1=87=D0=B0=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/backend/modules/chats.dart | 2 + lib/backend/modules/contacts.dart | 2 +- lib/backend/modules/messages.dart | 17 +++-- .../screens/chats/chat_list_screen.dart | 63 +++++++++++++++---- 4 files changed, 66 insertions(+), 18 deletions(-) diff --git a/lib/backend/modules/chats.dart b/lib/backend/modules/chats.dart index 8157055..e2251d2 100644 --- a/lib/backend/modules/chats.dart +++ b/lib/backend/modules/chats.dart @@ -132,6 +132,7 @@ class ChatsModule { final chatsConfig = configMap['chats'] is Map ? configMap['chats'] as Map : {}; + // Presence for online statuses final presenceMap = data['presence'] is Map ? data['presence'] as Map : {}; final cachedAt = DateTime.now().millisecondsSinceEpoch; @@ -272,6 +273,7 @@ class ChatsModule { dontDisturbUntil = (config['dontDisturbUntil'] as int?) ?? 0; } + int seenTime = 0; bool isOnline = false; if (type == 'DIALOG' && otherId != null) { diff --git a/lib/backend/modules/contacts.dart b/lib/backend/modules/contacts.dart index fda11db..ade6ad3 100644 --- a/lib/backend/modules/contacts.dart +++ b/lib/backend/modules/contacts.dart @@ -29,7 +29,7 @@ class CachedContact { bool get isOfficial => options.contains('OFFICIAL'); bool get isBot => options.contains('BOT'); bool get isServiceAccount => options.contains('SERVICE_ACCOUNT'); - bool get isVerified => isOfficial || isBot || isServiceAccount; + bool get isVerified => isOfficial; factory CachedContact.fromDbRow(Map row) => CachedContact( id: row['id'] as int, diff --git a/lib/backend/modules/messages.dart b/lib/backend/modules/messages.dart index 06ac5c7..b2b4b5c 100644 --- a/lib/backend/modules/messages.dart +++ b/lib/backend/modules/messages.dart @@ -8,19 +8,19 @@ import '../../models/attachment.dart'; class ContactCache { static final Map _nameCache = {}; static final Map _avatarCache = {}; + static final Map> _optionsCache = {}; - static void put(int id, String name) { - _nameCache[id] = name; - } + static void put(int id, String name) => _nameCache[id] = name; static void putAvatar(int id, String? baseUrl) { - if (baseUrl != null) { - _avatarCache[id] = baseUrl; - } + if (baseUrl != null) _avatarCache[id] = baseUrl; } + static void putOptions(int id, Set opts) => _optionsCache[id] = opts; + static String? get(int id) => _nameCache[id]; static String? getAvatar(int id) => _avatarCache[id]; + static bool isOfficial(int id) => _optionsCache[id]?.contains('OFFICIAL') ?? false; } class TranscriptionResult { @@ -545,6 +545,11 @@ class MessagesModule { final baseUrl = contact['baseUrl'] as String?; ContactCache.putAvatar(contactId, baseUrl); + final rawOpts = contact['options']; + if (rawOpts is List) { + ContactCache.putOptions(contactId, rawOpts.whereType().toSet()); + } + return fullName; } } diff --git a/lib/frontend/screens/chats/chat_list_screen.dart b/lib/frontend/screens/chats/chat_list_screen.dart index 13ebc9c..906dadc 100644 --- a/lib/frontend/screens/chats/chat_list_screen.dart +++ b/lib/frontend/screens/chats/chat_list_screen.dart @@ -376,13 +376,21 @@ class _ChatListScreenState extends State } List _chatsForPageIndex(int pageIndex) { - if (_folders.isEmpty) return _chats; - if (pageIndex < 0 || pageIndex >= _folders.length) return _chats; - final folder = _folders[pageIndex]; - if (FoldersModule.isAllChatsFolder(folder)) return _chats; - return _chats - .where((c) => FoldersModule.chatMatchesFolder(c, folder)) - .toList(); + List base; + if (_folders.isEmpty) { + base = _chats; + } else if (pageIndex < 0 || pageIndex >= _folders.length) { + base = _chats; + } else { + final folder = _folders[pageIndex]; + base = FoldersModule.isAllChatsFolder(folder) + ? _chats + : _chats.where((c) => FoldersModule.chatMatchesFolder(c, folder)).toList(); + } + final pinned = base.where((c) => (c.favIndex ?? 0) > 0).toList() + ..sort((a, b) => a.favIndex!.compareTo(b.favIndex!)); + final regular = base.where((c) => (c.favIndex ?? 0) <= 0).toList(); + return [...pinned, ...regular]; } void _syncFolderChatScrollControllers() { @@ -1088,7 +1096,25 @@ class _ChatListScreenState extends State if (_isInitialLoading) { return _buildChatShimmer(); } - final chat = chats[index]; + + final pinnedCount = chats.where((c) => (c.favIndex ?? 0) > 0).length; + final hasSeparator = pinnedCount > 0 && pinnedCount < chats.length; + + // Insert separator row between pinned and regular sections + if (hasSeparator && index == pinnedCount) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Divider( + height: 1, + thickness: 0.5, + color: cs.outlineVariant.withValues(alpha: 0.5), + ), + ); + } + + final chatIndex = hasSeparator && index > pinnedCount ? index - 1 : index; + final chat = chats[chatIndex]; + final isPinned = (chat.favIndex ?? 0) > 0; if (chat.type.isNotEmpty && chat.type == "DIALOG" && chat.id != 0) { final secondId = chat.participants.entries @@ -1097,6 +1123,9 @@ class _ChatListScreenState extends State .key; final name = ContactCache.get(secondId); final avatar = ContactCache.getAvatar(secondId); + // ContactCache.isOfficial covers contacts loaded via opcode 32; + // chat.isOfficial covers contacts from the login payload. + final isVerified = ContactCache.isOfficial(secondId) || chat.isOfficial; return _buildChatItem( chat.id.toString(), @@ -1107,7 +1136,8 @@ class _ChatListScreenState extends State isOnline: chat.isOnline, unreadCount: chat.unreadCount, isMuted: chat.dontDisturbUntil > 0, - isVerified: chat.isOfficial, + isVerified: isVerified, + isPinned: isPinned, chatType: "DIALOG", ); } else { @@ -1124,7 +1154,7 @@ class _ChatListScreenState extends State if (name?.isNotEmpty == true && chat.id != 0) { fullMsg += "$name: "; } - + if (chat.lastMsgText?.isNotEmpty == true) { fullMsg += chat.lastMsgText ?? ""; } @@ -1141,10 +1171,11 @@ class _ChatListScreenState extends State unreadCount: chat.unreadCount, isMuted: chat.dontDisturbUntil > 0, isVerified: chat.isOfficial, + isPinned: isPinned, chatType: chat.type, ); } - }, childCount: _isInitialLoading ? 10 : chats.length), + }, childCount: _isInitialLoading ? 10 : chats.length + (chats.any((c) => (c.favIndex ?? 0) > 0) && chats.any((c) => (c.favIndex ?? 0) <= 0) ? 1 : 0)), ), SliverPadding( padding: EdgeInsets.only( @@ -1760,6 +1791,7 @@ class _ChatListScreenState extends State int unreadCount = 0, bool isMuted = false, bool isVerified = false, + bool isPinned = false, String chatType = "CHAT", }) { final cs = Theme.of(context).colorScheme; @@ -1898,6 +1930,15 @@ Navigator.push( weight: 400, ), ], + if (isPinned) ...[ + const SizedBox(width: 4), + Icon( + Symbols.keep, + color: cs.outlineVariant, + size: 14, + weight: 400, + ), + ], const SizedBox(width: 8), Text( time,