feat(folders): исправление автосортировки папок
This commit is contained in:
@@ -544,7 +544,8 @@ class AccountModule {
|
||||
payload['lastLogin'] = sync.lastLogin;
|
||||
if (sync.configHash != null) payload['configHash'] = sync.configHash;
|
||||
} else {
|
||||
payload['presenceSync'] = 0;
|
||||
payload['presenceSync'] = -1;
|
||||
payload['chatsSync'] = -1;
|
||||
}
|
||||
|
||||
return payload;
|
||||
@@ -586,6 +587,12 @@ class AccountModule {
|
||||
await ContactsModule.syncFromLoginPayload(data, profile.id);
|
||||
await chats.syncFromLoginPayload(data, profile.id, profile.id);
|
||||
|
||||
try {
|
||||
await ContactsModule.syncFromServer(_api, profile.id);
|
||||
} catch (e) {
|
||||
logger.w('Контакты: $e');
|
||||
}
|
||||
|
||||
final config = data['config'];
|
||||
if (config is Map) {
|
||||
await FoldersModule.applyFromLoginConfig(
|
||||
|
||||
@@ -183,6 +183,14 @@ class ContactsModule {
|
||||
);
|
||||
}
|
||||
|
||||
static Future<void> syncFromServer(Api api, int accountId) async {
|
||||
final map = await api.sendRequestMap(Opcode.contactsGet, {
|
||||
'contactsSync': 0,
|
||||
});
|
||||
if (map == null) return;
|
||||
await syncFromLoginPayload(map.cast<dynamic, dynamic>(), accountId);
|
||||
}
|
||||
|
||||
static void _primeContactCache(Map<dynamic, dynamic> contact) {
|
||||
final id = contact['id'];
|
||||
if (id is! int) return;
|
||||
|
||||
@@ -55,31 +55,69 @@ class FoldersModule {
|
||||
});
|
||||
}
|
||||
|
||||
static bool chatMatchesFolder(CachedChat chat, ChatFolder folder) {
|
||||
if (folder.include != null && folder.include!.isNotEmpty) {
|
||||
return folder.include!.contains(chat.id);
|
||||
static const int filterUnread = 0;
|
||||
static const int filterChannel = 2;
|
||||
static const int filterGroup = 3;
|
||||
static const int filterContact = 8;
|
||||
static const int filterNotContact = 9;
|
||||
static const int filterBot = 10;
|
||||
|
||||
static int? _filterCode(dynamic raw) {
|
||||
if (raw is int) return raw;
|
||||
if (raw is String) {
|
||||
final n = int.tryParse(raw);
|
||||
if (n != null) return n;
|
||||
switch (raw) {
|
||||
case 'UNREAD':
|
||||
return filterUnread;
|
||||
case 'CHANNEL':
|
||||
return filterChannel;
|
||||
case 'GROUP':
|
||||
case 'CHAT':
|
||||
return filterGroup;
|
||||
case 'CONTACT':
|
||||
return filterContact;
|
||||
case 'NOT_CONTACT':
|
||||
return filterNotContact;
|
||||
case 'BOT':
|
||||
return filterBot;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static bool chatMatchesFolder(
|
||||
CachedChat chat,
|
||||
ChatFolder folder, {
|
||||
required int myId,
|
||||
required Set<int> contactIds,
|
||||
}) {
|
||||
if (folder.include != null && folder.include!.contains(chat.id)) {
|
||||
return true;
|
||||
}
|
||||
if (folder.filters.isEmpty) return false;
|
||||
|
||||
final hasContact = folder.filters.any(
|
||||
(f) => f == 9 || f == '9' || f == 'CONTACT',
|
||||
);
|
||||
final hasNotContact = folder.filters.any(
|
||||
(f) => f == 8 || f == '8' || f == 'NOT_CONTACT',
|
||||
);
|
||||
final isDialog = chat.type == 'DIALOG';
|
||||
final isBot = isDialog && chat.options.contains('BOT');
|
||||
final peerId = isDialog ? chat.id ^ myId : null;
|
||||
final isSelf = peerId != null && peerId == myId;
|
||||
final isContact =
|
||||
isDialog && !isSelf && peerId != null && contactIds.contains(peerId);
|
||||
|
||||
if (hasContact && hasNotContact) {
|
||||
if (chat.type != 'DIALOG') return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
for (final filter in folder.filters) {
|
||||
if (filter == 0 || filter == '0' || filter == 'UNREAD') {
|
||||
if (chat.unreadCount > 0) return true;
|
||||
} else if (filter == 9 || filter == '9' || filter == 'CONTACT') {
|
||||
if (chat.type == 'DIALOG') return true;
|
||||
} else if (filter == 8 || filter == '8' || filter == 'NOT_CONTACT') {
|
||||
if (chat.type == 'CHAT' || chat.type == 'CHANNEL') return true;
|
||||
for (final raw in folder.filters) {
|
||||
switch (_filterCode(raw)) {
|
||||
case filterUnread:
|
||||
if (chat.unreadCount > 0) return true;
|
||||
case filterChannel:
|
||||
if (chat.type == 'CHANNEL') return true;
|
||||
case filterGroup:
|
||||
if (chat.type == 'CHAT' || chat.type == 'GROUP') return true;
|
||||
case filterContact:
|
||||
if (isDialog && !isBot && isContact) return true;
|
||||
case filterNotContact:
|
||||
if (isDialog && !isBot && !isSelf && !isContact) return true;
|
||||
case filterBot:
|
||||
if (isBot) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -8,6 +8,7 @@ class KometSettings {
|
||||
static const _kGhostMode = 'komet_ghost_mode';
|
||||
static const _kAntiRead = 'komet_anti_read';
|
||||
static const _kSelfOnlineCheck = 'komet_self_online_check';
|
||||
static const _kHideAllChatsFolder = 'komet_hide_all_chats_folder';
|
||||
|
||||
static final ValueNotifier<bool> viewDeleted = ValueNotifier(false);
|
||||
static final ValueNotifier<bool> viewRedacted = ValueNotifier(false);
|
||||
@@ -15,6 +16,7 @@ class KometSettings {
|
||||
static final ValueNotifier<bool> ghostMode = ValueNotifier(false);
|
||||
static final ValueNotifier<bool> antiRead = ValueNotifier(false);
|
||||
static final ValueNotifier<bool> selfOnlineCheck = ValueNotifier(true);
|
||||
static final ValueNotifier<bool> hideAllChatsFolder = ValueNotifier(false);
|
||||
|
||||
static Future<void> load() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
@@ -24,6 +26,7 @@ class KometSettings {
|
||||
ghostMode.value = prefs.getBool(_kGhostMode) ?? false;
|
||||
antiRead.value = prefs.getBool(_kAntiRead) ?? false;
|
||||
selfOnlineCheck.value = prefs.getBool(_kSelfOnlineCheck) ?? true;
|
||||
hideAllChatsFolder.value = prefs.getBool(_kHideAllChatsFolder) ?? false;
|
||||
}
|
||||
|
||||
static Future<void> setViewDeleted(bool value) async {
|
||||
@@ -61,4 +64,10 @@ class KometSettings {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool(_kSelfOnlineCheck, value);
|
||||
}
|
||||
|
||||
static Future<void> setHideAllChatsFolder(bool value) async {
|
||||
hideAllChatsFolder.value = value;
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool(_kHideAllChatsFolder, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ abstract class Opcode {
|
||||
static const int reconnect = 3; // Реконнект
|
||||
static const int log = 5; // Аналитика / события
|
||||
static const int sessionInit = 6; // Инициализация сессии (хэндшейк)
|
||||
static const int contactsGet = 8; // Синхронизация списка контактов
|
||||
|
||||
// ── Profile ────────────────────────────────────────────────────────
|
||||
static const int profile = 16; // Обновление профиля
|
||||
@@ -226,6 +227,7 @@ abstract class Opcode {
|
||||
reconnect: 'RECONNECT',
|
||||
log: 'LOG',
|
||||
sessionInit: 'SESSION_INIT',
|
||||
contactsGet: 'CONTACTS_GET',
|
||||
profile: 'PROFILE',
|
||||
authRequest: 'AUTH_REQUEST',
|
||||
auth: 'AUTH',
|
||||
|
||||
@@ -36,10 +36,12 @@ import '../../../core/utils/haptics.dart';
|
||||
import '../../../core/config/app_animations.dart';
|
||||
import '../../../core/config/app_stories.dart';
|
||||
import '../../../core/config/app_colors.dart';
|
||||
import '../../../core/config/komet_settings.dart';
|
||||
import '../../../backend/models/chat_folder.dart';
|
||||
import '../../../backend/modules/account.dart';
|
||||
import '../../../backend/modules/chats.dart';
|
||||
import '../../../backend/modules/cloud_storage.dart';
|
||||
import '../../../backend/modules/contacts.dart';
|
||||
import '../../../backend/modules/folders.dart';
|
||||
import '../../../core/storage/app_database.dart';
|
||||
import '../../../core/storage/draft_store.dart';
|
||||
@@ -133,6 +135,7 @@ class _ChatListScreenState extends State<ChatListScreen>
|
||||
String? _selectedFolderId;
|
||||
|
||||
List<ChatFolder> _folders = [];
|
||||
Set<int> _contactIds = <int>{};
|
||||
|
||||
int _currentNavIndex = 0;
|
||||
|
||||
@@ -545,6 +548,7 @@ class _ChatListScreenState extends State<ChatListScreen>
|
||||
DraftStore.instance.revision.addListener(_onDraftsChanged);
|
||||
AppStories.current.addListener(_onStoriesEnabledChanged);
|
||||
storiesModule.storiesChanged.addListener(_onStoriesDataChanged);
|
||||
KometSettings.hideAllChatsFolder.addListener(_requestReload);
|
||||
_maybeLoadStories();
|
||||
_typingSub = api.pushStream
|
||||
.where((p) => p.opcode == Opcode.notifTyping)
|
||||
@@ -708,6 +712,9 @@ class _ChatListScreenState extends State<ChatListScreen>
|
||||
final loadedChats = await chats.getChats(p.id);
|
||||
var folders = await FoldersModule.loadFolders(p.id);
|
||||
final foldersKnown = await FoldersModule.hasReceivedFoldersList(p.id);
|
||||
final contactIds = (await ContactsModule.getContacts(p.id))
|
||||
.map((c) => c.id)
|
||||
.toSet();
|
||||
|
||||
final allChatsFolder = ChatFolder(
|
||||
id: 'all.chat.folder',
|
||||
@@ -717,7 +724,14 @@ class _ChatListScreenState extends State<ChatListScreen>
|
||||
widgets: [],
|
||||
);
|
||||
|
||||
if (!folders.any((f) => FoldersModule.isAllChatsFolder(f))) {
|
||||
final hasRealFolders = folders.any(
|
||||
(f) => !FoldersModule.isAllChatsFolder(f),
|
||||
);
|
||||
if (KometSettings.hideAllChatsFolder.value && hasRealFolders) {
|
||||
folders = folders
|
||||
.where((f) => !FoldersModule.isAllChatsFolder(f))
|
||||
.toList();
|
||||
} else if (!folders.any((f) => FoldersModule.isAllChatsFolder(f))) {
|
||||
folders = [allChatsFolder, ...folders];
|
||||
}
|
||||
|
||||
@@ -727,6 +741,7 @@ class _ChatListScreenState extends State<ChatListScreen>
|
||||
final filteredChats = loadedChats
|
||||
.where((c) => !CloudStorageModule.isCloudStorageGroup(c))
|
||||
.toList();
|
||||
|
||||
final newIds = filteredChats.map((c) => c.id.toString()).toSet();
|
||||
final entering = _didInitialChatLoad
|
||||
? newIds.difference(_knownChatIds)
|
||||
@@ -740,6 +755,7 @@ class _ChatListScreenState extends State<ChatListScreen>
|
||||
setState(() {
|
||||
_profile = p;
|
||||
_chats = filteredChats;
|
||||
_contactIds = contactIds;
|
||||
_enteringChatIds = entering;
|
||||
_chatListRevision++;
|
||||
_folders = folders;
|
||||
@@ -858,6 +874,7 @@ class _ChatListScreenState extends State<ChatListScreen>
|
||||
final baseKey = Object.hash(
|
||||
identityHashCode(_chats),
|
||||
identityHashCode(_folders),
|
||||
identityHashCode(_contactIds),
|
||||
);
|
||||
if (_pageChatsBaseKey != baseKey) {
|
||||
_pageChatsBaseKey = baseKey;
|
||||
@@ -873,10 +890,18 @@ class _ChatListScreenState extends State<ChatListScreen>
|
||||
base = _chats;
|
||||
} else {
|
||||
final folder = _folders[pageIndex];
|
||||
final myId = _profile?.id ?? 0;
|
||||
base = FoldersModule.isAllChatsFolder(folder)
|
||||
? _chats
|
||||
: _chats
|
||||
.where((c) => FoldersModule.chatMatchesFolder(c, folder))
|
||||
.where(
|
||||
(c) => FoldersModule.chatMatchesFolder(
|
||||
c,
|
||||
folder,
|
||||
myId: myId,
|
||||
contactIds: _contactIds,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
final pinned = base.where((c) => (c.favIndex ?? 0) > 0).toList()
|
||||
@@ -1127,6 +1152,7 @@ class _ChatListScreenState extends State<ChatListScreen>
|
||||
DraftStore.instance.revision.removeListener(_onDraftsChanged);
|
||||
AppStories.current.removeListener(_onStoriesEnabledChanged);
|
||||
storiesModule.storiesChanged.removeListener(_onStoriesDataChanged);
|
||||
KometSettings.hideAllChatsFolder.removeListener(_requestReload);
|
||||
_loginSub?.cancel();
|
||||
_stateSub?.cancel();
|
||||
_typingSub?.cancel();
|
||||
|
||||
@@ -67,6 +67,28 @@ class KometSettingsScreen extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
const SectionHeader(
|
||||
'Папки',
|
||||
padding: EdgeInsets.fromLTRB(8, 0, 8, 8),
|
||||
fontSize: 14,
|
||||
),
|
||||
SettingsCard(
|
||||
children: [
|
||||
ValueListenableBuilder<bool>(
|
||||
valueListenable: KometSettings.hideAllChatsFolder,
|
||||
builder: (context, value, _) => SettingsToggleTile(
|
||||
icon: Symbols.folder_off,
|
||||
label: 'Hide "All" folder',
|
||||
subtitle:
|
||||
'Скрыть папку «Все», когда есть другие папки. '
|
||||
'Чаты сортируются только по вашим папкам',
|
||||
value: value,
|
||||
onChanged: KometSettings.setHideAllChatsFolder,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
const SectionHeader(
|
||||
'Ghost Mode',
|
||||
padding: EdgeInsets.fromLTRB(8, 0, 8, 8),
|
||||
|
||||
Reference in New Issue
Block a user