Chat List Filter improvements

This commit is contained in:
Ali
2020-01-28 19:46:28 +04:00
parent 309a8b112b
commit 77826e91d4
13 changed files with 266 additions and 89 deletions

View File

@@ -1822,7 +1822,23 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
guard let strongSelf = self else { guard let strongSelf = self else {
return 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 }, updatePreset: { value in
guard let strongSelf = self else { guard let strongSelf = self else {
return return

View File

@@ -53,9 +53,11 @@ private func filterEntry(presentationData: ItemListPresentationData, arguments:
private enum ChatListFilterPresetEntryStableId: Hashable { private enum ChatListFilterPresetEntryStableId: Hashable {
case index(Int) case index(Int)
case peer(PeerId) case peer(PeerId)
case additionalPeerInfo
} }
private enum ChatListFilterPresetEntry: ItemListNodeEntry { private enum ChatListFilterPresetEntry: ItemListNodeEntry {
case nameHeader(String)
case name(placeholder: String, value: String) case name(placeholder: String, value: String)
case filterPrivateChats(title: String, value: Bool) case filterPrivateChats(title: String, value: Bool)
case filterSecretChats(title: String, value: Bool) case filterSecretChats(title: String, value: Bool)
@@ -68,46 +70,51 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
case additionalPeersHeader(String) case additionalPeersHeader(String)
case addAdditionalPeer(title: String) case addAdditionalPeer(title: String)
case additionalPeer(index: Int, peer: RenderedPeer, isRevealed: Bool) case additionalPeer(index: Int, peer: RenderedPeer, isRevealed: Bool)
case additionalPeerInfo(String)
var section: ItemListSectionId { var section: ItemListSectionId {
switch self { switch self {
case .name: case .nameHeader, .name:
return ChatListFilterPresetControllerSection.name.rawValue return ChatListFilterPresetControllerSection.name.rawValue
case .filterPrivateChats, .filterSecretChats, .filterPrivateGroups, .filterBots, .filterPublicGroups, .filterChannels: case .filterPrivateChats, .filterSecretChats, .filterPrivateGroups, .filterBots, .filterPublicGroups, .filterChannels:
return ChatListFilterPresetControllerSection.categories.rawValue return ChatListFilterPresetControllerSection.categories.rawValue
case .filterMuted, .filterRead: case .filterMuted, .filterRead:
return ChatListFilterPresetControllerSection.excludeCategories.rawValue return ChatListFilterPresetControllerSection.excludeCategories.rawValue
case .additionalPeersHeader, .addAdditionalPeer, .additionalPeer: case .additionalPeersHeader, .addAdditionalPeer, .additionalPeer, .additionalPeerInfo:
return ChatListFilterPresetControllerSection.additionalPeers.rawValue return ChatListFilterPresetControllerSection.additionalPeers.rawValue
} }
} }
var stableId: ChatListFilterPresetEntryStableId { var stableId: ChatListFilterPresetEntryStableId {
switch self { switch self {
case .name: case .nameHeader:
return .index(0) return .index(0)
case .filterPrivateChats: case .name:
return .index(1) return .index(1)
case .filterSecretChats: case .filterPrivateChats:
return .index(2) return .index(2)
case .filterPrivateGroups: case .filterSecretChats:
return .index(3) return .index(3)
case .filterBots: case .filterPrivateGroups:
return .index(4) return .index(4)
case .filterPublicGroups: case .filterBots:
return .index(5) return .index(5)
case .filterChannels: case .filterPublicGroups:
return .index(6) return .index(6)
case .filterMuted: case .filterChannels:
return .index(7) return .index(7)
case .filterRead: case .filterMuted:
return .index(8) return .index(8)
case .additionalPeersHeader: case .filterRead:
return .index(9) return .index(9)
case .addAdditionalPeer: case .additionalPeersHeader:
return .index(10) return .index(10)
case .addAdditionalPeer:
return .index(11)
case let .additionalPeer(additionalPeer): case let .additionalPeer(additionalPeer):
return .peer(additionalPeer.peer.peerId) return .peer(additionalPeer.peer.peerId)
case .additionalPeerInfo:
return .additionalPeerInfo
} }
} }
@@ -119,6 +126,8 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
return lhsIndex < rhsIndex return lhsIndex < rhsIndex
case .peer: case .peer:
return true return true
case .additionalPeerInfo:
return true
} }
case .peer: case .peer:
switch lhs { switch lhs {
@@ -126,6 +135,8 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
switch rhs.stableId { switch rhs.stableId {
case .index: case .index:
return false return false
case .additionalPeerInfo:
return true
case .peer: case .peer:
switch rhs { switch rhs {
case let .additionalPeer(rhsIndex, _, _): case let .additionalPeer(rhsIndex, _, _):
@@ -137,12 +148,23 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
default: default:
preconditionFailure() 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 { func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
let arguments = arguments as! ChatListFilterPresetControllerArguments let arguments = arguments as! ChatListFilterPresetControllerArguments
switch self { switch self {
case let .nameHeader(title):
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
case let .name(placeholder, value): 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 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 arguments.updateState { current in
@@ -201,6 +223,8 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
}, removePeer: { id in }, removePeer: { id in
arguments.deleteAdditionalPeer(id) 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] { private func chatListFilterPresetControllerEntries(presentationData: PresentationData, state: ChatListFilterPresetControllerState, peers: [RenderedPeer]) -> [ChatListFilterPresetEntry] {
var entries: [ChatListFilterPresetEntry] = [] var entries: [ChatListFilterPresetEntry] = []
entries.append(.nameHeader("NAME"))
entries.append(.name(placeholder: "Preset Name", value: state.name)) entries.append(.name(placeholder: "Preset Name", value: state.name))
entries.append(.filterPrivateChats(title: "Private Chats", value: state.includeCategories.contains(.privateChats))) 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(.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 return entries
} }
func chatListFilterPresetController(context: AccountContext, currentPreset: ChatListFilterPreset?) -> ViewController { func chatListFilterPresetController(context: AccountContext, currentPreset: ChatListFilterPreset?, updated: @escaping ([ChatListFilterPreset]) -> Void) -> ViewController {
let initialName: String let initialName: String
if let currentPreset = currentPreset { if let currentPreset = currentPreset {
switch currentPreset.name { 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 rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: state.isComplete, action: {
let state = stateValue.with { $0 } 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 let _ = (updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { settings in
var settings = settings var settings = settings
settings.presets = settings.presets.filter { $0 != preset && $0 != currentPreset } settings.presets = settings.presets.filter { $0 != preset && $0 != currentPreset }
settings.presets.append(preset) settings.presets.append(preset)
return settings return settings
}) })
|> deliverOnMainQueue).start(completed: { |> deliverOnMainQueue).start(next: { settings in
updated(settings.presets)
dismissImpl?() dismissImpl?()
}) })
}) })

View File

@@ -46,7 +46,7 @@ private func stringForUserCount(_ peers: [PeerId: SelectivePrivacyPeer], strings
private enum ChatListFilterPresetListEntryStableId: Hashable { private enum ChatListFilterPresetListEntryStableId: Hashable {
case listHeader case listHeader
case preset(ChatListFilterPresetName) case preset(Int64)
case addItem case addItem
case listFooter case listFooter
} }
@@ -82,7 +82,7 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
case .listHeader: case .listHeader:
return .listHeader return .listHeader
case let .preset(preset): case let .preset(preset):
return .preset(preset.preset.name) return .preset(preset.preset.id)
case .addItem: case .addItem:
return .addItem return .addItem
case .listFooter: case .listFooter:
@@ -141,7 +141,7 @@ private func chatListFilterPresetListControllerEntries(presentationData: Present
return entries return entries
} }
func chatListFilterPresetListController(context: AccountContext) -> ViewController { func chatListFilterPresetListController(context: AccountContext, updated: @escaping ([ChatListFilterPreset]) -> Void) -> ViewController {
let initialState = ChatListFilterPresetListControllerState() let initialState = ChatListFilterPresetListControllerState()
let statePromise = ValuePromise(initialState, ignoreRepeated: true) let statePromise = ValuePromise(initialState, ignoreRepeated: true)
let stateValue = Atomic(value: initialState) let stateValue = Atomic(value: initialState)
@@ -154,9 +154,9 @@ func chatListFilterPresetListController(context: AccountContext) -> ViewControll
var presentControllerImpl: ((ViewController, Any?) -> Void)? var presentControllerImpl: ((ViewController, Any?) -> Void)?
let arguments = ChatListFilterPresetListControllerArguments(context: context, openPreset: { preset in let arguments = ChatListFilterPresetListControllerArguments(context: context, openPreset: { preset in
pushControllerImpl?(chatListFilterPresetController(context: context, currentPreset: preset)) pushControllerImpl?(chatListFilterPresetController(context: context, currentPreset: preset, updated: updated))
}, addNew: { }, addNew: {
pushControllerImpl?(chatListFilterPresetController(context: context, currentPreset: nil)) pushControllerImpl?(chatListFilterPresetController(context: context, currentPreset: nil, updated: updated))
}, setItemWithRevealedOptions: { preset, fromPreset in }, setItemWithRevealedOptions: { preset, fromPreset in
updateState { state in updateState { state in
var state = state var state = state
@@ -166,13 +166,16 @@ func chatListFilterPresetListController(context: AccountContext) -> ViewControll
return state return state
} }
}, removePreset: { preset in }, removePreset: { preset in
let _ = updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { settings in let _ = (updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { settings in
var settings = settings var settings = settings
if let index = settings.presets.index(of: preset) { if let index = settings.presets.index(of: preset) {
settings.presets.remove(at: index) settings.presets.remove(at: index)
} }
return settings return settings
}).start() })
|> deliverOnMainQueue).start(next: { settings in
updated(settings.presets)
})
}) })
let preferences = context.account.postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.chatListFilterSettings]) let preferences = context.account.postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.chatListFilterSettings])

View File

@@ -370,6 +370,12 @@ public final class ChatListNode: ListView {
didSet { didSet {
if self.chatListFilter != oldValue { if self.chatListFilter != oldValue {
self.chatListFilterValue.set(self.chatListFilter) 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 viewProcessingQueue = self.viewProcessingQueue
let chatListViewUpdate = combineLatest(self.chatListLocation.get(), self.chatListFilterValue.get()) let chatListViewUpdate = self.chatListLocation.get()
|> distinctUntilChanged(isEqual: { lhs, rhs in |> distinctUntilChanged
if lhs.0 != rhs.0 { |> mapToSignal { location -> Signal<(ChatListNodeViewUpdate, ChatListFilterPreset?), NoError> in
return false return chatListViewForLocation(groupId: groupId, location: location, account: context.account)
}
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)
|> map { update in |> map { update in
return (update, filter) return (update, location.filter)
} }
} }
@@ -792,9 +790,9 @@ public final class ChatListNode: ListView {
if let range = range.loadedRange { if let range = range.loadedRange {
var location: ChatListNodeLocation? var location: ChatListNodeLocation?
if range.firstIndex < 5 && originalView.laterIndex != nil { 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 { } 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 { if let location = location, location != strongSelf.currentLocation {
@@ -850,10 +848,10 @@ public final class ChatListNode: ListView {
let initialLocation: ChatListNodeLocation let initialLocation: ChatListNodeLocation
switch mode { switch mode {
case .chatList: case .chatList:
initialLocation = .initial(count: 50) initialLocation = .initial(count: 50, filter: self.chatListFilter)
case .peers: case .peers:
initialLocation = .initial(count: 200) initialLocation = .initial(count: 200, filter: self.chatListFilter)
} }
self.setChatListLocation(initialLocation) self.setChatListLocation(initialLocation)
@@ -1332,12 +1330,12 @@ public final class ChatListNode: ListView {
if view.laterIndex == nil { 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 }) 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 { } 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) self.setChatListLocation(location)
} }
} else { } else {
let location: ChatListNodeLocation = .scroll(index: .absoluteUpperBound, sourceIndex: .absoluteLowerBound 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) self.setChatListLocation(location)
} }
} }
@@ -1373,11 +1371,11 @@ public final class ChatListNode: ListView {
if let index = index { if let index = index {
let location: ChatListNodeLocation = .scroll(index: index, sourceIndex: self?.currentlyVisibleLatestChatListIndex() ?? .absoluteUpperBound 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) strongSelf.setChatListLocation(location)
} else { } else {
let location: ChatListNodeLocation = .scroll(index: .absoluteUpperBound, sourceIndex: .absoluteLowerBound 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) strongSelf.setChatListLocation(location)
} }
}) })
@@ -1433,7 +1431,7 @@ public final class ChatListNode: ListView {
guard let strongSelf = self, let index = index else { guard let strongSelf = self, let index = index else {
return 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.setChatListLocation(location)
strongSelf.peerSelected?(index.messageIndex.id.peerId, false, false) strongSelf.peerSelected?(index.messageIndex.id.peerId, false, false)
}) })
@@ -1457,7 +1455,7 @@ public final class ChatListNode: ListView {
} }
} }
if let target = target { 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.setChatListLocation(location)
self.peerSelected?(target.1, false, false) self.peerSelected?(target.1, false, false)
} }
@@ -1473,12 +1471,12 @@ public final class ChatListNode: ListView {
guard let self = self else { guard let self = self else {
return 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) |> take(1)
|> deliverOnMainQueue).start(next: { update in |> deliverOnMainQueue).start(next: { update in
let entries = update.view.entries let entries = update.view.entries
if entries.count > index, case let .MessageEntry(index, _, _, _, _, renderedPeer, _, _, _) = entries[10 - index - 1] { 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.setChatListLocation(location)
self.peerSelected?(renderedPeer.peerId, false, false) self.peerSelected?(renderedPeer.peerId, false, false)
} }

View File

@@ -7,21 +7,18 @@ import Display
import TelegramUIPreferences import TelegramUIPreferences
enum ChatListNodeLocation: Equatable { enum ChatListNodeLocation: Equatable {
case initial(count: Int) case initial(count: Int, filter: ChatListFilterPreset?)
case navigation(index: ChatListIndex) case navigation(index: ChatListIndex, filter: ChatListFilterPreset?)
case scroll(index: ChatListIndex, sourceIndex: ChatListIndex, scrollPosition: ListViewScrollPosition, animated: Bool) case scroll(index: ChatListIndex, sourceIndex: ChatListIndex, scrollPosition: ListViewScrollPosition, animated: Bool, filter: ChatListFilterPreset?)
static func ==(lhs: ChatListNodeLocation, rhs: ChatListNodeLocation) -> Bool { var filter: ChatListFilterPreset? {
switch lhs { switch self {
case let .navigation(index): case let .initial(initial):
switch rhs { return initial.filter
case .navigation(index): case let .navigation(navigation):
return true return navigation.filter
default: case let .scroll(scroll):
return false return scroll.filter
}
default:
return false
} }
} }
} }
@@ -32,9 +29,9 @@ struct ChatListNodeViewUpdate {
let scrollPosition: ChatListNodeViewScrollPosition? let scrollPosition: ChatListNodeViewScrollPosition?
} }
func chatListViewForLocation(groupId: PeerGroupId, filter: ChatListFilterPreset?, location: ChatListNodeLocation, account: Account) -> Signal<ChatListNodeViewUpdate, NoError> { func chatListViewForLocation(groupId: PeerGroupId, location: ChatListNodeLocation, account: Account) -> Signal<ChatListNodeViewUpdate, NoError> {
let filterPredicate: ((Peer, PeerNotificationSettings?, Bool) -> Bool)? let filterPredicate: ((Peer, PeerNotificationSettings?, Bool) -> Bool)?
if let filter = filter { if let filter = location.filter {
let includePeers = Set(filter.additionallyIncludePeers) let includePeers = Set(filter.additionallyIncludePeers)
filterPredicate = { peer, notificationSettings, isUnread in filterPredicate = { peer, notificationSettings, isUnread in
if includePeers.contains(peer.id) { if includePeers.contains(peer.id) {
@@ -107,14 +104,14 @@ func chatListViewForLocation(groupId: PeerGroupId, filter: ChatListFilterPreset?
} }
switch location { switch location {
case let .initial(count): case let .initial(count, _):
let signal: Signal<(ChatListView, ViewUpdateType), NoError> let signal: Signal<(ChatListView, ViewUpdateType), NoError>
signal = account.viewTracker.tailChatListView(groupId: groupId, filterPredicate: filterPredicate, count: count) signal = account.viewTracker.tailChatListView(groupId: groupId, filterPredicate: filterPredicate, count: count)
return signal return signal
|> map { view, updateType -> ChatListNodeViewUpdate in |> map { view, updateType -> ChatListNodeViewUpdate in
return ChatListNodeViewUpdate(view: view, type: updateType, scrollPosition: nil) return ChatListNodeViewUpdate(view: view, type: updateType, scrollPosition: nil)
} }
case let .navigation(index): case let .navigation(index, _):
var first = true var first = true
return account.viewTracker.aroundChatListView(groupId: groupId, filterPredicate: filterPredicate, index: index, count: 80) return account.viewTracker.aroundChatListView(groupId: groupId, filterPredicate: filterPredicate, index: index, count: 80)
|> map { view, updateType -> ChatListNodeViewUpdate in |> map { view, updateType -> ChatListNodeViewUpdate in
@@ -127,7 +124,7 @@ func chatListViewForLocation(groupId: PeerGroupId, filter: ChatListFilterPreset?
} }
return ChatListNodeViewUpdate(view: view, type: genericType, scrollPosition: nil) 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 directionHint: ListViewScrollToItemDirectionHint = sourceIndex > index ? .Down : .Up
let chatScrollPosition: ChatListNodeViewScrollPosition = .index(index: index, position: scrollPosition, directionHint: directionHint, animated: animated) let chatScrollPosition: ChatListNodeViewScrollPosition = .index(index: index, position: scrollPosition, directionHint: directionHint, animated: animated)
var first = true var first = true

View File

@@ -167,7 +167,7 @@ func preparedChatListNodeViewTransition(from fromView: ChatListNodeView?, to toV
var fromEmptyView = false var fromEmptyView = false
if let fromView = fromView { if let fromView = fromView {
if fromView.filteredEntries.isEmpty { if fromView.filteredEntries.isEmpty || fromView.filter != toView.filter {
options.remove(.AnimateInsertion) options.remove(.AnimateInsertion)
options.remove(.AnimateAlpha) options.remove(.AnimateAlpha)
fromEmptyView = true fromEmptyView = true

View File

@@ -462,7 +462,7 @@ private final class TabBarChatListFilterControllerNode: ViewControllerTracingNod
if preset.includeCategories.contains(.publicGroups) { if preset.includeCategories.contains(.publicGroups) {
tags.append(.publicGroup) tags.append(.publicGroup)
} }
if preset.includeCategories.contains(.privateChats) { if preset.includeCategories.contains(.channels) {
tags.append(.channel) tags.append(.channel)
} }

View File

@@ -0,0 +1,63 @@
import Foundation
final class MutableAllChatListHolesView: MutablePostboxView {
fileprivate let groupId: PeerGroupId
private var holes = Set<ChatListHole>()
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
}
}

View File

@@ -570,13 +570,10 @@ final class ChatListIndexTable: Table {
func debugReindexUnreadCounts(postbox: Postbox) -> (ChatListTotalUnreadState, [PeerGroupId: PeerGroupUnreadCountersCombinedSummary]) { func debugReindexUnreadCounts(postbox: Postbox) -> (ChatListTotalUnreadState, [PeerGroupId: PeerGroupUnreadCountersCombinedSummary]) {
var peerIds: [PeerId] = [] var peerIds: [PeerId] = []
self.valueBox.scanInt64(self.table, values: { key, _ in for groupId in postbox.chatListTable.existingGroups() + [.root] {
let peerId = PeerId(key) let groupPeerIds = postbox.chatListTable.allPeerIds(groupId: groupId)
if peerId.namespace != Int32.max { peerIds.append(contentsOf: groupPeerIds)
peerIds.append(peerId) }
}
return true
})
var rootState = ChatListTotalUnreadState(absoluteCounters: [:], filteredCounters: [:]) var rootState = ChatListTotalUnreadState(absoluteCounters: [:], filteredCounters: [:])
var summaries: [PeerGroupId: PeerGroupUnreadCountersCombinedSummary] = [:] var summaries: [PeerGroupId: PeerGroupUnreadCountersCombinedSummary] = [:]
for peerId in peerIds { for peerId in peerIds {

View File

@@ -680,6 +680,35 @@ final class ChatListTable: Table {
return entries 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] { func entriesInRange(groupId: PeerGroupId, upperBound: ChatListIndex, lowerBound: ChatListIndex) -> [ChatListEntryInfo] {
var entries: [ChatListEntryInfo] = [] var entries: [ChatListEntryInfo] = []
let upperBoundKey: ValueBoxKey let upperBoundKey: ValueBoxKey

View File

@@ -27,6 +27,7 @@ public enum PostboxViewKey: Hashable {
case peerNotificationSettingsBehaviorTimestampView case peerNotificationSettingsBehaviorTimestampView
case peerChatInclusion(PeerId) case peerChatInclusion(PeerId)
case basicPeer(PeerId) case basicPeer(PeerId)
case allChatListHoles(PeerGroupId)
public var hashValue: Int { public var hashValue: Int {
switch self { switch self {
@@ -82,6 +83,8 @@ public enum PostboxViewKey: Hashable {
return peerId.hashValue return peerId.hashValue
case let .basicPeer(peerId): case let .basicPeer(peerId):
return peerId.hashValue return peerId.hashValue
case let .allChatListHoles(groupId):
return groupId.hashValue
} }
} }
@@ -243,6 +246,12 @@ public enum PostboxViewKey: Hashable {
} else { } else {
return false 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) return MutablePeerChatInclusionView(postbox: postbox, peerId: peerId)
case let .basicPeer(peerId): case let .basicPeer(peerId):
return MutableBasicPeerView(postbox: postbox, peerId: peerId) return MutableBasicPeerView(postbox: postbox, peerId: peerId)
case let .allChatListHoles(groupId):
return MutableAllChatListHolesView(postbox: postbox, groupId: groupId)
} }
} }

View File

@@ -4,6 +4,7 @@ import SwiftSignalKit
private final class ManagedChatListHolesState { private final class ManagedChatListHolesState {
private var holeDisposables: [ChatListHolesEntry: Disposable] = [:] private var holeDisposables: [ChatListHolesEntry: Disposable] = [:]
private var additionalLatestHoleDisposable: (ChatListHole, Disposable)?
func clearDisposables() -> [Disposable] { func clearDisposables() -> [Disposable] {
let disposables = Array(self.holeDisposables.values) let disposables = Array(self.holeDisposables.values)
@@ -11,7 +12,7 @@ private final class ManagedChatListHolesState {
return disposables return disposables
} }
func update(entries: Set<ChatListHolesEntry>) -> (removed: [Disposable], added: [ChatListHolesEntry: MetaDisposable]) { func update(entries: Set<ChatListHolesEntry>, additionalLatestHole: ChatListHole?) -> (removed: [Disposable], added: [ChatListHolesEntry: MetaDisposable], addedAdditionalLatestHole: (ChatListHole, MetaDisposable)?) {
var removed: [Disposable] = [] var removed: [Disposable] = []
var added: [ChatListHolesEntry: MetaDisposable] = [:] 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 return Signal { _ in
let state = Atomic(value: ManagedChatListHolesState()) let state = Atomic(value: ManagedChatListHolesState())
let disposable = postbox.chatListHolesView().start(next: { view in let topRootHoleKey = PostboxViewKey.allChatListHoles(.root)
let (removed, added) = state.with { state -> (removed: [Disposable], added: [ChatListHolesEntry: MetaDisposable]) in let topRootHole = postbox.combinedView(keys: [topRootHoleKey])
return state.update(entries: view.entries)
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 { for disposable in removed {
@@ -50,6 +73,10 @@ func managedChatListHoles(network: Network, postbox: Postbox, accountPeerId: Pee
for (entry, disposable) in added { for (entry, disposable) in added {
disposable.set(fetchChatListHole(postbox: postbox, network: network, accountPeerId: accountPeerId, groupId: entry.groupId, hole: entry.hole).start()) 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 { return ActionDisposable {

View File

@@ -59,23 +59,27 @@ public enum ChatListFilterPresetName: Equatable, Hashable, PostboxCoding {
} }
public struct ChatListFilterPreset: Equatable, PostboxCoding { public struct ChatListFilterPreset: Equatable, PostboxCoding {
public var id: Int64
public var name: ChatListFilterPresetName public var name: ChatListFilterPresetName
public var includeCategories: ChatListIncludeCategoryFilter public var includeCategories: ChatListIncludeCategoryFilter
public var additionallyIncludePeers: [PeerId] 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.name = name
self.includeCategories = includeCategories self.includeCategories = includeCategories
self.additionallyIncludePeers = additionallyIncludePeers self.additionallyIncludePeers = additionallyIncludePeers
} }
public init(decoder: PostboxDecoder) { 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.name = decoder.decodeObjectForKey("name", decoder: { ChatListFilterPresetName(decoder: $0) }) as? ChatListFilterPresetName ?? ChatListFilterPresetName.custom("Preset")
self.includeCategories = ChatListIncludeCategoryFilter(rawValue: decoder.decodeInt32ForKey("includeCategories", orElse: 0)) self.includeCategories = ChatListIncludeCategoryFilter(rawValue: decoder.decodeInt32ForKey("includeCategories", orElse: 0))
self.additionallyIncludePeers = decoder.decodeInt64ArrayForKey("additionallyIncludePeers").map(PeerId.init) self.additionallyIncludePeers = decoder.decodeInt64ArrayForKey("additionallyIncludePeers").map(PeerId.init)
} }
public func encode(_ encoder: PostboxEncoder) { public func encode(_ encoder: PostboxEncoder) {
encoder.encodeInt64(self.id, forKey: "id")
encoder.encodeObject(self.name, forKey: "name") encoder.encodeObject(self.name, forKey: "name")
encoder.encodeInt32(self.includeCategories.rawValue, forKey: "includeCategories") encoder.encodeInt32(self.includeCategories.rawValue, forKey: "includeCategories")
encoder.encodeInt64Array(self.additionallyIncludePeers.map { $0.toInt64() }, forKey: "additionallyIncludePeers") encoder.encodeInt64Array(self.additionallyIncludePeers.map { $0.toInt64() }, forKey: "additionallyIncludePeers")
@@ -88,6 +92,7 @@ public struct ChatListFilterSettings: PreferencesEntry, Equatable {
public static var `default`: ChatListFilterSettings { public static var `default`: ChatListFilterSettings {
return ChatListFilterSettings(presets: [ return ChatListFilterSettings(presets: [
ChatListFilterPreset( ChatListFilterPreset(
id: Int64(arc4random()),
name: .unread, name: .unread,
includeCategories: ChatListIncludeCategoryFilter.all.subtracting(.read), includeCategories: ChatListIncludeCategoryFilter.all.subtracting(.read),
additionallyIncludePeers: [] additionallyIncludePeers: []
@@ -116,12 +121,15 @@ public struct ChatListFilterSettings: PreferencesEntry, Equatable {
} }
} }
public func updateChatListFilterSettingsInteractively(postbox: Postbox, _ f: @escaping (ChatListFilterSettings) -> ChatListFilterSettings) -> Signal<Never, NoError> { public func updateChatListFilterSettingsInteractively(postbox: Postbox, _ f: @escaping (ChatListFilterSettings) -> ChatListFilterSettings) -> Signal<ChatListFilterSettings, NoError> {
return postbox.transaction { transaction -> Void in return postbox.transaction { transaction -> ChatListFilterSettings in
var result: ChatListFilterSettings?
transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.chatListFilterSettings, { entry in transaction.updatePreferencesEntry(key: ApplicationSpecificPreferencesKeys.chatListFilterSettings, { entry in
var settings = entry as? ChatListFilterSettings ?? ChatListFilterSettings.default var settings = entry as? ChatListFilterSettings ?? ChatListFilterSettings.default
return f(settings) let updated = f(settings)
result = updated
return updated
}) })
return result ?? .default
} }
|> ignoreValues
} }