From 77826e91d4b1f90e878b227c13d002c9af511b69 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 28 Jan 2020 19:46:28 +0400 Subject: [PATCH] Chat List Filter improvements --- .../Sources/ChatListController.swift | 18 +++++- .../ChatListFilterPresetController.swift | 60 +++++++++++++----- .../ChatListFilterPresetListController.swift | 17 ++--- .../Sources/Node/ChatListNode.swift | 52 ++++++++------- .../Sources/Node/ChatListNodeLocation.swift | 35 +++++------ .../Sources/Node/ChatListViewTransition.swift | 2 +- .../TabBarChatListFilterController.swift | 2 +- .../Sources/AllChatListHolesView.swift | 63 +++++++++++++++++++ .../Postbox/Sources/ChatListIndexTable.swift | 11 ++-- .../Postbox/Sources/ChatListTable.swift | 29 +++++++++ submodules/Postbox/Sources/Views.swift | 11 ++++ .../Sources/ManagedChatListHoles.swift | 37 +++++++++-- .../Sources/ChatListFilterSettings.swift | 18 ++++-- 13 files changed, 266 insertions(+), 89 deletions(-) create mode 100644 submodules/Postbox/Sources/AllChatListHolesView.swift diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index e86ca39607..57510f6e06 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1822,7 +1822,23 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, guard let strongSelf = self else { return } - strongSelf.push(chatListFilterPresetListController(context: strongSelf.context)) + strongSelf.push(chatListFilterPresetListController(context: strongSelf.context, updated: { presets in + guard let strongSelf = self else { + return + } + if let currentPreset = strongSelf.chatListDisplayNode.chatListNode.chatListFilter { + var found = false + if let index = presets.index(where: { $0.id == currentPreset.id }) { + found = true + if currentPreset != presets[index] { + strongSelf.chatListDisplayNode.chatListNode.chatListFilter = presets[index] + } + } + if !found { + strongSelf.chatListDisplayNode.chatListNode.chatListFilter = nil + } + } + })) }, updatePreset: { value in guard let strongSelf = self else { return diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift index 4599784653..517bc1a116 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift @@ -53,9 +53,11 @@ private func filterEntry(presentationData: ItemListPresentationData, arguments: private enum ChatListFilterPresetEntryStableId: Hashable { case index(Int) case peer(PeerId) + case additionalPeerInfo } private enum ChatListFilterPresetEntry: ItemListNodeEntry { + case nameHeader(String) case name(placeholder: String, value: String) case filterPrivateChats(title: String, value: Bool) case filterSecretChats(title: String, value: Bool) @@ -68,46 +70,51 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry { case additionalPeersHeader(String) case addAdditionalPeer(title: String) case additionalPeer(index: Int, peer: RenderedPeer, isRevealed: Bool) + case additionalPeerInfo(String) var section: ItemListSectionId { switch self { - case .name: + case .nameHeader, .name: return ChatListFilterPresetControllerSection.name.rawValue case .filterPrivateChats, .filterSecretChats, .filterPrivateGroups, .filterBots, .filterPublicGroups, .filterChannels: return ChatListFilterPresetControllerSection.categories.rawValue case .filterMuted, .filterRead: return ChatListFilterPresetControllerSection.excludeCategories.rawValue - case .additionalPeersHeader, .addAdditionalPeer, .additionalPeer: + case .additionalPeersHeader, .addAdditionalPeer, .additionalPeer, .additionalPeerInfo: return ChatListFilterPresetControllerSection.additionalPeers.rawValue } } var stableId: ChatListFilterPresetEntryStableId { switch self { - case .name: + case .nameHeader: return .index(0) - case .filterPrivateChats: + case .name: return .index(1) - case .filterSecretChats: + case .filterPrivateChats: return .index(2) - case .filterPrivateGroups: + case .filterSecretChats: return .index(3) - case .filterBots: + case .filterPrivateGroups: return .index(4) - case .filterPublicGroups: + case .filterBots: return .index(5) - case .filterChannels: + case .filterPublicGroups: return .index(6) - case .filterMuted: + case .filterChannels: return .index(7) - case .filterRead: + case .filterMuted: return .index(8) - case .additionalPeersHeader: + case .filterRead: return .index(9) - case .addAdditionalPeer: + case .additionalPeersHeader: return .index(10) + case .addAdditionalPeer: + return .index(11) case let .additionalPeer(additionalPeer): return .peer(additionalPeer.peer.peerId) + case .additionalPeerInfo: + return .additionalPeerInfo } } @@ -119,6 +126,8 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry { return lhsIndex < rhsIndex case .peer: return true + case .additionalPeerInfo: + return true } case .peer: switch lhs { @@ -126,6 +135,8 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry { switch rhs.stableId { case .index: return false + case .additionalPeerInfo: + return true case .peer: switch rhs { case let .additionalPeer(rhsIndex, _, _): @@ -137,12 +148,23 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry { default: preconditionFailure() } + case .additionalPeerInfo: + switch rhs.stableId { + case .index: + return false + case .peer: + return false + case .additionalPeerInfo: + return false + } } } func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { let arguments = arguments as! ChatListFilterPresetControllerArguments switch self { + case let .nameHeader(title): + return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) case let .name(placeholder, value): return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(), text: value, placeholder: placeholder, type: .regular(capitalization: true, autocorrection: false), sectionId: self.section, textUpdated: { value in arguments.updateState { current in @@ -201,6 +223,8 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry { }, removePeer: { id in arguments.deleteAdditionalPeer(id) }) + case let .additionalPeerInfo(text): + return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) } } } @@ -226,6 +250,7 @@ private struct ChatListFilterPresetControllerState: Equatable { private func chatListFilterPresetControllerEntries(presentationData: PresentationData, state: ChatListFilterPresetControllerState, peers: [RenderedPeer]) -> [ChatListFilterPresetEntry] { var entries: [ChatListFilterPresetEntry] = [] + entries.append(.nameHeader("NAME")) entries.append(.name(placeholder: "Preset Name", value: state.name)) entries.append(.filterPrivateChats(title: "Private Chats", value: state.includeCategories.contains(.privateChats))) @@ -245,10 +270,12 @@ private func chatListFilterPresetControllerEntries(presentationData: Presentatio entries.append(.additionalPeer(index: entries.count, peer: peer, isRevealed: state.revealedPeerId == peer.peerId)) } + entries.append(.additionalPeerInfo("These chats will always be included in the list.")) + return entries } -func chatListFilterPresetController(context: AccountContext, currentPreset: ChatListFilterPreset?) -> ViewController { +func chatListFilterPresetController(context: AccountContext, currentPreset: ChatListFilterPreset?, updated: @escaping ([ChatListFilterPreset]) -> Void) -> ViewController { let initialName: String if let currentPreset = currentPreset { switch currentPreset.name { @@ -352,14 +379,15 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat }) let rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: state.isComplete, action: { let state = stateValue.with { $0 } - let preset = ChatListFilterPreset(name: .custom(state.name), includeCategories: state.includeCategories, additionallyIncludePeers: state.additionallyIncludePeers) + let preset = ChatListFilterPreset(id: currentPreset?.id ?? arc4random64(), name: .custom(state.name), includeCategories: state.includeCategories, additionallyIncludePeers: state.additionallyIncludePeers) let _ = (updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { settings in var settings = settings settings.presets = settings.presets.filter { $0 != preset && $0 != currentPreset } settings.presets.append(preset) return settings }) - |> deliverOnMainQueue).start(completed: { + |> deliverOnMainQueue).start(next: { settings in + updated(settings.presets) dismissImpl?() }) }) diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift index d1f55c8d6d..3d51524b2a 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift @@ -46,7 +46,7 @@ private func stringForUserCount(_ peers: [PeerId: SelectivePrivacyPeer], strings private enum ChatListFilterPresetListEntryStableId: Hashable { case listHeader - case preset(ChatListFilterPresetName) + case preset(Int64) case addItem case listFooter } @@ -82,7 +82,7 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry { case .listHeader: return .listHeader case let .preset(preset): - return .preset(preset.preset.name) + return .preset(preset.preset.id) case .addItem: return .addItem case .listFooter: @@ -141,7 +141,7 @@ private func chatListFilterPresetListControllerEntries(presentationData: Present return entries } -func chatListFilterPresetListController(context: AccountContext) -> ViewController { +func chatListFilterPresetListController(context: AccountContext, updated: @escaping ([ChatListFilterPreset]) -> Void) -> ViewController { let initialState = ChatListFilterPresetListControllerState() let statePromise = ValuePromise(initialState, ignoreRepeated: true) let stateValue = Atomic(value: initialState) @@ -154,9 +154,9 @@ func chatListFilterPresetListController(context: AccountContext) -> ViewControll var presentControllerImpl: ((ViewController, Any?) -> Void)? let arguments = ChatListFilterPresetListControllerArguments(context: context, openPreset: { preset in - pushControllerImpl?(chatListFilterPresetController(context: context, currentPreset: preset)) + pushControllerImpl?(chatListFilterPresetController(context: context, currentPreset: preset, updated: updated)) }, addNew: { - pushControllerImpl?(chatListFilterPresetController(context: context, currentPreset: nil)) + pushControllerImpl?(chatListFilterPresetController(context: context, currentPreset: nil, updated: updated)) }, setItemWithRevealedOptions: { preset, fromPreset in updateState { state in var state = state @@ -166,13 +166,16 @@ func chatListFilterPresetListController(context: AccountContext) -> ViewControll return state } }, removePreset: { preset in - let _ = updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { settings in + let _ = (updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { settings in var settings = settings if let index = settings.presets.index(of: preset) { settings.presets.remove(at: index) } return settings - }).start() + }) + |> deliverOnMainQueue).start(next: { settings in + updated(settings.presets) + }) }) let preferences = context.account.postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.chatListFilterSettings]) diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index ab9abd8fe3..3bd95959cd 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -370,6 +370,12 @@ public final class ChatListNode: ListView { didSet { if self.chatListFilter != oldValue { self.chatListFilterValue.set(self.chatListFilter) + + if self.chatListFilter?.includeCategories != oldValue?.includeCategories || self.chatListFilter?.additionallyIncludePeers != oldValue?.additionallyIncludePeers { + if let currentLocation = self.currentLocation { + self.setChatListLocation(.initial(count: 50, filter: self.chatListFilter)) + } + } } } } @@ -533,20 +539,12 @@ public final class ChatListNode: ListView { let viewProcessingQueue = self.viewProcessingQueue - let chatListViewUpdate = combineLatest(self.chatListLocation.get(), self.chatListFilterValue.get()) - |> distinctUntilChanged(isEqual: { lhs, rhs in - if lhs.0 != rhs.0 { - return false - } - if lhs.1 != rhs.1 { - return false - } - return true - }) - |> mapToSignal { location, filter -> Signal<(ChatListNodeViewUpdate, ChatListFilterPreset?), NoError> in - return chatListViewForLocation(groupId: groupId, filter: filter, location: location, account: context.account) + let chatListViewUpdate = self.chatListLocation.get() + |> distinctUntilChanged + |> mapToSignal { location -> Signal<(ChatListNodeViewUpdate, ChatListFilterPreset?), NoError> in + return chatListViewForLocation(groupId: groupId, location: location, account: context.account) |> map { update in - return (update, filter) + return (update, location.filter) } } @@ -792,9 +790,9 @@ public final class ChatListNode: ListView { if let range = range.loadedRange { var location: ChatListNodeLocation? if range.firstIndex < 5 && originalView.laterIndex != nil { - location = .navigation(index: originalView.entries[originalView.entries.count - 1].index) + location = .navigation(index: originalView.entries[originalView.entries.count - 1].index, filter: strongSelf.chatListFilter) } else if range.firstIndex >= 5 && range.lastIndex >= originalView.entries.count - 5 && originalView.earlierIndex != nil { - location = .navigation(index: originalView.entries[0].index) + location = .navigation(index: originalView.entries[0].index, filter: strongSelf.chatListFilter) } if let location = location, location != strongSelf.currentLocation { @@ -850,10 +848,10 @@ public final class ChatListNode: ListView { let initialLocation: ChatListNodeLocation switch mode { - case .chatList: - initialLocation = .initial(count: 50) - case .peers: - initialLocation = .initial(count: 200) + case .chatList: + initialLocation = .initial(count: 50, filter: self.chatListFilter) + case .peers: + initialLocation = .initial(count: 200, filter: self.chatListFilter) } self.setChatListLocation(initialLocation) @@ -1332,12 +1330,12 @@ public final class ChatListNode: ListView { if view.laterIndex == nil { self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) } else { - let location: ChatListNodeLocation = .scroll(index: .absoluteUpperBound, sourceIndex: .absoluteLowerBound, scrollPosition: .top(0.0), animated: true) + let location: ChatListNodeLocation = .scroll(index: .absoluteUpperBound, sourceIndex: .absoluteLowerBound, scrollPosition: .top(0.0), animated: true, filter: self.chatListFilter) self.setChatListLocation(location) } } else { let location: ChatListNodeLocation = .scroll(index: .absoluteUpperBound, sourceIndex: .absoluteLowerBound - , scrollPosition: .top(0.0), animated: true) + , scrollPosition: .top(0.0), animated: true, filter: self.chatListFilter) self.setChatListLocation(location) } } @@ -1373,11 +1371,11 @@ public final class ChatListNode: ListView { if let index = index { let location: ChatListNodeLocation = .scroll(index: index, sourceIndex: self?.currentlyVisibleLatestChatListIndex() ?? .absoluteUpperBound - , scrollPosition: .center(.top), animated: true) + , scrollPosition: .center(.top), animated: true, filter: strongSelf.chatListFilter) strongSelf.setChatListLocation(location) } else { let location: ChatListNodeLocation = .scroll(index: .absoluteUpperBound, sourceIndex: .absoluteLowerBound - , scrollPosition: .top(0.0), animated: true) + , scrollPosition: .top(0.0), animated: true, filter: strongSelf.chatListFilter) strongSelf.setChatListLocation(location) } }) @@ -1433,7 +1431,7 @@ public final class ChatListNode: ListView { guard let strongSelf = self, let index = index else { return } - let location: ChatListNodeLocation = .scroll(index: index, sourceIndex: strongSelf.currentlyVisibleLatestChatListIndex() ?? .absoluteUpperBound, scrollPosition: .center(.top), animated: true) + let location: ChatListNodeLocation = .scroll(index: index, sourceIndex: strongSelf.currentlyVisibleLatestChatListIndex() ?? .absoluteUpperBound, scrollPosition: .center(.top), animated: true, filter: strongSelf.chatListFilter) strongSelf.setChatListLocation(location) strongSelf.peerSelected?(index.messageIndex.id.peerId, false, false) }) @@ -1457,7 +1455,7 @@ public final class ChatListNode: ListView { } } if let target = target { - let location: ChatListNodeLocation = .scroll(index: target.0, sourceIndex: .absoluteLowerBound, scrollPosition: .center(.top), animated: true) + let location: ChatListNodeLocation = .scroll(index: target.0, sourceIndex: .absoluteLowerBound, scrollPosition: .center(.top), animated: true, filter: self.chatListFilter) self.setChatListLocation(location) self.peerSelected?(target.1, false, false) } @@ -1473,12 +1471,12 @@ public final class ChatListNode: ListView { guard let self = self else { return } - let _ = (chatListViewForLocation(groupId: self.groupId, filter: filter, location: .initial(count: 10), account: self.context.account) + let _ = (chatListViewForLocation(groupId: self.groupId, location: .initial(count: 10, filter: filter), account: self.context.account) |> take(1) |> deliverOnMainQueue).start(next: { update in let entries = update.view.entries if entries.count > index, case let .MessageEntry(index, _, _, _, _, renderedPeer, _, _, _) = entries[10 - index - 1] { - let location: ChatListNodeLocation = .scroll(index: index, sourceIndex: .absoluteLowerBound, scrollPosition: .center(.top), animated: true) + let location: ChatListNodeLocation = .scroll(index: index, sourceIndex: .absoluteLowerBound, scrollPosition: .center(.top), animated: true, filter: filter) self.setChatListLocation(location) self.peerSelected?(renderedPeer.peerId, false, false) } diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift index 231509cbe6..314f319a9d 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift @@ -7,21 +7,18 @@ import Display import TelegramUIPreferences enum ChatListNodeLocation: Equatable { - case initial(count: Int) - case navigation(index: ChatListIndex) - case scroll(index: ChatListIndex, sourceIndex: ChatListIndex, scrollPosition: ListViewScrollPosition, animated: Bool) + case initial(count: Int, filter: ChatListFilterPreset?) + case navigation(index: ChatListIndex, filter: ChatListFilterPreset?) + case scroll(index: ChatListIndex, sourceIndex: ChatListIndex, scrollPosition: ListViewScrollPosition, animated: Bool, filter: ChatListFilterPreset?) - static func ==(lhs: ChatListNodeLocation, rhs: ChatListNodeLocation) -> Bool { - switch lhs { - case let .navigation(index): - switch rhs { - case .navigation(index): - return true - default: - return false - } - default: - return false + var filter: ChatListFilterPreset? { + switch self { + case let .initial(initial): + return initial.filter + case let .navigation(navigation): + return navigation.filter + case let .scroll(scroll): + return scroll.filter } } } @@ -32,9 +29,9 @@ struct ChatListNodeViewUpdate { let scrollPosition: ChatListNodeViewScrollPosition? } -func chatListViewForLocation(groupId: PeerGroupId, filter: ChatListFilterPreset?, location: ChatListNodeLocation, account: Account) -> Signal { +func chatListViewForLocation(groupId: PeerGroupId, location: ChatListNodeLocation, account: Account) -> Signal { let filterPredicate: ((Peer, PeerNotificationSettings?, Bool) -> Bool)? - if let filter = filter { + if let filter = location.filter { let includePeers = Set(filter.additionallyIncludePeers) filterPredicate = { peer, notificationSettings, isUnread in if includePeers.contains(peer.id) { @@ -107,14 +104,14 @@ func chatListViewForLocation(groupId: PeerGroupId, filter: ChatListFilterPreset? } switch location { - case let .initial(count): + case let .initial(count, _): let signal: Signal<(ChatListView, ViewUpdateType), NoError> signal = account.viewTracker.tailChatListView(groupId: groupId, filterPredicate: filterPredicate, count: count) return signal |> map { view, updateType -> ChatListNodeViewUpdate in return ChatListNodeViewUpdate(view: view, type: updateType, scrollPosition: nil) } - case let .navigation(index): + case let .navigation(index, _): var first = true return account.viewTracker.aroundChatListView(groupId: groupId, filterPredicate: filterPredicate, index: index, count: 80) |> map { view, updateType -> ChatListNodeViewUpdate in @@ -127,7 +124,7 @@ func chatListViewForLocation(groupId: PeerGroupId, filter: ChatListFilterPreset? } return ChatListNodeViewUpdate(view: view, type: genericType, scrollPosition: nil) } - case let .scroll(index, sourceIndex, scrollPosition, animated): + case let .scroll(index, sourceIndex, scrollPosition, animated, _): let directionHint: ListViewScrollToItemDirectionHint = sourceIndex > index ? .Down : .Up let chatScrollPosition: ChatListNodeViewScrollPosition = .index(index: index, position: scrollPosition, directionHint: directionHint, animated: animated) var first = true diff --git a/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift b/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift index f0a07f75cc..4b0f09f8c2 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift @@ -167,7 +167,7 @@ func preparedChatListNodeViewTransition(from fromView: ChatListNodeView?, to toV var fromEmptyView = false if let fromView = fromView { - if fromView.filteredEntries.isEmpty { + if fromView.filteredEntries.isEmpty || fromView.filter != toView.filter { options.remove(.AnimateInsertion) options.remove(.AnimateAlpha) fromEmptyView = true diff --git a/submodules/ChatListUI/Sources/TabBarChatListFilterController.swift b/submodules/ChatListUI/Sources/TabBarChatListFilterController.swift index 12bb93be3a..79e09e3b11 100644 --- a/submodules/ChatListUI/Sources/TabBarChatListFilterController.swift +++ b/submodules/ChatListUI/Sources/TabBarChatListFilterController.swift @@ -462,7 +462,7 @@ private final class TabBarChatListFilterControllerNode: ViewControllerTracingNod if preset.includeCategories.contains(.publicGroups) { tags.append(.publicGroup) } - if preset.includeCategories.contains(.privateChats) { + if preset.includeCategories.contains(.channels) { tags.append(.channel) } diff --git a/submodules/Postbox/Sources/AllChatListHolesView.swift b/submodules/Postbox/Sources/AllChatListHolesView.swift new file mode 100644 index 0000000000..6f40b007c8 --- /dev/null +++ b/submodules/Postbox/Sources/AllChatListHolesView.swift @@ -0,0 +1,63 @@ +import Foundation + +final class MutableAllChatListHolesView: MutablePostboxView { + fileprivate let groupId: PeerGroupId + private var holes = Set() + fileprivate var latestHole: ChatListHole? + + init(postbox: Postbox, groupId: PeerGroupId) { + self.groupId = groupId + self.holes = Set(postbox.chatListTable.allHoles(groupId: groupId)) + self.latestHole = self.holes.max(by: { $0.index < $1.index }) + } + + func replay(postbox: Postbox, transaction: PostboxTransaction) -> Bool { + if let operations = transaction.chatListOperations[self.groupId] { + var updated = false + for operation in operations { + switch operation { + case let .InsertHole(hole): + if !self.holes.contains(hole) { + self.holes.insert(hole) + updated = true + } + case let .RemoveHoles(indices): + for index in indices { + if self.holes.contains(ChatListHole(index: index.messageIndex)) { + self.holes.remove(ChatListHole(index: index.messageIndex)) + updated = true + } + } + default: + break + } + } + + if updated { + let updatedLatestHole = self.holes.max(by: { $0.index < $1.index }) + if updatedLatestHole != self.latestHole { + self.latestHole = updatedLatestHole + return true + } else { + return false + } + } else { + return false + } + } else { + return false + } + } + + func immutableView() -> PostboxView { + return AllChatListHolesView(self) + } +} + +public final class AllChatListHolesView: PostboxView { + public let latestHole: ChatListHole? + + init(_ view: MutableAllChatListHolesView) { + self.latestHole = view.latestHole + } +} diff --git a/submodules/Postbox/Sources/ChatListIndexTable.swift b/submodules/Postbox/Sources/ChatListIndexTable.swift index 3d12b9b25a..1a66b80bd7 100644 --- a/submodules/Postbox/Sources/ChatListIndexTable.swift +++ b/submodules/Postbox/Sources/ChatListIndexTable.swift @@ -570,13 +570,10 @@ final class ChatListIndexTable: Table { func debugReindexUnreadCounts(postbox: Postbox) -> (ChatListTotalUnreadState, [PeerGroupId: PeerGroupUnreadCountersCombinedSummary]) { var peerIds: [PeerId] = [] - self.valueBox.scanInt64(self.table, values: { key, _ in - let peerId = PeerId(key) - if peerId.namespace != Int32.max { - peerIds.append(peerId) - } - return true - }) + for groupId in postbox.chatListTable.existingGroups() + [.root] { + let groupPeerIds = postbox.chatListTable.allPeerIds(groupId: groupId) + peerIds.append(contentsOf: groupPeerIds) + } var rootState = ChatListTotalUnreadState(absoluteCounters: [:], filteredCounters: [:]) var summaries: [PeerGroupId: PeerGroupUnreadCountersCombinedSummary] = [:] for peerId in peerIds { diff --git a/submodules/Postbox/Sources/ChatListTable.swift b/submodules/Postbox/Sources/ChatListTable.swift index b0c84fb807..b44e99fc82 100644 --- a/submodules/Postbox/Sources/ChatListTable.swift +++ b/submodules/Postbox/Sources/ChatListTable.swift @@ -680,6 +680,35 @@ final class ChatListTable: Table { return entries } + func allPeerIds(groupId: PeerGroupId) -> [PeerId] { + var peerIds: [PeerId] = [] + self.valueBox.range(self.table, start: self.upperBound(groupId: groupId), end: self.lowerBound(groupId: groupId), keys: { key in + let (keyGroupId, pinningIndex, messageIndex, type) = extractKey(key) + assert(groupId == keyGroupId) + + let index = ChatListIndex(pinningIndex: pinningIndex, messageIndex: messageIndex) + if type == ChatListEntryType.message.rawValue { + peerIds.append(messageIndex.id.peerId) + } + return true + }, limit: 0) + return peerIds + } + + func allHoles(groupId: PeerGroupId) -> [ChatListHole] { + var entries: [ChatListHole] = [] + self.valueBox.range(self.table, start: self.upperBound(groupId: groupId), end: self.lowerBound(groupId: groupId), keys: { key in + let (keyGroupId, pinningIndex, messageIndex, type) = extractKey(key) + assert(groupId == keyGroupId) + if type == ChatListEntryType.hole.rawValue { + let index = ChatListIndex(pinningIndex: pinningIndex, messageIndex: messageIndex) + entries.append(ChatListHole(index: index.messageIndex)) + } + return true + }, limit: 0) + return entries + } + func entriesInRange(groupId: PeerGroupId, upperBound: ChatListIndex, lowerBound: ChatListIndex) -> [ChatListEntryInfo] { var entries: [ChatListEntryInfo] = [] let upperBoundKey: ValueBoxKey diff --git a/submodules/Postbox/Sources/Views.swift b/submodules/Postbox/Sources/Views.swift index 86e870ac6b..962d0fe2ec 100644 --- a/submodules/Postbox/Sources/Views.swift +++ b/submodules/Postbox/Sources/Views.swift @@ -27,6 +27,7 @@ public enum PostboxViewKey: Hashable { case peerNotificationSettingsBehaviorTimestampView case peerChatInclusion(PeerId) case basicPeer(PeerId) + case allChatListHoles(PeerGroupId) public var hashValue: Int { switch self { @@ -82,6 +83,8 @@ public enum PostboxViewKey: Hashable { return peerId.hashValue case let .basicPeer(peerId): return peerId.hashValue + case let .allChatListHoles(groupId): + return groupId.hashValue } } @@ -243,6 +246,12 @@ public enum PostboxViewKey: Hashable { } else { return false } + case let .allChatListHoles(groupId): + if case .allChatListHoles(groupId) = rhs { + return true + } else { + return false + } } } } @@ -301,5 +310,7 @@ func postboxViewForKey(postbox: Postbox, key: PostboxViewKey) -> MutablePostboxV return MutablePeerChatInclusionView(postbox: postbox, peerId: peerId) case let .basicPeer(peerId): return MutableBasicPeerView(postbox: postbox, peerId: peerId) + case let .allChatListHoles(groupId): + return MutableAllChatListHolesView(postbox: postbox, groupId: groupId) } } diff --git a/submodules/TelegramCore/Sources/ManagedChatListHoles.swift b/submodules/TelegramCore/Sources/ManagedChatListHoles.swift index 8dbe238d06..4783619e83 100644 --- a/submodules/TelegramCore/Sources/ManagedChatListHoles.swift +++ b/submodules/TelegramCore/Sources/ManagedChatListHoles.swift @@ -4,6 +4,7 @@ import SwiftSignalKit private final class ManagedChatListHolesState { private var holeDisposables: [ChatListHolesEntry: Disposable] = [:] + private var additionalLatestHoleDisposable: (ChatListHole, Disposable)? func clearDisposables() -> [Disposable] { let disposables = Array(self.holeDisposables.values) @@ -11,7 +12,7 @@ private final class ManagedChatListHolesState { return disposables } - func update(entries: Set) -> (removed: [Disposable], added: [ChatListHolesEntry: MetaDisposable]) { + func update(entries: Set, additionalLatestHole: ChatListHole?) -> (removed: [Disposable], added: [ChatListHolesEntry: MetaDisposable], addedAdditionalLatestHole: (ChatListHole, MetaDisposable)?) { var removed: [Disposable] = [] var added: [ChatListHolesEntry: MetaDisposable] = [:] @@ -30,7 +31,21 @@ private final class ManagedChatListHolesState { } } - return (removed, added) + var addedAdditionalLatestHole: (ChatListHole, MetaDisposable)? + if self.holeDisposables.isEmpty { + if self.additionalLatestHoleDisposable?.0 != additionalLatestHole { + if let (_, disposable) = self.additionalLatestHoleDisposable { + removed.append(disposable) + } + if let additionalLatestHole = additionalLatestHole { + let disposable = MetaDisposable() + self.additionalLatestHoleDisposable = (additionalLatestHole, disposable) + addedAdditionalLatestHole = (additionalLatestHole, disposable) + } + } + } + + return (removed, added, addedAdditionalLatestHole) } } @@ -38,9 +53,17 @@ func managedChatListHoles(network: Network, postbox: Postbox, accountPeerId: Pee return Signal { _ in let state = Atomic(value: ManagedChatListHolesState()) - let disposable = postbox.chatListHolesView().start(next: { view in - let (removed, added) = state.with { state -> (removed: [Disposable], added: [ChatListHolesEntry: MetaDisposable]) in - return state.update(entries: view.entries) + let topRootHoleKey = PostboxViewKey.allChatListHoles(.root) + let topRootHole = postbox.combinedView(keys: [topRootHoleKey]) + + let disposable = combineLatest(postbox.chatListHolesView(), topRootHole).start(next: { view, topRootHoleView in + var additionalLatestHole: ChatListHole? + if let topRootHole = topRootHoleView.views[topRootHoleKey] as? AllChatListHolesView { + additionalLatestHole = topRootHole.latestHole + } + + let (removed, added, addedAdditionalLatestHole) = state.with { state in + return state.update(entries: view.entries, additionalLatestHole: additionalLatestHole) } for disposable in removed { @@ -50,6 +73,10 @@ func managedChatListHoles(network: Network, postbox: Postbox, accountPeerId: Pee for (entry, disposable) in added { disposable.set(fetchChatListHole(postbox: postbox, network: network, accountPeerId: accountPeerId, groupId: entry.groupId, hole: entry.hole).start()) } + + if let (hole, disposable) = addedAdditionalLatestHole { + disposable.set(fetchChatListHole(postbox: postbox, network: network, accountPeerId: accountPeerId, groupId: .root, hole: hole).start()) + } }) return ActionDisposable { diff --git a/submodules/TelegramUIPreferences/Sources/ChatListFilterSettings.swift b/submodules/TelegramUIPreferences/Sources/ChatListFilterSettings.swift index de6105939d..684bdbaa1a 100644 --- a/submodules/TelegramUIPreferences/Sources/ChatListFilterSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/ChatListFilterSettings.swift @@ -59,23 +59,27 @@ public enum ChatListFilterPresetName: Equatable, Hashable, PostboxCoding { } public struct ChatListFilterPreset: Equatable, PostboxCoding { + public var id: Int64 public var name: ChatListFilterPresetName public var includeCategories: ChatListIncludeCategoryFilter public var additionallyIncludePeers: [PeerId] - public init(name: ChatListFilterPresetName, includeCategories: ChatListIncludeCategoryFilter, additionallyIncludePeers: [PeerId]) { + public init(id: Int64, name: ChatListFilterPresetName, includeCategories: ChatListIncludeCategoryFilter, additionallyIncludePeers: [PeerId]) { + self.id = id self.name = name self.includeCategories = includeCategories self.additionallyIncludePeers = additionallyIncludePeers } public init(decoder: PostboxDecoder) { + self.id = decoder.decodeInt64ForKey("id", orElse: 0) self.name = decoder.decodeObjectForKey("name", decoder: { ChatListFilterPresetName(decoder: $0) }) as? ChatListFilterPresetName ?? ChatListFilterPresetName.custom("Preset") self.includeCategories = ChatListIncludeCategoryFilter(rawValue: decoder.decodeInt32ForKey("includeCategories", orElse: 0)) self.additionallyIncludePeers = decoder.decodeInt64ArrayForKey("additionallyIncludePeers").map(PeerId.init) } public func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt64(self.id, forKey: "id") encoder.encodeObject(self.name, forKey: "name") encoder.encodeInt32(self.includeCategories.rawValue, forKey: "includeCategories") encoder.encodeInt64Array(self.additionallyIncludePeers.map { $0.toInt64() }, forKey: "additionallyIncludePeers") @@ -88,6 +92,7 @@ public struct ChatListFilterSettings: PreferencesEntry, Equatable { public static var `default`: ChatListFilterSettings { return ChatListFilterSettings(presets: [ ChatListFilterPreset( + id: Int64(arc4random()), name: .unread, includeCategories: ChatListIncludeCategoryFilter.all.subtracting(.read), additionallyIncludePeers: [] @@ -116,12 +121,15 @@ public struct ChatListFilterSettings: PreferencesEntry, Equatable { } } -public func updateChatListFilterSettingsInteractively(postbox: Postbox, _ f: @escaping (ChatListFilterSettings) -> ChatListFilterSettings) -> Signal { - return postbox.transaction { transaction -> Void in +public func updateChatListFilterSettingsInteractively(postbox: Postbox, _ f: @escaping (ChatListFilterSettings) -> ChatListFilterSettings) -> Signal { + return postbox.transaction { transaction -> ChatListFilterSettings in + var result: ChatListFilterSettings? transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.chatListFilterSettings, { entry in var settings = entry as? ChatListFilterSettings ?? ChatListFilterSettings.default - return f(settings) + let updated = f(settings) + result = updated + return updated }) + return result ?? .default } - |> ignoreValues }