mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-07 01:10:09 +00:00
Filter improvements
This commit is contained in:
parent
8341247b5d
commit
5e724b92ea
@ -5366,3 +5366,5 @@ Any member of this group will be able to see messages in the channel.";
|
||||
"Stats.ViewsBySourceTitle" = "VIEWS BY SOURCE";
|
||||
"Stats.FollowersBySourceTitle" = "FOLLOWERS BY SOURCE";
|
||||
"Stats.LanguagesTitle" = "LANGUAGES";
|
||||
|
||||
"ChatListFilter.AddChatsTitle" = "Add Chats";
|
||||
|
||||
@ -7,6 +7,7 @@ public enum ContactMultiselectionControllerMode {
|
||||
case groupCreation
|
||||
case peerSelection(searchChatList: Bool, searchGroups: Bool, searchChannels: Bool)
|
||||
case channelCreation
|
||||
case chatSelection
|
||||
}
|
||||
|
||||
public enum ContactListFilter {
|
||||
|
||||
@ -23,19 +23,26 @@ import LocalizedPeerData
|
||||
import TelegramIntents
|
||||
|
||||
private func fixListNodeScrolling(_ listNode: ListView, searchNode: NavigationBarSearchContentNode) -> Bool {
|
||||
if listNode.scroller.isDragging {
|
||||
return false
|
||||
}
|
||||
if searchNode.expansionProgress > 0.0 && searchNode.expansionProgress < 1.0 {
|
||||
let scrollToItem: ListViewScrollToItem
|
||||
let targetProgress: CGFloat
|
||||
let offset: CGFloat
|
||||
if searchNode.expansionProgress < 0.6 {
|
||||
scrollToItem = ListViewScrollToItem(index: 0, position: .top(-navigationBarSearchContentHeight), animated: true, curve: .Default(duration: nil), directionHint: .Up)
|
||||
targetProgress = 0.0
|
||||
offset = navigationBarSearchContentHeight
|
||||
} else {
|
||||
scrollToItem = ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up)
|
||||
targetProgress = 1.0
|
||||
offset = 0.0
|
||||
}
|
||||
searchNode.updateExpansionProgress(targetProgress, animated: true)
|
||||
//searchNode.updateExpansionProgress(targetProgress, animated: true)
|
||||
|
||||
listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: scrollToItem, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
//listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: scrollToItem, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
listNode.scrollToOffsetFromTop(offset)
|
||||
return true
|
||||
} else if searchNode.expansionProgress == 1.0 {
|
||||
var sortItemNode: ListViewItemNode?
|
||||
@ -138,13 +145,11 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
private var searchContentNode: NavigationBarSearchContentNode?
|
||||
|
||||
private let tabContainerNode: ChatListFilterTabContainerNode
|
||||
private var tabContainerData: ([ChatListFilterTabEntry], ChatListFilterTabEntryId)?
|
||||
|
||||
private let chatListFilterValue = Promise<ChatListFilter?>()
|
||||
private var tabContainerData: [ChatListFilterTabEntry]?
|
||||
|
||||
public override func updateNavigationCustomData(_ data: Any?, progress: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
if self.isNodeLoaded {
|
||||
self.chatListDisplayNode.chatListNode.updateSelectedChatLocation(data as? ChatLocation, progress: progress, transition: transition)
|
||||
self.chatListDisplayNode.containerNode.updateSelectedChatLocation(data: data as? ChatLocation, progress: progress, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
@ -232,12 +237,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
if strongSelf.chatListDisplayNode.searchDisplayController != nil {
|
||||
strongSelf.deactivateSearch(animated: true)
|
||||
} else {
|
||||
switch strongSelf.chatListDisplayNode.chatListNode.visibleContentOffset() {
|
||||
switch strongSelf.chatListDisplayNode.containerNode.currentItemNode.visibleContentOffset() {
|
||||
case .none, .unknown:
|
||||
if let searchContentNode = strongSelf.searchContentNode {
|
||||
searchContentNode.updateExpansionProgress(1.0, animated: true)
|
||||
}
|
||||
strongSelf.chatListDisplayNode.chatListNode.scrollToPosition(.top)
|
||||
strongSelf.chatListDisplayNode.containerNode.currentItemNode.scrollToPosition(.top)
|
||||
case let .known(offset):
|
||||
if offset <= navigationBarSearchContentHeight + 1.0 {
|
||||
strongSelf.tabContainerNode.tabSelected?(.all)
|
||||
@ -245,24 +250,11 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
if let searchContentNode = strongSelf.searchContentNode {
|
||||
searchContentNode.updateExpansionProgress(1.0, animated: true)
|
||||
}
|
||||
strongSelf.chatListDisplayNode.chatListNode.scrollToPosition(.top)
|
||||
strongSelf.chatListDisplayNode.containerNode.currentItemNode.scrollToPosition(.top)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.longTapWithTabBar = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if strongSelf.chatListDisplayNode.searchDisplayController != nil {
|
||||
strongSelf.deactivateSearch(animated: true)
|
||||
} else {
|
||||
if let searchContentNode = strongSelf.searchContentNode {
|
||||
searchContentNode.updateExpansionProgress(1.0, animated: true)
|
||||
}
|
||||
strongSelf.chatListDisplayNode.chatListNode.scrollToPosition(.auto)
|
||||
}
|
||||
}
|
||||
|
||||
let hasProxy = context.sharedContext.accountManager.sharedData(keys: [SharedDataKeys.proxySettings])
|
||||
|> map { sharedData -> (Bool, Bool) in
|
||||
@ -287,22 +279,17 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
context.account.networkState,
|
||||
hasProxy,
|
||||
passcode,
|
||||
self.chatListDisplayNode.chatListNode.state
|
||||
self.chatListDisplayNode.containerNode.currentItemState
|
||||
).start(next: { [weak self] networkState, proxy, passcode, state in
|
||||
if let strongSelf = self {
|
||||
let defaultTitle: String
|
||||
if strongSelf.groupId == .root {
|
||||
if let chatListFilter = strongSelf.filter {
|
||||
let title: String = chatListFilter.title ?? strongSelf.presentationData.strings.DialogList_Title
|
||||
defaultTitle = title
|
||||
} else {
|
||||
defaultTitle = strongSelf.presentationData.strings.DialogList_Title
|
||||
}
|
||||
defaultTitle = strongSelf.presentationData.strings.DialogList_Title
|
||||
} else {
|
||||
defaultTitle = strongSelf.presentationData.strings.ChatList_ArchivedChatsTitle
|
||||
}
|
||||
if state.editing {
|
||||
if strongSelf.groupId == .root && strongSelf.filter == nil {
|
||||
if strongSelf.groupId == .root {
|
||||
strongSelf.navigationItem.rightBarButtonItem = nil
|
||||
}
|
||||
|
||||
@ -312,11 +299,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
var isRoot = false
|
||||
if case .root = strongSelf.groupId {
|
||||
isRoot = true
|
||||
if strongSelf.filter == nil {
|
||||
let rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationComposeIcon(strongSelf.presentationData.theme), style: .plain, target: strongSelf, action: #selector(strongSelf.composePressed))
|
||||
rightBarButtonItem.accessibilityLabel = strongSelf.presentationData.strings.VoiceOver_Navigation_Compose
|
||||
strongSelf.navigationItem.rightBarButtonItem = rightBarButtonItem
|
||||
}
|
||||
let rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationComposeIcon(strongSelf.presentationData.theme), style: .plain, target: strongSelf, action: #selector(strongSelf.composePressed))
|
||||
rightBarButtonItem.accessibilityLabel = strongSelf.presentationData.strings.VoiceOver_Navigation_Compose
|
||||
strongSelf.navigationItem.rightBarButtonItem = rightBarButtonItem
|
||||
}
|
||||
|
||||
let (hasProxy, connectsViaProxy) = proxy
|
||||
@ -420,30 +405,39 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
}
|
||||
|
||||
if self.filter == nil {
|
||||
self.chatListDisplayNode.containerNode.currentItemFilterUpdated = { [weak self] filter, fraction, transition in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
guard let layout = strongSelf.validLayout else {
|
||||
return
|
||||
}
|
||||
guard let tabContainerData = strongSelf.tabContainerData else {
|
||||
return
|
||||
}
|
||||
strongSelf.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: tabContainerData, selectedFilter: filter, transitionFraction: fraction, presentationData: strongSelf.presentationData, transition: transition)
|
||||
}
|
||||
let preferencesKey: PostboxViewKey = .preferences(keys: Set([
|
||||
ApplicationSpecificPreferencesKeys.chatListFilterSettings
|
||||
]))
|
||||
let filterItems = chatListFilterItems(context: context)
|
||||
|> map { totalCount, items -> [ChatListFilterTabEntry] in
|
||||
var result: [ChatListFilterTabEntry] = []
|
||||
result.append(.all(unreadCount: totalCount))
|
||||
for (filter, unreadCount) in items {
|
||||
result.append(.filter(id: filter.id, text: filter.title ?? "", unreadCount: unreadCount))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
self.filterDisposable = (combineLatest(queue: .mainQueue(),
|
||||
context.account.postbox.combinedView(keys: [
|
||||
preferencesKey
|
||||
]),
|
||||
filterItems,
|
||||
self.chatListFilterValue.get() |> map { $0?.id } |> distinctUntilChanged
|
||||
filterItems
|
||||
)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] combinedView, filterItems, selectedFilter in
|
||||
|> deliverOnMainQueue).start(next: { [weak self] combinedView, countAndFilterItems in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let (totalCount, items) = countAndFilterItems
|
||||
var filterItems: [ChatListFilterTabEntry] = []
|
||||
filterItems.append(.all(unreadCount: totalCount))
|
||||
for (filter, unreadCount) in items {
|
||||
filterItems.append(.filter(id: filter.id, text: filter.title, unreadCount: unreadCount))
|
||||
}
|
||||
|
||||
var filterSettings: ChatListFilterSettings = .default
|
||||
if let preferencesView = combinedView.views[preferencesKey] as? PreferencesView {
|
||||
if let value = preferencesView.values[ApplicationSpecificPreferencesKeys.chatListFilterSettings] as? ChatListFilterSettings {
|
||||
@ -458,12 +452,18 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
|
||||
var wasEmpty = false
|
||||
if let tabContainerData = strongSelf.tabContainerData {
|
||||
wasEmpty = tabContainerData.0.count <= 1
|
||||
wasEmpty = tabContainerData.count <= 1
|
||||
} else {
|
||||
wasEmpty = true
|
||||
}
|
||||
let selectedEntryId: ChatListFilterTabEntryId = selectedFilter.flatMap { .filter($0) } ?? .all
|
||||
strongSelf.tabContainerData = (resolvedItems, selectedEntryId)
|
||||
let selectedEntryId = strongSelf.chatListDisplayNode.containerNode.currentItemFilter
|
||||
strongSelf.tabContainerData = resolvedItems
|
||||
var availableFilters: [ChatListContainerNodeFilter] = []
|
||||
availableFilters.append(.all)
|
||||
for item in items {
|
||||
availableFilters.append(.filter(item.0))
|
||||
}
|
||||
strongSelf.chatListDisplayNode.containerNode.updateAvailableFilters(availableFilters)
|
||||
|
||||
let isEmpty = resolvedItems.count <= 1
|
||||
|
||||
@ -479,7 +479,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
strongSelf.containerLayoutUpdated(layout, transition: .immediate)
|
||||
(strongSelf.parent as? TabBarController)?.updateLayout()
|
||||
} else {
|
||||
strongSelf.tabContainerNode.update(size: CGSize(width: layout.size.width, height: NavigationBar.defaultSecondaryContentHeight), sideInset: layout.safeInsets.left, filters: resolvedItems, selectedFilter: selectedEntryId, presentationData: strongSelf.presentationData, transition: .animated(duration: 0.4, curve: .spring))
|
||||
strongSelf.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: resolvedItems, selectedFilter: selectedEntryId, transitionFraction: strongSelf.chatListDisplayNode.containerNode.transitionFraction, presentationData: strongSelf.presentationData, transition: .animated(duration: 0.4, curve: .spring))
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -497,7 +497,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let previousFilter = strongSelf.chatListDisplayNode.chatListNode.chatListFilter
|
||||
let previousFilter = strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter
|
||||
let updatedFilter: ChatListFilter?
|
||||
switch id {
|
||||
case .all:
|
||||
@ -518,26 +518,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
updatedFilter = nil
|
||||
}
|
||||
}
|
||||
if previousFilter?.id != updatedFilter?.id {
|
||||
var paneSwitchAnimationDirection: ChatListNodePaneSwitchAnimationDirection?
|
||||
if let previousId = previousFilter?.id, let updatedId = updatedFilter?.id, let previousIndex = filters.index(where: { $0.id == previousId }), let updatedIndex = filters.index(where: { $0.id == updatedId }) {
|
||||
if previousIndex > updatedIndex {
|
||||
paneSwitchAnimationDirection = .right
|
||||
} else {
|
||||
paneSwitchAnimationDirection = .left
|
||||
}
|
||||
} else if (previousFilter != nil) != (updatedFilter != nil) {
|
||||
if previousFilter != nil {
|
||||
paneSwitchAnimationDirection = .right
|
||||
} else {
|
||||
paneSwitchAnimationDirection = .left
|
||||
}
|
||||
}
|
||||
if let direction = paneSwitchAnimationDirection {
|
||||
strongSelf.chatListDisplayNode.chatListNode.paneSwitchAnimation = (direction, .animated(duration: 0.4, curve: .spring))
|
||||
}
|
||||
}
|
||||
strongSelf.chatListDisplayNode.chatListNode.updateFilter(updatedFilter)
|
||||
strongSelf.chatListDisplayNode.containerNode.switchToFilter(id: updatedFilter.flatMap { .filter($0.id) } ?? .all)
|
||||
})
|
||||
}
|
||||
|
||||
@ -549,38 +530,17 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
var items: [ContextMenuItem] = []
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Common_Edit, icon: { _ in
|
||||
return nil
|
||||
}, action: { c, f in
|
||||
c.dismiss(completion: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let _ = (strongSelf.context.account.postbox.transaction { transaction -> [ChatListFilter] in
|
||||
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default
|
||||
return settings.filters
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { presetList in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
var found = false
|
||||
for filter in presetList {
|
||||
if filter.id == id {
|
||||
strongSelf.push(chatListFilterPresetController(context: strongSelf.context, currentPreset: filter, updated: { _ in }))
|
||||
f(.dismissWithoutContent)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})))
|
||||
if let chatListFilter = strongSelf.chatListDisplayNode.chatListNode.chatListFilter, chatListFilter.includePeers.count < 100 {
|
||||
//TODO:localization
|
||||
items.append(.action(ContextMenuActionItem(text: "Add Chats", icon: { _ in
|
||||
return nil
|
||||
let _ = (strongSelf.context.account.postbox.transaction { transaction -> [ChatListFilter] in
|
||||
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default
|
||||
return settings.filters
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] filters in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
var items: [ContextMenuItem] = []
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Common_Edit, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { c, f in
|
||||
c.dismiss(completion: {
|
||||
guard let strongSelf = self else {
|
||||
@ -597,7 +557,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
var found = false
|
||||
for filter in presetList {
|
||||
if filter.id == id {
|
||||
strongSelf.push(chatListFilterAddChatsController(context: strongSelf.context, filter: filter))
|
||||
strongSelf.push(chatListFilterPresetController(context: strongSelf.context, currentPreset: filter, updated: { _ in }))
|
||||
f(.dismissWithoutContent)
|
||||
found = true
|
||||
break
|
||||
@ -606,10 +566,74 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
})
|
||||
})
|
||||
})))
|
||||
}
|
||||
|
||||
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatListHeaderBarContextExtractedContentSource(controller: strongSelf, sourceNode: sourceNode)), items: .single(items), reactionItems: [], recognizer: nil, gesture: gesture)
|
||||
strongSelf.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller)
|
||||
if let filter = filters.first(where: { $0.id == id }), filter.data.includePeers.count < 100 {
|
||||
//TODO:localization
|
||||
items.append(.action(ContextMenuActionItem(text: "Add Chats", icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { c, f in
|
||||
c.dismiss(completion: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let _ = (strongSelf.context.account.postbox.transaction { transaction -> [ChatListFilter] in
|
||||
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default
|
||||
return settings.filters
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { presetList in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
var found = false
|
||||
for filter in presetList {
|
||||
if filter.id == id {
|
||||
strongSelf.push(chatListFilterAddChatsController(context: strongSelf.context, filter: filter))
|
||||
f(.dismissWithoutContent)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})))
|
||||
items.append(.action(ContextMenuActionItem(text: "Delete", textColor: .destructive, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
|
||||
}, action: { c, f in
|
||||
c.dismiss(completion: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData)
|
||||
|
||||
actionSheet.setItemGroups([
|
||||
ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Delete, color: .destructive, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let _ = updateChatListFilterSettingsInteractively(postbox: strongSelf.context.account.postbox, { settings in
|
||||
var settings = settings
|
||||
settings.filters = settings.filters.filter({ $0.id != id })
|
||||
return settings
|
||||
}).start()
|
||||
let _ = replaceRemoteChatListFilters(account: strongSelf.context.account).start()
|
||||
})
|
||||
]),
|
||||
ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
})
|
||||
])
|
||||
])
|
||||
strongSelf.present(actionSheet, in: .window(.root))
|
||||
})
|
||||
})))
|
||||
}
|
||||
|
||||
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatListHeaderBarContextExtractedContentSource(controller: strongSelf, sourceNode: sourceNode)), items: .single(items), reactionItems: [], recognizer: nil, gesture: gesture)
|
||||
strongSelf.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -642,11 +666,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
}
|
||||
|
||||
self.searchContentNode?.updateThemeAndPlaceholder(theme: self.presentationData.theme, placeholder: self.presentationData.strings.DialogList_SearchLabel)
|
||||
var editing = false
|
||||
self.chatListDisplayNode.chatListNode.updateState { state in
|
||||
editing = state.editing
|
||||
return state
|
||||
}
|
||||
let editing = self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing
|
||||
let editItem: UIBarButtonItem
|
||||
if editing {
|
||||
editItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed))
|
||||
@ -676,7 +696,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = ChatListControllerNode(context: self.context, groupId: self.groupId, filter: self.filter, previewing: self.previewing, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: self.presentationData, controller: self)
|
||||
self.chatListFilterValue.set(self.chatListDisplayNode.chatListNode.appliedChatListFilterSignal)
|
||||
|
||||
self.chatListDisplayNode.navigationBar = self.navigationBar
|
||||
|
||||
@ -684,37 +703,37 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
self?.deactivateSearch(animated: true)
|
||||
}
|
||||
|
||||
self.chatListDisplayNode.chatListNode.activateSearch = { [weak self] in
|
||||
self.chatListDisplayNode.containerNode.activateSearch = { [weak self] in
|
||||
self?.activateSearch()
|
||||
}
|
||||
|
||||
self.chatListDisplayNode.chatListNode.presentAlert = { [weak self] text in
|
||||
self.chatListDisplayNode.containerNode.presentAlert = { [weak self] text in
|
||||
if let strongSelf = self {
|
||||
self?.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
}
|
||||
}
|
||||
|
||||
self.chatListDisplayNode.chatListNode.present = { [weak self] c in
|
||||
self.chatListDisplayNode.containerNode.present = { [weak self] c in
|
||||
if let strongSelf = self {
|
||||
self?.present(c, in: .window(.root))
|
||||
}
|
||||
}
|
||||
|
||||
self.chatListDisplayNode.chatListNode.toggleArchivedFolderHiddenByDefault = { [weak self] in
|
||||
self.chatListDisplayNode.containerNode.toggleArchivedFolderHiddenByDefault = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.toggleArchivedFolderHiddenByDefault()
|
||||
}
|
||||
|
||||
self.chatListDisplayNode.chatListNode.deletePeerChat = { [weak self] peerId in
|
||||
self.chatListDisplayNode.containerNode.deletePeerChat = { [weak self] peerId in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.deletePeerChat(peerId: peerId)
|
||||
}
|
||||
|
||||
self.chatListDisplayNode.chatListNode.peerSelected = { [weak self] peerId, animated, isAd in
|
||||
self.chatListDisplayNode.containerNode.peerSelected = { [weak self] peer, animated, isAd in
|
||||
if let strongSelf = self {
|
||||
if let navigationController = strongSelf.navigationController as? NavigationController {
|
||||
if isAd {
|
||||
@ -738,37 +757,37 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
scrollToEndIfExists = true
|
||||
}
|
||||
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), scrollToEndIfExists: scrollToEndIfExists, options: strongSelf.groupId == PeerGroupId.root ? [.removeOnMasterDetails] : [], parentGroupId: strongSelf.groupId, completion: { [weak self] in
|
||||
self?.chatListDisplayNode.chatListNode.clearHighlightAnimated(true)
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer.id), scrollToEndIfExists: scrollToEndIfExists, options: strongSelf.groupId == PeerGroupId.root ? [.removeOnMasterDetails] : [], parentGroupId: strongSelf.groupId, completion: { [weak self] in
|
||||
self?.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true)
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.chatListDisplayNode.chatListNode.groupSelected = { [weak self] groupId in
|
||||
self.chatListDisplayNode.containerNode.groupSelected = { [weak self] groupId in
|
||||
if let strongSelf = self {
|
||||
if let navigationController = strongSelf.navigationController as? NavigationController {
|
||||
let chatListController = ChatListControllerImpl(context: strongSelf.context, groupId: groupId, controlsHistoryPreload: false, enableDebugActions: false)
|
||||
chatListController.navigationPresentation = .master
|
||||
navigationController.pushViewController(chatListController)
|
||||
strongSelf.chatListDisplayNode.chatListNode.clearHighlightAnimated(true)
|
||||
strongSelf.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.chatListDisplayNode.chatListNode.updatePeerGrouping = { [weak self] peerId, group in
|
||||
self.chatListDisplayNode.containerNode.updatePeerGrouping = { [weak self] peerId, group in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if group {
|
||||
strongSelf.archiveChats(peerIds: [peerId])
|
||||
} else {
|
||||
strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerId)
|
||||
strongSelf.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(peerId)
|
||||
let _ = updatePeerGroupIdInteractively(postbox: strongSelf.context.account.postbox, peerId: peerId, groupId: group ? Namespaces.PeerGroup.archive : .root).start(completed: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(nil)
|
||||
strongSelf.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(nil)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -786,7 +805,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(actualPeerId), subject: .message(messageId), purposefulAction: {
|
||||
self?.deactivateSearch(animated: false)
|
||||
}, scrollToEndIfExists: scrollToEndIfExists, options: strongSelf.groupId == PeerGroupId.root ? [.removeOnMasterDetails] : []))
|
||||
strongSelf.chatListDisplayNode.chatListNode.clearHighlightAnimated(true)
|
||||
strongSelf.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true)
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -815,7 +834,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer.id), purposefulAction: { [weak self] in
|
||||
self?.deactivateSearch(animated: false)
|
||||
}, scrollToEndIfExists: scrollToEndIfExists, options: strongSelf.groupId == PeerGroupId.root ? [.removeOnMasterDetails] : []))
|
||||
strongSelf.chatListDisplayNode.chatListNode.clearHighlightAnimated(true)
|
||||
strongSelf.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true)
|
||||
}
|
||||
}
|
||||
}))
|
||||
@ -867,7 +886,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
navigationController.filterController(strongSelf, animated: true)
|
||||
}
|
||||
|
||||
self.chatListDisplayNode.chatListNode.contentOffsetChanged = { [weak self] offset in
|
||||
self.chatListDisplayNode.containerNode.contentOffsetChanged = { [weak self] offset in
|
||||
if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode, let validLayout = strongSelf.validLayout {
|
||||
var offset = offset
|
||||
if validLayout.inVoiceOver {
|
||||
@ -877,7 +896,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
}
|
||||
}
|
||||
|
||||
self.chatListDisplayNode.chatListNode.contentScrollingEnded = { [weak self] listView in
|
||||
self.chatListDisplayNode.containerNode.contentScrollingEnded = { [weak self] listView in
|
||||
if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode {
|
||||
return fixListNodeScrolling(listView, searchNode: searchContentNode)
|
||||
} else {
|
||||
@ -897,7 +916,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let filter = strongSelf.chatListDisplayNode.chatListNode.chatListFilter {
|
||||
if let filter = strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter {
|
||||
strongSelf.push(chatListFilterPresetController(context: strongSelf.context, currentPreset: filter, updated: { _ in }))
|
||||
} else {
|
||||
strongSelf.composePressed()
|
||||
@ -908,7 +927,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
self?.toolbarActionSelected(action: action)
|
||||
}
|
||||
|
||||
self.chatListDisplayNode.chatListNode.activateChatPreview = { [weak self] item, node, gesture in
|
||||
self.chatListDisplayNode.containerNode.activateChatPreview = { [weak self] item, node, gesture in
|
||||
guard let strongSelf = self else {
|
||||
gesture?.cancel()
|
||||
return
|
||||
@ -940,7 +959,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
}
|
||||
|
||||
let context = self.context
|
||||
let peerIdsAndOptions: Signal<(ChatListSelectionOptions, Set<PeerId>)?, NoError> = self.chatListDisplayNode.chatListNode.state
|
||||
let peerIdsAndOptions: Signal<(ChatListSelectionOptions, Set<PeerId>)?, NoError> = self.chatListDisplayNode.containerNode.currentItemState
|
||||
|> map { state -> Set<PeerId>? in
|
||||
if !state.editing {
|
||||
return nil
|
||||
@ -988,7 +1007,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
}
|
||||
}
|
||||
}
|
||||
toolbar = Toolbar(leftAction: leftAction, rightAction: ToolbarAction(title: presentationData.strings.Common_Delete, isEnabled: options.delete), middleAction: strongSelf.chatListDisplayNode.chatListNode.chatListFilter != nil ? nil : ToolbarAction(title: presentationData.strings.ChatList_ArchiveAction, isEnabled: archiveEnabled))
|
||||
toolbar = Toolbar(leftAction: leftAction, rightAction: ToolbarAction(title: presentationData.strings.Common_Delete, isEnabled: options.delete), middleAction: strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter != nil ? nil : ToolbarAction(title: presentationData.strings.ChatList_ArchiveAction, isEnabled: archiveEnabled))
|
||||
}
|
||||
} else {
|
||||
if let (options, peerIds) = peerIdsAndOptions {
|
||||
@ -1006,7 +1025,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
strongSelf.setToolbar(toolbar, transition: .animated(duration: 0.3, curve: .easeInOut))
|
||||
}))
|
||||
|
||||
self.ready.set(self.chatListDisplayNode.chatListNode.ready)
|
||||
self.ready.set(self.chatListDisplayNode.containerNode.ready)
|
||||
|
||||
self.displayNodeDidLoad()
|
||||
}
|
||||
@ -1105,7 +1124,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
}))
|
||||
}
|
||||
|
||||
self.chatListDisplayNode.chatListNode.addedVisibleChatsWithPeerIds = { [weak self] peerIds in
|
||||
self.chatListDisplayNode.containerNode.addedVisibleChatsWithPeerIds = { [weak self] peerIds in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -1145,7 +1164,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
self.deactivateSearch(animated: false)
|
||||
}
|
||||
|
||||
self.chatListDisplayNode.chatListNode.clearHighlightAnimated(true)
|
||||
self.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true)
|
||||
}
|
||||
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
@ -1161,12 +1180,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
tabContainerOffset += 44.0 + 44.0 + 44.0
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.tabContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: self.visualNavigationInsetHeight - self.additionalHeight - NavigationBar.defaultSecondaryContentHeight + tabContainerOffset), size: CGSize(width: layout.size.width, height: NavigationBar.defaultSecondaryContentHeight)))
|
||||
self.tabContainerNode.update(size: CGSize(width: layout.size.width, height: NavigationBar.defaultSecondaryContentHeight), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.tabContainerData?.1, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring))
|
||||
transition.updateFrame(node: self.tabContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: self.visualNavigationInsetHeight - self.additionalHeight - 46.0 + tabContainerOffset), size: CGSize(width: layout.size.width, height: 46.0)))
|
||||
self.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData ?? [], selectedFilter: self.chatListDisplayNode.containerNode.currentItemFilter, transitionFraction: self.chatListDisplayNode.containerNode.transitionFraction, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring))
|
||||
|
||||
if let searchContentNode = self.searchContentNode, layout.inVoiceOver != wasInVoiceOver {
|
||||
searchContentNode.updateListVisibleContentOffset(.known(0.0))
|
||||
self.chatListDisplayNode.chatListNode.scrollToPosition(.top)
|
||||
self.chatListDisplayNode.scrollToTop()
|
||||
}
|
||||
|
||||
self.chatListDisplayNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationInsetHeight, visualNavigationHeight: self.visualNavigationInsetHeight, cleanNavigationBarHeight: self.cleanNavigationHeight, transition: transition)
|
||||
@ -1188,7 +1207,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
}
|
||||
self.searchContentNode?.setIsEnabled(false, animated: true)
|
||||
|
||||
self.chatListDisplayNode.chatListNode.updateState { state in
|
||||
self.chatListDisplayNode.containerNode.updateState { state in
|
||||
var state = state
|
||||
state.editing = true
|
||||
state.peerIdWithRevealedOptions = nil
|
||||
@ -1206,7 +1225,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
}
|
||||
(self.navigationController as? NavigationController)?.updateMasterDetailsBlackout(nil, transition: .animated(duration: 0.4, curve: .spring))
|
||||
self.searchContentNode?.setIsEnabled(true, animated: true)
|
||||
self.chatListDisplayNode.chatListNode.updateState { state in
|
||||
self.chatListDisplayNode.containerNode.updateState { state in
|
||||
var state = state
|
||||
state.editing = false
|
||||
state.peerIdWithRevealedOptions = nil
|
||||
@ -1217,7 +1236,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
|
||||
public func activateSearch() {
|
||||
if self.displayNavigationBar {
|
||||
let _ = (self.chatListDisplayNode.chatListNode.contentsReady
|
||||
let _ = (self.chatListDisplayNode.containerNode.currentItemNode.contentsReady
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(completed: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
@ -1303,10 +1322,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
return nil
|
||||
}
|
||||
|
||||
let listLocation = self.view.convert(location, to: self.chatListDisplayNode.chatListNode.view)
|
||||
let listLocation = self.view.convert(location, to: self.chatListDisplayNode.containerNode.currentItemNode.view)
|
||||
|
||||
var selectedNode: ChatListItemNode?
|
||||
self.chatListDisplayNode.chatListNode.forEachItemNode { itemNode in
|
||||
self.chatListDisplayNode.containerNode.currentItemNode.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ChatListItemNode, itemNode.frame.contains(listLocation), !itemNode.isDisplayingRevealedOptions {
|
||||
selectedNode = itemNode
|
||||
}
|
||||
@ -1346,12 +1365,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
chatController.updatePresentationMode(.standard(previewing: false))
|
||||
if let navigationController = self.navigationController as? NavigationController {
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, chatController: chatController, context: self.context, chatLocation: chatController.chatLocation, animated: false))
|
||||
self.chatListDisplayNode.chatListNode.clearHighlightAnimated(true)
|
||||
self.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true)
|
||||
}
|
||||
} else if let chatListController = viewControllerToCommit as? ChatListController {
|
||||
if let navigationController = self.navigationController as? NavigationController {
|
||||
navigationController.pushViewController(chatListController, animated: false, completion: {})
|
||||
self.chatListDisplayNode.chatListNode.clearHighlightAnimated(true)
|
||||
self.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1373,22 +1392,22 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
let inputShortcuts: [KeyShortcut] = [
|
||||
KeyShortcut(title: strings.KeyCommand_JumpToPreviousChat, input: UIKeyCommand.inputUpArrow, modifiers: [.alternate], action: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.chatListDisplayNode.chatListNode.selectChat(.previous(unread: false))
|
||||
strongSelf.chatListDisplayNode.containerNode.currentItemNode.selectChat(.previous(unread: false))
|
||||
}
|
||||
}),
|
||||
KeyShortcut(title: strings.KeyCommand_JumpToNextChat, input: UIKeyCommand.inputDownArrow, modifiers: [.alternate], action: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.chatListDisplayNode.chatListNode.selectChat(.next(unread: false))
|
||||
strongSelf.chatListDisplayNode.containerNode.currentItemNode.selectChat(.next(unread: false))
|
||||
}
|
||||
}),
|
||||
KeyShortcut(title: strings.KeyCommand_JumpToPreviousUnreadChat, input: UIKeyCommand.inputUpArrow, modifiers: [.alternate, .shift], action: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.chatListDisplayNode.chatListNode.selectChat(.previous(unread: true))
|
||||
strongSelf.chatListDisplayNode.containerNode.currentItemNode.selectChat(.previous(unread: true))
|
||||
}
|
||||
}),
|
||||
KeyShortcut(title: strings.KeyCommand_JumpToNextUnreadChat, input: UIKeyCommand.inputDownArrow, modifiers: [.alternate, .shift], action: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.chatListDisplayNode.chatListNode.selectChat(.next(unread: true))
|
||||
strongSelf.chatListDisplayNode.containerNode.currentItemNode.selectChat(.next(unread: true))
|
||||
}
|
||||
}),
|
||||
KeyShortcut(title: strings.KeyCommand_NewMessage, input: "N", modifiers: [.command], action: { [weak self] in
|
||||
@ -1403,9 +1422,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
let openChat: (Int) -> Void = { [weak self] index in
|
||||
if let strongSelf = self {
|
||||
if index == 0 {
|
||||
strongSelf.chatListDisplayNode.chatListNode.selectChat(.peerId(strongSelf.context.account.peerId))
|
||||
strongSelf.chatListDisplayNode.containerNode.currentItemNode.selectChat(.peerId(strongSelf.context.account.peerId))
|
||||
} else {
|
||||
strongSelf.chatListDisplayNode.chatListNode.selectChat(.index(index - 1))
|
||||
strongSelf.chatListDisplayNode.containerNode.currentItemNode.selectChat(.index(index - 1))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1420,7 +1439,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
}
|
||||
|
||||
override public func toolbarActionSelected(action: ToolbarActionOption) {
|
||||
let peerIds = self.chatListDisplayNode.chatListNode.currentState.selectedPeerIds
|
||||
let peerIds = self.chatListDisplayNode.containerNode.currentItemNode.currentState.selectedPeerIds
|
||||
if case .left = action {
|
||||
let signal: Signal<Void, NoError>
|
||||
let context = self.context
|
||||
@ -1433,7 +1452,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
} else {
|
||||
let groupId = self.groupId
|
||||
signal = self.context.account.postbox.transaction { transaction -> Void in
|
||||
markAllChatsAsReadInteractively(transaction: transaction, viewTracker: context.account.viewTracker, groupId: groupId, filterPredicate: self.chatListDisplayNode.chatListNode.chatListFilter.flatMap(chatListFilterPredicate))
|
||||
markAllChatsAsReadInteractively(transaction: transaction, viewTracker: context.account.viewTracker, groupId: groupId, filterPredicate: (self.chatListDisplayNode.containerNode.currentItemNode.chatListFilter?.data).flatMap(chatListFilterPredicate))
|
||||
}
|
||||
}
|
||||
let _ = (signal
|
||||
@ -1450,7 +1469,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.chatListDisplayNode.chatListNode.updateState({ state in
|
||||
strongSelf.chatListDisplayNode.containerNode.updateState({ state in
|
||||
var state = state
|
||||
for peerId in peerIds {
|
||||
state.pendingRemovalPeerIds.insert(peerId)
|
||||
@ -1494,15 +1513,15 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
|> deliverOnMainQueue).start()
|
||||
return true
|
||||
} else if value == .undo {
|
||||
strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerIds.first!)
|
||||
strongSelf.chatListDisplayNode.chatListNode.updateState({ state in
|
||||
strongSelf.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(peerIds.first!)
|
||||
strongSelf.chatListDisplayNode.containerNode.updateState({ state in
|
||||
var state = state
|
||||
for peerId in peerIds {
|
||||
state.pendingRemovalPeerIds.remove(peerId)
|
||||
}
|
||||
return state
|
||||
})
|
||||
self?.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerIds.first!)
|
||||
self?.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(peerIds.first!)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@ -1526,7 +1545,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
self.archiveChats(peerIds: Array(peerIds))
|
||||
} else {
|
||||
if !peerIds.isEmpty {
|
||||
self.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerIds.first!)
|
||||
self.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(peerIds.first!)
|
||||
let _ = (self.context.account.postbox.transaction { transaction -> Void in
|
||||
for peerId in peerIds {
|
||||
updatePeerGroupIdInteractively(transaction: transaction, peerId: peerId, groupId: .root)
|
||||
@ -1536,7 +1555,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(nil)
|
||||
strongSelf.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(nil)
|
||||
strongSelf.donePressed()
|
||||
})
|
||||
}
|
||||
@ -1559,7 +1578,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.chatListDisplayNode.chatListNode.updateState { state in
|
||||
strongSelf.chatListDisplayNode.containerNode.updateState { state in
|
||||
var state = state
|
||||
if value {
|
||||
state.archiveShouldBeTemporaryRevealed = false
|
||||
@ -1677,7 +1696,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.chatListDisplayNode.chatListNode.updateState({ state in
|
||||
strongSelf.chatListDisplayNode.containerNode.updateState({ state in
|
||||
var state = state
|
||||
state.pendingClearHistoryPeerIds.insert(peer.peerId)
|
||||
return state
|
||||
@ -1698,7 +1717,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.chatListDisplayNode.chatListNode.updateState({ state in
|
||||
strongSelf.chatListDisplayNode.containerNode.updateState({ state in
|
||||
var state = state
|
||||
state.pendingClearHistoryPeerIds.remove(peer.peerId)
|
||||
return state
|
||||
@ -1706,7 +1725,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
})
|
||||
return true
|
||||
} else if value == .undo {
|
||||
strongSelf.chatListDisplayNode.chatListNode.updateState({ state in
|
||||
strongSelf.chatListDisplayNode.containerNode.updateState({ state in
|
||||
var state = state
|
||||
state.pendingClearHistoryPeerIds.remove(peer.peerId)
|
||||
return state
|
||||
@ -1870,7 +1889,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
return
|
||||
}
|
||||
let postbox = self.context.account.postbox
|
||||
self.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerIds[0])
|
||||
self.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(peerIds[0])
|
||||
let _ = (ApplicationSpecificNotice.incrementArchiveChatTips(accountManager: self.context.sharedContext.accountManager, count: 1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] previousHintCount in
|
||||
let _ = (postbox.transaction { transaction -> Void in
|
||||
@ -1882,7 +1901,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(nil)
|
||||
strongSelf.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(nil)
|
||||
|
||||
for peerId in peerIds {
|
||||
deleteSendMessageIntents(peerId: peerId)
|
||||
@ -1893,7 +1912,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
return false
|
||||
}
|
||||
if value == .undo {
|
||||
strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerIds[0])
|
||||
strongSelf.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(peerIds[0])
|
||||
let _ = (postbox.transaction { transaction -> Void in
|
||||
for peerId in peerIds {
|
||||
updatePeerGroupIdInteractively(transaction: transaction, peerId: peerId, groupId: .root)
|
||||
@ -1903,7 +1922,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(nil)
|
||||
strongSelf.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(nil)
|
||||
})
|
||||
return true
|
||||
} else {
|
||||
@ -1949,13 +1968,13 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
}
|
||||
|
||||
let peerId = peer.peerId
|
||||
self.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerId)
|
||||
self.chatListDisplayNode.chatListNode.updateState({ state in
|
||||
self.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(peerId)
|
||||
self.chatListDisplayNode.containerNode.updateState({ state in
|
||||
var state = state
|
||||
state.pendingRemovalPeerIds.insert(peer.peerId)
|
||||
return state
|
||||
})
|
||||
self.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(nil)
|
||||
self.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(nil)
|
||||
let statusText: String
|
||||
if let channel = chatPeer as? TelegramChannel {
|
||||
if deleteGloballyIfPossible {
|
||||
@ -1999,7 +2018,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
return false
|
||||
}
|
||||
if value == .commit {
|
||||
strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerId)
|
||||
strongSelf.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(peerId)
|
||||
if let channel = chatPeer as? TelegramChannel {
|
||||
strongSelf.context.peerChannelMemberCategoriesContextsManager.externallyRemoved(peerId: channel.id, memberId: strongSelf.context.account.peerId)
|
||||
}
|
||||
@ -2007,25 +2026,25 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.chatListDisplayNode.chatListNode.updateState({ state in
|
||||
strongSelf.chatListDisplayNode.containerNode.updateState({ state in
|
||||
var state = state
|
||||
state.pendingRemovalPeerIds.remove(peer.peerId)
|
||||
return state
|
||||
})
|
||||
strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(nil)
|
||||
strongSelf.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(nil)
|
||||
|
||||
deleteSendMessageIntents(peerId: peerId)
|
||||
})
|
||||
completion()
|
||||
return true
|
||||
} else if value == .undo {
|
||||
strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerId)
|
||||
strongSelf.chatListDisplayNode.chatListNode.updateState({ state in
|
||||
strongSelf.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(peerId)
|
||||
strongSelf.chatListDisplayNode.containerNode.updateState({ state in
|
||||
var state = state
|
||||
state.pendingRemovalPeerIds.remove(peer.peerId)
|
||||
return state
|
||||
})
|
||||
strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(nil)
|
||||
strongSelf.chatListDisplayNode.containerNode.currentItemNode.setCurrentRemovingPeerId(nil)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@ -2054,35 +2073,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
}))
|
||||
}
|
||||
|
||||
public func presentTabBarPreviewingController(sourceNodes: [ASDisplayNode]) {
|
||||
if self.isNodeLoaded {
|
||||
let _ = (self.context.account.postbox.transaction { transaction -> [ChatListFilter] in
|
||||
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default
|
||||
return settings.filters
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presetList in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let controller = TabBarChatListFilterController(context: strongSelf.context, sourceNodes: sourceNodes, presetList: presetList, currentPreset: strongSelf.chatListDisplayNode.chatListNode.chatListFilter, setup: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.push(chatListFilterPresetListController(context: strongSelf.context, updated: { _ in
|
||||
}))
|
||||
}, updatePreset: { value in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let value = value {
|
||||
strongSelf.tabContainerNode.tabSelected?(.filter(value.id))
|
||||
}
|
||||
})
|
||||
strongSelf.context.sharedContext.mainWindow?.present(controller, on: .root)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override public func tabBarItemContextAction(sourceNode: ContextExtractedContentContainingNode, gesture: ContextGesture) {
|
||||
let _ = (combineLatest(queue: .mainQueue(),
|
||||
self.context.account.postbox.transaction { transaction -> [ChatListFilter] in
|
||||
@ -2120,6 +2110,18 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
})
|
||||
})))
|
||||
|
||||
if strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter != nil {
|
||||
items.append(.action(ContextMenuActionItem(text: "All Chats", icon: { theme in
|
||||
return nil
|
||||
}, action: { c, f in
|
||||
f(.dismissWithoutContent)
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.tabContainerNode.tabSelected?(.all)
|
||||
})))
|
||||
}
|
||||
|
||||
if !presetList.isEmpty {
|
||||
items.append(.separator)
|
||||
|
||||
@ -2135,25 +2137,25 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
case privateChats
|
||||
}
|
||||
let filterType: ChatListFilterType
|
||||
if preset.includePeers.isEmpty {
|
||||
if preset.categories == .all {
|
||||
if preset.excludeRead {
|
||||
if preset.data.includePeers.isEmpty {
|
||||
if preset.data.categories == .all {
|
||||
if preset.data.excludeRead {
|
||||
filterType = .unread
|
||||
} else if preset.excludeMuted {
|
||||
} else if preset.data.excludeMuted {
|
||||
filterType = .unmuted
|
||||
} else {
|
||||
filterType = .generic
|
||||
}
|
||||
} else {
|
||||
if preset.categories == .channels {
|
||||
if preset.data.categories == .channels {
|
||||
filterType = .channels
|
||||
} else if preset.categories.isSubset(of: [.publicGroups, .privateGroups]) {
|
||||
} else if preset.data.categories.isSubset(of: [.publicGroups, .privateGroups]) {
|
||||
filterType = .groups
|
||||
} else if preset.categories == .bots {
|
||||
} else if preset.data.categories == .bots {
|
||||
filterType = .bots
|
||||
} else if preset.categories == .secretChats {
|
||||
} else if preset.data.categories == .secretChats {
|
||||
filterType = .secretChats
|
||||
} else if preset.categories == .privateChats {
|
||||
} else if preset.data.categories == .privateChats {
|
||||
filterType = .privateChats
|
||||
} else {
|
||||
filterType = .generic
|
||||
@ -2203,6 +2205,27 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
strongSelf.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller)
|
||||
})
|
||||
}
|
||||
|
||||
override public func tabBarItemSwipeAction(direction: TabBarItemSwipeDirection) {
|
||||
guard let entries = self.tabContainerData, var index = entries.index(where: { $0.id == self.chatListDisplayNode.containerNode.currentItemFilter }) else {
|
||||
return
|
||||
}
|
||||
switch direction {
|
||||
case .right:
|
||||
if index == 0 {
|
||||
index = entries.count - 1
|
||||
} else {
|
||||
index -= 1
|
||||
}
|
||||
case .left:
|
||||
if index == entries.count - 1 {
|
||||
index = 0
|
||||
} else {
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
self.tabContainerNode.tabSelected?(entries[index].id)
|
||||
}
|
||||
}
|
||||
|
||||
private final class ChatListTabBarContextExtractedContentSource: ContextExtractedContentSource {
|
||||
|
||||
@ -26,19 +26,579 @@ private final class ChatListControllerNodeView: UITracingLayerView, PreviewingHo
|
||||
weak var controller: ChatListControllerImpl?
|
||||
}
|
||||
|
||||
private struct TestItem: Comparable, Identifiable {
|
||||
var value: Int
|
||||
var version: Int
|
||||
enum ChatListContainerNodeFilter: Equatable {
|
||||
case all
|
||||
case filter(ChatListFilter)
|
||||
|
||||
var stableId: Int {
|
||||
return self.value
|
||||
var id: ChatListFilterTabEntryId {
|
||||
switch self {
|
||||
case .all:
|
||||
return .all
|
||||
case let .filter(filter):
|
||||
return .filter(filter.id)
|
||||
}
|
||||
}
|
||||
|
||||
static func <(lhs: TestItem, rhs: TestItem) -> Bool {
|
||||
if lhs.version != rhs.version {
|
||||
return lhs.version < rhs.version
|
||||
var filter: ChatListFilter? {
|
||||
switch self {
|
||||
case .all:
|
||||
return nil
|
||||
case let .filter(filter):
|
||||
return filter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class ChatListContainerItemNode: ASDisplayNode {
|
||||
private var presentationData: PresentationData
|
||||
private let becameEmpty: (ChatListFilter?) -> Void
|
||||
private let emptyAction: (ChatListFilter?) -> Void
|
||||
|
||||
private var emptyNode: ChatListEmptyNode?
|
||||
let listNode: ChatListNode
|
||||
|
||||
private var validLayout: (CGSize, UIEdgeInsets, CGFloat)?
|
||||
|
||||
init(context: AccountContext, groupId: PeerGroupId, filter: ChatListFilter?, previewing: Bool, presentationData: PresentationData, becameEmpty: @escaping (ChatListFilter?) -> Void, emptyAction: @escaping (ChatListFilter?) -> Void) {
|
||||
self.presentationData = presentationData
|
||||
self.becameEmpty = becameEmpty
|
||||
self.emptyAction = emptyAction
|
||||
|
||||
self.listNode = ChatListNode(context: context, groupId: groupId, chatListFilter: filter, previewing: previewing, controlsHistoryPreload: false, mode: .chatList, theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: presentationData.disableAnimations)
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.listNode)
|
||||
|
||||
self.listNode.isEmptyUpdated = { [weak self] isEmptyState, _, _, transition in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
switch isEmptyState {
|
||||
case let .empty(isLoading):
|
||||
if let currentNode = strongSelf.emptyNode {
|
||||
currentNode.updateIsLoading(isLoading)
|
||||
} else {
|
||||
let emptyNode = ChatListEmptyNode(isFilter: filter != nil, isLoading: isLoading, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, action: {
|
||||
self?.emptyAction(filter)
|
||||
})
|
||||
strongSelf.emptyNode = emptyNode
|
||||
strongSelf.addSubnode(emptyNode)
|
||||
if let (size, insets, _) = strongSelf.validLayout {
|
||||
let emptyNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top - insets.bottom))
|
||||
emptyNode.frame = emptyNodeFrame
|
||||
emptyNode.updateLayout(size: emptyNodeFrame.size, transition: .immediate)
|
||||
}
|
||||
}
|
||||
strongSelf.becameEmpty(filter)
|
||||
case .notEmpty:
|
||||
if let emptyNode = strongSelf.emptyNode {
|
||||
strongSelf.emptyNode = nil
|
||||
transition.updateAlpha(node: emptyNode, alpha: 0.0, completion: { [weak emptyNode] _ in
|
||||
emptyNode?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updatePresentationData(_ presentationData: PresentationData) {
|
||||
self.presentationData = presentationData
|
||||
|
||||
self.listNode.updateThemeAndStrings(theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: presentationData.disableAnimations)
|
||||
|
||||
self.emptyNode?.updateThemeAndStrings(theme: presentationData.theme, strings: presentationData.strings)
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, insets: UIEdgeInsets, visualNavigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
self.validLayout = (size, insets, visualNavigationHeight)
|
||||
|
||||
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: size, insets: insets, duration: 0.0, curve: .Default(duration: 0.0))
|
||||
|
||||
transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||
self.listNode.visualInsets = UIEdgeInsets(top: visualNavigationHeight, left: 0.0, bottom: 0.0, right: 0.0)
|
||||
self.listNode.updateLayout(transition: .immediate, updateSizeAndInsets: updateSizeAndInsets)
|
||||
|
||||
if let emptyNode = self.emptyNode {
|
||||
let emptyNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top - insets.bottom))
|
||||
transition.updateFrame(node: emptyNode, frame: emptyNodeFrame)
|
||||
emptyNode.updateLayout(size: emptyNodeFrame.size, transition: transition)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
private let context: AccountContext
|
||||
private let groupId: PeerGroupId
|
||||
private let previewing: Bool
|
||||
private let filterBecameEmpty: (ChatListFilter?) -> Void
|
||||
private let filterEmptyAction: (ChatListFilter?) -> Void
|
||||
|
||||
private var presentationData: PresentationData
|
||||
|
||||
private var itemNodes: [ChatListFilterTabEntryId: ChatListContainerItemNode] = [:]
|
||||
private var pendingItemNode: (ChatListFilterTabEntryId, ChatListContainerItemNode, Disposable)?
|
||||
private var availableFilters: [ChatListContainerNodeFilter] = [.all]
|
||||
private var selectedId: ChatListFilterTabEntryId
|
||||
|
||||
private(set) var transitionFraction: CGFloat = 0.0
|
||||
private var disableItemNodeOperationsWhileAnimating: Bool = false
|
||||
private var validLayout: (layout: ContainerViewLayout, navigationBarHeight: CGFloat, visualNavigationHeight: CGFloat, cleanNavigationBarHeight: CGFloat)?
|
||||
|
||||
private let _ready = Promise<Bool>()
|
||||
var ready: Signal<Bool, NoError> {
|
||||
return _ready.get()
|
||||
}
|
||||
|
||||
private var currentItemNodeValue: ChatListContainerItemNode?
|
||||
var currentItemNode: ChatListNode {
|
||||
return self.currentItemNodeValue!.listNode
|
||||
}
|
||||
|
||||
private let currentItemStateValue = Promise<ChatListNodeState>()
|
||||
var currentItemState: Signal<ChatListNodeState, NoError> {
|
||||
return self.currentItemStateValue.get()
|
||||
}
|
||||
|
||||
var currentItemFilterUpdated: ((ChatListFilterTabEntryId, CGFloat, ContainedViewLayoutTransition) -> Void)?
|
||||
var currentItemFilter: ChatListFilterTabEntryId {
|
||||
return self.currentItemNode.chatListFilter.flatMap { .filter($0.id) } ?? .all
|
||||
}
|
||||
|
||||
private func applyItemNodeAsCurrent(id: ChatListFilterTabEntryId, itemNode: ChatListContainerItemNode) {
|
||||
if let previousItemNode = self.currentItemNodeValue {
|
||||
previousItemNode.listNode.activateSearch = nil
|
||||
previousItemNode.listNode.presentAlert = nil
|
||||
previousItemNode.listNode.present = nil
|
||||
previousItemNode.listNode.toggleArchivedFolderHiddenByDefault = nil
|
||||
previousItemNode.listNode.deletePeerChat = nil
|
||||
previousItemNode.listNode.peerSelected = nil
|
||||
previousItemNode.listNode.groupSelected = nil
|
||||
previousItemNode.listNode.updatePeerGrouping = nil
|
||||
previousItemNode.listNode.contentOffsetChanged = nil
|
||||
previousItemNode.listNode.contentScrollingEnded = nil
|
||||
previousItemNode.listNode.activateChatPreview = nil
|
||||
previousItemNode.listNode.addedVisibleChatsWithPeerIds = nil
|
||||
|
||||
previousItemNode.accessibilityElementsHidden = true
|
||||
}
|
||||
self.currentItemNodeValue = itemNode
|
||||
itemNode.accessibilityElementsHidden = false
|
||||
|
||||
itemNode.listNode.activateSearch = { [weak self] in
|
||||
self?.activateSearch?()
|
||||
}
|
||||
itemNode.listNode.presentAlert = { [weak self] text in
|
||||
self?.presentAlert?(text)
|
||||
}
|
||||
itemNode.listNode.present = { [weak self] c in
|
||||
self?.present?(c)
|
||||
}
|
||||
itemNode.listNode.toggleArchivedFolderHiddenByDefault = { [weak self] in
|
||||
self?.toggleArchivedFolderHiddenByDefault?()
|
||||
}
|
||||
itemNode.listNode.deletePeerChat = { [weak self] peerId in
|
||||
self?.deletePeerChat?(peerId)
|
||||
}
|
||||
itemNode.listNode.peerSelected = { [weak self] peerId, a, b in
|
||||
self?.peerSelected?(peerId, a, b)
|
||||
}
|
||||
itemNode.listNode.groupSelected = { [weak self] groupId in
|
||||
self?.groupSelected?(groupId)
|
||||
}
|
||||
itemNode.listNode.updatePeerGrouping = { [weak self] peerId, group in
|
||||
self?.updatePeerGrouping?(peerId, group)
|
||||
}
|
||||
itemNode.listNode.contentOffsetChanged = { [weak self] offset in
|
||||
self?.contentOffsetChanged?(offset)
|
||||
}
|
||||
itemNode.listNode.contentScrollingEnded = { [weak self] listView in
|
||||
return self?.contentScrollingEnded?(listView) ?? false
|
||||
}
|
||||
itemNode.listNode.activateChatPreview = { [weak self] item, sourceNode, gesture in
|
||||
self?.activateChatPreview?(item, sourceNode, gesture)
|
||||
}
|
||||
itemNode.listNode.addedVisibleChatsWithPeerIds = { [weak self] ids in
|
||||
self?.addedVisibleChatsWithPeerIds?(ids)
|
||||
}
|
||||
|
||||
self.currentItemStateValue.set(itemNode.listNode.state)
|
||||
}
|
||||
|
||||
var activateSearch: (() -> Void)?
|
||||
var presentAlert: ((String) -> Void)?
|
||||
var present: ((ViewController) -> Void)?
|
||||
var toggleArchivedFolderHiddenByDefault: (() -> Void)?
|
||||
var deletePeerChat: ((PeerId) -> Void)?
|
||||
var peerSelected: ((Peer, Bool, Bool) -> Void)?
|
||||
var groupSelected: ((PeerGroupId) -> Void)?
|
||||
var updatePeerGrouping: ((PeerId, Bool) -> Void)?
|
||||
var contentOffsetChanged: ((ListViewVisibleContentOffset) -> Void)?
|
||||
var contentScrollingEnded: ((ListView) -> Bool)?
|
||||
var activateChatPreview: ((ChatListItem, ASDisplayNode, ContextGesture?) -> Void)?
|
||||
var addedVisibleChatsWithPeerIds: (([PeerId]) -> Void)?
|
||||
|
||||
init(context: AccountContext, groupId: PeerGroupId, previewing: Bool, presentationData: PresentationData, filterBecameEmpty: @escaping (ChatListFilter?) -> Void, filterEmptyAction: @escaping (ChatListFilter?) -> Void) {
|
||||
self.context = context
|
||||
self.groupId = groupId
|
||||
self.previewing = previewing
|
||||
self.filterBecameEmpty = filterBecameEmpty
|
||||
self.filterEmptyAction = filterEmptyAction
|
||||
|
||||
self.presentationData = presentationData
|
||||
|
||||
self.selectedId = .all
|
||||
|
||||
super.init()
|
||||
|
||||
let itemNode = ChatListContainerItemNode(context: self.context, groupId: self.groupId, filter: nil, previewing: self.previewing, presentationData: presentationData, becameEmpty: { [weak self] filter in
|
||||
self?.filterBecameEmpty(filter)
|
||||
}, emptyAction: { [weak self] filter in
|
||||
self?.filterEmptyAction(filter)
|
||||
})
|
||||
self.itemNodes[.all] = itemNode
|
||||
self.addSubnode(itemNode)
|
||||
|
||||
self._ready.set(itemNode.listNode.ready)
|
||||
|
||||
self.applyItemNodeAsCurrent(id: .all, itemNode: itemNode)
|
||||
|
||||
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] _ in
|
||||
guard let strongSelf = self, let index = strongSelf.availableFilters.index(where: { $0.id == strongSelf.selectedId }) else {
|
||||
return []
|
||||
}
|
||||
var directions: InteractiveTransitionGestureRecognizerDirections = [.left, .right]
|
||||
if strongSelf.availableFilters.count > 1 {
|
||||
if index == 0 {
|
||||
directions.remove(.right)
|
||||
}
|
||||
if index == strongSelf.availableFilters.count - 1 {
|
||||
directions.remove(.left)
|
||||
}
|
||||
} else {
|
||||
directions = []
|
||||
}
|
||||
return directions
|
||||
})
|
||||
panRecognizer.delegate = self
|
||||
panRecognizer.delaysTouchesBegan = false
|
||||
panRecognizer.cancelsTouchesInView = true
|
||||
self.view.addGestureRecognizer(panRecognizer)
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.pendingItemNode?.2.dispose()
|
||||
}
|
||||
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
if let _ = otherGestureRecognizer as? InteractiveTransitionGestureRecognizer {
|
||||
return false
|
||||
}
|
||||
if let _ = otherGestureRecognizer as? UIPanGestureRecognizer {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
|
||||
switch recognizer.state {
|
||||
case .changed:
|
||||
if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight) = self.validLayout, let selectedIndex = self.availableFilters.index(where: { $0.id == self.selectedId }) {
|
||||
let translation = recognizer.translation(in: self.view)
|
||||
var transitionFraction = translation.x / layout.size.width
|
||||
if selectedIndex <= 0 {
|
||||
transitionFraction = min(0.0, transitionFraction)
|
||||
}
|
||||
if selectedIndex >= self.availableFilters.count - 1 {
|
||||
transitionFraction = max(0.0, transitionFraction)
|
||||
}
|
||||
self.transitionFraction = transitionFraction
|
||||
if let currentItemNode = self.currentItemNodeValue {
|
||||
let isNavigationHidden = currentItemNode.listNode.isNavigationHidden
|
||||
for (_, itemNode) in self.itemNodes {
|
||||
if itemNode !== currentItemNode {
|
||||
itemNode.listNode.adjustScrollOffsetForNavigation(isNavigationHidden: isNavigationHidden)
|
||||
}
|
||||
}
|
||||
}
|
||||
self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, transition: .immediate)
|
||||
self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, .immediate)
|
||||
}
|
||||
case .cancelled, .ended:
|
||||
if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight) = self.validLayout, let selectedIndex = self.availableFilters.index(where: { $0.id == self.selectedId }) {
|
||||
let translation = recognizer.translation(in: self.view)
|
||||
let velocity = recognizer.velocity(in: self.view)
|
||||
var directionIsToRight: Bool?
|
||||
if abs(velocity.x) > 10.0 {
|
||||
directionIsToRight = velocity.x < 0.0
|
||||
} else {
|
||||
if abs(translation.x) > layout.size.width / 2.0 {
|
||||
directionIsToRight = translation.x > layout.size.width / 2.0
|
||||
}
|
||||
}
|
||||
if let directionIsToRight = directionIsToRight {
|
||||
var updatedIndex = selectedIndex
|
||||
if directionIsToRight {
|
||||
updatedIndex = min(updatedIndex + 1, self.availableFilters.count - 1)
|
||||
} else {
|
||||
updatedIndex = max(updatedIndex - 1, 0)
|
||||
}
|
||||
let switchToId = self.availableFilters[updatedIndex].id
|
||||
if switchToId != self.selectedId, let itemNode = self.itemNodes[switchToId] {
|
||||
self.selectedId = switchToId
|
||||
self.applyItemNodeAsCurrent(id: switchToId, itemNode: itemNode)
|
||||
}
|
||||
}
|
||||
self.transitionFraction = 0.0
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.45, curve: .spring)
|
||||
self.disableItemNodeOperationsWhileAnimating = true
|
||||
self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, transition: transition)
|
||||
self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, transition)
|
||||
transition.updateBounds(node: self, bounds: self.bounds, force: true, completion: { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.disableItemNodeOperationsWhileAnimating = false
|
||||
if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight) = strongSelf.validLayout {
|
||||
strongSelf.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, transition: .immediate)
|
||||
}
|
||||
})
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func updatePresentationData(_ presentationData: PresentationData) {
|
||||
self.presentationData = presentationData
|
||||
|
||||
for (_, itemNode) in self.itemNodes {
|
||||
itemNode.updatePresentationData(presentationData)
|
||||
}
|
||||
}
|
||||
|
||||
func playArchiveAnimation() {
|
||||
if let itemNode = self.itemNodes[self.selectedId] {
|
||||
itemNode.listNode.forEachVisibleItemNode { node in
|
||||
if let node = node as? ChatListItemNode {
|
||||
node.playArchiveAnimation()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func scrollToTop() {
|
||||
if let itemNode = self.itemNodes[self.selectedId] {
|
||||
itemNode.listNode.scrollToPosition(.top)
|
||||
}
|
||||
}
|
||||
|
||||
func updateSelectedChatLocation(data: ChatLocation?, progress: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
for (_, itemNode) in self.itemNodes {
|
||||
itemNode.listNode.updateSelectedChatLocation(data, progress: progress, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
func updateState(_ f: (ChatListNodeState) -> ChatListNodeState) {
|
||||
self.currentItemNode.updateState(f)
|
||||
let updatedState = self.currentItemNode.currentState
|
||||
for (id, itemNode) in self.itemNodes {
|
||||
if id != self.selectedId {
|
||||
itemNode.listNode.updateState { state in
|
||||
var state = state
|
||||
state.editing = updatedState.editing
|
||||
state.selectedPeerIds = updatedState.selectedPeerIds
|
||||
return state
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateAvailableFilters(_ availableFilters: [ChatListContainerNodeFilter]) {
|
||||
if self.availableFilters != availableFilters {
|
||||
self.availableFilters = availableFilters
|
||||
if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight) = self.validLayout {
|
||||
self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func switchToFilter(id: ChatListFilterTabEntryId) {
|
||||
guard let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight) = self.validLayout else {
|
||||
return
|
||||
}
|
||||
if id != self.selectedId, let index = self.availableFilters.index(where: { $0.id == id }) {
|
||||
if let itemNode = self.itemNodes[id] {
|
||||
self.selectedId = id
|
||||
if let currentItemNode = self.currentItemNodeValue {
|
||||
itemNode.listNode.adjustScrollOffsetForNavigation(isNavigationHidden: currentItemNode.listNode.isNavigationHidden)
|
||||
}
|
||||
self.applyItemNodeAsCurrent(id: id, itemNode: itemNode)
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.35, curve: .spring)
|
||||
self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, transition: transition)
|
||||
self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, transition)
|
||||
} else if self.pendingItemNode == nil {
|
||||
let itemNode = ChatListContainerItemNode(context: self.context, groupId: self.groupId, filter: self.availableFilters[index].filter, previewing: self.previewing, presentationData: self.presentationData, becameEmpty: { [weak self] filter in
|
||||
self?.filterBecameEmpty(filter)
|
||||
}, emptyAction: { [weak self] filter in
|
||||
self?.filterEmptyAction(filter)
|
||||
})
|
||||
let disposable = MetaDisposable()
|
||||
self.pendingItemNode = (id, itemNode, disposable)
|
||||
|
||||
disposable.set((itemNode.listNode.ready
|
||||
|> filter { $0 }
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self, weak itemNode] _ in
|
||||
guard let strongSelf = self, let itemNode = itemNode, itemNode === strongSelf.pendingItemNode?.1 else {
|
||||
return
|
||||
}
|
||||
guard let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight) = strongSelf.validLayout else {
|
||||
return
|
||||
}
|
||||
strongSelf.pendingItemNode = nil
|
||||
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.35, curve: .spring)
|
||||
|
||||
if let previousIndex = strongSelf.availableFilters.index(where: { $0.id == strongSelf.selectedId }), let index = strongSelf.availableFilters.index(where: { $0.id == id }) {
|
||||
let previousId = strongSelf.selectedId
|
||||
let offsetDirection: CGFloat = index < previousIndex ? 1.0 : -1.0
|
||||
let offset = offsetDirection * layout.size.width
|
||||
|
||||
var validNodeIds: [ChatListFilterTabEntryId] = []
|
||||
for i in max(0, index - 1) ... min(strongSelf.availableFilters.count - 1, index + 1) {
|
||||
validNodeIds.append(strongSelf.availableFilters[i].id)
|
||||
}
|
||||
|
||||
var removeIds: [ChatListFilterTabEntryId] = []
|
||||
for (id, _) in strongSelf.itemNodes {
|
||||
if !validNodeIds.contains(id) {
|
||||
removeIds.append(id)
|
||||
}
|
||||
}
|
||||
for id in removeIds {
|
||||
if let itemNode = strongSelf.itemNodes.removeValue(forKey: id) {
|
||||
if id == previousId {
|
||||
transition.updateFrame(node: itemNode, frame: itemNode.frame.offsetBy(dx: offset, dy: 0.0), completion: { [weak itemNode] _ in
|
||||
itemNode?.removeFromSupernode()
|
||||
})
|
||||
} else {
|
||||
itemNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.itemNodes[id] = itemNode
|
||||
strongSelf.addSubnode(itemNode)
|
||||
|
||||
let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: layout.size)
|
||||
itemNode.frame = itemFrame
|
||||
|
||||
transition.animatePositionAdditive(node: itemNode, offset: CGPoint(x: -offset, y: 0.0))
|
||||
|
||||
var insets = layout.insets(options: [.input])
|
||||
insets.top += navigationBarHeight
|
||||
|
||||
insets.left += layout.safeInsets.left
|
||||
insets.right += layout.safeInsets.right
|
||||
|
||||
itemNode.updateLayout(size: layout.size, insets: insets, visualNavigationHeight: visualNavigationHeight, transition: .immediate)
|
||||
|
||||
strongSelf.selectedId = id
|
||||
if let currentItemNode = strongSelf.currentItemNodeValue {
|
||||
itemNode.listNode.adjustScrollOffsetForNavigation(isNavigationHidden: currentItemNode.listNode.isNavigationHidden)
|
||||
}
|
||||
strongSelf.applyItemNodeAsCurrent(id: id, itemNode: itemNode)
|
||||
|
||||
strongSelf.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, transition: .immediate)
|
||||
|
||||
strongSelf.currentItemFilterUpdated?(strongSelf.currentItemFilter, strongSelf.transitionFraction, transition)
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func update(layout: ContainerViewLayout, navigationBarHeight: CGFloat, visualNavigationHeight: CGFloat, cleanNavigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
self.validLayout = (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight)
|
||||
|
||||
var insets = layout.insets(options: [.input])
|
||||
insets.top += navigationBarHeight
|
||||
|
||||
insets.left += layout.safeInsets.left
|
||||
insets.right += layout.safeInsets.right
|
||||
|
||||
if let selectedIndex = self.availableFilters.index(where: { $0.id == self.selectedId }) {
|
||||
var validNodeIds: [ChatListFilterTabEntryId] = []
|
||||
for i in max(0, selectedIndex - 1) ... min(self.availableFilters.count - 1, selectedIndex + 1) {
|
||||
let id = self.availableFilters[i].id
|
||||
validNodeIds.append(id)
|
||||
|
||||
if self.itemNodes[id] == nil && !self.disableItemNodeOperationsWhileAnimating {
|
||||
let itemNode = ChatListContainerItemNode(context: self.context, groupId: self.groupId, filter: self.availableFilters[i].filter, previewing: self.previewing, presentationData: self.presentationData, becameEmpty: { [weak self] filter in
|
||||
self?.filterBecameEmpty(filter)
|
||||
}, emptyAction: { [weak self] filter in
|
||||
self?.filterEmptyAction(filter)
|
||||
})
|
||||
self.itemNodes[id] = itemNode
|
||||
}
|
||||
}
|
||||
|
||||
var removeIds: [ChatListFilterTabEntryId] = []
|
||||
var animateSlidingIds: [ChatListFilterTabEntryId] = []
|
||||
var slidingOffset: CGFloat?
|
||||
for (id, itemNode) in self.itemNodes {
|
||||
if !validNodeIds.contains(id) {
|
||||
removeIds.append(id)
|
||||
}
|
||||
guard let index = self.availableFilters.index(where: { $0.id == id }) else {
|
||||
continue
|
||||
}
|
||||
let indexDistance = CGFloat(index - selectedIndex) + self.transitionFraction
|
||||
|
||||
let wasAdded = itemNode.supernode == nil
|
||||
var nodeTransition = transition
|
||||
if wasAdded {
|
||||
self.addSubnode(itemNode)
|
||||
nodeTransition = .immediate
|
||||
}
|
||||
|
||||
let itemFrame = CGRect(origin: CGPoint(x: indexDistance * layout.size.width, y: 0.0), size: layout.size)
|
||||
if !wasAdded && slidingOffset == nil {
|
||||
slidingOffset = itemNode.frame.minX - itemFrame.minX
|
||||
}
|
||||
nodeTransition.updateFrame(node: itemNode, frame: itemFrame, completion: { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
itemNode.updateLayout(size: layout.size, insets: insets, visualNavigationHeight: visualNavigationHeight, transition: nodeTransition)
|
||||
|
||||
if wasAdded, case .animated = transition {
|
||||
animateSlidingIds.append(id)
|
||||
}
|
||||
}
|
||||
if let slidingOffset = slidingOffset {
|
||||
for id in animateSlidingIds {
|
||||
if let itemNode = self.itemNodes[id] {
|
||||
transition.animatePositionAdditive(node: itemNode, offset: CGPoint(x: slidingOffset, y: 0.0), completion: {
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
if !self.disableItemNodeOperationsWhileAnimating {
|
||||
for id in removeIds {
|
||||
if let itemNode = self.itemNodes.removeValue(forKey: id) {
|
||||
itemNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return lhs.value < rhs.value
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,9 +607,7 @@ final class ChatListControllerNode: ASDisplayNode {
|
||||
private let groupId: PeerGroupId
|
||||
private var presentationData: PresentationData
|
||||
|
||||
private var chatListEmptyNodeContainer: ChatListEmptyNodeContainer
|
||||
private var chatListEmptyIndicator: ActivityIndicator?
|
||||
let chatListNode: ChatListNode
|
||||
let containerNode: ChatListContainerNode
|
||||
var navigationBar: NavigationBar?
|
||||
weak var controller: ChatListControllerImpl?
|
||||
|
||||
@ -78,8 +636,13 @@ final class ChatListControllerNode: ASDisplayNode {
|
||||
self.groupId = groupId
|
||||
self.presentationData = presentationData
|
||||
|
||||
self.chatListEmptyNodeContainer = ChatListEmptyNodeContainer(theme: presentationData.theme, strings: presentationData.strings)
|
||||
self.chatListNode = ChatListNode(context: context, groupId: groupId, chatListFilter: filter, previewing: previewing, controlsHistoryPreload: controlsHistoryPreload, mode: .chatList, theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: presentationData.disableAnimations)
|
||||
var filterBecameEmpty: ((ChatListFilter?) -> Void)?
|
||||
var filterEmptyAction: ((ChatListFilter?) -> Void)?
|
||||
self.containerNode = ChatListContainerNode(context: context, groupId: groupId, previewing: previewing, presentationData: presentationData, filterBecameEmpty: { filter in
|
||||
filterBecameEmpty?(filter)
|
||||
}, filterEmptyAction: { filter in
|
||||
filterEmptyAction?(filter)
|
||||
})
|
||||
|
||||
self.controller = controller
|
||||
|
||||
@ -91,35 +654,24 @@ final class ChatListControllerNode: ASDisplayNode {
|
||||
|
||||
self.backgroundColor = presentationData.theme.chatList.backgroundColor
|
||||
|
||||
self.addSubnode(self.chatListNode)
|
||||
self.addSubnode(self.chatListEmptyNodeContainer)
|
||||
self.chatListNode.isEmptyUpdated = { [weak self] isEmptyState, isFilter, transitionDirection, transition in
|
||||
self.addSubnode(self.containerNode)
|
||||
|
||||
self.addSubnode(self.debugListView)
|
||||
|
||||
filterBecameEmpty = { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
switch isEmptyState {
|
||||
case .empty(false):
|
||||
if case .group = strongSelf.groupId {
|
||||
strongSelf.dismissSelf?()
|
||||
} else {
|
||||
strongSelf.chatListEmptyNodeContainer.update(state: isEmptyState, isFilter: isFilter, direction: transitionDirection, transition: transition)
|
||||
}
|
||||
case .notEmpty(false):
|
||||
if case .group = strongSelf.groupId {
|
||||
strongSelf.dismissSelf?()
|
||||
} else {
|
||||
strongSelf.chatListEmptyNodeContainer.update(state: isEmptyState, isFilter: isFilter, direction: transitionDirection, transition: transition)
|
||||
}
|
||||
default:
|
||||
strongSelf.chatListEmptyNodeContainer.update(state: isEmptyState, isFilter: isFilter, direction: transitionDirection, transition: transition)
|
||||
if case .group = strongSelf.groupId {
|
||||
strongSelf.dismissSelf?()
|
||||
}
|
||||
}
|
||||
|
||||
self.chatListEmptyNodeContainer.action = { [weak self] in
|
||||
self?.emptyListAction?()
|
||||
filterEmptyAction = { [weak self] filter in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.emptyListAction?()
|
||||
}
|
||||
|
||||
self.addSubnode(self.debugListView)
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
@ -133,9 +685,8 @@ final class ChatListControllerNode: ASDisplayNode {
|
||||
|
||||
self.backgroundColor = self.presentationData.theme.chatList.backgroundColor
|
||||
|
||||
self.chatListNode.updateThemeAndStrings(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: self.presentationData.disableAnimations)
|
||||
self.containerNode.updatePresentationData(presentationData)
|
||||
self.searchDisplayController?.updatePresentationData(presentationData)
|
||||
self.chatListEmptyNodeContainer.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings)
|
||||
|
||||
if let toolbarNode = self.toolbarNode {
|
||||
toolbarNode.updateTheme(TabBarControllerTheme(rootControllerTheme: self.presentationData.theme))
|
||||
@ -194,23 +745,8 @@ final class ChatListControllerNode: ASDisplayNode {
|
||||
})
|
||||
}
|
||||
|
||||
self.chatListNode.bounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)
|
||||
self.chatListNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0)
|
||||
|
||||
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
|
||||
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, duration: duration, curve: curve)
|
||||
|
||||
self.chatListNode.visualInsets = UIEdgeInsets(top: visualNavigationHeight, left: 0.0, bottom: 0.0, right: 0.0)
|
||||
self.chatListNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets)
|
||||
|
||||
let emptySize = CGSize(width: updateSizeAndInsets.size.width, height: updateSizeAndInsets.size.height - updateSizeAndInsets.insets.top - updateSizeAndInsets.insets.bottom)
|
||||
transition.updateFrame(node: self.chatListEmptyNodeContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: updateSizeAndInsets.insets.top), size: emptySize))
|
||||
self.chatListEmptyNodeContainer.updateLayout(size: emptySize, transition: transition)
|
||||
|
||||
if let chatListEmptyIndicator = self.chatListEmptyIndicator {
|
||||
let indicatorSize = chatListEmptyIndicator.measure(CGSize(width: 100.0, height: 100.0))
|
||||
transition.updateFrame(node: chatListEmptyIndicator, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - indicatorSize.width) / 2.0), y: updateSizeAndInsets.insets.top + floor((layout.size.height - updateSizeAndInsets.insets.top - updateSizeAndInsets.insets.bottom - indicatorSize.height) / 2.0)), size: indicatorSize))
|
||||
}
|
||||
transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
self.containerNode.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, transition: transition)
|
||||
|
||||
if let searchDisplayController = self.searchDisplayController {
|
||||
searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: cleanNavigationBarHeight, transition: transition)
|
||||
@ -242,7 +778,7 @@ final class ChatListControllerNode: ASDisplayNode {
|
||||
requestDeactivateSearch()
|
||||
}
|
||||
})
|
||||
self.chatListNode.accessibilityElementsHidden = true
|
||||
self.containerNode.accessibilityElementsHidden = true
|
||||
|
||||
self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: cleanNavigationBarHeight, transition: .immediate)
|
||||
self.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in
|
||||
@ -260,23 +796,19 @@ final class ChatListControllerNode: ASDisplayNode {
|
||||
if let searchDisplayController = self.searchDisplayController {
|
||||
searchDisplayController.deactivate(placeholder: placeholderNode, animated: animated)
|
||||
self.searchDisplayController = nil
|
||||
self.chatListNode.accessibilityElementsHidden = false
|
||||
self.containerNode.accessibilityElementsHidden = false
|
||||
}
|
||||
}
|
||||
|
||||
func playArchiveAnimation() {
|
||||
self.chatListNode.forEachVisibleItemNode { node in
|
||||
if let node = node as? ChatListItemNode {
|
||||
node.playArchiveAnimation()
|
||||
}
|
||||
}
|
||||
self.containerNode.playArchiveAnimation()
|
||||
}
|
||||
|
||||
func scrollToTop() {
|
||||
if let searchDisplayController = self.searchDisplayController {
|
||||
searchDisplayController.contentNode.scrollToTop()
|
||||
} else {
|
||||
self.chatListNode.scrollToPosition(.top)
|
||||
self.containerNode.scrollToTop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,13 +18,15 @@ private final class ChatListFilterPresetControllerArguments {
|
||||
let openAddPeer: () -> Void
|
||||
let deleteAdditionalPeer: (PeerId) -> Void
|
||||
let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
|
||||
let focusOnName: () -> Void
|
||||
|
||||
init(context: AccountContext, updateState: @escaping ((ChatListFilterPresetControllerState) -> ChatListFilterPresetControllerState) -> Void, openAddPeer: @escaping () -> Void, deleteAdditionalPeer: @escaping (PeerId) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void) {
|
||||
init(context: AccountContext, updateState: @escaping ((ChatListFilterPresetControllerState) -> ChatListFilterPresetControllerState) -> Void, openAddPeer: @escaping () -> Void, deleteAdditionalPeer: @escaping (PeerId) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, focusOnName: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.updateState = updateState
|
||||
self.openAddPeer = openAddPeer
|
||||
self.deleteAdditionalPeer = deleteAdditionalPeer
|
||||
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
|
||||
self.focusOnName = focusOnName
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,13 +170,15 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
|
||||
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
|
||||
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(), text: value, placeholder: placeholder, type: .regular(capitalization: true, autocorrection: false), clearType: .always, sectionId: self.section, textUpdated: { value in
|
||||
arguments.updateState { current in
|
||||
var state = current
|
||||
state.name = value
|
||||
return state
|
||||
}
|
||||
}, action: {})
|
||||
}, action: {}, cleared: {
|
||||
arguments.focusOnName()
|
||||
})
|
||||
case let .typesHeader(text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .filterPrivateChats(title, value):
|
||||
@ -276,7 +280,7 @@ private func chatListFilterPresetControllerEntries(presentationData: Presentatio
|
||||
}
|
||||
|
||||
func chatListFilterAddChatsController(context: AccountContext, filter: ChatListFilter) -> ViewController {
|
||||
let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .peerSelection(searchChatList: true, searchGroups: true, searchChannels: true), options: []))
|
||||
let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .chatSelection, options: []))
|
||||
controller.navigationPresentation = .modal
|
||||
let _ = (controller.result
|
||||
|> take(1)
|
||||
@ -285,7 +289,7 @@ func chatListFilterAddChatsController(context: AccountContext, filter: ChatListF
|
||||
var settings = settings
|
||||
for i in 0 ..< settings.filters.count {
|
||||
if settings.filters[i].id == filter.id {
|
||||
let previousIncludePeers = settings.filters[i].includePeers
|
||||
let previousIncludePeers = settings.filters[i].data.includePeers
|
||||
|
||||
var chatPeerIds: [PeerId] = []
|
||||
for peerId in peerIds {
|
||||
@ -296,7 +300,7 @@ func chatListFilterAddChatsController(context: AccountContext, filter: ChatListF
|
||||
break
|
||||
}
|
||||
}
|
||||
settings.filters[i].includePeers = chatPeerIds + previousIncludePeers.filter { peerId in
|
||||
settings.filters[i].data.includePeers = chatPeerIds + previousIncludePeers.filter { peerId in
|
||||
return !chatPeerIds.contains(peerId)
|
||||
}
|
||||
}
|
||||
@ -305,6 +309,8 @@ func chatListFilterAddChatsController(context: AccountContext, filter: ChatListF
|
||||
})
|
||||
|> deliverOnMainQueue).start(next: { settings in
|
||||
controller?.dismiss()
|
||||
|
||||
let _ = replaceRemoteChatListFilters(account: context.account).start()
|
||||
})
|
||||
})
|
||||
return controller
|
||||
@ -317,7 +323,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
||||
} else {
|
||||
initialName = "New Filter"
|
||||
}
|
||||
let initialState = ChatListFilterPresetControllerState(name: initialName, includeCategories: currentPreset?.categories ?? .all, excludeMuted: currentPreset?.excludeMuted ?? false, excludeRead: currentPreset?.excludeRead ?? false, additionallyIncludePeers: currentPreset?.includePeers ?? [])
|
||||
let initialState = ChatListFilterPresetControllerState(name: initialName, includeCategories: currentPreset?.data.categories ?? .all, excludeMuted: currentPreset?.data.excludeMuted ?? false, excludeRead: currentPreset?.data.excludeRead ?? false, additionallyIncludePeers: currentPreset?.data.includePeers ?? [])
|
||||
let stateValue = Atomic(value: initialState)
|
||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||
let updateState: ((ChatListFilterPresetControllerState) -> ChatListFilterPresetControllerState) -> Void = { f in
|
||||
@ -331,6 +337,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
||||
|
||||
var presentControllerImpl: ((ViewController, Any?) -> Void)?
|
||||
var dismissImpl: (() -> Void)?
|
||||
var focusOnNameImpl: (() -> Void)?
|
||||
|
||||
let arguments = ChatListFilterPresetControllerArguments(
|
||||
context: context,
|
||||
@ -338,7 +345,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
||||
updateState(f)
|
||||
},
|
||||
openAddPeer: {
|
||||
let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .peerSelection(searchChatList: true, searchGroups: true, searchChannels: true), options: []))
|
||||
let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .chatSelection, options: []))
|
||||
addPeerDisposable.set((controller.result
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak controller] peerIds in
|
||||
@ -377,6 +384,9 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
||||
}
|
||||
return state
|
||||
}
|
||||
},
|
||||
focusOnName: {
|
||||
focusOnNameImpl?()
|
||||
}
|
||||
)
|
||||
|
||||
@ -409,7 +419,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
||||
})
|
||||
let rightNavigationButton = ItemListNavigationButton(content: .text(currentPreset == nil ? presentationData.strings.Common_Create : presentationData.strings.Common_Done), style: .bold, enabled: state.isComplete, action: {
|
||||
let state = stateValue.with { $0 }
|
||||
let preset = ChatListFilter(id: currentPreset?.id ?? -1, title: state.name, categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, includePeers: state.additionallyIncludePeers)
|
||||
let preset = ChatListFilter(id: currentPreset?.id ?? -1, title: state.name, data: ChatListFilterData(categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, includePeers: state.additionallyIncludePeers))
|
||||
let _ = (updateChatListFilterSettingsInteractively(postbox: context.account.postbox, { settings in
|
||||
var preset = preset
|
||||
if currentPreset == nil {
|
||||
@ -435,6 +445,8 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
||||
|> deliverOnMainQueue).start(next: { settings in
|
||||
updated(settings.filters)
|
||||
dismissImpl?()
|
||||
|
||||
let _ = replaceRemoteChatListFilters(account: context.account).start()
|
||||
})
|
||||
})
|
||||
|
||||
@ -455,6 +467,16 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
||||
dismissImpl = { [weak controller] in
|
||||
let _ = controller?.dismiss()
|
||||
}
|
||||
focusOnNameImpl = { [weak controller] in
|
||||
guard let controller = controller else {
|
||||
return
|
||||
}
|
||||
controller.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ItemListSingleLineInputItemNode {
|
||||
itemNode.focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
||||
|
||||
@ -53,7 +53,7 @@ private enum ChatListFilterPresetListEntryStableId: Hashable {
|
||||
|
||||
private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
|
||||
case listHeader(String)
|
||||
case preset(index: Int, title: String?, preset: ChatListFilter, canBeReordered: Bool, canBeDeleted: Bool, isEditing: Bool)
|
||||
case preset(index: Int, title: String, label: String, preset: ChatListFilter, canBeReordered: Bool, canBeDeleted: Bool, isEditing: Bool)
|
||||
case addItem(text: String, isEditing: Bool)
|
||||
case listFooter(String)
|
||||
|
||||
@ -99,8 +99,8 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
|
||||
switch self {
|
||||
case let .listHeader(text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, multiline: true, sectionId: self.section)
|
||||
case let .preset(index, title, preset, canBeReordered, canBeDeleted, isEditing):
|
||||
return ChatListFilterPresetListItem(presentationData: presentationData, preset: preset, title: title ?? "", editing: ChatListFilterPresetListItemEditing(editable: true, editing: isEditing, revealed: false), canBeReordered: canBeReordered, canBeDeleted: canBeDeleted, sectionId: self.section, action: {
|
||||
case let .preset(index, title, label, preset, canBeReordered, canBeDeleted, isEditing):
|
||||
return ChatListFilterPresetListItem(presentationData: presentationData, preset: preset, title: title ?? "", label: label, editing: ChatListFilterPresetListItemEditing(editable: true, editing: isEditing, revealed: false), canBeReordered: canBeReordered, canBeDeleted: canBeDeleted, sectionId: self.section, action: {
|
||||
arguments.openPreset(preset)
|
||||
}, setItemWithRevealedOptions: { lhs, rhs in
|
||||
arguments.setItemWithRevealedOptions(lhs, rhs)
|
||||
@ -122,14 +122,14 @@ private struct ChatListFilterPresetListControllerState: Equatable {
|
||||
var revealedPreset: Int32? = nil
|
||||
}
|
||||
|
||||
private func chatListFilterPresetListControllerEntries(presentationData: PresentationData, state: ChatListFilterPresetListControllerState, filtersState: ChatListFiltersState, settings: ChatListFilterSettings) -> [ChatListFilterPresetListEntry] {
|
||||
private func chatListFilterPresetListControllerEntries(presentationData: PresentationData, state: ChatListFilterPresetListControllerState, filters: [(ChatListFilter, Int)], settings: ChatListFilterSettings) -> [ChatListFilterPresetListEntry] {
|
||||
var entries: [ChatListFilterPresetListEntry] = []
|
||||
|
||||
entries.append(.listHeader("FILTERS"))
|
||||
for preset in filtersState.filters {
|
||||
entries.append(.preset(index: entries.count, title: preset.title, preset: preset, canBeReordered: filtersState.filters.count > 1, canBeDeleted: true, isEditing: state.isEditing))
|
||||
for (filter, chatCount) in filters {
|
||||
entries.append(.preset(index: entries.count, title: filter.title, label: chatCount == 0 ? "" : "\(chatCount)", preset: filter, canBeReordered: filters.count > 1, canBeDeleted: true, isEditing: state.isEditing))
|
||||
}
|
||||
if filtersState.filters.count < 10 {
|
||||
if filters.count < 10 {
|
||||
entries.append(.addItem(text: "Create New Filter", isEditing: state.isEditing))
|
||||
}
|
||||
entries.append(.listFooter("Tap \"Edit\" to change the order or delete filters."))
|
||||
@ -137,7 +137,7 @@ private func chatListFilterPresetListControllerEntries(presentationData: Present
|
||||
return entries
|
||||
}
|
||||
|
||||
func chatListFilterPresetListController(context: AccountContext, updated: @escaping ([ChatListFilter]) -> Void) -> ViewController {
|
||||
public func chatListFilterPresetListController(context: AccountContext, updated: @escaping ([ChatListFilter]) -> Void) -> ViewController {
|
||||
let initialState = ChatListFilterPresetListControllerState()
|
||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: initialState)
|
||||
@ -171,21 +171,51 @@ func chatListFilterPresetListController(context: AccountContext, updated: @escap
|
||||
})
|
||||
|> deliverOnMainQueue).start(next: { settings in
|
||||
updated(settings.filters)
|
||||
|
||||
let _ = replaceRemoteChatListFilters(account: context.account).start()
|
||||
})
|
||||
})
|
||||
|
||||
let preferences = context.account.postbox.preferencesView(keys: [PreferencesKeys.chatListFilters, ApplicationSpecificPreferencesKeys.chatListFilterSettings])
|
||||
let chatCountCache = Atomic<[ChatListFilterData: Int]>(value: [:])
|
||||
|
||||
let filtersWithCounts = context.account.postbox.preferencesView(keys: [PreferencesKeys.chatListFilters])
|
||||
|> map { preferences -> [ChatListFilter] in
|
||||
let filtersState = preferences.values[PreferencesKeys.chatListFilters] as? ChatListFiltersState ?? ChatListFiltersState.default
|
||||
return filtersState.filters
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|> mapToSignal { filters -> Signal<[(ChatListFilter, Int)], NoError> in
|
||||
return context.account.postbox.transaction { transaction -> [(ChatListFilter, Int)] in
|
||||
return filters.map { filter -> (ChatListFilter, Int) in
|
||||
let count: Int
|
||||
if let cachedValue = chatCountCache.with({ dict -> Int? in
|
||||
return dict[filter.data]
|
||||
}) {
|
||||
count = cachedValue
|
||||
} else {
|
||||
count = transaction.getChatCountMatchingPredicate(chatListFilterPredicate(filter: filter.data))
|
||||
let _ = chatCountCache.modify { dict in
|
||||
var dict = dict
|
||||
dict[filter.data] = count
|
||||
return dict
|
||||
}
|
||||
}
|
||||
return (filter, count)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let preferences = context.account.postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.chatListFilterSettings])
|
||||
|
||||
let signal = combineLatest(queue: .mainQueue(),
|
||||
context.sharedContext.presentationData,
|
||||
statePromise.get(),
|
||||
filtersWithCounts,
|
||||
preferences
|
||||
)
|
||||
|> map { presentationData, state, preferences -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let filtersState = preferences.values[PreferencesKeys.chatListFilters] as? ChatListFiltersState ?? ChatListFiltersState.default
|
||||
|> map { presentationData, state, filtersWithCounts, preferences -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let filterSettings = preferences.values[ApplicationSpecificPreferencesKeys.chatListFilterSettings] as? ChatListFilterSettings ?? ChatListFilterSettings.default
|
||||
let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Close), style: .regular, enabled: true, action: {
|
||||
let _ = replaceRemoteChatListFilters(account: context.account).start()
|
||||
dismissImpl?()
|
||||
})
|
||||
let rightNavigationButton: ItemListNavigationButton
|
||||
@ -208,7 +238,7 @@ func chatListFilterPresetListController(context: AccountContext, updated: @escap
|
||||
}
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text("Filters"), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: chatListFilterPresetListControllerEntries(presentationData: presentationData, state: state, filtersState: filtersState, settings: filterSettings), style: .blocks, animateChanges: true)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: chatListFilterPresetListControllerEntries(presentationData: presentationData, state: state, filters: filtersWithCounts, settings: filterSettings), style: .blocks, animateChanges: true)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
@ -217,6 +247,9 @@ func chatListFilterPresetListController(context: AccountContext, updated: @escap
|
||||
|
||||
let controller = ItemListController(context: context, state: signal)
|
||||
controller.navigationPresentation = .modal
|
||||
controller.willDisappear = { _ in
|
||||
let _ = replaceRemoteChatListFilters(account: context.account).start()
|
||||
}
|
||||
pushControllerImpl = { [weak controller] c in
|
||||
controller?.push(c)
|
||||
}
|
||||
|
||||
@ -20,6 +20,7 @@ final class ChatListFilterPresetListItem: ListViewItem, ItemListItem {
|
||||
let presentationData: ItemListPresentationData
|
||||
let preset: ChatListFilter
|
||||
let title: String
|
||||
let label: String
|
||||
let editing: ChatListFilterPresetListItemEditing
|
||||
let canBeReordered: Bool
|
||||
let canBeDeleted: Bool
|
||||
@ -32,6 +33,7 @@ final class ChatListFilterPresetListItem: ListViewItem, ItemListItem {
|
||||
presentationData: ItemListPresentationData,
|
||||
preset: ChatListFilter,
|
||||
title: String,
|
||||
label: String,
|
||||
editing: ChatListFilterPresetListItemEditing,
|
||||
canBeReordered: Bool,
|
||||
canBeDeleted: Bool,
|
||||
@ -43,6 +45,7 @@ final class ChatListFilterPresetListItem: ListViewItem, ItemListItem {
|
||||
self.presentationData = presentationData
|
||||
self.preset = preset
|
||||
self.title = title
|
||||
self.label = label
|
||||
self.editing = editing
|
||||
self.canBeReordered = canBeReordered
|
||||
self.canBeDeleted = canBeDeleted
|
||||
@ -108,6 +111,8 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
|
||||
private let maskNode: ASImageNode
|
||||
|
||||
private let titleNode: TextNode
|
||||
private let labelNode: TextNode
|
||||
private let arrowNode: ASImageNode
|
||||
|
||||
private let activateArea: AccessibilityAreaNode
|
||||
|
||||
@ -141,6 +146,14 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
|
||||
self.titleNode.contentMode = .left
|
||||
self.titleNode.contentsScale = UIScreen.main.scale
|
||||
|
||||
self.labelNode = TextNode()
|
||||
self.labelNode.isUserInteractionEnabled = false
|
||||
|
||||
self.arrowNode = ASImageNode()
|
||||
self.arrowNode.displayWithoutProcessing = true
|
||||
self.arrowNode.displaysAsynchronously = false
|
||||
self.arrowNode.isLayerBacked = true
|
||||
|
||||
self.activateArea = AccessibilityAreaNode()
|
||||
|
||||
self.highlightedBackgroundNode = ASDisplayNode()
|
||||
@ -149,6 +162,8 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
|
||||
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.labelNode)
|
||||
self.addSubnode(self.arrowNode)
|
||||
self.addSubnode(self.activateArea)
|
||||
|
||||
self.activateArea.activate = { [weak self] in
|
||||
@ -159,6 +174,7 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
|
||||
|
||||
func asyncLayout() -> (_ item: ChatListFilterPresetListItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> Void) {
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
|
||||
let editableControlLayout = ItemListEditableControlNode.asyncLayout(self.editableControlNode)
|
||||
let reorderControlLayout = ItemListEditableReorderControlNode.asyncLayout(self.reorderControlNode)
|
||||
|
||||
@ -166,9 +182,11 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
|
||||
|
||||
return { item, params, neighbors in
|
||||
var updatedTheme: PresentationTheme?
|
||||
var updateArrowImage: UIImage?
|
||||
|
||||
if currentItem?.presentationData.theme !== item.presentationData.theme {
|
||||
updatedTheme = item.presentationData.theme
|
||||
updateArrowImage = PresentationResourcesItemList.disclosureArrowImage(item.presentationData.theme)
|
||||
}
|
||||
|
||||
let peerRevealOptions: [ItemListRevealOption]
|
||||
@ -187,21 +205,27 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
|
||||
var editingOffset: CGFloat = 0.0
|
||||
var reorderInset: CGFloat = 0.0
|
||||
|
||||
if item.editing.editing && item.canBeReordered {
|
||||
if item.editing.editing {
|
||||
let sizeAndApply = editableControlLayout(item.presentationData.theme, false)
|
||||
editableControlSizeAndApply = sizeAndApply
|
||||
editingOffset = sizeAndApply.0
|
||||
|
||||
let reorderSizeAndApply = reorderControlLayout(item.presentationData.theme)
|
||||
reorderControlSizeAndApply = reorderSizeAndApply
|
||||
reorderInset = reorderSizeAndApply.0
|
||||
if item.canBeReordered {
|
||||
let reorderSizeAndApply = reorderControlLayout(item.presentationData.theme)
|
||||
reorderControlSizeAndApply = reorderSizeAndApply
|
||||
reorderInset = reorderSizeAndApply.0
|
||||
}
|
||||
}
|
||||
|
||||
let leftInset: CGFloat = 16.0 + params.leftInset
|
||||
let rightInset: CGFloat = params.rightInset + max(reorderInset, 55.0)
|
||||
let rightArrowInset: CGFloat = 34.0 + params.rightInset
|
||||
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: params.width - leftInset - 12.0 - editingOffset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let labelConstrain: CGFloat = params.width - params.rightInset - leftInset - 40.0 - titleLayout.size.width - 10.0
|
||||
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.label, font: titleFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: labelConstrain, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let insets = itemListNeighborsGroupedInsets(neighbors)
|
||||
let contentSize = CGSize(width: params.width, height: titleLayout.size.height + 11.0 * 2.0)
|
||||
let separatorHeight = UIScreenPixel
|
||||
@ -280,6 +304,7 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
|
||||
}
|
||||
|
||||
let _ = titleApply()
|
||||
let _ = labelApply()
|
||||
|
||||
if strongSelf.backgroundNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
||||
@ -326,6 +351,20 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
|
||||
|
||||
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 11.0), size: titleLayout.size))
|
||||
|
||||
let labelFrame = CGRect(origin: CGPoint(x: params.width - rightArrowInset - labelLayout.size.width, y: 11.0), size: labelLayout.size)
|
||||
strongSelf.labelNode.frame = labelFrame
|
||||
|
||||
transition.updateAlpha(node: strongSelf.labelNode, alpha: reorderControlSizeAndApply != nil ? 0.0 : 1.0)
|
||||
transition.updateAlpha(node: strongSelf.arrowNode, alpha: reorderControlSizeAndApply != nil ? 0.0 : 1.0)
|
||||
|
||||
if let updateArrowImage = updateArrowImage {
|
||||
strongSelf.arrowNode.image = updateArrowImage
|
||||
}
|
||||
|
||||
if let arrowImage = strongSelf.arrowNode.image {
|
||||
strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 7.0 - arrowImage.size.width, y: floorToScreenPixels((layout.contentSize.height - arrowImage.size.height) / 2.0)), size: arrowImage.size)
|
||||
}
|
||||
|
||||
strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 0.0), size: CGSize(width: params.width - params.rightInset - 56.0 - (leftInset + revealOffset + editingOffset), height: layout.contentSize.height))
|
||||
|
||||
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: layout.contentSize.height + UIScreenPixel + UIScreenPixel))
|
||||
|
||||
@ -257,13 +257,12 @@ final class ChatListFilterTabContainerNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
func update(size: CGSize, sideInset: CGFloat, filters: [ChatListFilterTabEntry], selectedFilter: ChatListFilterTabEntryId?, presentationData: PresentationData, transition: ContainedViewLayoutTransition) {
|
||||
let focusOnSelectedFilter = self.currentParams?.selectedFilter != selectedFilter
|
||||
var previousSelectedAbsFrame: CGRect?
|
||||
private var previousSelectedAbsFrame: CGRect?
|
||||
private var previousSelectedFrame: CGRect?
|
||||
|
||||
func update(size: CGSize, sideInset: CGFloat, filters: [ChatListFilterTabEntry], selectedFilter: ChatListFilterTabEntryId?, transitionFraction: CGFloat, presentationData: PresentationData, transition: ContainedViewLayoutTransition) {
|
||||
var focusOnSelectedFilter = self.currentParams?.selectedFilter != selectedFilter
|
||||
let previousScrollBounds = self.scrollNode.bounds
|
||||
if let currentSelectedFilter = self.currentParams?.selectedFilter, let itemNode = self.itemNodes[currentSelectedFilter] {
|
||||
previousSelectedAbsFrame = itemNode.frame.offsetBy(dx: -self.scrollNode.bounds.minX, dy: 0.0)
|
||||
}
|
||||
|
||||
if self.currentParams?.presentationData.theme !== presentationData.theme {
|
||||
self.selectedLineNode.image = generateImage(CGSize(width: 7.0, height: 4.0), rotatedContext: { size, context in
|
||||
@ -362,7 +361,7 @@ final class ChatListFilterTabContainerNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
let minSpacing: CGFloat = 30.0
|
||||
let minSpacing: CGFloat = 26.0
|
||||
|
||||
let sideInset: CGFloat = 16.0
|
||||
var leftOffset: CGFloat = sideInset
|
||||
@ -391,7 +390,8 @@ final class ChatListFilterTabContainerNode: ASDisplayNode {
|
||||
|
||||
self.scrollNode.view.contentSize = CGSize(width: leftOffset - minSpacing + sideInset - 5.0, height: size.height)
|
||||
|
||||
let transitionFraction: CGFloat = 0.0
|
||||
var previousFrame: CGRect?
|
||||
var nextFrame: CGRect?
|
||||
var selectedFrame: CGRect?
|
||||
if let selectedFilter = selectedFilter, let currentIndex = filters.index(where: { $0.id == selectedFilter }) {
|
||||
func interpolateFrame(from fromValue: CGRect, to toValue: CGRect, t: CGFloat) -> CGRect {
|
||||
@ -422,28 +422,41 @@ final class ChatListFilterTabContainerNode: ASDisplayNode {
|
||||
} else {
|
||||
transition.updateFrame(node: self.selectedLineNode, frame: lineFrame)
|
||||
}
|
||||
if !transitionFraction.isZero {
|
||||
if previousScrollBounds.minX.isZero {
|
||||
focusOnSelectedFilter = true
|
||||
} else if previousScrollBounds.maxX == previousScrollBounds.width {
|
||||
focusOnSelectedFilter = true
|
||||
} else if let previousSelectedFrame = self.previousSelectedFrame, abs(previousSelectedFrame.offsetBy(dx: -previousScrollBounds.minX, dy: 0.0).midX - previousScrollBounds.width / 2.0) < 1.0 {
|
||||
focusOnSelectedFilter = true
|
||||
}
|
||||
}
|
||||
if focusOnSelectedFilter {
|
||||
if selectedFilter == filters.first?.id {
|
||||
if transitionFraction.isZero && selectedFilter == filters.first?.id {
|
||||
transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(), size: self.scrollNode.bounds.size))
|
||||
} else if selectedFilter == filters.last?.id {
|
||||
} else if transitionFraction.isZero && selectedFilter == filters.last?.id {
|
||||
transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: max(0.0, self.scrollNode.view.contentSize.width - self.scrollNode.bounds.width), y: 0.0), size: self.scrollNode.bounds.size))
|
||||
} else {
|
||||
let contentOffsetX = max(0.0, min(self.scrollNode.view.contentSize.width - self.scrollNode.bounds.width, floor(selectedFrame.midX - self.scrollNode.bounds.width / 2.0)))
|
||||
transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: contentOffsetX, y: 0.0), size: self.scrollNode.bounds.size))
|
||||
}
|
||||
} else if !wasAdded, let previousSelectedAbsFrame = previousSelectedAbsFrame {
|
||||
} else if !wasAdded, transitionFraction.isZero, let previousSelectedAbsFrame = self.previousSelectedAbsFrame {
|
||||
let contentOffsetX: CGFloat
|
||||
if previousScrollBounds.minX.isZero {
|
||||
contentOffsetX = 0.0
|
||||
} else if previousScrollBounds.maxX == previousScrollBounds.width {
|
||||
contentOffsetX = self.scrollNode.view.contentSize.width - self.scrollNode.bounds.width
|
||||
} else {
|
||||
contentOffsetX = selectedFrame.midX - previousSelectedAbsFrame.midX
|
||||
contentOffsetX = max(0.0, min(self.scrollNode.view.contentSize.width - self.scrollNode.bounds.width, selectedFrame.midX - previousSelectedAbsFrame.midX))
|
||||
}
|
||||
transition.updateBounds(node: self.scrollNode, bounds: CGRect(origin: CGPoint(x: contentOffsetX, y: 0.0), size: self.scrollNode.bounds.size))
|
||||
}
|
||||
self.previousSelectedAbsFrame = selectedFrame.offsetBy(dx: -self.scrollNode.bounds.minX, dy: 0.0)
|
||||
self.previousSelectedFrame = selectedFrame
|
||||
} else {
|
||||
self.selectedLineNode.isHidden = true
|
||||
self.previousSelectedAbsFrame = nil
|
||||
self.previousSelectedFrame = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@ import SearchUI
|
||||
|
||||
public enum ChatListNodeMode {
|
||||
case chatList
|
||||
case peers(filter: ChatListNodePeersFilter)
|
||||
case peers(filter: ChatListNodePeersFilter, isSelecting: Bool)
|
||||
}
|
||||
|
||||
struct ChatListNodeListViewTransition {
|
||||
@ -153,7 +153,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
switch mode {
|
||||
case .chatList:
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, context: context, peerGroupId: peerGroupId, isInFilter: isInFilter, index: index, content: .peer(message: message, peer: peer, combinedReadState: combinedReadState, notificationSettings: notificationSettings, presence: presence, summaryInfo: summaryInfo, embeddedState: embeddedState, inputActivities: inputActivities, isAd: isAd, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: hasFailedMessages), editing: editing, hasActiveRevealControls: hasActiveRevealControls, selected: selected, header: nil, enableContextActions: true, hiddenOffset: false, interaction: nodeInteraction), directionHint: entry.directionHint)
|
||||
case let .peers(filter):
|
||||
case let .peers(filter, _):
|
||||
let itemPeer = peer.chatMainPeer
|
||||
var chatPeer: Peer?
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
@ -218,7 +218,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
}
|
||||
}
|
||||
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings), sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, context: context, peerMode: .generalSearch, peer: .peer(peer: itemPeer, chatPeer: chatPeer), status: .none, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: nil, action: { _ in
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings), sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, context: context, peerMode: .generalSearch, peer: .peer(peer: itemPeer, chatPeer: chatPeer), status: .none, enabled: enabled, selection: editing ? .selectable(selected: selected) : .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: nil, action: { _ in
|
||||
if let chatPeer = chatPeer {
|
||||
nodeInteraction.peerSelected(chatPeer)
|
||||
}
|
||||
@ -245,7 +245,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
switch mode {
|
||||
case .chatList:
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, context: context, peerGroupId: peerGroupId, isInFilter: isInFilter, index: index, content: .peer(message: message, peer: peer, combinedReadState: combinedReadState, notificationSettings: notificationSettings, presence: presence, summaryInfo: summaryInfo, embeddedState: embeddedState, inputActivities: inputActivities, isAd: isAd, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: hasFailedMessages), editing: editing, hasActiveRevealControls: hasActiveRevealControls, selected: selected, header: nil, enableContextActions: true, hiddenOffset: false, interaction: nodeInteraction), directionHint: entry.directionHint)
|
||||
case let .peers(filter):
|
||||
case let .peers(filter, _):
|
||||
let itemPeer = peer.chatMainPeer
|
||||
var chatPeer: Peer?
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
@ -266,7 +266,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings), sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, context: context, peerMode: .generalSearch, peer: .peer(peer: itemPeer, chatPeer: chatPeer), status: .none, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: nil, action: { _ in
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings), sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, context: context, peerMode: .generalSearch, peer: .peer(peer: itemPeer, chatPeer: chatPeer), status: .none, enabled: enabled, selection: editing ? .selectable(selected: selected) : .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: nil, action: { _ in
|
||||
if let chatPeer = chatPeer {
|
||||
nodeInteraction.peerSelected(chatPeer)
|
||||
}
|
||||
@ -352,7 +352,7 @@ public final class ChatListNode: ListView {
|
||||
return _contentsReady.get()
|
||||
}
|
||||
|
||||
public var peerSelected: ((PeerId, Bool, Bool) -> Void)?
|
||||
public var peerSelected: ((Peer, Bool, Bool) -> Void)?
|
||||
public var disabledPeerSelected: ((Peer) -> Void)?
|
||||
public var groupSelected: ((PeerGroupId) -> Void)?
|
||||
public var addContact: ((String) -> Void)?
|
||||
@ -373,7 +373,7 @@ public final class ChatListNode: ListView {
|
||||
private var dequeuedInitialTransitionOnLayout = false
|
||||
private var enqueuedTransition: (ChatListNodeListViewTransition, () -> Void)?
|
||||
|
||||
private(set) var currentState: ChatListNodeState
|
||||
public private(set) var currentState: ChatListNodeState
|
||||
private let statePromise: ValuePromise<ChatListNodeState>
|
||||
public var state: Signal<ChatListNodeState, NoError> {
|
||||
return self.statePromise.get()
|
||||
@ -453,7 +453,12 @@ public final class ChatListNode: ListView {
|
||||
self.controlsHistoryPreload = controlsHistoryPreload
|
||||
self.mode = mode
|
||||
|
||||
self.currentState = ChatListNodeState(presentationData: ChatListPresentationData(theme: theme, fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, disableAnimations: disableAnimations), editing: false, peerIdWithRevealedOptions: nil, selectedPeerIds: Set(), peerInputActivities: nil, pendingRemovalPeerIds: Set(), pendingClearHistoryPeerIds: Set(), archiveShouldBeTemporaryRevealed: false)
|
||||
var isSelecting = false
|
||||
if case .peers(_, true) = mode {
|
||||
isSelecting = true
|
||||
}
|
||||
|
||||
self.currentState = ChatListNodeState(presentationData: ChatListPresentationData(theme: theme, fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, disableAnimations: disableAnimations), editing: isSelecting, peerIdWithRevealedOptions: nil, selectedPeerIds: Set(), peerInputActivities: nil, pendingRemovalPeerIds: Set(), pendingClearHistoryPeerIds: Set(), archiveShouldBeTemporaryRevealed: false)
|
||||
self.statePromise = ValuePromise(self.currentState, ignoreRepeated: true)
|
||||
|
||||
self.theme = theme
|
||||
@ -471,7 +476,7 @@ public final class ChatListNode: ListView {
|
||||
}
|
||||
}, peerSelected: { [weak self] peer in
|
||||
if let strongSelf = self, let peerSelected = strongSelf.peerSelected {
|
||||
peerSelected(peer.id, true, false)
|
||||
peerSelected(peer, true, false)
|
||||
}
|
||||
}, disabledPeerSelected: { [weak self] peer in
|
||||
if let strongSelf = self, let disabledPeerSelected = strongSelf.disabledPeerSelected {
|
||||
@ -491,7 +496,7 @@ public final class ChatListNode: ListView {
|
||||
}
|
||||
}, messageSelected: { [weak self] peer, message, isAd in
|
||||
if let strongSelf = self, let peerSelected = strongSelf.peerSelected {
|
||||
peerSelected(peer.id, true, isAd)
|
||||
peerSelected(peer, true, isAd)
|
||||
}
|
||||
}, groupSelected: { [weak self] groupId in
|
||||
if let strongSelf = self, let groupSelected = strongSelf.groupSelected {
|
||||
@ -586,7 +591,7 @@ public final class ChatListNode: ListView {
|
||||
let currentRemovingPeerId = self.currentRemovingPeerId
|
||||
|
||||
let savedMessagesPeer: Signal<Peer?, NoError>
|
||||
if case let .peers(filter) = mode, filter.contains(.onlyWriteable) {
|
||||
if case let .peers(filter, _) = mode, filter.contains(.onlyWriteable) {
|
||||
savedMessagesPeer = context.account.postbox.loadedPeerWithId(context.account.peerId)
|
||||
|> map(Optional.init)
|
||||
} else {
|
||||
@ -639,7 +644,7 @@ public final class ChatListNode: ListView {
|
||||
switch mode {
|
||||
case .chatList:
|
||||
return true
|
||||
case let .peers(filter):
|
||||
case let .peers(filter, _):
|
||||
guard !filter.contains(.excludeSavedMessages) || peer.peerId != currentPeerId else { return false }
|
||||
guard !filter.contains(.excludeSecretChats) || peer.peerId.namespace != Namespaces.Peer.SecretChat else { return false }
|
||||
guard !filter.contains(.onlyPrivateChats) || peer.peerId.namespace == Namespaces.Peer.CloudUser else { return false }
|
||||
@ -1422,6 +1427,35 @@ public final class ChatListNode: ListView {
|
||||
}
|
||||
}
|
||||
|
||||
var isNavigationHidden: Bool {
|
||||
switch self.visibleContentOffset() {
|
||||
case let .known(value) where abs(value) < navigationBarSearchContentHeight:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func adjustScrollOffsetForNavigation(isNavigationHidden: Bool) {
|
||||
if self.isNavigationHidden == isNavigationHidden {
|
||||
return
|
||||
}
|
||||
var scrollToItem: ListViewScrollToItem?
|
||||
switch self.visibleContentOffset() {
|
||||
case let .known(value) where abs(value) < navigationBarSearchContentHeight:
|
||||
if isNavigationHidden {
|
||||
scrollToItem = ListViewScrollToItem(index: 0, position: .top(-navigationBarSearchContentHeight), animated: false, curve: .Default(duration: 0.0), directionHint: .Up)
|
||||
}
|
||||
default:
|
||||
if !isNavigationHidden {
|
||||
scrollToItem = ListViewScrollToItem(index: 0, position: .top(0.0), animated: false, curve: .Default(duration: 0.0), directionHint: .Up)
|
||||
}
|
||||
}
|
||||
if let scrollToItem = scrollToItem {
|
||||
self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: scrollToItem, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
}
|
||||
}
|
||||
|
||||
public func updateLayout(transition: ContainedViewLayoutTransition, updateSizeAndInsets: ListViewUpdateSizeAndInsets) {
|
||||
self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
|
||||
@ -1524,9 +1558,9 @@ public final class ChatListNode: ListView {
|
||||
}
|
||||
|
||||
let entryCount = chatListView.filteredEntries.count
|
||||
var current: (ChatListIndex, PeerId, Int)? = nil
|
||||
var previous: (ChatListIndex, PeerId)? = nil
|
||||
var next: (ChatListIndex, PeerId)? = nil
|
||||
var current: (ChatListIndex, Peer, Int)? = nil
|
||||
var previous: (ChatListIndex, Peer)? = nil
|
||||
var next: (ChatListIndex, Peer)? = nil
|
||||
|
||||
outer: for i in range.firstIndex ..< range.lastIndex {
|
||||
if i < 0 || i >= entryCount {
|
||||
@ -1536,7 +1570,7 @@ public final class ChatListNode: ListView {
|
||||
switch chatListView.filteredEntries[entryCount - i - 1] {
|
||||
case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _):
|
||||
if interaction.highlightedChatLocation?.location == ChatLocation.peer(peer.peerId) {
|
||||
current = (index, peer.peerId, entryCount - i - 1)
|
||||
current = (index, peer.peer!, entryCount - i - 1)
|
||||
break outer
|
||||
}
|
||||
default:
|
||||
@ -1556,22 +1590,35 @@ public final class ChatListNode: ListView {
|
||||
} else {
|
||||
position = .later(than: nil)
|
||||
}
|
||||
let _ = (relativeUnreadChatListIndex(position: position) |> deliverOnMainQueue).start(next: { [weak self] index in
|
||||
guard let strongSelf = self, let index = index else {
|
||||
let postbox = self.context.account.postbox
|
||||
let _ = (relativeUnreadChatListIndex(position: position)
|
||||
|> mapToSignal { index -> Signal<(ChatListIndex, Peer)?, NoError> in
|
||||
if let index = index {
|
||||
return postbox.transaction { transaction -> (ChatListIndex, Peer)? in
|
||||
return transaction.getPeer(index.messageIndex.id.peerId).flatMap { peer -> (ChatListIndex, Peer)? in
|
||||
(index, peer)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] indexAndPeer in
|
||||
guard let strongSelf = self, let (index, peer) = indexAndPeer else {
|
||||
return
|
||||
}
|
||||
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)
|
||||
strongSelf.peerSelected?(peer, false, false)
|
||||
})
|
||||
case .previous(unread: false), .next(unread: false):
|
||||
var target: (ChatListIndex, PeerId)? = nil
|
||||
var target: (ChatListIndex, Peer)? = nil
|
||||
if let current = current, entryCount > 1 {
|
||||
if current.2 > 0, case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[current.2 - 1] {
|
||||
next = (index, peer.peerId)
|
||||
next = (index, peer.peer!)
|
||||
}
|
||||
if current.2 <= entryCount - 2, case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[current.2 + 1] {
|
||||
previous = (index, peer.peerId)
|
||||
previous = (index, peer.peer!)
|
||||
}
|
||||
if case .previous = option {
|
||||
target = previous
|
||||
@ -1580,7 +1627,7 @@ public final class ChatListNode: ListView {
|
||||
}
|
||||
} else if entryCount > 0 {
|
||||
if case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[entryCount - 1] {
|
||||
target = (index, peer.peerId)
|
||||
target = (index, peer.peer!)
|
||||
}
|
||||
}
|
||||
if let target = target {
|
||||
@ -1589,7 +1636,15 @@ public final class ChatListNode: ListView {
|
||||
self.peerSelected?(target.1, false, false)
|
||||
}
|
||||
case let .peerId(peerId):
|
||||
self.peerSelected?(peerId, false, false)
|
||||
let _ = (self.context.account.postbox.transaction { transaction -> Peer? in
|
||||
return transaction.getPeer(peerId)
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let strongSelf = self, let peer = peer else {
|
||||
return
|
||||
}
|
||||
strongSelf.peerSelected?(peer, false, false)
|
||||
})
|
||||
case let .index(index):
|
||||
guard index < 10 else {
|
||||
return
|
||||
@ -1607,7 +1662,7 @@ public final class ChatListNode: ListView {
|
||||
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, filter: filter)
|
||||
self.setChatListLocation(location)
|
||||
self.peerSelected?(renderedPeer.peerId, false, false)
|
||||
self.peerSelected?(renderedPeer.peer!, false, false)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@ -29,7 +29,7 @@ struct ChatListNodeViewUpdate {
|
||||
let scrollPosition: ChatListNodeViewScrollPosition?
|
||||
}
|
||||
|
||||
func chatListFilterPredicate(filter: ChatListFilter) -> ChatListFilterPredicate {
|
||||
func chatListFilterPredicate(filter: ChatListFilterData) -> ChatListFilterPredicate {
|
||||
let includePeers = Set(filter.includePeers)
|
||||
return ChatListFilterPredicate(includePeerIds: includePeers, include: { peer, notificationSettings, isUnread in
|
||||
if filter.excludeRead {
|
||||
@ -97,7 +97,7 @@ func chatListFilterPredicate(filter: ChatListFilter) -> ChatListFilterPredicate
|
||||
}
|
||||
|
||||
func chatListViewForLocation(groupId: PeerGroupId, location: ChatListNodeLocation, account: Account) -> Signal<ChatListNodeViewUpdate, NoError> {
|
||||
let filterPredicate: ChatListFilterPredicate? = location.filter.flatMap(chatListFilterPredicate)
|
||||
let filterPredicate: ChatListFilterPredicate? = (location.filter?.data).flatMap(chatListFilterPredicate)
|
||||
|
||||
switch location {
|
||||
case let .initial(count, _):
|
||||
|
||||
@ -320,7 +320,7 @@ func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatListFilt
|
||||
unreadCountItems.append(.total(nil))
|
||||
var additionalPeerIds = Set<PeerId>()
|
||||
for filter in filters {
|
||||
additionalPeerIds.formUnion(filter.includePeers)
|
||||
additionalPeerIds.formUnion(filter.data.includePeers)
|
||||
}
|
||||
if !additionalPeerIds.isEmpty {
|
||||
for peerId in additionalPeerIds {
|
||||
@ -390,22 +390,22 @@ func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatListFilt
|
||||
var shouldUpdateLayout = false
|
||||
for filter in filters {
|
||||
var tags: [PeerSummaryCounterTags] = []
|
||||
if filter.categories.contains(.privateChats) {
|
||||
if filter.data.categories.contains(.privateChats) {
|
||||
tags.append(.privateChat)
|
||||
}
|
||||
if filter.categories.contains(.secretChats) {
|
||||
if filter.data.categories.contains(.secretChats) {
|
||||
tags.append(.secretChat)
|
||||
}
|
||||
if filter.categories.contains(.privateGroups) {
|
||||
if filter.data.categories.contains(.privateGroups) {
|
||||
tags.append(.privateGroup)
|
||||
}
|
||||
if filter.categories.contains(.bots) {
|
||||
if filter.data.categories.contains(.bots) {
|
||||
tags.append(.bot)
|
||||
}
|
||||
if filter.categories.contains(.publicGroups) {
|
||||
if filter.data.categories.contains(.publicGroups) {
|
||||
tags.append(.publicGroup)
|
||||
}
|
||||
if filter.categories.contains(.channels) {
|
||||
if filter.data.categories.contains(.channels) {
|
||||
tags.append(.channel)
|
||||
}
|
||||
|
||||
@ -417,7 +417,7 @@ func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatListFilt
|
||||
}
|
||||
}
|
||||
}
|
||||
for peerId in filter.includePeers {
|
||||
for peerId in filter.data.includePeers {
|
||||
if let (tag, peerCount) = peerTagAndCount[peerId] {
|
||||
if !tags.contains(tag) {
|
||||
if peerCount != 0 {
|
||||
@ -513,7 +513,7 @@ private final class TabBarChatListFilterControllerNode: ViewControllerTracingNod
|
||||
unreadCountItems.append(.total(nil))
|
||||
var additionalPeerIds = Set<PeerId>()
|
||||
for preset in presetList {
|
||||
additionalPeerIds.formUnion(preset.includePeers)
|
||||
additionalPeerIds.formUnion(preset.data.includePeers)
|
||||
}
|
||||
if !additionalPeerIds.isEmpty {
|
||||
for peerId in additionalPeerIds {
|
||||
@ -567,22 +567,22 @@ private final class TabBarChatListFilterControllerNode: ViewControllerTracingNod
|
||||
let badgeString: String
|
||||
if let preset = contentNode.preset {
|
||||
var tags: [PeerSummaryCounterTags] = []
|
||||
if preset.categories.contains(.privateChats) {
|
||||
if preset.data.categories.contains(.privateChats) {
|
||||
tags.append(.privateChat)
|
||||
}
|
||||
if preset.categories.contains(.secretChats) {
|
||||
if preset.data.categories.contains(.secretChats) {
|
||||
tags.append(.secretChat)
|
||||
}
|
||||
if preset.categories.contains(.privateGroups) {
|
||||
if preset.data.categories.contains(.privateGroups) {
|
||||
tags.append(.privateGroup)
|
||||
}
|
||||
if preset.categories.contains(.bots) {
|
||||
if preset.data.categories.contains(.bots) {
|
||||
tags.append(.bot)
|
||||
}
|
||||
if preset.categories.contains(.publicGroups) {
|
||||
if preset.data.categories.contains(.publicGroups) {
|
||||
tags.append(.publicGroup)
|
||||
}
|
||||
if preset.categories.contains(.channels) {
|
||||
if preset.data.categories.contains(.channels) {
|
||||
tags.append(.channel)
|
||||
}
|
||||
|
||||
@ -594,7 +594,7 @@ private final class TabBarChatListFilterControllerNode: ViewControllerTracingNod
|
||||
}
|
||||
}
|
||||
}
|
||||
for peerId in preset.includePeers {
|
||||
for peerId in preset.data.includePeers {
|
||||
if let (tag, peerCount) = peerTagAndCount[peerId] {
|
||||
if !tags.contains(tag) {
|
||||
count += peerCount
|
||||
|
||||
@ -747,7 +747,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
self.withoutBlurDimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: transitionDuration * animationDurationFactor, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
self.actionsContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2 * animationDurationFactor, removeOnCompletion: false, completion: { _ in
|
||||
self.actionsContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15 * animationDurationFactor, removeOnCompletion: false, completion: { _ in
|
||||
completedActionsNode = true
|
||||
intermediateCompletion()
|
||||
})
|
||||
|
||||
@ -38,18 +38,23 @@ public struct InteractiveTransitionGestureRecognizerDirections: OptionSet {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
public static let left = InteractiveTransitionGestureRecognizerDirections(rawValue: 1 << 0)
|
||||
public static let right = InteractiveTransitionGestureRecognizerDirections(rawValue: 1 << 1)
|
||||
public static let leftEdge = InteractiveTransitionGestureRecognizerDirections(rawValue: 1 << 2)
|
||||
public static let rightEdge = InteractiveTransitionGestureRecognizerDirections(rawValue: 1 << 3)
|
||||
public static let leftCenter = InteractiveTransitionGestureRecognizerDirections(rawValue: 1 << 0)
|
||||
public static let rightCenter = InteractiveTransitionGestureRecognizerDirections(rawValue: 1 << 1)
|
||||
|
||||
public static let left: InteractiveTransitionGestureRecognizerDirections = [.leftEdge, .leftCenter]
|
||||
public static let right: InteractiveTransitionGestureRecognizerDirections = [.rightEdge, .rightCenter]
|
||||
}
|
||||
|
||||
public class InteractiveTransitionGestureRecognizer: UIPanGestureRecognizer {
|
||||
private let allowedDirections: () -> InteractiveTransitionGestureRecognizerDirections
|
||||
private let allowedDirections: (CGPoint) -> InteractiveTransitionGestureRecognizerDirections
|
||||
|
||||
private var validatedGesture = false
|
||||
private var firstLocation: CGPoint = CGPoint()
|
||||
private var currentAllowedDirections: InteractiveTransitionGestureRecognizerDirections = []
|
||||
|
||||
public init(target: Any?, action: Selector?, allowedDirections: @escaping () -> InteractiveTransitionGestureRecognizerDirections) {
|
||||
public init(target: Any?, action: Selector?, allowedDirections: @escaping (CGPoint) -> InteractiveTransitionGestureRecognizerDirections) {
|
||||
self.allowedDirections = allowedDirections
|
||||
|
||||
super.init(target: target, action: action)
|
||||
@ -65,37 +70,50 @@ public class InteractiveTransitionGestureRecognizer: UIPanGestureRecognizer {
|
||||
}
|
||||
|
||||
override public func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
self.currentAllowedDirections = self.allowedDirections()
|
||||
if self.currentAllowedDirections.isEmpty {
|
||||
let touch = touches.first!
|
||||
let point = touch.location(in: self.view)
|
||||
|
||||
var allowedDirections = self.allowedDirections(point)
|
||||
if allowedDirections.isEmpty {
|
||||
self.state = .failed
|
||||
return
|
||||
}
|
||||
|
||||
super.touchesBegan(touches, with: event)
|
||||
|
||||
let touch = touches.first!
|
||||
self.firstLocation = touch.location(in: self.view)
|
||||
self.firstLocation = point
|
||||
|
||||
if let target = self.view?.hitTest(self.firstLocation, with: event) {
|
||||
if hasHorizontalGestures(target, point: self.view?.convert(self.firstLocation, to: target)) {
|
||||
self.state = .cancelled
|
||||
allowedDirections.remove(.leftCenter)
|
||||
allowedDirections.remove(.rightCenter)
|
||||
}
|
||||
}
|
||||
|
||||
if allowedDirections.isEmpty {
|
||||
self.state = .failed
|
||||
} else {
|
||||
self.currentAllowedDirections = allowedDirections
|
||||
}
|
||||
}
|
||||
|
||||
override public func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
let location = touches.first!.location(in: self.view)
|
||||
let translation = CGPoint(x: location.x - firstLocation.x, y: location.y - firstLocation.y)
|
||||
let translation = CGPoint(x: location.x - self.firstLocation.x, y: location.y - self.firstLocation.y)
|
||||
|
||||
let absTranslationX: CGFloat = abs(translation.x)
|
||||
let absTranslationY: CGFloat = abs(translation.y)
|
||||
|
||||
let size = self.view?.bounds.size ?? CGSize()
|
||||
|
||||
if !self.validatedGesture {
|
||||
if self.currentAllowedDirections.contains(.right) && self.firstLocation.x < 16.0 {
|
||||
if self.currentAllowedDirections.contains(.rightEdge) && self.firstLocation.x < 16.0 {
|
||||
self.validatedGesture = true
|
||||
} else if !self.currentAllowedDirections.contains(.left) && translation.x < 0.0 {
|
||||
} else if self.currentAllowedDirections.contains(.leftEdge) && self.firstLocation.x > size.width - 16.0 {
|
||||
self.validatedGesture = true
|
||||
} else if !self.currentAllowedDirections.contains(.leftCenter) && translation.x < 0.0 {
|
||||
self.state = .failed
|
||||
} else if !self.currentAllowedDirections.contains(.right) && translation.x > 0.0 {
|
||||
} else if !self.currentAllowedDirections.contains(.rightCenter) && translation.x > 0.0 {
|
||||
self.state = .failed
|
||||
} else if absTranslationY > 2.0 && absTranslationY > absTranslationX * 2.0 {
|
||||
self.state = .failed
|
||||
@ -104,7 +122,7 @@ public class InteractiveTransitionGestureRecognizer: UIPanGestureRecognizer {
|
||||
}
|
||||
}
|
||||
|
||||
if validatedGesture {
|
||||
if self.validatedGesture {
|
||||
super.touchesMoved(touches, with: event)
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,7 +111,7 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] in
|
||||
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] _ in
|
||||
guard let strongSelf = self, strongSelf.controllers.count > 1 else {
|
||||
return []
|
||||
}
|
||||
|
||||
@ -90,7 +90,7 @@ final class NavigationModalContainer: ASDisplayNode, UIScrollViewDelegate, UIGes
|
||||
self.scrollNode.view.clipsToBounds = false
|
||||
self.scrollNode.view.delegate = self
|
||||
|
||||
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] in
|
||||
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] _ in
|
||||
guard let strongSelf = self, !strongSelf.isDismissed else {
|
||||
return []
|
||||
}
|
||||
|
||||
@ -25,10 +25,10 @@ final class TabBarControllerNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
init(theme: TabBarControllerTheme, navigationBar: NavigationBar?, itemSelected: @escaping (Int, Bool, [ASDisplayNode]) -> Void, contextAction: @escaping (Int, ContextExtractedContentContainingNode, ContextGesture) -> Void, toolbarActionSelected: @escaping (ToolbarActionOption) -> Void) {
|
||||
init(theme: TabBarControllerTheme, navigationBar: NavigationBar?, itemSelected: @escaping (Int, Bool, [ASDisplayNode]) -> Void, contextAction: @escaping (Int, ContextExtractedContentContainingNode, ContextGesture) -> Void, swipeAction: @escaping (Int, TabBarItemSwipeDirection) -> Void, toolbarActionSelected: @escaping (ToolbarActionOption) -> Void) {
|
||||
self.theme = theme
|
||||
self.navigationBar = navigationBar
|
||||
self.tabBarNode = TabBarNode(theme: theme, itemSelected: itemSelected, contextAction: contextAction)
|
||||
self.tabBarNode = TabBarNode(theme: theme, itemSelected: itemSelected, contextAction: contextAction, swipeAction: swipeAction)
|
||||
self.toolbarActionSelected = toolbarActionSelected
|
||||
|
||||
super.init()
|
||||
|
||||
@ -240,6 +240,13 @@ open class TabBarController: ViewController {
|
||||
if index >= 0 && index < strongSelf.controllers.count {
|
||||
strongSelf.controllers[index].tabBarItemContextAction(sourceNode: node, gesture: gesture)
|
||||
}
|
||||
}, swipeAction: { [weak self] index, direction in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if index >= 0 && index < strongSelf.controllers.count {
|
||||
strongSelf.controllers[index].tabBarItemSwipeAction(direction: direction)
|
||||
}
|
||||
}, toolbarActionSelected: { [weak self] action in
|
||||
self?.currentController?.toolbarActionSelected(action: action)
|
||||
})
|
||||
|
||||
@ -88,6 +88,11 @@ private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor:
|
||||
|
||||
private let badgeFont = Font.regular(13.0)
|
||||
|
||||
public enum TabBarItemSwipeDirection {
|
||||
case left
|
||||
case right
|
||||
}
|
||||
|
||||
private final class TabBarItemNode: ASDisplayNode {
|
||||
let extractedContainerNode: ContextExtractedContentContainingNode
|
||||
let containerNode: ContextControllerSourceNode
|
||||
@ -97,6 +102,8 @@ private final class TabBarItemNode: ASDisplayNode {
|
||||
let contextTextImageNode: ASImageNode
|
||||
var contentWidth: CGFloat?
|
||||
|
||||
var swiped: ((TabBarItemSwipeDirection) -> Void)?
|
||||
|
||||
override init() {
|
||||
self.extractedContainerNode = ContextExtractedContentContainingNode()
|
||||
self.containerNode = ContextControllerSourceNode()
|
||||
@ -146,6 +153,26 @@ private final class TabBarItemNode: ASDisplayNode {
|
||||
transition.updateAlpha(node: strongSelf.contextImageNode, alpha: isExtracted ? 1.0 : 0.0)
|
||||
transition.updateAlpha(node: strongSelf.contextTextImageNode, alpha: isExtracted ? 1.0 : 0.0)
|
||||
}
|
||||
|
||||
let leftSwipe = UISwipeGestureRecognizer(target: self, action: #selector(self.swipeGesture(_:)))
|
||||
leftSwipe.direction = .left
|
||||
self.containerNode.view.addGestureRecognizer(leftSwipe)
|
||||
let rightSwipe = UISwipeGestureRecognizer(target: self, action: #selector(self.swipeGesture(_:)))
|
||||
rightSwipe.direction = .right
|
||||
self.containerNode.view.addGestureRecognizer(rightSwipe)
|
||||
}
|
||||
|
||||
@objc private func swipeGesture(_ gesture: UISwipeGestureRecognizer) {
|
||||
if case .ended = gesture.state {
|
||||
self.containerNode.cancelGesture()
|
||||
|
||||
switch gesture.direction {
|
||||
case .left:
|
||||
self.swiped?(.left)
|
||||
default:
|
||||
self.swiped?(.right)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -173,7 +200,7 @@ private final class TabBarNodeContainer {
|
||||
var selectedImageValue: UIImage?
|
||||
var appliedSelectedImageValue: UIImage?
|
||||
|
||||
init(item: TabBarNodeItem, imageNode: TabBarItemNode, updateBadge: @escaping (String) -> Void, updateTitle: @escaping (String, Bool) -> Void, updateImage: @escaping (UIImage?) -> Void, updateSelectedImage: @escaping (UIImage?) -> Void, contextAction: @escaping (ContextExtractedContentContainingNode, ContextGesture) -> Void) {
|
||||
init(item: TabBarNodeItem, imageNode: TabBarItemNode, updateBadge: @escaping (String) -> Void, updateTitle: @escaping (String, Bool) -> Void, updateImage: @escaping (UIImage?) -> Void, updateSelectedImage: @escaping (UIImage?) -> Void, contextAction: @escaping (ContextExtractedContentContainingNode, ContextGesture) -> Void, swipeAction: @escaping (TabBarItemSwipeDirection) -> Void) {
|
||||
self.item = item.item
|
||||
|
||||
self.imageNode = imageNode
|
||||
@ -225,6 +252,9 @@ private final class TabBarNodeContainer {
|
||||
}
|
||||
contextAction(strongSelf.imageNode.extractedContainerNode, gesture)
|
||||
}
|
||||
imageNode.swiped = { [weak self] direction in
|
||||
swipeAction(direction)
|
||||
}
|
||||
imageNode.containerNode.isGestureEnabled = item.hasContext
|
||||
}
|
||||
|
||||
@ -269,6 +299,7 @@ class TabBarNode: ASDisplayNode {
|
||||
|
||||
private let itemSelected: (Int, Bool, [ASDisplayNode]) -> Void
|
||||
private let contextAction: (Int, ContextExtractedContentContainingNode, ContextGesture) -> Void
|
||||
private let swipeAction: (Int, TabBarItemSwipeDirection) -> Void
|
||||
|
||||
private var theme: TabBarControllerTheme
|
||||
private var validLayout: (CGSize, CGFloat, CGFloat, CGFloat)?
|
||||
@ -282,9 +313,10 @@ class TabBarNode: ASDisplayNode {
|
||||
|
||||
private var tapRecognizer: TapLongTapOrDoubleTapGestureRecognizer?
|
||||
|
||||
init(theme: TabBarControllerTheme, itemSelected: @escaping (Int, Bool, [ASDisplayNode]) -> Void, contextAction: @escaping (Int, ContextExtractedContentContainingNode, ContextGesture) -> Void) {
|
||||
init(theme: TabBarControllerTheme, itemSelected: @escaping (Int, Bool, [ASDisplayNode]) -> Void, contextAction: @escaping (Int, ContextExtractedContentContainingNode, ContextGesture) -> Void, swipeAction: @escaping (Int, TabBarItemSwipeDirection) -> Void) {
|
||||
self.itemSelected = itemSelected
|
||||
self.contextAction = contextAction
|
||||
self.swipeAction = swipeAction
|
||||
self.theme = theme
|
||||
|
||||
self.separatorNode = ASDisplayNode()
|
||||
@ -381,6 +413,8 @@ class TabBarNode: ASDisplayNode {
|
||||
}, contextAction: { [weak self] node, gesture in
|
||||
self?.tapRecognizer?.cancel()
|
||||
self?.contextAction(i, node, gesture)
|
||||
}, swipeAction: { [weak self] direction in
|
||||
self?.swipeAction(i, direction)
|
||||
})
|
||||
if let selectedIndex = self.selectedIndex, selectedIndex == i {
|
||||
let (textImage, contentWidth) = tabBarItemImage(item.item.selectedImage, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered)
|
||||
|
||||
@ -632,6 +632,9 @@ public enum ViewControllerNavigationPresentation {
|
||||
|
||||
open func tabBarItemContextAction(sourceNode: ContextExtractedContentContainingNode, gesture: ContextGesture) {
|
||||
}
|
||||
|
||||
open func tabBarItemSwipeAction(direction: TabBarItemSwipeDirection) {
|
||||
}
|
||||
}
|
||||
|
||||
func traceIsOpaque(layer: CALayer, rect: CGRect) -> Bool {
|
||||
|
||||
@ -91,6 +91,7 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, UIGestureRecognizerD
|
||||
|
||||
let recognizer = ItemListRevealOptionsGestureRecognizer(target: self, action: #selector(self.revealGesture(_:)))
|
||||
self.recognizer = recognizer
|
||||
recognizer.delegate = self
|
||||
recognizer.allowAnyDirection = self.allowAnyDirection
|
||||
self.view.addGestureRecognizer(recognizer)
|
||||
|
||||
|
||||
@ -45,9 +45,10 @@ public class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
|
||||
let shouldUpdateText: (String) -> Bool
|
||||
let processPaste: ((String) -> String)?
|
||||
let updatedFocus: ((Bool) -> Void)?
|
||||
let cleared: (() -> Void)?
|
||||
public let tag: ItemListItemTag?
|
||||
|
||||
public init(presentationData: ItemListPresentationData, title: NSAttributedString, text: String, placeholder: String, type: ItemListSingleLineInputItemType = .regular(capitalization: true, autocorrection: true), returnKeyType: UIReturnKeyType = .`default`, spacing: CGFloat = 0.0, clearType: ItemListSingleLineInputClearType = .none, enabled: Bool = true, tag: ItemListItemTag? = nil, sectionId: ItemListSectionId, textUpdated: @escaping (String) -> Void, shouldUpdateText: @escaping (String) -> Bool = { _ in return true }, processPaste: ((String) -> String)? = nil, updatedFocus: ((Bool) -> Void)? = nil, action: @escaping () -> Void) {
|
||||
public init(presentationData: ItemListPresentationData, title: NSAttributedString, text: String, placeholder: String, type: ItemListSingleLineInputItemType = .regular(capitalization: true, autocorrection: true), returnKeyType: UIReturnKeyType = .`default`, spacing: CGFloat = 0.0, clearType: ItemListSingleLineInputClearType = .none, enabled: Bool = true, tag: ItemListItemTag? = nil, sectionId: ItemListSectionId, textUpdated: @escaping (String) -> Void, shouldUpdateText: @escaping (String) -> Bool = { _ in return true }, processPaste: ((String) -> String)? = nil, updatedFocus: ((Bool) -> Void)? = nil, action: @escaping () -> Void, cleared: (() -> Void)? = nil) {
|
||||
self.presentationData = presentationData
|
||||
self.title = title
|
||||
self.text = text
|
||||
@ -64,6 +65,7 @@ public class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
|
||||
self.processPaste = processPaste
|
||||
self.updatedFocus = updatedFocus
|
||||
self.action = action
|
||||
self.cleared = cleared
|
||||
}
|
||||
|
||||
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||
@ -420,6 +422,7 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
|
||||
@objc private func clearButtonPressed() {
|
||||
self.textNode.textField.text = ""
|
||||
self.textUpdated("")
|
||||
self.item?.cleared?()
|
||||
}
|
||||
|
||||
private func textUpdated(_ text: String) {
|
||||
|
||||
@ -641,6 +641,25 @@ final class ChatListTable: Table {
|
||||
return entries
|
||||
}
|
||||
|
||||
func countWithPredicate(groupId: PeerGroupId, predicate: (PeerId) -> Bool) -> Int {
|
||||
var result = 0
|
||||
self.valueBox.filteredRange(self.table, start: self.lowerBound(groupId: groupId), end: self.upperBound(groupId: groupId), keys: { key in
|
||||
let (_, _, messageIndex, type) = extractKey(key)
|
||||
|
||||
if type == ChatListEntryType.message.rawValue {
|
||||
if predicate(messageIndex.id.peerId) {
|
||||
result += 1
|
||||
return .accept
|
||||
} else {
|
||||
return .skip
|
||||
}
|
||||
} else {
|
||||
return .skip
|
||||
}
|
||||
}, limit: 10000)
|
||||
return result
|
||||
}
|
||||
|
||||
func getStandalone(peerId: PeerId, messageHistoryTable: MessageHistoryTable) -> ChatListIntermediateEntry? {
|
||||
let index = self.indexTable.get(peerId: peerId)
|
||||
switch index.inclusion {
|
||||
|
||||
@ -320,6 +320,7 @@ final class MutableChatListView {
|
||||
fileprivate var additionalItemIds: Set<PeerId>
|
||||
fileprivate var additionalItemEntries: [MutableChatListEntry]
|
||||
fileprivate var additionalMixedItemIds: Set<PeerId>
|
||||
fileprivate var additionalMixedPinnedItemIds: Set<PeerId>
|
||||
fileprivate var additionalMixedItemEntries: [MutableChatListEntry]
|
||||
fileprivate var earlier: MutableChatListEntry?
|
||||
fileprivate var later: MutableChatListEntry?
|
||||
@ -340,10 +341,17 @@ final class MutableChatListView {
|
||||
self.additionalItemEntries = []
|
||||
self.additionalMixedItemEntries = []
|
||||
self.additionalMixedItemIds = Set()
|
||||
self.additionalMixedPinnedItemIds = Set()
|
||||
if let filterPredicate = self.filterPredicate {
|
||||
self.additionalMixedItemIds.formUnion(filterPredicate.includePeerIds)
|
||||
for (itemId, _) in postbox.chatListTable.getPinnedItemIds(groupId: self.groupId, messageHistoryTable: postbox.messageHistoryTable, peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable) {
|
||||
switch itemId {
|
||||
case let .peer(peerId):
|
||||
self.additionalMixedPinnedItemIds.insert(peerId)
|
||||
}
|
||||
}
|
||||
}
|
||||
for peerId in self.additionalMixedItemIds {
|
||||
for peerId in self.additionalMixedItemIds.union(self.additionalMixedPinnedItemIds) {
|
||||
if let entry = postbox.chatListTable.getEntry(peerId: peerId, messageHistoryTable: postbox.messageHistoryTable, peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable) {
|
||||
self.additionalMixedItemEntries.append(MutableChatListEntry(entry, cachedDataTable: postbox.cachedPeerDataTable, readStateTable: postbox.readStateTable, messageHistoryTable: postbox.messageHistoryTable))
|
||||
}
|
||||
@ -554,7 +562,7 @@ final class MutableChatListView {
|
||||
if !updatedPeerNotificationSettings.isEmpty {
|
||||
if let filterPredicate = self.filterPredicate {
|
||||
for (peerId, settingsChange) in updatedPeerNotificationSettings {
|
||||
if let peer = postbox.peerTable.get(peerId) {
|
||||
if let peer = postbox.peerTable.get(peerId), !self.additionalMixedItemIds.contains(peerId), !self.additionalMixedPinnedItemIds.contains(peerId) {
|
||||
let isUnread = postbox.readStateTable.getCombinedState(peerId)?.isUnread ?? false
|
||||
let wasIncluded = filterPredicate.includes(peer: peer, notificationSettings: settingsChange.0, isUnread: isUnread)
|
||||
let isIncluded = filterPredicate.includes(peer: peer, notificationSettings: settingsChange.1, isUnread: isUnread)
|
||||
@ -611,6 +619,22 @@ final class MutableChatListView {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0 ..< self.additionalMixedItemEntries.count {
|
||||
switch self.additionalMixedItemEntries[i] {
|
||||
case let .MessageEntry(index, message, readState, _, embeddedState, peer, peerPresence, summaryInfo, hasFailed):
|
||||
var notificationSettingsPeerId = peer.peerId
|
||||
if let peer = peer.peers[peer.peerId], let associatedPeerId = peer.associatedPeerId {
|
||||
notificationSettingsPeerId = associatedPeerId
|
||||
}
|
||||
if let (_, settings) = updatedPeerNotificationSettings[notificationSettingsPeerId] {
|
||||
self.additionalMixedItemEntries[i] = .MessageEntry(index, message, readState, settings, embeddedState, peer, peerPresence, summaryInfo, hasFailed)
|
||||
hasChanges = true
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !transaction.updatedFailedMessagePeerIds.isEmpty {
|
||||
@ -723,7 +747,7 @@ final class MutableChatListView {
|
||||
hasChanges = true
|
||||
}
|
||||
var updateAdditionalMixedItems = false
|
||||
for peerId in self.additionalMixedItemIds {
|
||||
for peerId in self.additionalMixedItemIds.union(self.additionalMixedPinnedItemIds) {
|
||||
if transaction.currentOperationsByPeerId[peerId] != nil {
|
||||
updateAdditionalMixedItems = true
|
||||
}
|
||||
@ -736,7 +760,7 @@ final class MutableChatListView {
|
||||
}
|
||||
if updateAdditionalMixedItems {
|
||||
self.additionalMixedItemEntries.removeAll()
|
||||
for peerId in self.additionalMixedItemIds {
|
||||
for peerId in self.additionalMixedItemIds.union(self.additionalMixedPinnedItemIds) {
|
||||
if let entry = postbox.chatListTable.getEntry(peerId: peerId, messageHistoryTable: postbox.messageHistoryTable, peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable) {
|
||||
self.additionalMixedItemEntries.append(MutableChatListEntry(entry, cachedDataTable: postbox.cachedPeerDataTable, readStateTable: postbox.readStateTable, messageHistoryTable: postbox.messageHistoryTable))
|
||||
}
|
||||
@ -755,6 +779,12 @@ final class MutableChatListView {
|
||||
if !filterPredicate.includes(peer: peer, notificationSettings: postbox.peerNotificationSettingsTable.getEffective(index.messageIndex.id.peerId), isUnread: isUnread) {
|
||||
return false
|
||||
}
|
||||
if self.additionalMixedItemIds.contains(peer.id) {
|
||||
return false
|
||||
}
|
||||
if self.additionalMixedPinnedItemIds.contains(peer.id) {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
@ -1076,12 +1106,17 @@ public final class ChatListView {
|
||||
existingIds.insert(messageEntry.0.messageIndex.id.peerId)
|
||||
}
|
||||
}
|
||||
for entry in mutableView.additionalMixedItemEntries {
|
||||
loop: for entry in mutableView.additionalMixedItemEntries {
|
||||
if case let .MessageEntry(messageEntry) = entry {
|
||||
if !existingIds.contains(messageEntry.0.messageIndex.id.peerId) {
|
||||
switch entry {
|
||||
case let .MessageEntry(index, message, combinedReadState, notificationSettings, embeddedState, peer, peerPresence, summaryInfo, hasFailed):
|
||||
entries.append(.MessageEntry(index, message, combinedReadState, notificationSettings, embeddedState, peer, peerPresence, summaryInfo, hasFailed))
|
||||
if let filterPredicate = mutableView.filterPredicate, let peerValue = peer.peer {
|
||||
if filterPredicate.includes(peer: peerValue, notificationSettings: notificationSettings, isUnread: combinedReadState?.isUnread ?? false) {
|
||||
existingIds.insert(messageEntry.0.messageIndex.id.peerId)
|
||||
entries.append(.MessageEntry(ChatListIndex(pinningIndex: nil, messageIndex: index.messageIndex), message, combinedReadState, notificationSettings, embeddedState, peer, peerPresence, summaryInfo, hasFailed))
|
||||
}
|
||||
}
|
||||
case let .HoleEntry(hole):
|
||||
entries.append(.HoleEntry(hole))
|
||||
case .IntermediateMessageEntry:
|
||||
@ -1104,12 +1139,8 @@ public final class ChatListView {
|
||||
additionalItemEntries.append(.MessageEntry(index, message, combinedReadState, notificationSettings, embeddedState, peer, peerPresence, summaryInfo, hasFailed))
|
||||
case .HoleEntry:
|
||||
assertionFailure()
|
||||
/*case .GroupReferenceEntry:
|
||||
assertionFailure()*/
|
||||
case .IntermediateMessageEntry:
|
||||
assertionFailure()
|
||||
/*case .IntermediateGroupReferenceEntry:
|
||||
assertionFailure()*/
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -763,6 +763,39 @@ public final class Transaction {
|
||||
}
|
||||
}
|
||||
|
||||
public func getChatCountMatchingPredicate(_ predicate: ChatListFilterPredicate) -> Int {
|
||||
assert(!self.disposed)
|
||||
guard let postbox = self.postbox else {
|
||||
return 0
|
||||
}
|
||||
var includedPeerIds: [PeerId: Bool] = [:]
|
||||
for peerId in predicate.includePeerIds {
|
||||
includedPeerIds[peerId] = false
|
||||
}
|
||||
var count = postbox.chatListTable.countWithPredicate(groupId: .root, predicate: { peerId in
|
||||
if let peer = postbox.peerTable.get(peerId) {
|
||||
let isUnread = postbox.readStateTable.getCombinedState(peerId)?.isUnread ?? false
|
||||
let notificationsPeerId = peer.notificationSettingsPeerId ?? peerId
|
||||
if predicate.includes(peer: peer, notificationSettings: postbox.peerNotificationSettingsTable.getEffective(notificationsPeerId), isUnread: isUnread) {
|
||||
includedPeerIds[peer.id] = true
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
for (peerId, included) in includedPeerIds {
|
||||
if !included {
|
||||
if postbox.chatListTable.getPeerChatListIndex(peerId: peerId) != nil {
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
public func legacyGetAccessChallengeData() -> PostboxAccessChallengeData {
|
||||
assert(!self.disposed)
|
||||
if let postbox = self.postbox {
|
||||
@ -1657,9 +1690,13 @@ public final class Postbox {
|
||||
return { entry in
|
||||
switch entry {
|
||||
case let .message(index, _, _):
|
||||
if index.pinningIndex != nil {
|
||||
return false
|
||||
}
|
||||
if let peer = self.peerTable.get(index.messageIndex.id.peerId) {
|
||||
let isUnread = self.readStateTable.getCombinedState(index.messageIndex.id.peerId)?.isUnread ?? false
|
||||
if predicate.includes(peer: peer, notificationSettings: self.peerNotificationSettingsTable.getEffective(index.messageIndex.id.peerId), isUnread: isUnread) {
|
||||
let notificationsPeerId = peer.notificationSettingsPeerId ?? peer.id
|
||||
if predicate.includes(peer: peer, notificationSettings: self.peerNotificationSettingsTable.getEffective(notificationsPeerId), isUnread: isUnread) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
|
||||
@ -1501,7 +1501,10 @@ public final class SqliteValueBox: ValueBox {
|
||||
public func filteredRange(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey, values: (ValueBoxKey, ReadBuffer) -> ValueBoxFilterResult, limit: Int) {
|
||||
var currentStart = start
|
||||
var acceptedCount = 0
|
||||
while acceptedCount < limit {
|
||||
while true {
|
||||
if limit > 0 && acceptedCount >= limit {
|
||||
break
|
||||
}
|
||||
var hadStop = false
|
||||
var lastKey: ValueBoxKey?
|
||||
self.range(table, start: currentStart, end: end, values: { key, value in
|
||||
@ -1530,6 +1533,41 @@ public final class SqliteValueBox: ValueBox {
|
||||
}
|
||||
}
|
||||
|
||||
public func filteredRange(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey, keys: (ValueBoxKey) -> ValueBoxFilterResult, limit: Int) {
|
||||
var currentStart = start
|
||||
var acceptedCount = 0
|
||||
while true {
|
||||
if limit > 0 && acceptedCount >= limit {
|
||||
break
|
||||
}
|
||||
var hadStop = false
|
||||
var lastKey: ValueBoxKey?
|
||||
self.range(table, start: currentStart, end: end, keys: { key in
|
||||
lastKey = key
|
||||
let result = keys(key)
|
||||
switch result {
|
||||
case .accept:
|
||||
acceptedCount += 1
|
||||
return true
|
||||
case .skip:
|
||||
return true
|
||||
case .stop:
|
||||
hadStop = true
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}, limit: limit)
|
||||
if let lastKey = lastKey {
|
||||
currentStart = lastKey
|
||||
} else {
|
||||
break
|
||||
}
|
||||
if hadStop {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func range(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey, keys: (ValueBoxKey) -> Bool, limit: Int) {
|
||||
precondition(self.queue.isCurrent())
|
||||
if let _ = self.tables[table.id] {
|
||||
|
||||
@ -73,6 +73,7 @@ public protocol ValueBox {
|
||||
|
||||
func range(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey, values: (ValueBoxKey, ReadBuffer) -> Bool, limit: Int)
|
||||
func filteredRange(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey, values: (ValueBoxKey, ReadBuffer) -> ValueBoxFilterResult, limit: Int)
|
||||
func filteredRange(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey, keys: (ValueBoxKey) -> ValueBoxFilterResult, limit: Int)
|
||||
func range(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey, keys: (ValueBoxKey) -> Bool, limit: Int)
|
||||
func scan(_ table: ValueBoxTable, values: (ValueBoxKey, ReadBuffer) -> Bool)
|
||||
func scan(_ table: ValueBoxTable, keys: (ValueBoxKey) -> Bool)
|
||||
|
||||
@ -114,6 +114,7 @@ private final class SettingsItemArguments {
|
||||
let openPhoneNumberChange: () -> Void
|
||||
let accountContextAction: (AccountRecordId, ASDisplayNode, ContextGesture?) -> Void
|
||||
let openDevices: () -> Void
|
||||
let openFilters: () -> Void
|
||||
|
||||
init(
|
||||
sharedContext: SharedAccountContext,
|
||||
@ -147,7 +148,8 @@ private final class SettingsItemArguments {
|
||||
keepPhone: @escaping () -> Void,
|
||||
openPhoneNumberChange: @escaping () -> Void,
|
||||
accountContextAction: @escaping (AccountRecordId, ASDisplayNode, ContextGesture?) -> Void,
|
||||
openDevices: @escaping () -> Void
|
||||
openDevices: @escaping () -> Void,
|
||||
openFilters: @escaping () -> Void
|
||||
) {
|
||||
self.sharedContext = sharedContext
|
||||
self.avatarAndNameInfoContext = avatarAndNameInfoContext
|
||||
@ -181,6 +183,7 @@ private final class SettingsItemArguments {
|
||||
self.openPhoneNumberChange = openPhoneNumberChange
|
||||
self.accountContextAction = accountContextAction
|
||||
self.openDevices = openDevices
|
||||
self.openFilters = openFilters
|
||||
}
|
||||
}
|
||||
|
||||
@ -211,6 +214,8 @@ private indirect enum SettingsEntry: ItemListNodeEntry {
|
||||
|
||||
case devices(PresentationTheme, UIImage?, String, String)
|
||||
|
||||
case filters(PresentationTheme, UIImage?, String, String)
|
||||
|
||||
case savedMessages(PresentationTheme, UIImage?, String)
|
||||
case recentCalls(PresentationTheme, UIImage?, String)
|
||||
case stickers(PresentationTheme, UIImage?, String, String, [ArchivedStickerPackItem]?)
|
||||
@ -240,7 +245,7 @@ private indirect enum SettingsEntry: ItemListNodeEntry {
|
||||
return SettingsSection.accounts.rawValue
|
||||
case .proxy:
|
||||
return SettingsSection.proxy.rawValue
|
||||
case .devices:
|
||||
case .devices, .filters:
|
||||
return SettingsSection.media.rawValue
|
||||
case .savedMessages, .recentCalls, .stickers:
|
||||
return SettingsSection.media.rawValue
|
||||
@ -285,30 +290,32 @@ private indirect enum SettingsEntry: ItemListNodeEntry {
|
||||
return 1006
|
||||
case .devices:
|
||||
return 1007
|
||||
case .notificationsAndSounds:
|
||||
case .filters:
|
||||
return 1008
|
||||
case .privacyAndSecurity:
|
||||
case .notificationsAndSounds:
|
||||
return 1009
|
||||
case .dataAndStorage:
|
||||
case .privacyAndSecurity:
|
||||
return 1010
|
||||
case .themes:
|
||||
case .dataAndStorage:
|
||||
return 1011
|
||||
case .language:
|
||||
case .themes:
|
||||
return 1012
|
||||
case .contentStickers:
|
||||
case .language:
|
||||
return 1013
|
||||
case .contentStickers:
|
||||
return 1014
|
||||
#if ENABLE_WALLET
|
||||
case .wallet:
|
||||
return 1014
|
||||
return 1015
|
||||
#endif
|
||||
case .passport:
|
||||
return 1015
|
||||
case .watch:
|
||||
return 1016
|
||||
case .askAQuestion:
|
||||
case .watch:
|
||||
return 1017
|
||||
case .faq:
|
||||
case .askAQuestion:
|
||||
return 1018
|
||||
case .faq:
|
||||
return 1019
|
||||
}
|
||||
}
|
||||
|
||||
@ -406,6 +413,12 @@ private indirect enum SettingsEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .filters(lhsTheme, lhsImage, lhsText, lhsValue):
|
||||
if case let .filters(rhsTheme, rhsImage, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsImage === rhsImage, lhsText == rhsText, lhsValue == rhsValue {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .savedMessages(lhsTheme, lhsImage, lhsText):
|
||||
if case let .savedMessages(rhsTheme, rhsImage, rhsText) = rhs, lhsTheme === rhsTheme, lhsImage === rhsImage, lhsText == rhsText {
|
||||
return true
|
||||
@ -567,6 +580,10 @@ private indirect enum SettingsEntry: ItemListNodeEntry {
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: image, title: text, label: value, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
||||
arguments.openDevices()
|
||||
})
|
||||
case let .filters(theme, image, text, value):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: image, title: text, label: value, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
||||
arguments.openFilters()
|
||||
})
|
||||
case let .savedMessages(theme, image, text):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: image, title: text, label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
||||
arguments.openSavedMessages()
|
||||
@ -635,7 +652,7 @@ private struct SettingsState: Equatable {
|
||||
var isSearching: Bool
|
||||
}
|
||||
|
||||
private func settingsEntries(account: Account, presentationData: PresentationData, state: SettingsState, view: PeerView, proxySettings: ProxySettings, notifyExceptions: NotificationExceptionsList?, notificationsAuthorizationStatus: AccessType, notificationsWarningSuppressed: Bool, unreadTrendingStickerPacks: Int, archivedPacks: [ArchivedStickerPackItem]?, privacySettings: AccountPrivacySettings?, hasWallet: Bool, hasPassport: Bool, hasWatchApp: Bool, accountsAndPeers: [(Account, Peer, Int32)], inAppNotificationSettings: InAppNotificationSettings, experimentalUISettings: ExperimentalUISettings, displayPhoneNumberConfirmation: Bool, otherSessionCount: Int, enableQRLogin: Bool) -> [SettingsEntry] {
|
||||
private func settingsEntries(account: Account, presentationData: PresentationData, state: SettingsState, view: PeerView, proxySettings: ProxySettings, notifyExceptions: NotificationExceptionsList?, notificationsAuthorizationStatus: AccessType, notificationsWarningSuppressed: Bool, unreadTrendingStickerPacks: Int, archivedPacks: [ArchivedStickerPackItem]?, privacySettings: AccountPrivacySettings?, hasWallet: Bool, hasPassport: Bool, hasWatchApp: Bool, accountsAndPeers: [(Account, Peer, Int32)], inAppNotificationSettings: InAppNotificationSettings, experimentalUISettings: ExperimentalUISettings, displayPhoneNumberConfirmation: Bool, otherSessionCount: Int, enableQRLogin: Bool, enableFilters: Bool) -> [SettingsEntry] {
|
||||
var entries: [SettingsEntry] = []
|
||||
|
||||
if let peer = peerViewMainPeer(view) as? TelegramUser {
|
||||
@ -688,6 +705,10 @@ private func settingsEntries(account: Account, presentationData: PresentationDat
|
||||
} else {
|
||||
entries.append(.devices(presentationData.theme, UIImage(bundleImageName: "Settings/MenuIcons/Sessions")?.precomposed(), presentationData.strings.Settings_Devices, otherSessionCount == 0 ? "" : "\(otherSessionCount + 1)"))
|
||||
}
|
||||
if enableFilters {
|
||||
//TODO:localize
|
||||
entries.append(.filters(presentationData.theme, UIImage(bundleImageName: "Settings/MenuIcons/SavedMessages")?.precomposed(), "Chat Filters", ""))
|
||||
}
|
||||
|
||||
let notificationsWarning = shouldDisplayNotificationsPermissionWarning(status: notificationsAuthorizationStatus, suppressed: notificationsWarningSuppressed)
|
||||
entries.append(.notificationsAndSounds(presentationData.theme, PresentationResourcesSettings.notifications, presentationData.strings.Settings_NotificationsAndSounds, notifyExceptions, notificationsWarning))
|
||||
@ -925,6 +946,7 @@ public func settingsController(context: AccountContext, accountManager: AccountM
|
||||
let privacySettings = Promise<AccountPrivacySettings?>(nil)
|
||||
|
||||
let enableQRLogin = Promise<Bool>()
|
||||
let enableFilters = Promise<Bool>()
|
||||
|
||||
let openFaq: (Promise<ResolvedUrl>, String?) -> Void = { resolvedUrl, customAnchor in
|
||||
let _ = (contextValue.get()
|
||||
@ -1221,17 +1243,27 @@ public func settingsController(context: AccountContext, accountManager: AccountM
|
||||
gesture?.cancel()
|
||||
}
|
||||
}, openDevices: {
|
||||
let _ = (combineLatest(queue: .mainQueue(),
|
||||
activeSessionsContextAndCount.get(),
|
||||
enableQRLogin.get()
|
||||
)
|
||||
|> take(1)).start(next: { activeSessionsContextAndCount, enableQRLogin in
|
||||
let (activeSessionsContext, count, webSessionsContext) = activeSessionsContextAndCount
|
||||
if count == 0 && enableQRLogin {
|
||||
pushControllerImpl?(AuthDataTransferSplashScreen(context: context, activeSessionsContext: activeSessionsContext))
|
||||
} else {
|
||||
pushControllerImpl?(recentSessionsController(context: context, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext, websitesOnly: false))
|
||||
}
|
||||
let _ = (contextValue.get()
|
||||
|> deliverOnMainQueue
|
||||
|> take(1)).start(next: { context in
|
||||
let _ = (combineLatest(queue: .mainQueue(),
|
||||
activeSessionsContextAndCount.get(),
|
||||
enableQRLogin.get()
|
||||
)
|
||||
|> take(1)).start(next: { activeSessionsContextAndCount, enableQRLogin in
|
||||
let (activeSessionsContext, count, webSessionsContext) = activeSessionsContextAndCount
|
||||
if count == 0 && enableQRLogin {
|
||||
pushControllerImpl?(AuthDataTransferSplashScreen(context: context, activeSessionsContext: activeSessionsContext))
|
||||
} else {
|
||||
pushControllerImpl?(recentSessionsController(context: context, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext, websitesOnly: false))
|
||||
}
|
||||
})
|
||||
})
|
||||
}, openFilters: {
|
||||
let _ = (contextValue.get()
|
||||
|> deliverOnMainQueue
|
||||
|> take(1)).start(next: { context in
|
||||
pushControllerImpl?(chatListFilterPresetListController(context: context, updated: { _ in }))
|
||||
})
|
||||
})
|
||||
|
||||
@ -1496,7 +1528,21 @@ public func settingsController(context: AccountContext, accountManager: AccountM
|
||||
}
|
||||
enableQRLogin.set(enableQRLoginSignal)
|
||||
|
||||
let signal = combineLatest(queue: Queue.mainQueue(), contextValue.get(), updatedPresentationData, statePromise.get(), peerView, combineLatest(queue: Queue.mainQueue(), preferences, notifyExceptions.get(), notificationsAuthorizationStatus.get(), notificationsWarningSuppressed.get(), privacySettings.get(), displayPhoneNumberConfirmation.get()), combineLatest(featuredStickerPacks, archivedPacks.get()), combineLatest(hasWallet, hasPassport.get(), hasWatchApp, enableQRLogin.get()), accountsAndPeers.get(), activeSessionsContextAndCount.get())
|
||||
let enableFiltersSignal = contextValue.get()
|
||||
|> mapToSignal { context -> Signal<Bool, NoError> in
|
||||
return context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
|
||||
|> map { view -> Bool in
|
||||
guard let appConfiguration = view.values[PreferencesKeys.appConfiguration] as? AppConfiguration else {
|
||||
return false
|
||||
}
|
||||
let configuration = ChatListFilteringConfiguration(appConfiguration: appConfiguration)
|
||||
return configuration.isEnabled
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
}
|
||||
enableFilters.set(enableFiltersSignal)
|
||||
|
||||
let signal = combineLatest(queue: Queue.mainQueue(), contextValue.get(), updatedPresentationData, statePromise.get(), peerView, combineLatest(queue: Queue.mainQueue(), preferences, notifyExceptions.get(), notificationsAuthorizationStatus.get(), notificationsWarningSuppressed.get(), privacySettings.get(), displayPhoneNumberConfirmation.get()), combineLatest(featuredStickerPacks, archivedPacks.get()), combineLatest(hasWallet, hasPassport.get(), hasWatchApp, enableQRLogin.get(), enableFilters.get()), accountsAndPeers.get(), activeSessionsContextAndCount.get())
|
||||
|> map { context, presentationData, state, view, preferencesAndExceptions, featuredAndArchived, hasWalletPassportAndWatch, accountsAndPeers, activeSessionsContextAndCount -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let otherSessionCount = activeSessionsContextAndCount.1
|
||||
|
||||
@ -1536,8 +1582,8 @@ public func settingsController(context: AccountContext, accountManager: AccountM
|
||||
pushControllerImpl?(c)
|
||||
}, getNavigationController: getNavigationControllerImpl, exceptionsList: notifyExceptions.get(), archivedStickerPacks: archivedPacks.get(), privacySettings: privacySettings.get(), hasWallet: hasWallet, activeSessionsContext: activeSessionsContextAndCountSignal |> map { $0.0 } |> distinctUntilChanged(isEqual: { $0 === $1 }), webSessionsContext: activeSessionsContextAndCountSignal |> map { $0.2 } |> distinctUntilChanged(isEqual: { $0 === $1 }))
|
||||
|
||||
let (hasWallet, hasPassport, hasWatchApp, enableQRLogin) = hasWalletPassportAndWatch
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: settingsEntries(account: context.account, presentationData: presentationData, state: state, view: view, proxySettings: proxySettings, notifyExceptions: preferencesAndExceptions.1, notificationsAuthorizationStatus: preferencesAndExceptions.2, notificationsWarningSuppressed: preferencesAndExceptions.3, unreadTrendingStickerPacks: unreadTrendingStickerPacks, archivedPacks: featuredAndArchived.1, privacySettings: preferencesAndExceptions.4, hasWallet: hasWallet, hasPassport: hasPassport, hasWatchApp: hasWatchApp, accountsAndPeers: accountsAndPeers.1, inAppNotificationSettings: inAppNotificationSettings, experimentalUISettings: experimentalUISettings, displayPhoneNumberConfirmation: preferencesAndExceptions.5, otherSessionCount: otherSessionCount, enableQRLogin: enableQRLogin), style: .blocks, searchItem: searchItem, initialScrollToItem: ListViewScrollToItem(index: 0, position: .top(-navigationBarSearchContentHeight), animated: false, curve: .Default(duration: 0.0), directionHint: .Up))
|
||||
let (hasWallet, hasPassport, hasWatchApp, enableQRLogin, enableFilters) = hasWalletPassportAndWatch
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: settingsEntries(account: context.account, presentationData: presentationData, state: state, view: view, proxySettings: proxySettings, notifyExceptions: preferencesAndExceptions.1, notificationsAuthorizationStatus: preferencesAndExceptions.2, notificationsWarningSuppressed: preferencesAndExceptions.3, unreadTrendingStickerPacks: unreadTrendingStickerPacks, archivedPacks: featuredAndArchived.1, privacySettings: preferencesAndExceptions.4, hasWallet: hasWallet, hasPassport: hasPassport, hasWatchApp: hasWatchApp, accountsAndPeers: accountsAndPeers.1, inAppNotificationSettings: inAppNotificationSettings, experimentalUISettings: experimentalUISettings, displayPhoneNumberConfirmation: preferencesAndExceptions.5, otherSessionCount: otherSessionCount, enableQRLogin: enableQRLogin, enableFilters: enableFilters), style: .blocks, searchItem: searchItem, initialScrollToItem: ListViewScrollToItem(index: 0, position: .top(-navigationBarSearchContentHeight), animated: false, curve: .Default(duration: 0.0), directionHint: .Up))
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
||||
@ -1044,7 +1044,7 @@ public class Account {
|
||||
self.managedOperationsDisposable.add(managedApplyPendingMessageReactionsActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
||||
self.managedOperationsDisposable.add(managedSynchronizeEmojiKeywordsOperations(postbox: self.postbox, network: self.network).start())
|
||||
self.managedOperationsDisposable.add(managedApplyPendingScheduledMessagesActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
||||
//self.managedOperationsDisposable.add(managedChatListFilters(postbox: self.postbox, network: self.network).start())
|
||||
self.managedOperationsDisposable.add(managedChatListFilters(postbox: self.postbox, network: self.network).start())
|
||||
|
||||
let importantBackgroundOperations: [Signal<AccountRunningImportantTasks, NoError>] = [
|
||||
managedSynchronizeChatInputStateOperations(postbox: self.postbox, network: self.network) |> map { $0 ? AccountRunningImportantTasks.other : [] },
|
||||
|
||||
@ -5,7 +5,19 @@ import TelegramApi
|
||||
|
||||
import SyncCore
|
||||
|
||||
public struct ChatListFilterPeerCategories: OptionSet {
|
||||
public struct ChatListFilteringConfiguration: Equatable {
|
||||
public let isEnabled: Bool
|
||||
|
||||
public init(appConfiguration: AppConfiguration) {
|
||||
var isEnabled = false
|
||||
if let data = appConfiguration.data, let value = data["dialog_filters_enabled"] as? Bool, value {
|
||||
isEnabled = true
|
||||
}
|
||||
self.isEnabled = isEnabled
|
||||
}
|
||||
}
|
||||
|
||||
public struct ChatListFilterPeerCategories: OptionSet, Hashable {
|
||||
public var rawValue: Int32
|
||||
|
||||
public init(rawValue: Int32) {
|
||||
@ -93,50 +105,58 @@ extension ChatListFilterPeerCategories {
|
||||
}
|
||||
}
|
||||
|
||||
public struct ChatListFilter: PostboxCoding, Equatable {
|
||||
public var id: Int32
|
||||
public var title: String?
|
||||
public struct ChatListFilterData: Equatable, Hashable {
|
||||
public var categories: ChatListFilterPeerCategories
|
||||
public var excludeMuted: Bool
|
||||
public var excludeRead: Bool
|
||||
public var includePeers: [PeerId]
|
||||
|
||||
public init(
|
||||
id: Int32,
|
||||
title: String?,
|
||||
categories: ChatListFilterPeerCategories,
|
||||
excludeMuted: Bool,
|
||||
excludeRead: Bool,
|
||||
includePeers: [PeerId]
|
||||
) {
|
||||
self.id = id
|
||||
self.title = title
|
||||
self.categories = categories
|
||||
self.excludeMuted = excludeMuted
|
||||
self.excludeRead = excludeRead
|
||||
self.includePeers = includePeers
|
||||
}
|
||||
}
|
||||
|
||||
public struct ChatListFilter: PostboxCoding, Equatable {
|
||||
public var id: Int32
|
||||
public var title: String
|
||||
public var data: ChatListFilterData
|
||||
|
||||
public init(
|
||||
id: Int32,
|
||||
title: String,
|
||||
data: ChatListFilterData
|
||||
) {
|
||||
self.id = id
|
||||
self.title = title
|
||||
self.data = data
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.id = decoder.decodeInt32ForKey("id", orElse: 0)
|
||||
self.title = decoder.decodeOptionalStringForKey("title")
|
||||
self.categories = ChatListFilterPeerCategories(rawValue: decoder.decodeInt32ForKey("categories", orElse: 0))
|
||||
self.excludeMuted = decoder.decodeInt32ForKey("excludeMuted", orElse: 0) != 0
|
||||
self.excludeRead = decoder.decodeInt32ForKey("excludeRead", orElse: 0) != 0
|
||||
self.includePeers = decoder.decodeInt64ArrayForKey("includePeers").map(PeerId.init)
|
||||
self.title = decoder.decodeStringForKey("title", orElse: "")
|
||||
self.data = ChatListFilterData(
|
||||
categories: ChatListFilterPeerCategories(rawValue: decoder.decodeInt32ForKey("categories", orElse: 0)),
|
||||
excludeMuted: decoder.decodeInt32ForKey("excludeMuted", orElse: 0) != 0,
|
||||
excludeRead: decoder.decodeInt32ForKey("excludeRead", orElse: 0) != 0,
|
||||
includePeers: decoder.decodeInt64ArrayForKey("includePeers").map(PeerId.init)
|
||||
)
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt32(self.id, forKey: "id")
|
||||
if let title = self.title {
|
||||
encoder.encodeString(title, forKey: "title")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "title")
|
||||
}
|
||||
encoder.encodeInt32(self.categories.rawValue, forKey: "categories")
|
||||
encoder.encodeInt32(self.excludeMuted ? 1 : 0, forKey: "excludeMuted")
|
||||
encoder.encodeInt32(self.excludeRead ? 1 : 0, forKey: "excludeRead")
|
||||
encoder.encodeInt64Array(self.includePeers.map { $0.toInt64() }, forKey: "includePeers")
|
||||
encoder.encodeString(self.title, forKey: "title")
|
||||
encoder.encodeInt32(self.data.categories.rawValue, forKey: "categories")
|
||||
encoder.encodeInt32(self.data.excludeMuted ? 1 : 0, forKey: "excludeMuted")
|
||||
encoder.encodeInt32(self.data.excludeRead ? 1 : 0, forKey: "excludeRead")
|
||||
encoder.encodeInt64Array(self.data.includePeers.map { $0.toInt64() }, forKey: "includePeers")
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,36 +166,38 @@ extension ChatListFilter {
|
||||
case let .dialogFilter(flags, id, title, includePeers):
|
||||
self.init(
|
||||
id: id,
|
||||
title: title.isEmpty ? nil : title,
|
||||
categories: ChatListFilterPeerCategories(apiFlags: flags),
|
||||
excludeMuted: (flags & (1 << 11)) != 0,
|
||||
excludeRead: (flags & (1 << 12)) != 0,
|
||||
includePeers: includePeers.compactMap { peer -> PeerId? in
|
||||
switch peer {
|
||||
case let .inputPeerUser(userId, _):
|
||||
return PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
|
||||
case let .inputPeerChat(chatId):
|
||||
return PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId)
|
||||
case let .inputPeerChannel(channelId, _):
|
||||
return PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId)
|
||||
default:
|
||||
return nil
|
||||
title: title,
|
||||
data: ChatListFilterData(
|
||||
categories: ChatListFilterPeerCategories(apiFlags: flags),
|
||||
excludeMuted: (flags & (1 << 11)) != 0,
|
||||
excludeRead: (flags & (1 << 12)) != 0,
|
||||
includePeers: includePeers.compactMap { peer -> PeerId? in
|
||||
switch peer {
|
||||
case let .inputPeerUser(userId, _):
|
||||
return PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
|
||||
case let .inputPeerChat(chatId):
|
||||
return PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId)
|
||||
case let .inputPeerChannel(channelId, _):
|
||||
return PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func apiFilter(transaction: Transaction) -> Api.DialogFilter {
|
||||
var flags: Int32 = 0
|
||||
if self.excludeMuted {
|
||||
if self.data.excludeMuted {
|
||||
flags |= 1 << 11
|
||||
}
|
||||
if self.excludeRead {
|
||||
if self.data.excludeRead {
|
||||
flags |= 1 << 12
|
||||
}
|
||||
flags |= self.categories.apiFlags
|
||||
return .dialogFilter(flags: flags, id: self.id, title: self.title ?? "", includePeers: self.includePeers.compactMap { peerId -> Api.InputPeer? in
|
||||
flags |= self.data.categories.apiFlags
|
||||
return .dialogFilter(flags: flags, id: self.id, title: self.title, includePeers: self.data.includePeers.compactMap { peerId -> Api.InputPeer? in
|
||||
return transaction.getPeer(peerId).flatMap(apiInputPeer)
|
||||
})
|
||||
}
|
||||
@ -287,46 +309,70 @@ func managedChatListFilters(postbox: Postbox, network: Network) -> Signal<Never,
|
||||
}
|
||||
|
||||
public func replaceRemoteChatListFilters(account: Account) -> Signal<Never, NoError> {
|
||||
return requestChatListFilters(postbox: account.postbox, network: account.network)
|
||||
|> `catch` { _ -> Signal<[ChatListFilter], NoError> in
|
||||
return .complete()
|
||||
return account.postbox.transaction { transaction -> [ChatListFilter] in
|
||||
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default
|
||||
return settings.filters
|
||||
}
|
||||
|> mapToSignal { remoteFilters -> Signal<Never, NoError> in
|
||||
var deleteSignals: [Signal<Never, NoError>] = []
|
||||
for filter in remoteFilters {
|
||||
deleteSignals.append(requestUpdateChatListFilter(account: account, id: filter.id, filter: nil)
|
||||
|> `catch` { _ -> Signal<Never, NoError> in
|
||||
return .complete()
|
||||
}
|
||||
|> ignoreValues)
|
||||
|> mapToSignal { filters -> Signal<Never, NoError> in
|
||||
return requestChatListFilters(postbox: account.postbox, network: account.network)
|
||||
|> `catch` { _ -> Signal<[ChatListFilter], NoError> in
|
||||
return .complete()
|
||||
}
|
||||
|
||||
let addFilters = account.postbox.transaction { transaction -> [(Int32, ChatListFilter)] in
|
||||
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default
|
||||
return settings.filters.map { filter -> (Int32, ChatListFilter) in
|
||||
return (filter.id, filter)
|
||||
|> mapToSignal { remoteFilters -> Signal<Never, NoError> in
|
||||
var deleteSignals: [Signal<Never, NoError>] = []
|
||||
for filter in remoteFilters {
|
||||
if !filters.contains(where: { $0.id == filter.id }) {
|
||||
deleteSignals.append(requestUpdateChatListFilter(account: account, id: filter.id, filter: nil)
|
||||
|> `catch` { _ -> Signal<Never, NoError> in
|
||||
return .complete()
|
||||
}
|
||||
|> ignoreValues)
|
||||
}
|
||||
}
|
||||
}
|
||||
|> mapToSignal { filters -> Signal<Never, NoError> in
|
||||
var signals: [Signal<Never, NoError>] = []
|
||||
for (id, filter) in filters {
|
||||
signals.append(requestUpdateChatListFilter(account: account, id: id, filter: filter)
|
||||
|
||||
let addFilters = account.postbox.transaction { transaction -> [(Int32, ChatListFilter)] in
|
||||
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default
|
||||
return settings.filters.map { filter -> (Int32, ChatListFilter) in
|
||||
return (filter.id, filter)
|
||||
}
|
||||
}
|
||||
|> mapToSignal { filters -> Signal<Never, NoError> in
|
||||
var signals: [Signal<Never, NoError>] = []
|
||||
for (id, filter) in filters {
|
||||
if !remoteFilters.contains(filter) {
|
||||
signals.append(requestUpdateChatListFilter(account: account, id: id, filter: filter)
|
||||
|> `catch` { _ -> Signal<Never, NoError> in
|
||||
return .complete()
|
||||
}
|
||||
|> ignoreValues)
|
||||
}
|
||||
}
|
||||
return combineLatest(signals)
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
let reorderFilters: Signal<Never, NoError>
|
||||
if remoteFilters.map({ $0.id }) != filters.map({ $0.id }) {
|
||||
reorderFilters = account.network.request(Api.functions.messages.updateDialogFiltersOrder(order: filters.map { $0.id }))
|
||||
|> ignoreValues
|
||||
|> `catch` { _ -> Signal<Never, NoError> in
|
||||
return .complete()
|
||||
}
|
||||
|> ignoreValues)
|
||||
} else {
|
||||
reorderFilters = .complete()
|
||||
}
|
||||
return combineLatest(signals)
|
||||
|
||||
return combineLatest(
|
||||
deleteSignals
|
||||
)
|
||||
|> ignoreValues
|
||||
|> then(
|
||||
addFilters
|
||||
)
|
||||
|> then(
|
||||
reorderFilters
|
||||
)
|
||||
}
|
||||
|
||||
return combineLatest(
|
||||
deleteSignals
|
||||
)
|
||||
|> ignoreValues
|
||||
|> then(
|
||||
addFilters
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -2,7 +2,7 @@
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "ic_list.pdf"
|
||||
"filename" : "ic_folder.pdf"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
Binary file not shown.
@ -83,7 +83,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
||||
|
||||
self.scrollToTop = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.contactsNode.contactListNode.scrollToTop()
|
||||
strongSelf.contactsNode.scrollToTop()
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,33 +132,51 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
||||
|
||||
private func updateTitle() {
|
||||
switch self.mode {
|
||||
case .groupCreation:
|
||||
let maxCount: Int32 = self.limitsConfiguration?.maxSupergroupMemberCount ?? 5000
|
||||
let count = self.contactsNode.contactListNode.selectionState?.selectedPeerIndices.count ?? 0
|
||||
self.titleView.title = CounterContollerTitle(title: self.presentationData.strings.Compose_NewGroupTitle, counter: "\(count)/\(maxCount)")
|
||||
let rightNavigationButton = UIBarButtonItem(title: self.presentationData.strings.Common_Next, style: .done, target: self, action: #selector(self.rightNavigationButtonPressed))
|
||||
self.rightNavigationButton = rightNavigationButton
|
||||
self.navigationItem.rightBarButtonItem = self.rightNavigationButton
|
||||
rightNavigationButton.isEnabled = count != 0
|
||||
case .channelCreation:
|
||||
self.titleView.title = CounterContollerTitle(title: self.presentationData.strings.GroupInfo_AddParticipantTitle, counter: "")
|
||||
let rightNavigationButton = UIBarButtonItem(title: self.presentationData.strings.Common_Next, style: .done, target: self, action: #selector(self.rightNavigationButtonPressed))
|
||||
self.rightNavigationButton = rightNavigationButton
|
||||
self.navigationItem.rightBarButtonItem = self.rightNavigationButton
|
||||
rightNavigationButton.isEnabled = true
|
||||
case .peerSelection:
|
||||
self.titleView.title = CounterContollerTitle(title: self.presentationData.strings.PrivacyLastSeenSettings_EmpryUsersPlaceholder, counter: "")
|
||||
let rightNavigationButton = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.rightNavigationButtonPressed))
|
||||
self.rightNavigationButton = rightNavigationButton
|
||||
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(cancelPressed))
|
||||
self.navigationItem.rightBarButtonItem = self.rightNavigationButton
|
||||
rightNavigationButton.isEnabled = false
|
||||
case .groupCreation:
|
||||
let maxCount: Int32 = self.limitsConfiguration?.maxSupergroupMemberCount ?? 5000
|
||||
let count: Int
|
||||
switch self.contactsNode.contentNode {
|
||||
case let .contacts(contactsNode):
|
||||
count = contactsNode.selectionState?.selectedPeerIndices.count ?? 0
|
||||
case let .chats(chatsNode):
|
||||
count = chatsNode.currentState.selectedPeerIds.count
|
||||
}
|
||||
self.titleView.title = CounterContollerTitle(title: self.presentationData.strings.Compose_NewGroupTitle, counter: "\(count)/\(maxCount)")
|
||||
let rightNavigationButton = UIBarButtonItem(title: self.presentationData.strings.Common_Next, style: .done, target: self, action: #selector(self.rightNavigationButtonPressed))
|
||||
self.rightNavigationButton = rightNavigationButton
|
||||
self.navigationItem.rightBarButtonItem = self.rightNavigationButton
|
||||
rightNavigationButton.isEnabled = count != 0
|
||||
case .channelCreation:
|
||||
self.titleView.title = CounterContollerTitle(title: self.presentationData.strings.GroupInfo_AddParticipantTitle, counter: "")
|
||||
let rightNavigationButton = UIBarButtonItem(title: self.presentationData.strings.Common_Next, style: .done, target: self, action: #selector(self.rightNavigationButtonPressed))
|
||||
self.rightNavigationButton = rightNavigationButton
|
||||
self.navigationItem.rightBarButtonItem = self.rightNavigationButton
|
||||
rightNavigationButton.isEnabled = true
|
||||
case .peerSelection:
|
||||
self.titleView.title = CounterContollerTitle(title: self.presentationData.strings.PrivacyLastSeenSettings_EmpryUsersPlaceholder, counter: "")
|
||||
let rightNavigationButton = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.rightNavigationButtonPressed))
|
||||
self.rightNavigationButton = rightNavigationButton
|
||||
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(cancelPressed))
|
||||
self.navigationItem.rightBarButtonItem = self.rightNavigationButton
|
||||
rightNavigationButton.isEnabled = false
|
||||
case .chatSelection:
|
||||
self.titleView.title = CounterContollerTitle(title: self.presentationData.strings.ChatListFilter_AddChatsTitle, counter: "")
|
||||
let rightNavigationButton = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.rightNavigationButtonPressed))
|
||||
self.rightNavigationButton = rightNavigationButton
|
||||
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(cancelPressed))
|
||||
self.navigationItem.rightBarButtonItem = self.rightNavigationButton
|
||||
rightNavigationButton.isEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
override func loadDisplayNode() {
|
||||
self.displayNode = ContactMultiselectionControllerNode(context: self.context, mode: self.mode, options: self.options, filters: filters)
|
||||
self._listReady.set(self.contactsNode.contactListNode.ready)
|
||||
switch self.contactsNode.contentNode {
|
||||
case let .contacts(contactsNode):
|
||||
self._listReady.set(contactsNode.ready)
|
||||
case let .chats(chatsNode):
|
||||
self._listReady.set(chatsNode.ready)
|
||||
}
|
||||
|
||||
self.contactsNode.dismiss = { [weak self] in
|
||||
self?.presentingViewController?.dismiss(animated: true, completion: nil)
|
||||
@ -174,25 +192,47 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
||||
var displayCountAlert = false
|
||||
|
||||
var selectionState: ContactListNodeGroupSelectionState?
|
||||
strongSelf.contactsNode.contactListNode.updateSelectionState { state in
|
||||
if let state = state {
|
||||
var updatedState = state.withToggledPeerId(.peer(peer.id))
|
||||
if updatedState.selectedPeerIndices[.peer(peer.id)] == nil {
|
||||
switch strongSelf.contactsNode.contentNode {
|
||||
case let .contacts(contactsNode):
|
||||
contactsNode.updateSelectionState { state in
|
||||
if let state = state {
|
||||
var updatedState = state.withToggledPeerId(.peer(peer.id))
|
||||
if updatedState.selectedPeerIndices[.peer(peer.id)] == nil {
|
||||
removedTokenId = peer.id
|
||||
} else {
|
||||
if updatedState.selectedPeerIndices.count >= maxRegularCount {
|
||||
displayCountAlert = true
|
||||
updatedState = updatedState.withToggledPeerId(.peer(peer.id))
|
||||
} else {
|
||||
addedToken = EditableTokenListToken(id: peer.id, title: peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder))
|
||||
}
|
||||
}
|
||||
updatedCount = updatedState.selectedPeerIndices.count
|
||||
selectionState = updatedState
|
||||
return updatedState
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
case let .chats(chatsNode):
|
||||
chatsNode.updateState { state in
|
||||
var state = state
|
||||
if state.selectedPeerIds.contains(peer.id) {
|
||||
state.selectedPeerIds.remove(peer.id)
|
||||
removedTokenId = peer.id
|
||||
} else {
|
||||
if updatedState.selectedPeerIndices.count >= maxRegularCount {
|
||||
displayCountAlert = true
|
||||
updatedState = updatedState.withToggledPeerId(.peer(peer.id))
|
||||
} else {
|
||||
addedToken = EditableTokenListToken(id: peer.id, title: peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder))
|
||||
}
|
||||
addedToken = EditableTokenListToken(id: peer.id, title: peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder))
|
||||
state.selectedPeerIds.insert(peer.id)
|
||||
}
|
||||
updatedCount = state.selectedPeerIds.count
|
||||
var updatedState = ContactListNodeGroupSelectionState()
|
||||
for peerId in state.selectedPeerIds {
|
||||
updatedState = updatedState.withToggledPeerId(.peer(peerId))
|
||||
}
|
||||
updatedCount = updatedState.selectedPeerIndices.count
|
||||
selectionState = updatedState
|
||||
return updatedState
|
||||
} else {
|
||||
return nil
|
||||
return state
|
||||
}
|
||||
break
|
||||
}
|
||||
if let searchResultsNode = strongSelf.contactsNode.searchResultsNode {
|
||||
searchResultsNode.updateSelectionState { _ in
|
||||
@ -202,7 +242,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
||||
|
||||
if let updatedCount = updatedCount {
|
||||
switch strongSelf.mode {
|
||||
case .groupCreation, .peerSelection:
|
||||
case .groupCreation, .peerSelection, .chatSelection:
|
||||
strongSelf.rightNavigationButton?.isEnabled = updatedCount != 0
|
||||
case .channelCreation:
|
||||
break
|
||||
@ -212,7 +252,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
||||
case .groupCreation:
|
||||
let maxCount: Int32 = strongSelf.limitsConfiguration?.maxSupergroupMemberCount ?? 5000
|
||||
strongSelf.titleView.title = CounterContollerTitle(title: strongSelf.presentationData.strings.Compose_NewGroupTitle, counter: "\(updatedCount)/\(maxCount)")
|
||||
case .peerSelection, .channelCreation:
|
||||
case .peerSelection, .channelCreation, .chatSelection:
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -238,19 +278,39 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
||||
var removedTokenId: AnyHashable?
|
||||
|
||||
var selectionState: ContactListNodeGroupSelectionState?
|
||||
strongSelf.contactsNode.contactListNode.updateSelectionState { state in
|
||||
if let state = state {
|
||||
let updatedState = state.withToggledPeerId(peerId)
|
||||
if updatedState.selectedPeerIndices[peerId] == nil {
|
||||
if case let .peer(peerId) = peerId {
|
||||
switch strongSelf.contactsNode.contentNode {
|
||||
case let .contacts(contactsNode):
|
||||
contactsNode.updateSelectionState { state in
|
||||
if let state = state {
|
||||
let updatedState = state.withToggledPeerId(peerId)
|
||||
if updatedState.selectedPeerIndices[peerId] == nil {
|
||||
if case let .peer(peerId) = peerId {
|
||||
removedTokenId = peerId
|
||||
}
|
||||
}
|
||||
updatedCount = updatedState.selectedPeerIndices.count
|
||||
selectionState = updatedState
|
||||
return updatedState
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
case let .chats(chatsNode):
|
||||
chatsNode.updateState { state in
|
||||
var state = state
|
||||
if case let .peer(peerIdValue) = peerId {
|
||||
if state.selectedPeerIds.contains(peerIdValue) {
|
||||
state.selectedPeerIds.remove(peerIdValue)
|
||||
removedTokenId = peerId
|
||||
}
|
||||
}
|
||||
updatedCount = updatedState.selectedPeerIndices.count
|
||||
updatedCount = state.selectedPeerIds.count
|
||||
var updatedState = ContactListNodeGroupSelectionState()
|
||||
for peerId in state.selectedPeerIds {
|
||||
updatedState = updatedState.withToggledPeerId(.peer(peerId))
|
||||
}
|
||||
selectionState = updatedState
|
||||
return updatedState
|
||||
} else {
|
||||
return nil
|
||||
return state
|
||||
}
|
||||
}
|
||||
if let searchResultsNode = strongSelf.contactsNode.searchResultsNode {
|
||||
@ -261,7 +321,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
||||
|
||||
if let updatedCount = updatedCount {
|
||||
switch strongSelf.mode {
|
||||
case .groupCreation, .peerSelection:
|
||||
case .groupCreation, .peerSelection, .chatSelection:
|
||||
strongSelf.rightNavigationButton?.isEnabled = updatedCount != 0
|
||||
case .channelCreation:
|
||||
break
|
||||
@ -270,7 +330,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
||||
case .groupCreation:
|
||||
let maxCount: Int32 = strongSelf.limitsConfiguration?.maxSupergroupMemberCount ?? 5000
|
||||
strongSelf.titleView.title = CounterContollerTitle(title: strongSelf.presentationData.strings.Compose_NewGroupTitle, counter: "\(updatedCount)/\(maxCount)")
|
||||
case .peerSelection, .channelCreation:
|
||||
case .peerSelection, .channelCreation, .chatSelection:
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -290,7 +350,12 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
self.contactsNode.contactListNode.enableUpdates = true
|
||||
switch self.contactsNode.contentNode {
|
||||
case let .contacts(contactsNode):
|
||||
contactsNode.enableUpdates = true
|
||||
case .chats:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
@ -307,7 +372,12 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
||||
override func viewDidDisappear(_ animated: Bool) {
|
||||
super.viewDidDisappear(animated)
|
||||
|
||||
self.contactsNode.contactListNode.enableUpdates = false
|
||||
switch self.contactsNode.contentNode {
|
||||
case let .contacts(contactsNode):
|
||||
contactsNode.enableUpdates = false
|
||||
case .chats:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
@ -323,11 +393,18 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
||||
|
||||
@objc func rightNavigationButtonPressed() {
|
||||
var peerIds: [ContactListPeerId] = []
|
||||
self.contactsNode.contactListNode.updateSelectionState { state in
|
||||
if let state = state {
|
||||
peerIds = Array(state.selectedPeerIndices.keys)
|
||||
switch self.contactsNode.contentNode {
|
||||
case let .contacts(contactsNode):
|
||||
contactsNode.updateSelectionState { state in
|
||||
if let state = state {
|
||||
peerIds = Array(state.selectedPeerIndices.keys)
|
||||
}
|
||||
return state
|
||||
}
|
||||
case let .chats(chatsNode):
|
||||
for peerId in chatsNode.currentState.selectedPeerIds {
|
||||
peerIds.append(.peer(peerId))
|
||||
}
|
||||
return state
|
||||
}
|
||||
self._result.set(.single(peerIds))
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ import TelegramPresentationData
|
||||
import MergeLists
|
||||
import AccountContext
|
||||
import ContactListUI
|
||||
import ChatListUI
|
||||
|
||||
private struct SearchResultEntry: Identifiable {
|
||||
let index: Int
|
||||
@ -27,8 +28,22 @@ private struct SearchResultEntry: Identifiable {
|
||||
}
|
||||
}
|
||||
|
||||
enum ContactMultiselectionContentNode {
|
||||
case contacts(ContactListNode)
|
||||
case chats(ChatListNode)
|
||||
|
||||
var node: ASDisplayNode {
|
||||
switch self {
|
||||
case let .contacts(contacts):
|
||||
return contacts
|
||||
case let .chats(chats):
|
||||
return chats
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class ContactMultiselectionControllerNode: ASDisplayNode {
|
||||
let contactListNode: ContactListNode
|
||||
let contentNode: ContactMultiselectionContentNode
|
||||
let tokenListNode: EditableTokenListNode
|
||||
var searchResultsNode: ContactListNode?
|
||||
|
||||
@ -53,7 +68,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
||||
self.context = context
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
let placeholder: String
|
||||
var placeholder: String
|
||||
var includeChatList = false
|
||||
switch mode {
|
||||
case let .peerSelection(_, searchGroups, searchChannels):
|
||||
@ -67,7 +82,13 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
||||
placeholder = self.presentationData.strings.Compose_TokenListPlaceholder
|
||||
}
|
||||
|
||||
self.contactListNode = ContactListNode(context: context, presentation: .single(.natural(options: options, includeChatList: includeChatList)), filters: filters, selectionState: ContactListNodeGroupSelectionState())
|
||||
if case .chatSelection = mode {
|
||||
placeholder = self.presentationData.strings.Common_Search
|
||||
self.contentNode = .chats(ChatListNode(context: context, groupId: .root, previewing: false, controlsHistoryPreload: false, mode: .peers(filter: [.excludeSavedMessages], isSelecting: true), theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true))
|
||||
} else {
|
||||
self.contentNode = .contacts(ContactListNode(context: context, presentation: .single(.natural(options: options, includeChatList: includeChatList)), filters: filters, selectionState: ContactListNodeGroupSelectionState()))
|
||||
}
|
||||
|
||||
self.tokenListNode = EditableTokenListNode(theme: EditableTokenListNodeTheme(backgroundColor: self.presentationData.theme.rootController.navigationBar.backgroundColor, separatorColor: self.presentationData.theme.rootController.navigationBar.separatorColor, placeholderTextColor: self.presentationData.theme.list.itemPlaceholderTextColor, primaryTextColor: self.presentationData.theme.list.itemPrimaryTextColor, selectedTextColor: self.presentationData.theme.list.itemCheckColors.foregroundColor, selectedBackgroundColor: self.presentationData.theme.list.itemCheckColors.fillColor, accentColor: self.presentationData.theme.list.itemAccentColor, keyboardColor: self.presentationData.theme.rootController.keyboardColor), placeholder: placeholder)
|
||||
|
||||
super.init()
|
||||
@ -78,11 +99,18 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
||||
|
||||
self.backgroundColor = self.presentationData.theme.chatList.backgroundColor
|
||||
|
||||
self.addSubnode(self.contactListNode)
|
||||
self.addSubnode(self.contentNode.node)
|
||||
self.addSubnode(self.tokenListNode)
|
||||
|
||||
self.contactListNode.openPeer = { [weak self] peer in
|
||||
self?.openPeer?(peer)
|
||||
switch self.contentNode {
|
||||
case let .contacts(contactsNode):
|
||||
contactsNode.openPeer = { [weak self] peer in
|
||||
self?.openPeer?(peer)
|
||||
}
|
||||
case let .chats(chatsNode):
|
||||
chatsNode.peerSelected = { [weak self] peer, _, _ in
|
||||
self?.openPeer?(.peer(peer: peer, isGlobal: false, participantCount: nil))
|
||||
}
|
||||
}
|
||||
|
||||
let searchText = ValuePromise<String>()
|
||||
@ -102,9 +130,14 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
||||
} else {
|
||||
if strongSelf.searchResultsNode == nil {
|
||||
var selectionState: ContactListNodeGroupSelectionState?
|
||||
strongSelf.contactListNode.updateSelectionState { state in
|
||||
selectionState = state
|
||||
return state
|
||||
switch strongSelf.contentNode {
|
||||
case let .contacts(contactsNode):
|
||||
contactsNode.updateSelectionState { state in
|
||||
selectionState = state
|
||||
return state
|
||||
}
|
||||
case .chats:
|
||||
break
|
||||
}
|
||||
var searchChatList = false
|
||||
var searchGroups = false
|
||||
@ -113,6 +146,10 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
||||
searchChatList = peerSelection.searchChatList
|
||||
searchGroups = peerSelection.searchGroups
|
||||
searchChannels = peerSelection.searchChannels
|
||||
} else if case .chatSelection = mode {
|
||||
searchChatList = true
|
||||
searchGroups = true
|
||||
searchChannels = true
|
||||
}
|
||||
let searchResultsNode = ContactListNode(context: context, presentation: .single(.search(signal: searchText.get(), searchChatList: searchChatList, searchDeviceContacts: false, searchGroups: searchGroups, searchChannels: searchChannels)), filters: filters, selectionState: selectionState)
|
||||
searchResultsNode.openPeer = { peer in
|
||||
@ -136,7 +173,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
||||
|
||||
strongSelf.searchResultsReadyDisposable.set((searchResultsNode.ready |> deliverOnMainQueue).start(next: { _ in
|
||||
if let strongSelf = self, let searchResultsNode = strongSelf.searchResultsNode {
|
||||
strongSelf.insertSubnode(searchResultsNode, aboveSubnode: strongSelf.contactListNode)
|
||||
strongSelf.insertSubnode(searchResultsNode, aboveSubnode: strongSelf.contentNode.node)
|
||||
}
|
||||
}))
|
||||
}
|
||||
@ -167,6 +204,15 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
||||
self.backgroundColor = self.presentationData.theme.chatList.backgroundColor
|
||||
}
|
||||
|
||||
func scrollToTop() {
|
||||
switch self.contentNode {
|
||||
case let .contacts(contactsNode):
|
||||
contactsNode.scrollToTop()
|
||||
case let .chats(chatsNode):
|
||||
chatsNode.scrollToPosition(.top)
|
||||
}
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, actualNavigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
self.containerLayout = (layout, navigationBarHeight, actualNavigationBarHeight)
|
||||
|
||||
@ -183,8 +229,15 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
||||
insets.top += tokenListHeight
|
||||
headerInsets.top += tokenListHeight
|
||||
|
||||
self.contactListNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: insets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), headerInsets: headerInsets, transition: transition)
|
||||
self.contactListNode.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||
switch self.contentNode {
|
||||
case let .contacts(contactsNode):
|
||||
contactsNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: insets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), headerInsets: headerInsets, transition: transition)
|
||||
case let .chats(chatsNode):
|
||||
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
|
||||
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, headerInsets: headerInsets, duration: duration, curve: curve)
|
||||
chatsNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets)
|
||||
}
|
||||
self.contentNode.node.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||
|
||||
if let searchResultsNode = self.searchResultsNode {
|
||||
searchResultsNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: insets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), headerInsets: headerInsets, transition: transition)
|
||||
|
||||
@ -509,7 +509,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] in
|
||||
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] _ in
|
||||
guard let strongSelf = self, let currentPaneKey = strongSelf.currentPaneKey, let availablePanes = strongSelf.currentParams?.data?.availablePanes, let index = availablePanes.index(of: currentPaneKey) else {
|
||||
return []
|
||||
}
|
||||
|
||||
@ -85,7 +85,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
self.segmentedControlNode = nil
|
||||
}
|
||||
|
||||
self.chatListNode = ChatListNode(context: context, groupId: .root, previewing: false, controlsHistoryPreload: false, mode: .peers(filter: filter), theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: presentationData.disableAnimations)
|
||||
self.chatListNode = ChatListNode(context: context, groupId: .root, previewing: false, controlsHistoryPreload: false, mode: .peers(filter: filter, isSelecting: false), theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: presentationData.disableAnimations)
|
||||
|
||||
super.init()
|
||||
|
||||
@ -99,8 +99,8 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
self?.requestActivateSearch?()
|
||||
}
|
||||
|
||||
self.chatListNode.peerSelected = { [weak self] peerId, _, _ in
|
||||
self?.requestOpenPeer?(peerId)
|
||||
self.chatListNode.peerSelected = { [weak self] peer, _, _ in
|
||||
self?.requestOpenPeer?(peer.id)
|
||||
}
|
||||
|
||||
self.chatListNode.disabledPeerSelected = { [weak self] peer in
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -448,12 +448,12 @@ public final class WalletStrings: Equatable {
|
||||
public var Wallet_SecureStorageReset_Title: String { return self._s[218]! }
|
||||
public var Wallet_Receive_CommentHeader: String { return self._s[219]! }
|
||||
public var Wallet_Info_ReceiveGrams: String { return self._s[220]! }
|
||||
public func Wallet_Updated_HoursAgo(_ value: Int32) -> String {
|
||||
public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String {
|
||||
let form = getPluralizationForm(self.lc, value)
|
||||
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
|
||||
return String(format: self._ps[0 * 6 + Int(form.rawValue)]!, stringValue)
|
||||
}
|
||||
public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String {
|
||||
public func Wallet_Updated_HoursAgo(_ value: Int32) -> String {
|
||||
let form = getPluralizationForm(self.lc, value)
|
||||
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
|
||||
return String(format: self._ps[1 * 6 + Int(form.rawValue)]!, stringValue)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user