mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-08 19:10:53 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
5d505ff41a
1
.bazelrc
1
.bazelrc
@ -1,4 +1,5 @@
|
||||
build --experimental_guard_against_concurrent_changes
|
||||
build --action_env=ZERO_AR_DATE=1
|
||||
|
||||
build --strategy=Genrule=local
|
||||
build --apple_platform_type=ios
|
||||
|
@ -2212,6 +2212,7 @@ Unused sets are archived when you add more.";
|
||||
"DialogList.Pin" = "Pin";
|
||||
"DialogList.Unpin" = "Unpin";
|
||||
"DialogList.PinLimitError" = "Sorry, you can pin no more than %@ chats to the top.";
|
||||
"DialogList.UnknownPinLimitError" = "Sorry, you can't pin any more chats to the top.";
|
||||
|
||||
"Conversation.DeleteMessagesForMe" = "Delete for me";
|
||||
"Conversation.DeleteMessagesFor" = "Delete for me and %@";
|
||||
@ -5349,7 +5350,8 @@ Any member of this group will be able to see messages in the channel.";
|
||||
"External.OpenIn" = "Open in %@";
|
||||
|
||||
"ChatList.EmptyChatList" = "You have no\nconversations yet.";
|
||||
"ChatList.EmptyChatFilterList" = "No chats currently\nmatch this filter.";
|
||||
"ChatList.EmptyChatListFilterTitle" = "Folder is empty.";
|
||||
"ChatList.EmptyChatListFilterText" = "No chats currently match this folder.";
|
||||
|
||||
"ChatList.EmptyChatListNewMessage" = "New Message";
|
||||
"ChatList.EmptyChatListEditFilter" = "Edit Folder";
|
||||
@ -5456,3 +5458,19 @@ Any member of this group will be able to see messages in the channel.";
|
||||
"ChatListFolder.DiscardCancel" = "No";
|
||||
"ChatListFolder.IncludeChatsTitle" = "Include Chats";
|
||||
"ChatListFolder.ExcludeChatsTitle" = "Exclude Chats";
|
||||
|
||||
"ChatListFolderSettings.AddRecommended" = "ADD";
|
||||
|
||||
"ChatListFilter.ShowMoreChats_0" = "Show %@ More Chats";
|
||||
"ChatListFilter.ShowMoreChats_1" = "Show %@ More Chat";
|
||||
"ChatListFilter.ShowMoreChats_2" = "Show %@ More Chats";
|
||||
"ChatListFilter.ShowMoreChats_3_10" = "Show %@ More Chats";
|
||||
"ChatListFilter.ShowMoreChats_many" = "Show %@ More Chats";
|
||||
"ChatListFilter.ShowMoreChats_any" = "Show %@ More Chats";
|
||||
|
||||
"SetupUsername.ChangeNameWarningChannel" = "Warning, if you change the name of your channel, it will loose its verified status. You will need to send a new application to @verification_bot";
|
||||
"SetupUsername.ChangeNameWarningGroup" = "Warning, if you change the name of your group, it will loose its verified status. You will need to send a new application to @verification_bot";
|
||||
"SetupUsername.ChangeLinkWarningChannel" = "Warning, if you change the short link to your channel, it will loose its verified status. You will need to send a new application to @verification_bot";
|
||||
"SetupUsername.ChangeLinkWarningGroup" = "Warning, if you change the short link to your group, it will loose its verified status. You will need to send a new application to @verification_bot";
|
||||
|
||||
"MuteFor.Forever" = "Mute Forever";
|
||||
|
@ -40,7 +40,7 @@ func archiveContextMenuItems(context: AccountContext, groupId: PeerGroupId, chat
|
||||
}
|
||||
|
||||
enum ChatContextMenuSource {
|
||||
case chatList(isFilter: Bool)
|
||||
case chatList(filter: ChatListFilter?)
|
||||
case search(ChatListSearchContextActionSource)
|
||||
}
|
||||
|
||||
@ -146,17 +146,24 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, source: ChatC
|
||||
})))
|
||||
}
|
||||
|
||||
if case .chatList(false) = source {
|
||||
let isPinned = index.pinningIndex != nil
|
||||
if case let .chatList(filter) = source {
|
||||
let location: TogglePeerChatPinnedLocation
|
||||
if let filter = filter {
|
||||
location = .filter(filter.id)
|
||||
} else {
|
||||
location = .group(group)
|
||||
}
|
||||
|
||||
let isPinned = getPinnedItemIds(transaction: transaction, location: location).contains(.peer(peerId))
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: isPinned ? strings.ChatList_Context_Unpin : strings.ChatList_Context_Pin, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isPinned ? "Chat/Context Menu/Unpin" : "Chat/Context Menu/Pin"), color: theme.contextMenu.primaryColor) }, action: { _, f in
|
||||
let _ = (toggleItemPinned(postbox: context.account.postbox, groupId: group, itemId: .peer(peerId))
|
||||
let _ = (toggleItemPinned(postbox: context.account.postbox, location: location, itemId: .peer(peerId))
|
||||
|> deliverOnMainQueue).start(next: { result in
|
||||
switch result {
|
||||
case .done:
|
||||
break
|
||||
case let .limitExceeded(maxCount):
|
||||
case .limitExceeded:
|
||||
break
|
||||
//strongSelf.presentAlert?(strongSelf.currentState.presentationData.strings.DialogList_PinLimitError("\(maxCount)").0)
|
||||
}
|
||||
f(.default)
|
||||
})
|
||||
@ -177,7 +184,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, source: ChatC
|
||||
}
|
||||
} else {
|
||||
if case .search = source {
|
||||
if let channel = peer as? TelegramChannel {
|
||||
if let _ = peer as? TelegramChannel {
|
||||
items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_JoinChannel, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor) }, action: { _, f in
|
||||
var createSignal = context.peerChannelMemberCategoriesContextsManager.join(account: context.account, peerId: peerId)
|
||||
var cancelImpl: (() -> Void)?
|
||||
|
@ -143,7 +143,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
private var searchContentNode: NavigationBarSearchContentNode?
|
||||
|
||||
private let tabContainerNode: ChatListFilterTabContainerNode
|
||||
private var tabContainerData: [ChatListFilterTabEntry]?
|
||||
private var tabContainerData: ([ChatListFilterTabEntry], Bool)?
|
||||
|
||||
public override func updateNavigationCustomData(_ data: Any?, progress: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
if self.isNodeLoaded {
|
||||
@ -169,7 +169,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
|
||||
super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), mediaAccessoryPanelVisibility: .always, locationBroadcastPanelSource: .summary)
|
||||
|
||||
self.tabBarItemContextActionType = .whenActive
|
||||
self.tabBarItemContextActionType = .always
|
||||
|
||||
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
|
||||
|
||||
@ -243,7 +243,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
strongSelf.chatListDisplayNode.containerNode.currentItemNode.scrollToPosition(.top)
|
||||
case let .known(offset):
|
||||
if offset <= navigationBarSearchContentHeight + 1.0 && strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter != nil {
|
||||
strongSelf.tabContainerNode.tabSelected?(.all)
|
||||
strongSelf.selectTab(id: .all)
|
||||
} else {
|
||||
if let searchContentNode = strongSelf.searchContentNode {
|
||||
searchContentNode.updateExpansionProgress(1.0, animated: true)
|
||||
@ -279,7 +279,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
passcode,
|
||||
self.chatListDisplayNode.containerNode.currentItemState,
|
||||
self.isReorderingTabsValue.get()
|
||||
).start(next: { [weak self] networkState, proxy, passcode, state, isReorderingTabs in
|
||||
).start(next: { [weak self] networkState, proxy, passcode, stateAndFilterId, isReorderingTabs in
|
||||
if let strongSelf = self {
|
||||
let defaultTitle: String
|
||||
if strongSelf.groupId == .root {
|
||||
@ -287,12 +287,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
} else {
|
||||
defaultTitle = strongSelf.presentationData.strings.ChatList_ArchivedChatsTitle
|
||||
}
|
||||
if state.editing {
|
||||
if stateAndFilterId.state.editing {
|
||||
if strongSelf.groupId == .root {
|
||||
strongSelf.navigationItem.rightBarButtonItem = nil
|
||||
}
|
||||
|
||||
let title = !state.selectedPeerIds.isEmpty ? strongSelf.presentationData.strings.ChatList_SelectedChats(Int32(state.selectedPeerIds.count)) : defaultTitle
|
||||
let title = !stateAndFilterId.state.selectedPeerIds.isEmpty ? strongSelf.presentationData.strings.ChatList_SelectedChats(Int32(stateAndFilterId.state.selectedPeerIds.count)) : defaultTitle
|
||||
strongSelf.titleView.title = NetworkStatusTitle(text: title, activity: false, hasProxy: false, connectsViaProxy: false, isPasscodeSet: false, isManuallyLocked: false)
|
||||
} else if isReorderingTabs {
|
||||
if strongSelf.groupId == .root {
|
||||
@ -334,7 +334,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
strongSelf.navigationItem.leftBarButtonItem = leftBarButtonItem
|
||||
} else {
|
||||
let editItem: UIBarButtonItem
|
||||
if state.editing {
|
||||
if stateAndFilterId.state.editing {
|
||||
editItem = UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(strongSelf.donePressed))
|
||||
editItem.accessibilityLabel = strongSelf.presentationData.strings.Common_Done
|
||||
} else {
|
||||
@ -458,158 +458,13 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
}
|
||||
if force {
|
||||
strongSelf.tabContainerNode.cancelAnimations()
|
||||
strongSelf.chatListDisplayNode.inlineTabContainerNode.cancelAnimations()
|
||||
}
|
||||
strongSelf.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: tabContainerData, selectedFilter: filter, isReordering: strongSelf.chatListDisplayNode.isReorderingFilters || strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing, isEditing: false, transitionFraction: fraction, presentationData: strongSelf.presentationData, transition: transition)
|
||||
strongSelf.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: tabContainerData.0, selectedFilter: filter, isReordering: strongSelf.chatListDisplayNode.isReorderingFilters || strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing, isEditing: false, transitionFraction: fraction, presentationData: strongSelf.presentationData, transition: transition)
|
||||
strongSelf.chatListDisplayNode.inlineTabContainerNode.update(size: CGSize(width: layout.size.width, height: 40.0), sideInset: layout.safeInsets.left, filters: tabContainerData.0, selectedFilter: filter, isReordering: strongSelf.chatListDisplayNode.isReorderingFilters || strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing, isEditing: false, transitionFraction: fraction, presentationData: strongSelf.presentationData, transition: transition)
|
||||
}
|
||||
self.reloadFilters()
|
||||
}
|
||||
|
||||
self.tabContainerNode.tabSelected = { [weak self] id in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let _ = (currentChatListFilters(postbox: strongSelf.context.account.postbox)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] filters in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let updatedFilter: ChatListFilter?
|
||||
switch id {
|
||||
case .all:
|
||||
updatedFilter = nil
|
||||
case let .filter(id):
|
||||
var found = false
|
||||
var foundValue: ChatListFilter?
|
||||
for filter in filters {
|
||||
if filter.id == id {
|
||||
foundValue = filter
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if found {
|
||||
updatedFilter = foundValue
|
||||
} else {
|
||||
updatedFilter = nil
|
||||
}
|
||||
}
|
||||
strongSelf.chatListDisplayNode.containerNode.switchToFilter(id: updatedFilter.flatMap { .filter($0.id) } ?? .all)
|
||||
})
|
||||
}
|
||||
|
||||
self.tabContainerNode.tabRequestedDeletion = { [weak self] id in
|
||||
if case let .filter(id) = id {
|
||||
self?.askForFilterRemoval(id: id)
|
||||
}
|
||||
}
|
||||
|
||||
self.tabContainerNode.addFilter = { [weak self] in
|
||||
self?.openFilterSettings()
|
||||
}
|
||||
|
||||
self.tabContainerNode.contextGesture = { [weak self] id, sourceNode, gesture in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let _ = (currentChatListFilters(postbox: strongSelf.context.account.postbox)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] filters in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
var items: [ContextMenuItem] = []
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChatList_EditFolder, 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 {
|
||||
return
|
||||
}
|
||||
let _ = (currentChatListFilters(postbox: strongSelf.context.account.postbox)
|
||||
|> 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 !found {
|
||||
f(.default)
|
||||
}
|
||||
})
|
||||
})
|
||||
})))
|
||||
if let filter = filters.first(where: { $0.id == id }), filter.data.includePeers.count < 100 {
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChatList_AddChatsToFolder, 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 _ = (currentChatListFilters(postbox: strongSelf.context.account.postbox)
|
||||
|> 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
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
f(.default)
|
||||
}
|
||||
})
|
||||
})
|
||||
})))
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChatList_RemoveFolder, 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
|
||||
}
|
||||
strongSelf.askForFilterRemoval(id: id)
|
||||
})
|
||||
})))
|
||||
|
||||
if filters.count > 1 {
|
||||
items.append(.separator)
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChatList_ReorderTabs, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { c, f in
|
||||
//f(.default)
|
||||
c.dismiss(completion: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.chatListDisplayNode.isReorderingFilters = true
|
||||
strongSelf.isReorderingTabsValue.set(true)
|
||||
strongSelf.searchContentNode?.setIsEnabled(false, animated: true)
|
||||
if let layout = strongSelf.validLayout {
|
||||
strongSelf.updateLayout(layout: layout, transition: .animated(duration: 0.2, curve: .easeInOut))
|
||||
}
|
||||
})
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
@ -666,7 +521,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData))
|
||||
|
||||
if let layout = self.validLayout {
|
||||
self.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData ?? [], selectedFilter: self.chatListDisplayNode.containerNode.currentItemFilter, isReordering: self.chatListDisplayNode.isReorderingFilters || self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing, isEditing: false, transitionFraction: self.chatListDisplayNode.containerNode.transitionFraction, presentationData: self.presentationData, transition: .immediate)
|
||||
self.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.chatListDisplayNode.containerNode.currentItemFilter, isReordering: self.chatListDisplayNode.isReorderingFilters || self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing, isEditing: false, transitionFraction: self.chatListDisplayNode.containerNode.transitionFraction, presentationData: self.presentationData, transition: .immediate)
|
||||
self.chatListDisplayNode.inlineTabContainerNode.update(size: CGSize(width: layout.size.width, height: 40.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.chatListDisplayNode.containerNode.currentItemFilter, isReordering: self.chatListDisplayNode.isReorderingFilters || self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing, isEditing: false, transitionFraction: self.chatListDisplayNode.containerNode.transitionFraction, presentationData: self.presentationData, transition: .immediate)
|
||||
}
|
||||
|
||||
if self.isNodeLoaded {
|
||||
@ -923,7 +779,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
case let .peer(peer):
|
||||
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(peer.peer.peerId), subject: nil, botStart: nil, mode: .standard(previewing: true))
|
||||
chatController.canReadHistory.set(false)
|
||||
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: chatContextMenuItems(context: strongSelf.context, peerId: peer.peer.peerId, source: .chatList(isFilter: strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter != nil), chatListController: strongSelf), reactionItems: [], gesture: gesture)
|
||||
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: chatContextMenuItems(context: strongSelf.context, peerId: peer.peer.peerId, source: .chatList(filter: strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter), chatListController: strongSelf), reactionItems: [], gesture: gesture)
|
||||
strongSelf.presentInGlobalOverlay(contextController)
|
||||
}
|
||||
}
|
||||
@ -942,16 +798,24 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
|
||||
let context = self.context
|
||||
let peerIdsAndOptions: Signal<(ChatListSelectionOptions, Set<PeerId>)?, NoError> = self.chatListDisplayNode.containerNode.currentItemState
|
||||
|> map { state -> Set<PeerId>? in
|
||||
|> map { state, filterId -> (Set<PeerId>, Int32?)? in
|
||||
if !state.editing {
|
||||
return nil
|
||||
}
|
||||
return state.selectedPeerIds
|
||||
return (state.selectedPeerIds, filterId)
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|> mapToSignal { selectedPeerIds -> Signal<(ChatListSelectionOptions, Set<PeerId>)?, NoError> in
|
||||
if let selectedPeerIds = selectedPeerIds {
|
||||
return chatListSelectionOptions(postbox: context.account.postbox, peerIds: selectedPeerIds)
|
||||
|> distinctUntilChanged(isEqual: { lhs, rhs in
|
||||
if lhs?.0 != rhs?.0 {
|
||||
return false
|
||||
}
|
||||
if lhs?.1 != rhs?.1 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|> mapToSignal { selectedPeerIdsAndFilterId -> Signal<(ChatListSelectionOptions, Set<PeerId>)?, NoError> in
|
||||
if let (selectedPeerIds, filterId) = selectedPeerIdsAndFilterId {
|
||||
return chatListSelectionOptions(postbox: context.account.postbox, peerIds: selectedPeerIds, filterId: filterId)
|
||||
|> map { options -> (ChatListSelectionOptions, Set<PeerId>)? in
|
||||
return (options, selectedPeerIds)
|
||||
}
|
||||
@ -1007,6 +871,147 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
strongSelf.setToolbar(toolbar, transition: .animated(duration: 0.3, curve: .easeInOut))
|
||||
}))
|
||||
|
||||
self.tabContainerNode.tabSelected = { [weak self] id in
|
||||
self?.selectTab(id: id)
|
||||
}
|
||||
self.chatListDisplayNode.inlineTabContainerNode.tabSelected = { [weak self] id in
|
||||
self?.selectTab(id: id)
|
||||
}
|
||||
|
||||
self.tabContainerNode.tabRequestedDeletion = { [weak self] id in
|
||||
if case let .filter(id) = id {
|
||||
self?.askForFilterRemoval(id: id)
|
||||
}
|
||||
}
|
||||
self.chatListDisplayNode.inlineTabContainerNode.tabRequestedDeletion = { [weak self] id in
|
||||
if case let .filter(id) = id {
|
||||
self?.askForFilterRemoval(id: id)
|
||||
}
|
||||
}
|
||||
|
||||
let tabContextGesture: (Int32?, ContextExtractedContentContainingNode, ContextGesture, Bool) -> Void = { [weak self] id, sourceNode, gesture, keepInPlace in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let _ = (currentChatListFilters(postbox: strongSelf.context.account.postbox)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] filters in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
var items: [ContextMenuItem] = []
|
||||
if let id = id {
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChatList_EditFolder, 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 {
|
||||
return
|
||||
}
|
||||
let _ = (currentChatListFilters(postbox: strongSelf.context.account.postbox)
|
||||
|> 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 !found {
|
||||
f(.default)
|
||||
}
|
||||
})
|
||||
})
|
||||
})))
|
||||
if let filter = filters.first(where: { $0.id == id }), filter.data.includePeers.peers.count < 100 {
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChatList_AddChatsToFolder, 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 _ = (currentChatListFilters(postbox: strongSelf.context.account.postbox)
|
||||
|> 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
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
f(.default)
|
||||
}
|
||||
})
|
||||
})
|
||||
})))
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChatList_RemoveFolder, 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
|
||||
}
|
||||
strongSelf.askForFilterRemoval(id: id)
|
||||
})
|
||||
})))
|
||||
}
|
||||
} else {
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChatList_EditFolders, 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 {
|
||||
return
|
||||
}
|
||||
strongSelf.openFilterSettings()
|
||||
})
|
||||
})))
|
||||
}
|
||||
|
||||
if filters.count > 1 {
|
||||
items.append(.separator)
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChatList_ReorderTabs, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { c, f in
|
||||
c.dismiss(completion: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.chatListDisplayNode.isReorderingFilters = true
|
||||
strongSelf.isReorderingTabsValue.set(true)
|
||||
strongSelf.searchContentNode?.setIsEnabled(false, animated: true)
|
||||
(strongSelf.parent as? TabBarController)?.updateIsTabBarEnabled(false, transition: .animated(duration: 0.2, curve: .easeInOut))
|
||||
if let layout = strongSelf.validLayout {
|
||||
strongSelf.updateLayout(layout: layout, transition: .animated(duration: 0.2, curve: .easeInOut))
|
||||
}
|
||||
})
|
||||
})))
|
||||
}
|
||||
|
||||
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatListHeaderBarContextExtractedContentSource(controller: strongSelf, sourceNode: sourceNode, keepInPlace: keepInPlace)), items: .single(items), reactionItems: [], recognizer: nil, gesture: gesture)
|
||||
strongSelf.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller)
|
||||
})
|
||||
}
|
||||
self.tabContainerNode.contextGesture = { id, sourceNode, gesture in
|
||||
tabContextGesture(id, sourceNode, gesture, false)
|
||||
}
|
||||
self.chatListDisplayNode.inlineTabContainerNode.contextGesture = { id, sourceNode, gesture in
|
||||
tabContextGesture(id, sourceNode, gesture, true)
|
||||
}
|
||||
|
||||
self.ready.set(self.chatListDisplayNode.containerNode.ready)
|
||||
|
||||
self.displayNodeDidLoad()
|
||||
@ -1130,52 +1135,72 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
})
|
||||
}
|
||||
|
||||
if !self.processedFeaturedFilters {
|
||||
self.featuredFiltersDisposable.set((
|
||||
self.context.account.postbox.transaction { transaction -> ChatListFiltersFeaturedState? in
|
||||
return transaction.getPreferencesEntry(key: PreferencesKeys.chatListFiltersFeaturedState) as? ChatListFiltersFeaturedState
|
||||
self.chatListDisplayNode.containerNode.didBeginSelectingChats = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if !strongSelf.chatListDisplayNode.didBeginSelectingChatsWhileEditing {
|
||||
strongSelf.chatListDisplayNode.didBeginSelectingChatsWhileEditing = true
|
||||
if let layout = strongSelf.validLayout {
|
||||
strongSelf.updateLayout(layout: layout, transition: .animated(duration: 0.2, curve: .easeInOut))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !self.processedFeaturedFilters {
|
||||
let initializedFeatured = self.context.account.postbox.preferencesView(keys: [
|
||||
PreferencesKeys.chatListFiltersFeaturedState
|
||||
])
|
||||
|> mapToSignal { view -> Signal<Bool, NoError> in
|
||||
if let entry = view.values[PreferencesKeys.chatListFiltersFeaturedState] as? ChatListFiltersFeaturedState {
|
||||
return .single(!entry.filters.isEmpty && !entry.isSeen)
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
|> take(1)
|
||||
|
||||
let initializedFilters = updatedChatListFiltersInfo(postbox: self.context.account.postbox)
|
||||
|> mapToSignal { (filters, isInitialized) -> Signal<Bool, NoError> in
|
||||
if isInitialized {
|
||||
return .single(!filters.isEmpty)
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
|> take(1)
|
||||
|
||||
self.featuredFiltersDisposable.set((
|
||||
combineLatest(initializedFeatured, initializedFilters)
|
||||
|> take(1)
|
||||
|> delay(1.0, queue: .mainQueue())
|
||||
|> deliverOnMainQueue
|
||||
).start(next: { [weak self] featuredState in
|
||||
guard let strongSelf = self, let featuredState = featuredState else {
|
||||
).start(next: { [weak self] hasFeatured, hasFilters in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = (currentChatListFilters(postbox: strongSelf.context.account.postbox)
|
||||
|> deliverOnMainQueue).start(next: { filters in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.processedFeaturedFilters = true
|
||||
if !featuredState.isSeen && !featuredState.filters.isEmpty {
|
||||
let _ = (currentChatListFilters(postbox: strongSelf.context.account.postbox)
|
||||
|> deliverOnMainQueue).start(next: { filters in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
strongSelf.processedFeaturedFilters = true
|
||||
if hasFeatured {
|
||||
if let _ = strongSelf.validLayout, let parentController = strongSelf.parent as? TabBarController, let sourceFrame = parentController.frameForControllerTab(controller: strongSelf) {
|
||||
let absoluteFrame = sourceFrame
|
||||
let text: String
|
||||
if hasFilters {
|
||||
text = strongSelf.presentationData.strings.ChatList_TabIconFoldersTooltipNonEmptyFolders
|
||||
} else {
|
||||
text = strongSelf.presentationData.strings.ChatList_TabIconFoldersTooltipEmptyFolders
|
||||
}
|
||||
parentController.present(TooltipScreen(text: text, location: CGPoint(x: absoluteFrame.midX - 14.0, y: absoluteFrame.minY - 8.0), shouldDismissOnTouch: { point in
|
||||
guard let strongSelf = self, let parentController = strongSelf.parent as? TabBarController else {
|
||||
return true
|
||||
}
|
||||
let hasFilters = !filters.isEmpty
|
||||
if let _ = strongSelf.validLayout, let parentController = strongSelf.parent as? TabBarController, let sourceFrame = parentController.frameForControllerTab(controller: strongSelf) {
|
||||
let absoluteFrame = sourceFrame
|
||||
let text: String
|
||||
if hasFilters {
|
||||
text = strongSelf.presentationData.strings.ChatList_TabIconFoldersTooltipNonEmptyFolders
|
||||
} else {
|
||||
text = strongSelf.presentationData.strings.ChatList_TabIconFoldersTooltipEmptyFolders
|
||||
}
|
||||
parentController.present(TooltipScreen(text: text, location: CGPoint(x: absoluteFrame.midX - 14.0, y: absoluteFrame.minY - 8.0), shouldDismissOnTouch: { point in
|
||||
guard let strongSelf = self, let parentController = strongSelf.parent as? TabBarController else {
|
||||
return true
|
||||
}
|
||||
if parentController.isPointInsideContentArea(point: point) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}), in: .current)
|
||||
if parentController.isPointInsideContentArea(point: point) {
|
||||
return false
|
||||
}
|
||||
})
|
||||
return true
|
||||
}), in: .current)
|
||||
}
|
||||
})
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
@ -1229,7 +1254,13 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
}
|
||||
|
||||
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, isReordering: self.chatListDisplayNode.isReorderingFilters || self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing, isEditing: false, transitionFraction: self.chatListDisplayNode.containerNode.transitionFraction, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring))
|
||||
self.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.chatListDisplayNode.containerNode.currentItemFilter, isReordering: self.chatListDisplayNode.isReorderingFilters || (self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !self.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: false, transitionFraction: self.chatListDisplayNode.containerNode.transitionFraction, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring))
|
||||
if let tabContainerData = self.tabContainerData {
|
||||
self.chatListDisplayNode.inlineTabContainerNode.isHidden = !tabContainerData.1 || tabContainerData.0.count <= 1
|
||||
} else {
|
||||
self.chatListDisplayNode.inlineTabContainerNode.isHidden = true
|
||||
}
|
||||
self.chatListDisplayNode.inlineTabContainerNode.update(size: CGSize(width: layout.size.width, height: 40.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.chatListDisplayNode.containerNode.currentItemFilter, isReordering: self.chatListDisplayNode.isReorderingFilters || (self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !self.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: false, transitionFraction: self.chatListDisplayNode.containerNode.transitionFraction, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring))
|
||||
|
||||
self.chatListDisplayNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationInsetHeight, visualNavigationHeight: self.visualNavigationInsetHeight, cleanNavigationBarHeight: self.cleanNavigationHeight, transition: transition)
|
||||
}
|
||||
@ -1250,6 +1281,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
}
|
||||
self.searchContentNode?.setIsEnabled(false, animated: true)
|
||||
|
||||
self.chatListDisplayNode.didBeginSelectingChatsWhileEditing = false
|
||||
self.chatListDisplayNode.containerNode.updateState { state in
|
||||
var state = state
|
||||
state.editing = true
|
||||
@ -1274,6 +1306,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.didBeginSelectingChatsWhileEditing = false
|
||||
self.chatListDisplayNode.containerNode.updateState { state in
|
||||
var state = state
|
||||
state.editing = false
|
||||
@ -1288,7 +1321,26 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
}
|
||||
|
||||
@objc private func reorderingDonePressed() {
|
||||
if let reorderedFilterIds = self.tabContainerNode.reorderedFilterIds {
|
||||
guard let defaultFilters = self.tabContainerData else {
|
||||
return
|
||||
}
|
||||
let defaultFilterIds = defaultFilters.0.compactMap { entry -> Int32? in
|
||||
switch entry {
|
||||
case .all:
|
||||
return nil
|
||||
case let .filter(filter):
|
||||
return filter.id
|
||||
}
|
||||
}
|
||||
|
||||
var reorderedFilterIdsValue: [Int32]?
|
||||
if let reorderedFilterIds = self.chatListDisplayNode.inlineTabContainerNode.reorderedFilterIds, reorderedFilterIds != defaultFilterIds {
|
||||
reorderedFilterIdsValue = reorderedFilterIds
|
||||
} else if let reorderedFilterIds = self.tabContainerNode.reorderedFilterIds {
|
||||
reorderedFilterIdsValue = reorderedFilterIds
|
||||
}
|
||||
|
||||
if let reorderedFilterIds = reorderedFilterIdsValue {
|
||||
let _ = (updateChatListFiltersInteractively(postbox: self.context.account.postbox, { stateFilters in
|
||||
var updatedFilters: [ChatListFilter] = []
|
||||
for id in reorderedFilterIds {
|
||||
@ -1315,6 +1367,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
}
|
||||
strongSelf.chatListDisplayNode.isReorderingFilters = false
|
||||
strongSelf.isReorderingTabsValue.set(false)
|
||||
(strongSelf.parent as? TabBarController)?.updateIsTabBarEnabled(true, transition: .animated(duration: 0.2, curve: .easeInOut))
|
||||
strongSelf.searchContentNode?.setIsEnabled(true, animated: true)
|
||||
if let layout = strongSelf.validLayout {
|
||||
strongSelf.updateLayout(layout: layout, transition: .animated(duration: 0.2, curve: .easeInOut))
|
||||
@ -1328,15 +1381,24 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
let preferencesKey: PostboxViewKey = .preferences(keys: Set([
|
||||
ApplicationSpecificPreferencesKeys.chatListFilterSettings
|
||||
]))
|
||||
let filterItems = chatListFilterItems(context: self.context)
|
||||
let experimentalUISettingsKey: ValueBoxKey = ApplicationSpecificSharedDataKeys.experimentalUISettings
|
||||
let displayTabsAtBottom = self.context.sharedContext.accountManager.sharedData(keys: Set([experimentalUISettingsKey]))
|
||||
|> map { sharedData -> Bool in
|
||||
let settings: ExperimentalUISettings = sharedData.entries[experimentalUISettingsKey] as? ExperimentalUISettings ?? ExperimentalUISettings.defaultSettings
|
||||
return settings.foldersTabAtBottom
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|
||||
let filterItems = chatListFilterItems(postbox: self.context.account.postbox)
|
||||
var notifiedFirstUpdate = false
|
||||
self.filterDisposable.set((combineLatest(queue: .mainQueue(),
|
||||
context.account.postbox.combinedView(keys: [
|
||||
preferencesKey
|
||||
]),
|
||||
filterItems
|
||||
filterItems,
|
||||
displayTabsAtBottom
|
||||
)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] _, countAndFilterItems in
|
||||
|> deliverOnMainQueue).start(next: { [weak self] _, countAndFilterItems, displayTabsAtBottom in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -1354,7 +1416,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
|
||||
var wasEmpty = false
|
||||
if let tabContainerData = strongSelf.tabContainerData {
|
||||
wasEmpty = tabContainerData.count <= 1
|
||||
wasEmpty = tabContainerData.0.count <= 1 || tabContainerData.1
|
||||
} else {
|
||||
wasEmpty = true
|
||||
}
|
||||
@ -1364,10 +1426,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
resetCurrentEntry = true
|
||||
if let tabContainerData = strongSelf.tabContainerData {
|
||||
var found = false
|
||||
if let index = tabContainerData.firstIndex(where: { $0.id == selectedEntryId }) {
|
||||
if let index = tabContainerData.0.firstIndex(where: { $0.id == selectedEntryId }) {
|
||||
for i in (0 ..< index - 1).reversed() {
|
||||
if resolvedItems.contains(where: { $0.id == tabContainerData[i].id }) {
|
||||
selectedEntryId = tabContainerData[i].id
|
||||
if resolvedItems.contains(where: { $0.id == tabContainerData.0[i].id }) {
|
||||
selectedEntryId = tabContainerData.0[i].id
|
||||
found = true
|
||||
break
|
||||
}
|
||||
@ -1380,7 +1442,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
selectedEntryId = .all
|
||||
}
|
||||
}
|
||||
strongSelf.tabContainerData = resolvedItems
|
||||
strongSelf.tabContainerData = (resolvedItems, displayTabsAtBottom)
|
||||
var availableFilters: [ChatListContainerNodeFilter] = []
|
||||
availableFilters.append(.all)
|
||||
for item in items {
|
||||
@ -1388,7 +1450,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
}
|
||||
strongSelf.chatListDisplayNode.containerNode.updateAvailableFilters(availableFilters)
|
||||
|
||||
let isEmpty = resolvedItems.count <= 1
|
||||
let isEmpty = resolvedItems.count <= 1 || displayTabsAtBottom
|
||||
|
||||
if wasEmpty != isEmpty {
|
||||
strongSelf.navigationBar?.setSecondaryContentNode(isEmpty ? nil : strongSelf.tabContainerNode)
|
||||
@ -1403,6 +1465,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
(strongSelf.parent as? TabBarController)?.updateLayout()
|
||||
} else {
|
||||
strongSelf.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: resolvedItems, selectedFilter: selectedEntryId, isReordering: strongSelf.chatListDisplayNode.isReorderingFilters || strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing, isEditing: false, transitionFraction: strongSelf.chatListDisplayNode.containerNode.transitionFraction, presentationData: strongSelf.presentationData, transition: .animated(duration: 0.4, curve: .spring))
|
||||
strongSelf.chatListDisplayNode.inlineTabContainerNode.update(size: CGSize(width: layout.size.width, height: 40.0), sideInset: layout.safeInsets.left, filters: resolvedItems, selectedFilter: selectedEntryId, isReordering: strongSelf.chatListDisplayNode.isReorderingFilters || strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing, isEditing: false, transitionFraction: strongSelf.chatListDisplayNode.containerNode.transitionFraction, presentationData: strongSelf.presentationData, transition: .animated(duration: 0.4, curve: .spring))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1412,11 +1475,58 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
}
|
||||
|
||||
if resetCurrentEntry {
|
||||
strongSelf.tabContainerNode.tabSelected?(selectedEntryId)
|
||||
strongSelf.selectTab(id: selectedEntryId)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
private func selectTab(id: ChatListFilterTabEntryId) {
|
||||
if self.parent == nil {
|
||||
if let navigationController = self.context.sharedContext.mainWindow?.viewController as? NavigationController {
|
||||
for controller in navigationController.viewControllers {
|
||||
if let controller = controller as? TabBarController {
|
||||
if let index = controller.controllers.firstIndex(of: self) {
|
||||
controller.selectedIndex = index
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = (currentChatListFilters(postbox: self.context.account.postbox)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] filters in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let updatedFilter: ChatListFilter?
|
||||
switch id {
|
||||
case .all:
|
||||
updatedFilter = nil
|
||||
case let .filter(id):
|
||||
var found = false
|
||||
var foundValue: ChatListFilter?
|
||||
for filter in filters {
|
||||
if filter.id == id {
|
||||
foundValue = filter
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if found {
|
||||
updatedFilter = foundValue
|
||||
} else {
|
||||
updatedFilter = nil
|
||||
}
|
||||
}
|
||||
if strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter?.id == updatedFilter?.id {
|
||||
strongSelf.scrollToTop?()
|
||||
} else {
|
||||
strongSelf.chatListDisplayNode.containerNode.switchToFilter(id: updatedFilter.flatMap { .filter($0.id) } ?? .all)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func askForFilterRemoval(id: Int32) {
|
||||
let actionSheet = ActionSheetController(presentationData: self.presentationData)
|
||||
|
||||
@ -1686,8 +1796,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
}
|
||||
} else {
|
||||
let groupId = self.groupId
|
||||
let filterPredicate = (self.chatListDisplayNode.containerNode.currentItemNode.chatListFilter?.data).flatMap(chatListFilterPredicate)
|
||||
signal = self.context.account.postbox.transaction { transaction -> Void in
|
||||
let filterPredicate = (self.chatListDisplayNode.containerNode.currentItemNode.chatListFilter?.data).flatMap(chatListFilterPredicate)
|
||||
markAllChatsAsReadInteractively(transaction: transaction, viewTracker: context.account.viewTracker, groupId: groupId, filterPredicate: filterPredicate)
|
||||
if let filterPredicate = filterPredicate {
|
||||
for additionalGroupId in filterPredicate.includeAdditionalPeerGroupIds {
|
||||
@ -2312,15 +2422,21 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
|
||||
private func openFilterSettings() {
|
||||
self.chatListDisplayNode.containerNode.updateEnableAdjacentFilterLoading(false)
|
||||
self.push(chatListFilterPresetListController(context: self.context, mode: .modal, dismissed: { [weak self] in
|
||||
self?.chatListDisplayNode.containerNode.updateEnableAdjacentFilterLoading(true)
|
||||
}))
|
||||
if let navigationController = self.context.sharedContext.mainWindow?.viewController as? NavigationController {
|
||||
navigationController.pushViewController(chatListFilterPresetListController(context: self.context, mode: .modal, dismissed: { [weak self] in
|
||||
self?.chatListDisplayNode.containerNode.updateEnableAdjacentFilterLoading(true)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
override public func tabBarDisabledAction() {
|
||||
self.donePressed()
|
||||
}
|
||||
|
||||
override public func tabBarItemContextAction(sourceNode: ContextExtractedContentContainingNode, gesture: ContextGesture) {
|
||||
let _ = (combineLatest(queue: .mainQueue(),
|
||||
currentChatListFilters(postbox: self.context.account.postbox),
|
||||
chatListFilterItems(context: self.context)
|
||||
chatListFilterItems(postbox: self.context.account.postbox)
|
||||
|> take(1)
|
||||
)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presetList, filterItemsAndTotalCount in
|
||||
@ -2352,7 +2468,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.tabContainerNode.tabSelected?(.all)
|
||||
strongSelf.selectTab(id: .all)
|
||||
})))
|
||||
}
|
||||
|
||||
@ -2393,7 +2509,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.tabContainerNode.tabSelected?(.filter(preset.id))
|
||||
strongSelf.selectTab(id: .filter(preset.id))
|
||||
})))
|
||||
}
|
||||
}
|
||||
@ -2402,27 +2518,6 @@ 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.firstIndex(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 {
|
||||
@ -2447,15 +2542,16 @@ private final class ChatListTabBarContextExtractedContentSource: ContextExtracte
|
||||
}
|
||||
|
||||
private final class ChatListHeaderBarContextExtractedContentSource: ContextExtractedContentSource {
|
||||
let keepInPlace: Bool = false
|
||||
let keepInPlace: Bool
|
||||
let ignoreContentTouches: Bool = true
|
||||
|
||||
private let controller: ChatListController
|
||||
private let sourceNode: ContextExtractedContentContainingNode
|
||||
|
||||
init(controller: ChatListController, sourceNode: ContextExtractedContentContainingNode) {
|
||||
init(controller: ChatListController, sourceNode: ContextExtractedContentContainingNode, keepInPlace: Bool) {
|
||||
self.controller = controller
|
||||
self.sourceNode = sourceNode
|
||||
self.keepInPlace = keepInPlace
|
||||
}
|
||||
|
||||
func takeView() -> ContextControllerTakeViewInfo? {
|
||||
|
@ -200,7 +200,7 @@ private final class ChatListShimmerNode: ASDisplayNode {
|
||||
}, present: { _ in })
|
||||
|
||||
let items = (0 ..< 2).map { _ -> ChatListItem in
|
||||
return ChatListItem(presentationData: chatListPresentationData, context: context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer1, text: "Text", attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []), peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)
|
||||
return ChatListItem(presentationData: chatListPresentationData, context: context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer1, text: "Text", attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []), peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)
|
||||
}
|
||||
|
||||
var itemNodes: [ChatListItemNode] = []
|
||||
@ -341,7 +341,7 @@ private final class ChatListContainerItemNode: ASDisplayNode {
|
||||
if strongSelf.emptyShimmerEffectNode == nil {
|
||||
let emptyShimmerEffectNode = ChatListShimmerNode()
|
||||
strongSelf.emptyShimmerEffectNode = emptyShimmerEffectNode
|
||||
strongSelf.addSubnode(emptyShimmerEffectNode)
|
||||
strongSelf.insertSubnode(emptyShimmerEffectNode, belowSubnode: strongSelf.listNode)
|
||||
if let (size, insets, _) = strongSelf.validLayout, let offset = strongSelf.floatingHeaderOffset {
|
||||
strongSelf.layoutEmptyShimmerEffectNode(node: emptyShimmerEffectNode, size: size, insets: insets, verticalOffset: offset, transition: .immediate)
|
||||
}
|
||||
@ -431,8 +431,8 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
return self.currentItemNodeValue!.listNode
|
||||
}
|
||||
|
||||
private let currentItemStateValue = Promise<ChatListNodeState>()
|
||||
var currentItemState: Signal<ChatListNodeState, NoError> {
|
||||
private let currentItemStateValue = Promise<(state: ChatListNodeState, filterId: Int32?)>()
|
||||
var currentItemState: Signal<(state: ChatListNodeState, filterId: Int32?), NoError> {
|
||||
return self.currentItemStateValue.get()
|
||||
}
|
||||
|
||||
@ -455,6 +455,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
previousItemNode.listNode.contentScrollingEnded = nil
|
||||
previousItemNode.listNode.activateChatPreview = nil
|
||||
previousItemNode.listNode.addedVisibleChatsWithPeerIds = nil
|
||||
previousItemNode.listNode.didBeginSelectingChats = nil
|
||||
|
||||
previousItemNode.accessibilityElementsHidden = true
|
||||
}
|
||||
@ -497,8 +498,20 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
itemNode.listNode.addedVisibleChatsWithPeerIds = { [weak self] ids in
|
||||
self?.addedVisibleChatsWithPeerIds?(ids)
|
||||
}
|
||||
itemNode.listNode.didBeginSelectingChats = { [weak self] in
|
||||
self?.didBeginSelectingChats?()
|
||||
}
|
||||
|
||||
self.currentItemStateValue.set(itemNode.listNode.state)
|
||||
self.currentItemStateValue.set(itemNode.listNode.state |> map { state in
|
||||
let filterId: Int32?
|
||||
switch id {
|
||||
case .all:
|
||||
filterId = nil
|
||||
case let .filter(filter):
|
||||
filterId = filter
|
||||
}
|
||||
return (state, filterId)
|
||||
})
|
||||
|
||||
if self.controlsHistoryPreload {
|
||||
self.context.account.viewTracker.chatListPreloadItems.set(itemNode.listNode.preloadItems.get())
|
||||
@ -517,6 +530,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
var contentScrollingEnded: ((ListView) -> Bool)?
|
||||
var activateChatPreview: ((ChatListItem, ASDisplayNode, ContextGesture?) -> Void)?
|
||||
var addedVisibleChatsWithPeerIds: (([PeerId]) -> Void)?
|
||||
var didBeginSelectingChats: (() -> Void)?
|
||||
|
||||
init(context: AccountContext, groupId: PeerGroupId, previewing: Bool, controlsHistoryPreload: Bool, presentationData: PresentationData, filterBecameEmpty: @escaping (ChatListFilter?) -> Void, filterEmptyAction: @escaping (ChatListFilter?) -> Void) {
|
||||
self.context = context
|
||||
@ -556,6 +570,9 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
case .none, .unknown:
|
||||
break
|
||||
}
|
||||
if !strongSelf.currentItemNode.isNavigationInAFinalState {
|
||||
return []
|
||||
}
|
||||
let directions: InteractiveTransitionGestureRecognizerDirections = [.leftCenter, .rightCenter]
|
||||
return directions
|
||||
}, edgeWidth: .widthMultiplier(factor: 1.0 / 6.0, min: 22.0, max: 80.0))
|
||||
@ -782,6 +799,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.35, curve: .spring)
|
||||
self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: transition)
|
||||
self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, transition, false)
|
||||
itemNode.emptyNode?.restartAnimation()
|
||||
completion?()
|
||||
} else if self.pendingItemNode == nil {
|
||||
let itemNode = ChatListContainerItemNode(context: self.context, groupId: self.groupId, filter: self.availableFilters[index].filter, previewing: self.previewing, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: self.presentationData, becameEmpty: { [weak self] filter in
|
||||
@ -954,6 +972,7 @@ final class ChatListControllerNode: ASDisplayNode {
|
||||
private var presentationData: PresentationData
|
||||
|
||||
let containerNode: ChatListContainerNode
|
||||
let inlineTabContainerNode: ChatListFilterTabInlineContainerNode
|
||||
private var tapRecognizer: UITapGestureRecognizer?
|
||||
var navigationBar: NavigationBar?
|
||||
weak var controller: ChatListControllerImpl?
|
||||
@ -965,6 +984,7 @@ final class ChatListControllerNode: ASDisplayNode {
|
||||
private(set) var searchDisplayController: SearchDisplayController?
|
||||
|
||||
var isReorderingFilters: Bool = false
|
||||
var didBeginSelectingChatsWhileEditing: Bool = false
|
||||
var isEditing: Bool = false
|
||||
|
||||
private var containerLayout: (ContainerViewLayout, CGFloat, CGFloat, CGFloat)?
|
||||
@ -995,6 +1015,8 @@ final class ChatListControllerNode: ASDisplayNode {
|
||||
filterEmptyAction?(filter)
|
||||
})
|
||||
|
||||
self.inlineTabContainerNode = ChatListFilterTabInlineContainerNode()
|
||||
|
||||
self.controller = controller
|
||||
|
||||
super.init()
|
||||
@ -1006,6 +1028,7 @@ final class ChatListControllerNode: ASDisplayNode {
|
||||
self.backgroundColor = presentationData.theme.chatList.backgroundColor
|
||||
|
||||
self.addSubnode(self.containerNode)
|
||||
self.addSubnode(self.inlineTabContainerNode)
|
||||
|
||||
self.addSubnode(self.debugListView)
|
||||
|
||||
@ -1110,6 +1133,8 @@ final class ChatListControllerNode: ASDisplayNode {
|
||||
transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
self.containerNode.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, isReorderingFilters: self.isReorderingFilters, isEditing: self.isEditing, transition: transition)
|
||||
|
||||
transition.updateFrame(node: self.inlineTabContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - layout.intrinsicInsets.bottom - 8.0 - 40.0), size: CGSize(width: layout.size.width, height: 40.0)))
|
||||
|
||||
self.tapRecognizer?.isEnabled = self.isReorderingFilters
|
||||
|
||||
if let searchDisplayController = self.searchDisplayController {
|
||||
|
@ -9,11 +9,15 @@ import SolidRoundedButtonNode
|
||||
import ActivityIndicator
|
||||
|
||||
final class ChatListEmptyNode: ASDisplayNode {
|
||||
private let action: () -> Void
|
||||
|
||||
let isFilter: Bool
|
||||
private(set) var isLoading: Bool
|
||||
private let textNode: ImmediateTextNode
|
||||
private let descriptionNode: ImmediateTextNode
|
||||
private let animationNode: AnimatedStickerNode
|
||||
private let buttonNode: SolidRoundedButtonNode
|
||||
private let buttonTextNode: ImmediateTextNode
|
||||
private let buttonNode: HighlightTrackingButtonNode
|
||||
private let activityIndicator: ActivityIndicator
|
||||
|
||||
private var animationSize: CGSize = CGSize()
|
||||
@ -21,6 +25,7 @@ final class ChatListEmptyNode: ASDisplayNode {
|
||||
private var validLayout: CGSize?
|
||||
|
||||
init(isFilter: Bool, isLoading: Bool, theme: PresentationTheme, strings: PresentationStrings, action: @escaping () -> Void) {
|
||||
self.action = action
|
||||
self.isFilter = isFilter
|
||||
self.isLoading = isLoading
|
||||
|
||||
@ -33,7 +38,17 @@ final class ChatListEmptyNode: ASDisplayNode {
|
||||
self.textNode.textAlignment = .center
|
||||
self.textNode.lineSpacing = 0.1
|
||||
|
||||
self.buttonNode = SolidRoundedButtonNode(title: isFilter ? strings.ChatList_EmptyChatListEditFilter : strings.ChatList_EmptyChatListNewMessage, theme: SolidRoundedButtonTheme(backgroundColor: theme.list.itemCheckColors.fillColor, foregroundColor: theme.list.itemCheckColors.foregroundColor), height: 50.0, cornerRadius: 10.0, gloss: false)
|
||||
self.descriptionNode = ImmediateTextNode()
|
||||
self.descriptionNode.displaysAsynchronously = false
|
||||
self.descriptionNode.maximumNumberOfLines = 0
|
||||
self.descriptionNode.isUserInteractionEnabled = false
|
||||
self.descriptionNode.textAlignment = .center
|
||||
self.descriptionNode.lineSpacing = 0.1
|
||||
|
||||
self.buttonNode = HighlightTrackingButtonNode()
|
||||
|
||||
self.buttonTextNode = ImmediateTextNode()
|
||||
self.buttonTextNode.displaysAsynchronously = false
|
||||
|
||||
self.activityIndicator = ActivityIndicator(type: .custom(theme.list.itemAccentColor, 22.0, 1.0, false))
|
||||
|
||||
@ -41,6 +56,8 @@ final class ChatListEmptyNode: ASDisplayNode {
|
||||
|
||||
self.addSubnode(self.animationNode)
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.descriptionNode)
|
||||
self.addSubnode(self.buttonTextNode)
|
||||
self.addSubnode(self.buttonNode)
|
||||
self.addSubnode(self.activityIndicator)
|
||||
|
||||
@ -56,16 +73,42 @@ final class ChatListEmptyNode: ASDisplayNode {
|
||||
self.animationNode.visibility = true
|
||||
}
|
||||
|
||||
self.buttonNode.pressed = {
|
||||
action()
|
||||
}
|
||||
|
||||
self.animationNode.isHidden = self.isLoading
|
||||
self.textNode.isHidden = self.isLoading
|
||||
self.descriptionNode.isHidden = self.isLoading
|
||||
self.buttonNode.isHidden = self.isLoading
|
||||
self.buttonTextNode.isHidden = self.isLoading
|
||||
self.activityIndicator.isHidden = !self.isLoading
|
||||
|
||||
self.buttonNode.hitTestSlop = UIEdgeInsets(top: -10.0, left: -10.0, bottom: -10.0, right: -10.0)
|
||||
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||
self.buttonNode.highligthedChanged = { [weak self] highlighted in
|
||||
if let strongSelf = self {
|
||||
if highlighted {
|
||||
strongSelf.buttonTextNode.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.buttonTextNode.alpha = 0.4
|
||||
} else {
|
||||
strongSelf.buttonTextNode.alpha = 1.0
|
||||
strongSelf.buttonTextNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.updateThemeAndStrings(theme: theme, strings: strings)
|
||||
|
||||
self.animationNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.animationTapGesture(_:))))
|
||||
}
|
||||
|
||||
@objc private func buttonPressed() {
|
||||
self.action()
|
||||
}
|
||||
|
||||
@objc private func animationTapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
if !self.animationNode.isPlaying {
|
||||
self.animationNode.play()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func restartAnimation() {
|
||||
@ -73,10 +116,17 @@ final class ChatListEmptyNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) {
|
||||
let string = NSMutableAttributedString(string: self.isFilter ? strings.ChatList_EmptyChatFilterList : strings.ChatList_EmptyChatList, font: Font.medium(17.0), textColor: theme.list.itemPrimaryTextColor)
|
||||
let string = NSMutableAttributedString(string: self.isFilter ? strings.ChatList_EmptyChatListFilterTitle : strings.ChatList_EmptyChatList, font: Font.medium(17.0), textColor: theme.list.itemPrimaryTextColor)
|
||||
let descriptionString: NSAttributedString
|
||||
if self.isFilter {
|
||||
descriptionString = NSAttributedString(string: strings.ChatList_EmptyChatListFilterText, font: Font.regular(14.0), textColor: theme.list.itemSecondaryTextColor)
|
||||
} else {
|
||||
descriptionString = NSAttributedString()
|
||||
}
|
||||
self.textNode.attributedText = string
|
||||
self.descriptionNode.attributedText = descriptionString
|
||||
|
||||
self.buttonNode.updateTheme(SolidRoundedButtonTheme(backgroundColor: theme.list.itemCheckColors.fillColor, foregroundColor: theme.list.itemCheckColors.foregroundColor))
|
||||
self.buttonTextNode.attributedText = NSAttributedString(string: isFilter ? strings.ChatList_EmptyChatListEditFilter : strings.ChatList_EmptyChatListNewMessage, font: Font.regular(17.0), textColor: theme.list.itemAccentColor)
|
||||
|
||||
self.activityIndicator.type = .custom(theme.list.itemAccentColor, 22.0, 1.0, false)
|
||||
|
||||
@ -92,7 +142,9 @@ final class ChatListEmptyNode: ASDisplayNode {
|
||||
self.isLoading = isLoading
|
||||
self.animationNode.isHidden = self.isLoading
|
||||
self.textNode.isHidden = self.isLoading
|
||||
self.descriptionNode.isHidden = self.isLoading
|
||||
self.buttonNode.isHidden = self.isLoading
|
||||
self.buttonTextNode.isHidden = self.isLoading
|
||||
self.activityIndicator.isHidden = !self.isLoading
|
||||
}
|
||||
|
||||
@ -102,11 +154,13 @@ final class ChatListEmptyNode: ASDisplayNode {
|
||||
let indicatorSize = self.activityIndicator.measure(CGSize(width: 100.0, height: 100.0))
|
||||
transition.updateFrame(node: self.activityIndicator, frame: CGRect(origin: CGPoint(x: floor((size.width - indicatorSize.width) / 2.0), y: floor((size.height - indicatorSize.height - 50.0) / 2.0)), size: indicatorSize))
|
||||
|
||||
let animationSpacing: CGFloat = 10.0
|
||||
let animationSpacing: CGFloat = 24.0
|
||||
let descriptionSpacing: CGFloat = 8.0
|
||||
let buttonSpacing: CGFloat = 24.0
|
||||
let buttonSideInset: CGFloat = 16.0
|
||||
|
||||
let textSize = self.textNode.updateLayout(CGSize(width: size.width - 40.0, height: size.height))
|
||||
let descriptionSize = self.descriptionNode.updateLayout(CGSize(width: size.width - 40.0, height: size.height))
|
||||
|
||||
let buttonWidth = min(size.width - buttonSideInset * 2.0, 280.0)
|
||||
let buttonSize = CGSize(width: buttonWidth, height: 50.0)
|
||||
@ -123,7 +177,8 @@ final class ChatListEmptyNode: ASDisplayNode {
|
||||
|
||||
let animationFrame = CGRect(origin: CGPoint(x: floor((size.width - self.animationSize.width) / 2.0), y: floor((size.height - contentHeight) / 2.0) + contentOffset), size: self.animationSize)
|
||||
let textFrame = CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: animationFrame.maxY + animationSpacing), size: textSize)
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: floor((size.width - buttonSize.width) / 2.0), y: textFrame.maxY + buttonSpacing), size: buttonSize)
|
||||
let descpriptionFrame = CGRect(origin: CGPoint(x: floor((size.width - descriptionSize.width) / 2.0), y: textFrame.maxY + descriptionSpacing), size: descriptionSize)
|
||||
let bottomTextEdge: CGFloat = descpriptionFrame.width.isZero ? textFrame.maxY : descpriptionFrame.maxY
|
||||
|
||||
if !self.animationSize.width.isZero {
|
||||
self.animationNode.updateLayout(size: self.animationSize)
|
||||
@ -131,15 +186,22 @@ final class ChatListEmptyNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.textNode, frame: textFrame)
|
||||
transition.updateFrame(node: self.descriptionNode, frame: descpriptionFrame)
|
||||
|
||||
let buttonTextSize = self.buttonTextNode.updateLayout(CGSize(width: size.width, height: .greatestFiniteMagnitude))
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: floor((size.width - buttonTextSize.width) / 2.0), y: bottomTextEdge + buttonSpacing), size: buttonTextSize)
|
||||
|
||||
self.buttonNode.updateLayout(width: buttonFrame.width, transition: transition)
|
||||
transition.updateFrame(node: self.buttonNode, frame: buttonFrame)
|
||||
transition.updateFrame(node: self.buttonTextNode, frame: buttonFrame)
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if self.buttonNode.frame.contains(point) {
|
||||
return self.buttonNode.view.hitTest(self.view.convert(point, to: self.buttonNode.view), with: event)
|
||||
}
|
||||
if self.animationNode.frame.contains(point) {
|
||||
return self.animationNode.view.hitTest(self.view.convert(point, to: self.animationNode.view), with: event)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,11 @@ import ItemListPeerItem
|
||||
import ItemListPeerActionItem
|
||||
import AvatarNode
|
||||
|
||||
private enum FilterSection: Int32, Hashable {
|
||||
case include
|
||||
case exclude
|
||||
}
|
||||
|
||||
private final class ChatListFilterPresetControllerArguments {
|
||||
let context: AccountContext
|
||||
let updateState: ((ChatListFilterPresetControllerState) -> ChatListFilterPresetControllerState) -> Void
|
||||
@ -25,6 +30,7 @@ private final class ChatListFilterPresetControllerArguments {
|
||||
let deleteIncludeCategory: (ChatListFilterIncludeCategory) -> Void
|
||||
let deleteExcludeCategory: (ChatListFilterExcludeCategory) -> Void
|
||||
let focusOnName: () -> Void
|
||||
let expandSection: (FilterSection) -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
@ -36,7 +42,8 @@ private final class ChatListFilterPresetControllerArguments {
|
||||
setItemIdWithRevealedOptions: @escaping (ChatListFilterRevealedItemId?, ChatListFilterRevealedItemId?) -> Void,
|
||||
deleteIncludeCategory: @escaping (ChatListFilterIncludeCategory) -> Void,
|
||||
deleteExcludeCategory: @escaping (ChatListFilterExcludeCategory) -> Void,
|
||||
focusOnName: @escaping () -> Void
|
||||
focusOnName: @escaping () -> Void,
|
||||
expandSection: @escaping (FilterSection) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.updateState = updateState
|
||||
@ -48,6 +55,7 @@ private final class ChatListFilterPresetControllerArguments {
|
||||
self.deleteIncludeCategory = deleteIncludeCategory
|
||||
self.deleteExcludeCategory = deleteExcludeCategory
|
||||
self.focusOnName = focusOnName
|
||||
self.expandSection = expandSection
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,6 +73,8 @@ private enum ChatListFilterPresetEntryStableId: Hashable {
|
||||
case excludePeerInfo
|
||||
case includeCategory(ChatListFilterIncludeCategory)
|
||||
case excludeCategory(ChatListFilterExcludeCategory)
|
||||
case includeExpand
|
||||
case excludeExpand
|
||||
}
|
||||
|
||||
private enum ChatListFilterPresetEntrySortId: Comparable {
|
||||
@ -222,6 +232,8 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
|
||||
case excludeCategory(index: Int, category: ChatListFilterExcludeCategory, title: String, isRevealed: Bool)
|
||||
case excludePeer(index: Int, peer: RenderedPeer, isRevealed: Bool)
|
||||
case excludePeerInfo(String)
|
||||
case includeExpand(String)
|
||||
case excludeExpand(String)
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
@ -229,9 +241,9 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
|
||||
return ChatListFilterPresetControllerSection.screenHeader.rawValue
|
||||
case .nameHeader, .name:
|
||||
return ChatListFilterPresetControllerSection.name.rawValue
|
||||
case .includePeersHeader, .addIncludePeer, .includeCategory, .includePeer, .includePeerInfo:
|
||||
case .includePeersHeader, .addIncludePeer, .includeCategory, .includePeer, .includePeerInfo, .includeExpand:
|
||||
return ChatListFilterPresetControllerSection.includePeers.rawValue
|
||||
case .excludePeersHeader, .addExcludePeer, .excludeCategory, .excludePeer, .excludePeerInfo:
|
||||
case .excludePeersHeader, .addExcludePeer, .excludeCategory, .excludePeer, .excludePeerInfo, .excludeExpand:
|
||||
return ChatListFilterPresetControllerSection.excludePeers.rawValue
|
||||
}
|
||||
}
|
||||
@ -250,16 +262,20 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
|
||||
return .index(4)
|
||||
case let .includeCategory(includeCategory):
|
||||
return .includeCategory(includeCategory.category)
|
||||
case .includePeerInfo:
|
||||
case .includeExpand:
|
||||
return .index(5)
|
||||
case .excludePeersHeader:
|
||||
case .includePeerInfo:
|
||||
return .index(6)
|
||||
case .addExcludePeer:
|
||||
case .excludePeersHeader:
|
||||
return .index(7)
|
||||
case .addExcludePeer:
|
||||
return .index(8)
|
||||
case let .excludeCategory(excludeCategory):
|
||||
return .excludeCategory(excludeCategory.category)
|
||||
case .excludeExpand:
|
||||
return .index(9)
|
||||
case .excludePeerInfo:
|
||||
return .index(8)
|
||||
return .index(10)
|
||||
case let .includePeer(peer):
|
||||
return .peer(peer.peer.peerId)
|
||||
case let .excludePeer(peer):
|
||||
@ -283,6 +299,8 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
|
||||
return .includeIndex(2 + includeCategory.index)
|
||||
case let .includePeer(includePeer):
|
||||
return .includeIndex(200 + includePeer.index)
|
||||
case .includeExpand:
|
||||
return .includeIndex(999)
|
||||
case .includePeerInfo:
|
||||
return .includeIndex(1000)
|
||||
case .excludePeersHeader:
|
||||
@ -293,6 +311,8 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
|
||||
return .excludeIndex(2 + excludeCategory.index)
|
||||
case let .excludePeer(excludePeer):
|
||||
return .excludeIndex(200 + excludePeer.index)
|
||||
case .excludeExpand:
|
||||
return .excludeIndex(999)
|
||||
case .excludePeerInfo:
|
||||
return .excludeIndex(1000)
|
||||
}
|
||||
@ -310,7 +330,7 @@ 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), clearType: .always, maxLength: 20, sectionId: self.section, textUpdated: { value in
|
||||
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(), text: value, placeholder: placeholder, type: .regular(capitalization: true, autocorrection: false), clearType: .always, maxLength: 12, sectionId: self.section, textUpdated: { value in
|
||||
arguments.updateState { current in
|
||||
var state = current
|
||||
state.name = value
|
||||
@ -384,6 +404,14 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
|
||||
}, removePeer: { id in
|
||||
arguments.deleteExcludePeer(id)
|
||||
})
|
||||
case let .includeExpand(text):
|
||||
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(presentationData.theme), title: text, sectionId: self.section, editing: false, action: {
|
||||
arguments.expandSection(.include)
|
||||
})
|
||||
case let .excludeExpand(text):
|
||||
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(presentationData.theme), title: text, sectionId: self.section, editing: false, action: {
|
||||
arguments.expandSection(.exclude)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -399,6 +427,7 @@ private struct ChatListFilterPresetControllerState: Equatable {
|
||||
var additionallyExcludePeers: [PeerId]
|
||||
|
||||
var revealedItemId: ChatListFilterRevealedItemId?
|
||||
var expandedSections: Set<FilterSection>
|
||||
|
||||
var isComplete: Bool {
|
||||
if self.name.isEmpty {
|
||||
@ -446,8 +475,18 @@ private func chatListFilterPresetControllerEntries(presentationData: Presentatio
|
||||
includeCategoryIndex += 1
|
||||
}
|
||||
|
||||
for peer in includePeers {
|
||||
entries.append(.includePeer(index: entries.count, peer: peer, isRevealed: state.revealedItemId == .peer(peer.peerId)))
|
||||
if !includePeers.isEmpty {
|
||||
var count = 0
|
||||
for peer in includePeers {
|
||||
entries.append(.includePeer(index: entries.count, peer: peer, isRevealed: state.revealedItemId == .peer(peer.peerId)))
|
||||
count += 1
|
||||
if includePeers.count >= 7 && count == 5 && !state.expandedSections.contains(.include) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if count < includePeers.count {
|
||||
entries.append(.includeExpand(presentationData.strings.ChatListFilter_ShowMoreChats(Int32(includePeers.count - count))))
|
||||
}
|
||||
}
|
||||
|
||||
entries.append(.includePeerInfo(presentationData.strings.ChatListFolder_IncludeSectionInfo))
|
||||
@ -473,8 +512,18 @@ private func chatListFilterPresetControllerEntries(presentationData: Presentatio
|
||||
excludeCategoryIndex += 1
|
||||
}
|
||||
|
||||
for peer in excludePeers {
|
||||
entries.append(.excludePeer(index: entries.count, peer: peer, isRevealed: state.revealedItemId == .peer(peer.peerId)))
|
||||
if !excludePeers.isEmpty {
|
||||
var count = 0
|
||||
for peer in excludePeers {
|
||||
entries.append(.excludePeer(index: entries.count, peer: peer, isRevealed: state.revealedItemId == .peer(peer.peerId)))
|
||||
count += 1
|
||||
if excludePeers.count >= 7 && count == 5 && !state.expandedSections.contains(.exclude) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if count < excludePeers.count {
|
||||
entries.append(.excludeExpand(presentationData.strings.ChatListFilter_ShowMoreChats(Int32(excludePeers.count - count))))
|
||||
}
|
||||
}
|
||||
|
||||
entries.append(.excludePeerInfo(presentationData.strings.ChatListFolder_ExcludeSectionInfo))
|
||||
@ -543,7 +592,7 @@ private func internalChatListFilterAddChatsController(context: AccountContext, f
|
||||
}
|
||||
}
|
||||
|
||||
let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .chatSelection(title: presentationData.strings.ChatListFolder_IncludeChatsTitle, selectedChats: Set(filter.data.includePeers), additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories)), options: [], alwaysEnabled: true))
|
||||
let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .chatSelection(title: presentationData.strings.ChatListFolder_IncludeChatsTitle, selectedChats: Set(filter.data.includePeers.peers), additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories)), options: [], filters: [], alwaysEnabled: true))
|
||||
controller.navigationPresentation = .modal
|
||||
let _ = (controller.result
|
||||
|> take(1)
|
||||
@ -579,8 +628,8 @@ private func internalChatListFilterAddChatsController(context: AccountContext, f
|
||||
for i in 0 ..< filters.count {
|
||||
if filters[i].id == filter.id {
|
||||
filters[i].data.categories = categories
|
||||
filters[i].data.includePeers = includePeers
|
||||
filters[i].data.excludePeers = filters[i].data.excludePeers.filter { !filters[i].data.includePeers.contains($0) }
|
||||
filters[i].data.includePeers.setPeers(includePeers)
|
||||
filters[i].data.excludePeers = filters[i].data.excludePeers.filter { !filters[i].data.includePeers.peers.contains($0) }
|
||||
}
|
||||
}
|
||||
return filters
|
||||
@ -591,8 +640,8 @@ private func internalChatListFilterAddChatsController(context: AccountContext, f
|
||||
} else {
|
||||
var filter = filter
|
||||
filter.data.categories = categories
|
||||
filter.data.includePeers = includePeers
|
||||
filter.data.excludePeers = filter.data.excludePeers.filter { !filter.data.includePeers.contains($0) }
|
||||
filter.data.includePeers.setPeers(includePeers)
|
||||
filter.data.excludePeers = filter.data.excludePeers.filter { !filter.data.includePeers.peers.contains($0) }
|
||||
updated(filter)
|
||||
controller?.dismiss()
|
||||
}
|
||||
@ -630,7 +679,7 @@ private func internalChatListFilterExcludeChatsController(context: AccountContex
|
||||
selectedCategories.insert(AdditionalExcludeCategoryId.archived.rawValue)
|
||||
}
|
||||
|
||||
let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .chatSelection(title: presentationData.strings.ChatListFolder_ExcludeChatsTitle, selectedChats: Set(filter.data.excludePeers), additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories)), options: [], alwaysEnabled: true))
|
||||
let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .chatSelection(title: presentationData.strings.ChatListFolder_ExcludeChatsTitle, selectedChats: Set(filter.data.excludePeers), additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories)), options: [], filters: [], alwaysEnabled: true))
|
||||
controller.navigationPresentation = .modal
|
||||
let _ = (controller.result
|
||||
|> take(1)
|
||||
@ -660,7 +709,7 @@ private func internalChatListFilterExcludeChatsController(context: AccountContex
|
||||
filters[i].data.excludeRead = additionalCategoryIds.contains(AdditionalExcludeCategoryId.read.rawValue)
|
||||
filters[i].data.excludeArchived = additionalCategoryIds.contains(AdditionalExcludeCategoryId.archived.rawValue)
|
||||
filters[i].data.excludePeers = excludePeers
|
||||
filters[i].data.includePeers = filters[i].data.includePeers.filter { !filters[i].data.excludePeers.contains($0) }
|
||||
filters[i].data.includePeers.setPeers(filters[i].data.includePeers.peers.filter { !filters[i].data.excludePeers.contains($0) })
|
||||
}
|
||||
}
|
||||
return filters
|
||||
@ -674,7 +723,7 @@ private func internalChatListFilterExcludeChatsController(context: AccountContex
|
||||
filter.data.excludeRead = additionalCategoryIds.contains(AdditionalExcludeCategoryId.read.rawValue)
|
||||
filter.data.excludeArchived = additionalCategoryIds.contains(AdditionalExcludeCategoryId.archived.rawValue)
|
||||
filter.data.excludePeers = excludePeers
|
||||
filter.data.includePeers = filter.data.includePeers.filter { !filter.data.excludePeers.contains($0) }
|
||||
filter.data.includePeers.setPeers(filter.data.includePeers.peers.filter { !filter.data.excludePeers.contains($0) })
|
||||
updated(filter)
|
||||
controller?.dismiss()
|
||||
}
|
||||
@ -695,7 +744,7 @@ enum ChatListFilterType {
|
||||
|
||||
func chatListFilterType(_ filter: ChatListFilter) -> ChatListFilterType {
|
||||
let filterType: ChatListFilterType
|
||||
if filter.data.includePeers.isEmpty {
|
||||
if filter.data.includePeers.peers.isEmpty {
|
||||
if filter.data.categories == .all {
|
||||
if filter.data.excludeRead {
|
||||
filterType = .unread
|
||||
@ -732,7 +781,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
||||
} else {
|
||||
initialName = ""
|
||||
}
|
||||
let initialState = ChatListFilterPresetControllerState(name: initialName, changedName: currentPreset != nil, includeCategories: currentPreset?.data.categories ?? [], excludeMuted: currentPreset?.data.excludeMuted ?? false, excludeRead: currentPreset?.data.excludeRead ?? false, excludeArchived: currentPreset?.data.excludeArchived ?? false, additionallyIncludePeers: currentPreset?.data.includePeers ?? [], additionallyExcludePeers: currentPreset?.data.excludePeers ?? [])
|
||||
let initialState = ChatListFilterPresetControllerState(name: initialName, changedName: currentPreset != nil, includeCategories: currentPreset?.data.categories ?? [], excludeMuted: currentPreset?.data.excludeMuted ?? false, excludeRead: currentPreset?.data.excludeRead ?? false, excludeArchived: currentPreset?.data.excludeArchived ?? false, additionallyIncludePeers: currentPreset?.data.includePeers.peers ?? [], additionallyExcludePeers: currentPreset?.data.excludePeers ?? [], expandedSections: [])
|
||||
let stateValue = Atomic(value: initialState)
|
||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||
let updateState: ((ChatListFilterPresetControllerState) -> ChatListFilterPresetControllerState) -> Void = { f in
|
||||
@ -740,7 +789,9 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
||||
var state = f(current)
|
||||
if !state.changedName {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let filter = ChatListFilter(id: currentPreset?.id ?? -1, title: state.name, data: ChatListFilterData(categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, excludeArchived: state.excludeArchived, includePeers: state.additionallyIncludePeers, excludePeers: state.additionallyExcludePeers))
|
||||
var includePeers = ChatListFilterIncludePeers()
|
||||
includePeers.setPeers(state.additionallyIncludePeers)
|
||||
let filter = ChatListFilter(id: currentPreset?.id ?? -1, title: state.name, emoticon: currentPreset?.emoticon, data: ChatListFilterData(categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, excludeArchived: state.excludeArchived, includePeers: includePeers, excludePeers: state.additionallyExcludePeers))
|
||||
switch chatListFilterType(filter) {
|
||||
case .generic:
|
||||
state.name = initialName
|
||||
@ -781,13 +832,15 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
||||
},
|
||||
openAddIncludePeer: {
|
||||
let state = stateValue.with { $0 }
|
||||
let filter = ChatListFilter(id: currentPreset?.id ?? -1, title: state.name, data: ChatListFilterData(categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, excludeArchived: state.excludeArchived, includePeers: state.additionallyIncludePeers, excludePeers: state.additionallyExcludePeers))
|
||||
var includePeers = ChatListFilterIncludePeers()
|
||||
includePeers.setPeers(state.additionallyIncludePeers)
|
||||
let filter = ChatListFilter(id: currentPreset?.id ?? -1, title: state.name, emoticon: currentPreset?.emoticon, data: ChatListFilterData(categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, excludeArchived: state.excludeArchived, includePeers: includePeers, excludePeers: state.additionallyExcludePeers))
|
||||
|
||||
let controller = internalChatListFilterAddChatsController(context: context, filter: filter, applyAutomatically: false, updated: { filter in
|
||||
skipStateAnimation = true
|
||||
updateState { state in
|
||||
var state = state
|
||||
state.additionallyIncludePeers = filter.data.includePeers
|
||||
state.additionallyIncludePeers = filter.data.includePeers.peers
|
||||
state.additionallyExcludePeers = filter.data.excludePeers
|
||||
state.includeCategories = filter.data.categories
|
||||
return state
|
||||
@ -797,13 +850,15 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
||||
},
|
||||
openAddExcludePeer: {
|
||||
let state = stateValue.with { $0 }
|
||||
let filter = ChatListFilter(id: currentPreset?.id ?? -1, title: state.name, data: ChatListFilterData(categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, excludeArchived: state.excludeArchived, includePeers: state.additionallyIncludePeers, excludePeers: state.additionallyExcludePeers))
|
||||
var includePeers = ChatListFilterIncludePeers()
|
||||
includePeers.setPeers(state.additionallyIncludePeers)
|
||||
let filter = ChatListFilter(id: currentPreset?.id ?? -1, title: state.name, emoticon: currentPreset?.emoticon, data: ChatListFilterData(categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, excludeArchived: state.excludeArchived, includePeers: includePeers, excludePeers: state.additionallyExcludePeers))
|
||||
|
||||
let controller = internalChatListFilterExcludeChatsController(context: context, filter: filter, applyAutomatically: false, updated: { filter in
|
||||
skipStateAnimation = true
|
||||
updateState { state in
|
||||
var state = state
|
||||
state.additionallyIncludePeers = filter.data.includePeers
|
||||
state.additionallyIncludePeers = filter.data.includePeers.peers
|
||||
state.additionallyExcludePeers = filter.data.excludePeers
|
||||
state.includeCategories = filter.data.categories
|
||||
state.excludeRead = filter.data.excludeRead
|
||||
@ -864,6 +919,13 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
||||
},
|
||||
focusOnName: {
|
||||
focusOnNameImpl?()
|
||||
},
|
||||
expandSection: { section in
|
||||
updateState { state in
|
||||
var state = state
|
||||
state.expandedSections.insert(section)
|
||||
return state
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@ -916,34 +978,38 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
||||
}
|
||||
|
||||
var attemptNavigationImpl: (() -> Bool)?
|
||||
var applyImpl: (() -> Void)? = {
|
||||
let applyImpl: (() -> Void)? = {
|
||||
let state = stateValue.with { $0 }
|
||||
let preset = ChatListFilter(id: currentPreset?.id ?? -1, title: state.name, data: ChatListFilterData(categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, excludeArchived: state.excludeArchived, includePeers: state.additionallyIncludePeers, excludePeers: state.additionallyExcludePeers))
|
||||
let _ = (updateChatListFiltersInteractively(postbox: context.account.postbox, { filters in
|
||||
var preset = preset
|
||||
var includePeers = ChatListFilterIncludePeers()
|
||||
includePeers.setPeers(state.additionallyIncludePeers)
|
||||
var updatedFilter = ChatListFilter(id: currentPreset?.id ?? -1, title: state.name, emoticon: currentPreset?.emoticon, data: ChatListFilterData(categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, excludeArchived: state.excludeArchived, includePeers: includePeers, excludePeers: state.additionallyExcludePeers))
|
||||
if currentPreset == nil {
|
||||
preset.id = max(2, filters.map({ $0.id + 1 }).max() ?? 2)
|
||||
updatedFilter.id = max(2, filters.map({ $0.id + 1 }).max() ?? 2)
|
||||
}
|
||||
var filters = filters
|
||||
if let _ = currentPreset {
|
||||
var found = false
|
||||
for i in 0 ..< filters.count {
|
||||
if filters[i].id == preset.id {
|
||||
filters[i] = preset
|
||||
if filters[i].id == updatedFilter.id {
|
||||
var includePeers = filters[i].data.includePeers
|
||||
includePeers.setPeers(state.additionallyIncludePeers)
|
||||
updatedFilter.data.includePeers = includePeers
|
||||
filters[i] = updatedFilter
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
filters = filters.filter { listFilter in
|
||||
if listFilter.title == preset.title && listFilter.data == preset.data {
|
||||
if listFilter.title == updatedFilter.title && listFilter.data == updatedFilter.data {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
filters.append(preset)
|
||||
filters.append(updatedFilter)
|
||||
}
|
||||
} else {
|
||||
filters.append(preset)
|
||||
filters.append(updatedFilter)
|
||||
}
|
||||
return filters
|
||||
})
|
||||
@ -953,6 +1019,8 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
||||
})
|
||||
}
|
||||
|
||||
var previousState = stateValue.with { $0 }
|
||||
|
||||
let signal = combineLatest(queue: .mainQueue(),
|
||||
context.sharedContext.presentationData,
|
||||
stateWithPeers
|
||||
@ -970,6 +1038,12 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
||||
applyImpl?()
|
||||
})
|
||||
|
||||
let previousStateValue = previousState
|
||||
previousState = state
|
||||
if previousStateValue.expandedSections != state.expandedSections {
|
||||
skipStateAnimation = true
|
||||
}
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(currentPreset != nil ? presentationData.strings.ChatListFolder_TitleEdit : presentationData.strings.ChatListFolder_TitleCreate), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: chatListFilterPresetControllerEntries(presentationData: presentationData, isNewFilter: currentPreset == nil, state: state, includePeers: includePeers, excludePeers: excludePeers), style: .blocks, emptyStateItem: nil, animateChanges: !skipStateAnimation)
|
||||
skipStateAnimation = false
|
||||
@ -1013,8 +1087,15 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
||||
attemptNavigationImpl = {
|
||||
let state = stateValue.with { $0 }
|
||||
if let currentPreset = currentPreset {
|
||||
let filter = ChatListFilter(id: currentPreset.id, title: state.name, data: ChatListFilterData(categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, excludeArchived: state.excludeArchived, includePeers: state.additionallyIncludePeers, excludePeers: state.additionallyExcludePeers))
|
||||
if currentPreset != filter {
|
||||
var currentPresetWithoutPinnerPeers = currentPreset
|
||||
var currentIncludePeers = ChatListFilterIncludePeers()
|
||||
currentIncludePeers.setPeers(currentPresetWithoutPinnerPeers.data.includePeers.peers)
|
||||
currentPresetWithoutPinnerPeers.data.includePeers = currentIncludePeers
|
||||
|
||||
var includePeers = ChatListFilterIncludePeers()
|
||||
includePeers.setPeers(state.additionallyIncludePeers)
|
||||
let filter = ChatListFilter(id: currentPreset.id, title: state.name, emoticon: currentPreset.emoticon, data: ChatListFilterData(categories: state.includeCategories, excludeMuted: state.excludeMuted, excludeRead: state.excludeRead, excludeArchived: state.excludeArchived, includePeers: includePeers, excludePeers: state.additionallyExcludePeers))
|
||||
if currentPresetWithoutPinnerPeers != filter {
|
||||
displaySaveAlert()
|
||||
return false
|
||||
}
|
||||
|
@ -244,13 +244,14 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
|
||||
|
||||
var dismissImpl: (() -> Void)?
|
||||
var pushControllerImpl: ((ViewController) -> Void)?
|
||||
var presentControllerImpl: ((ViewController) -> Void)?
|
||||
|
||||
let arguments = ChatListFilterPresetListControllerArguments(context: context,
|
||||
addSuggestedPresed: { title, data in
|
||||
let _ = (updateChatListFiltersInteractively(postbox: context.account.postbox, { filters in
|
||||
var filters = filters
|
||||
let id = generateNewChatListFilterId(filters: filters)
|
||||
filters.insert(ChatListFilter(id: id, title: title, data: data), at: 0)
|
||||
filters.insert(ChatListFilter(id: id, title: title, emoticon: nil, data: data), at: 0)
|
||||
return filters
|
||||
})
|
||||
|> deliverOnMainQueue).start(next: { _ in
|
||||
@ -268,15 +269,32 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
|
||||
return state
|
||||
}
|
||||
}, removePreset: { id in
|
||||
let _ = (updateChatListFiltersInteractively(postbox: context.account.postbox, { filters in
|
||||
var filters = filters
|
||||
if let index = filters.firstIndex(where: { $0.id == id }) {
|
||||
filters.remove(at: index)
|
||||
}
|
||||
return filters
|
||||
})
|
||||
|> deliverOnMainQueue).start(next: { _ in
|
||||
})
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let actionSheet = ActionSheetController(presentationData: presentationData)
|
||||
|
||||
actionSheet.setItemGroups([
|
||||
ActionSheetItemGroup(items: [
|
||||
ActionSheetTextItem(title: presentationData.strings.ChatList_RemoveFolderConfirmation),
|
||||
ActionSheetButtonItem(title: presentationData.strings.ChatList_RemoveFolderAction, color: .destructive, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
|
||||
let _ = (updateChatListFiltersInteractively(postbox: context.account.postbox, { filters in
|
||||
var filters = filters
|
||||
if let index = filters.firstIndex(where: { $0.id == id }) {
|
||||
filters.remove(at: index)
|
||||
}
|
||||
return filters
|
||||
})
|
||||
|> deliverOnMainQueue).start()
|
||||
})
|
||||
]),
|
||||
ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
})
|
||||
])
|
||||
])
|
||||
presentControllerImpl?(actionSheet)
|
||||
})
|
||||
|
||||
let chatCountCache = Atomic<[ChatListFilterData: Int]>(value: [:])
|
||||
@ -420,6 +438,9 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
|
||||
pushControllerImpl = { [weak controller] c in
|
||||
controller?.push(c)
|
||||
}
|
||||
presentControllerImpl = { [weak controller] c in
|
||||
controller?.present(c, in: .window(.root))
|
||||
}
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.dismiss()
|
||||
}
|
||||
|
@ -190,7 +190,7 @@ public class ChatListFilterPresetListSuggestedItemNode: ListViewItemNode, ItemLi
|
||||
|
||||
let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize)
|
||||
|
||||
let (buttonTitleLayout, buttonTitleApply) = makeButtonTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Stickers_Install, font: Font.semibold(14.0), textColor: item.presentationData.theme.list.itemCheckColors.foregroundColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - 20.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (buttonTitleLayout, buttonTitleApply) = makeButtonTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.ChatListFolderSettings_AddRecommended, font: Font.semibold(14.0), textColor: item.presentationData.theme.list.itemCheckColors.foregroundColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - 20.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let additionalTextRightInset: CGFloat = buttonTitleLayout.size.width + 14.0 * 2.0
|
||||
|
||||
|
@ -94,7 +94,9 @@ class ChatListFilterSettingsHeaderItemNode: ListViewItemNode {
|
||||
|
||||
@objc private func animationTapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
self.animationNode.play()
|
||||
if !self.animationNode.isPlaying {
|
||||
self.animationNode.play()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,8 +64,12 @@ private final class ItemNode: ASDisplayNode {
|
||||
private let containerNode: ContextControllerSourceNode
|
||||
|
||||
private let extractedBackgroundNode: ASImageNode
|
||||
private let titleContainer: ASDisplayNode
|
||||
private let titleNode: ImmediateTextNode
|
||||
private let titleActiveNode: ImmediateTextNode
|
||||
private let shortTitleContainer: ASDisplayNode
|
||||
private let shortTitleNode: ImmediateTextNode
|
||||
private let shortTitleActiveNode: ImmediateTextNode
|
||||
private let badgeContainerNode: ASDisplayNode
|
||||
private let badgeTextNode: ImmediateTextNode
|
||||
private let badgeBackgroundActiveNode: ASImageNode
|
||||
@ -74,7 +78,7 @@ private final class ItemNode: ASDisplayNode {
|
||||
private var deleteButtonNode: ItemNodeDeleteButtonNode?
|
||||
private let buttonNode: HighlightTrackingButtonNode
|
||||
|
||||
private var isSelected: Bool = false
|
||||
private var selectionFraction: CGFloat = 0.0
|
||||
private(set) var unreadCount: Int = 0
|
||||
|
||||
private var isReordering: Bool = false
|
||||
@ -91,12 +95,31 @@ private final class ItemNode: ASDisplayNode {
|
||||
self.extractedBackgroundNode = ASImageNode()
|
||||
self.extractedBackgroundNode.alpha = 0.0
|
||||
|
||||
let titleInset: CGFloat = 4.0
|
||||
|
||||
self.titleContainer = ASDisplayNode()
|
||||
|
||||
self.titleNode = ImmediateTextNode()
|
||||
self.titleNode.displaysAsynchronously = false
|
||||
self.titleNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0)
|
||||
|
||||
self.titleActiveNode = ImmediateTextNode()
|
||||
self.titleActiveNode.displaysAsynchronously = false
|
||||
self.titleActiveNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0)
|
||||
self.titleActiveNode.alpha = 0.0
|
||||
|
||||
self.shortTitleContainer = ASDisplayNode()
|
||||
|
||||
self.shortTitleNode = ImmediateTextNode()
|
||||
self.shortTitleNode.displaysAsynchronously = false
|
||||
self.shortTitleNode.alpha = 0.0
|
||||
self.shortTitleNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0)
|
||||
|
||||
self.shortTitleActiveNode = ImmediateTextNode()
|
||||
self.shortTitleActiveNode.displaysAsynchronously = false
|
||||
self.shortTitleActiveNode.alpha = 0.0
|
||||
self.shortTitleActiveNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0)
|
||||
self.shortTitleActiveNode.alpha = 0.0
|
||||
|
||||
self.badgeContainerNode = ASDisplayNode()
|
||||
|
||||
@ -110,17 +133,20 @@ private final class ItemNode: ASDisplayNode {
|
||||
self.badgeBackgroundInactiveNode = ASImageNode()
|
||||
self.badgeBackgroundInactiveNode.displaysAsynchronously = false
|
||||
self.badgeBackgroundInactiveNode.displayWithoutProcessing = true
|
||||
self.badgeBackgroundInactiveNode.isHidden = true
|
||||
|
||||
self.buttonNode = HighlightTrackingButtonNode()
|
||||
|
||||
super.init()
|
||||
|
||||
self.extractedContainerNode.contentNode.addSubnode(self.extractedBackgroundNode)
|
||||
self.extractedContainerNode.contentNode.addSubnode(self.titleNode)
|
||||
self.extractedContainerNode.contentNode.addSubnode(self.shortTitleNode)
|
||||
self.badgeContainerNode.addSubnode(self.badgeBackgroundActiveNode)
|
||||
self.extractedContainerNode.contentNode.addSubnode(self.titleContainer)
|
||||
self.titleContainer.addSubnode(self.titleNode)
|
||||
self.titleContainer.addSubnode(self.titleActiveNode)
|
||||
self.extractedContainerNode.contentNode.addSubnode(self.shortTitleContainer)
|
||||
self.shortTitleContainer.addSubnode(self.shortTitleNode)
|
||||
self.shortTitleContainer.addSubnode(self.shortTitleActiveNode)
|
||||
self.badgeContainerNode.addSubnode(self.badgeBackgroundInactiveNode)
|
||||
self.badgeContainerNode.addSubnode(self.badgeBackgroundActiveNode)
|
||||
self.badgeContainerNode.addSubnode(self.badgeTextNode)
|
||||
self.extractedContainerNode.contentNode.addSubnode(self.badgeContainerNode)
|
||||
self.extractedContainerNode.contentNode.addSubnode(self.buttonNode)
|
||||
@ -158,7 +184,7 @@ private final class ItemNode: ASDisplayNode {
|
||||
self.pressed()
|
||||
}
|
||||
|
||||
func updateText(title: String, shortTitle: String, unreadCount: Int, unreadHasUnmuted: Bool, isNoFilter: Bool, isSelected: Bool, isEditing: Bool, isAllChats: Bool, isReordering: Bool, presentationData: PresentationData, transition: ContainedViewLayoutTransition) {
|
||||
func updateText(title: String, shortTitle: String, unreadCount: Int, unreadHasUnmuted: Bool, isNoFilter: Bool, selectionFraction: CGFloat, isEditing: Bool, isAllChats: Bool, isReordering: Bool, presentationData: PresentationData, transition: ContainedViewLayoutTransition) {
|
||||
if self.theme !== presentationData.theme {
|
||||
self.theme = presentationData.theme
|
||||
|
||||
@ -166,10 +192,10 @@ private final class ItemNode: ASDisplayNode {
|
||||
self.badgeBackgroundInactiveNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: presentationData.theme.chatList.unreadBadgeInactiveBackgroundColor)
|
||||
}
|
||||
|
||||
self.containerNode.isGestureEnabled = !isNoFilter && !isEditing && !isReordering
|
||||
self.containerNode.isGestureEnabled = !isEditing && !isReordering
|
||||
self.buttonNode.isUserInteractionEnabled = !isEditing && !isReordering
|
||||
|
||||
self.isSelected = isSelected
|
||||
self.selectionFraction = selectionFraction
|
||||
self.unreadCount = unreadCount
|
||||
|
||||
transition.updateAlpha(node: self.containerNode, alpha: isReordering && isAllChats ? 0.5 : 1.0)
|
||||
@ -196,12 +222,28 @@ private final class ItemNode: ASDisplayNode {
|
||||
|
||||
transition.updateAlpha(node: self.badgeContainerNode, alpha: (isReordering || unreadCount == 0) ? 0.0 : 1.0)
|
||||
|
||||
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(14.0), textColor: isSelected ? presentationData.theme.list.itemAccentColor : presentationData.theme.list.itemSecondaryTextColor)
|
||||
self.shortTitleNode.attributedText = NSAttributedString(string: shortTitle, font: Font.medium(14.0), textColor: isSelected ? presentationData.theme.list.itemAccentColor : presentationData.theme.list.itemSecondaryTextColor)
|
||||
let selectionAlpha: CGFloat = selectionFraction * selectionFraction
|
||||
let deselectionAlpha: CGFloat = 1.0// - selectionFraction
|
||||
|
||||
transition.updateAlpha(node: self.titleNode, alpha: deselectionAlpha)
|
||||
transition.updateAlpha(node: self.titleActiveNode, alpha: selectionAlpha)
|
||||
transition.updateAlpha(node: self.shortTitleNode, alpha: deselectionAlpha)
|
||||
transition.updateAlpha(node: self.shortTitleActiveNode, alpha: selectionAlpha)
|
||||
|
||||
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(14.0), textColor: presentationData.theme.list.itemSecondaryTextColor)
|
||||
self.titleActiveNode.attributedText = NSAttributedString(string: title, font: Font.medium(14.0), textColor: presentationData.theme.list.itemAccentColor)
|
||||
self.shortTitleNode.attributedText = NSAttributedString(string: shortTitle, font: Font.medium(14.0), textColor: presentationData.theme.list.itemSecondaryTextColor)
|
||||
self.shortTitleActiveNode.attributedText = NSAttributedString(string: shortTitle, font: Font.medium(14.0), textColor: presentationData.theme.list.itemAccentColor)
|
||||
if unreadCount != 0 {
|
||||
self.badgeTextNode.attributedText = NSAttributedString(string: "\(unreadCount)", font: Font.regular(14.0), textColor: presentationData.theme.list.itemCheckColors.foregroundColor)
|
||||
self.badgeBackgroundActiveNode.isHidden = !isSelected && !unreadHasUnmuted
|
||||
self.badgeBackgroundInactiveNode.isHidden = isSelected || unreadHasUnmuted
|
||||
let badgeSelectionFraction: CGFloat = unreadHasUnmuted ? 1.0 : selectionFraction
|
||||
|
||||
let badgeSelectionAlpha: CGFloat = badgeSelectionFraction
|
||||
//let badgeDeselectionAlpha: CGFloat = 1.0 - badgeSelectionFraction
|
||||
|
||||
transition.updateAlpha(node: self.badgeBackgroundActiveNode, alpha: badgeSelectionAlpha * badgeSelectionAlpha)
|
||||
//transition.updateAlpha(node: self.badgeBackgroundInactiveNode, alpha: badgeDeselectionAlpha)
|
||||
self.badgeBackgroundInactiveNode.alpha = 1.0
|
||||
}
|
||||
|
||||
if self.isReordering != isReordering {
|
||||
@ -217,10 +259,18 @@ private final class ItemNode: ASDisplayNode {
|
||||
|
||||
func updateLayout(height: CGFloat, transition: ContainedViewLayoutTransition) -> (width: CGFloat, shortWidth: CGFloat) {
|
||||
let titleSize = self.titleNode.updateLayout(CGSize(width: 160.0, height: .greatestFiniteMagnitude))
|
||||
self.titleNode.frame = CGRect(origin: CGPoint(x: 0.0, y: floor((height - titleSize.height) / 2.0)), size: titleSize)
|
||||
let _ = self.titleActiveNode.updateLayout(CGSize(width: 160.0, height: .greatestFiniteMagnitude))
|
||||
let titleFrame = CGRect(origin: CGPoint(x: -self.titleNode.insets.left, y: floor((height - titleSize.height) / 2.0)), size: titleSize)
|
||||
self.titleContainer.frame = titleFrame
|
||||
self.titleNode.frame = CGRect(origin: CGPoint(), size: titleFrame.size)
|
||||
self.titleActiveNode.frame = CGRect(origin: CGPoint(), size: titleFrame.size)
|
||||
|
||||
let shortTitleSize = self.shortTitleNode.updateLayout(CGSize(width: 160.0, height: .greatestFiniteMagnitude))
|
||||
self.shortTitleNode.frame = CGRect(origin: CGPoint(x: 0.0, y: floor((height - shortTitleSize.height) / 2.0)), size: shortTitleSize)
|
||||
let _ = self.shortTitleActiveNode.updateLayout(CGSize(width: 160.0, height: .greatestFiniteMagnitude))
|
||||
let shortTitleFrame = CGRect(origin: CGPoint(x: -self.shortTitleNode.insets.left, y: floor((height - shortTitleSize.height) / 2.0)), size: shortTitleSize)
|
||||
self.shortTitleContainer.frame = shortTitleFrame
|
||||
self.shortTitleNode.frame = CGRect(origin: CGPoint(), size: shortTitleFrame.size)
|
||||
self.shortTitleActiveNode.frame = CGRect(origin: CGPoint(), size: shortTitleFrame.size)
|
||||
|
||||
if let deleteButtonNode = self.deleteButtonNode {
|
||||
if let theme = self.theme {
|
||||
@ -231,7 +281,7 @@ private final class ItemNode: ASDisplayNode {
|
||||
|
||||
let badgeSize = self.badgeTextNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude))
|
||||
let badgeInset: CGFloat = 4.0
|
||||
let badgeBackgroundFrame = CGRect(origin: CGPoint(x: titleSize.width + 5.0, y: floor((height - 18.0) / 2.0)), size: CGSize(width: max(18.0, badgeSize.width + badgeInset * 2.0), height: 18.0))
|
||||
let badgeBackgroundFrame = CGRect(origin: CGPoint(x: titleSize.width - self.titleNode.insets.left - self.titleNode.insets.right + 4.0, y: floor((height - 18.0) / 2.0)), size: CGSize(width: max(18.0, badgeSize.width + badgeInset * 2.0), height: 18.0))
|
||||
self.badgeContainerNode.frame = badgeBackgroundFrame
|
||||
self.badgeBackgroundActiveNode.frame = CGRect(origin: CGPoint(), size: badgeBackgroundFrame.size)
|
||||
self.badgeBackgroundInactiveNode.frame = CGRect(origin: CGPoint(), size: badgeBackgroundFrame.size)
|
||||
@ -242,7 +292,7 @@ private final class ItemNode: ASDisplayNode {
|
||||
if !self.isReordering {
|
||||
self.badgeContainerNode.alpha = 0.0
|
||||
}
|
||||
width = titleSize.width
|
||||
width = titleSize.width - self.titleNode.insets.left - self.titleNode.insets.right
|
||||
} else {
|
||||
if !self.isReordering {
|
||||
self.badgeContainerNode.alpha = 1.0
|
||||
@ -250,16 +300,12 @@ private final class ItemNode: ASDisplayNode {
|
||||
width = badgeBackgroundFrame.maxX
|
||||
}
|
||||
|
||||
let extractedBackgroundHeight: CGFloat = 36.0
|
||||
let extractedBackgroundInset: CGFloat = 14.0
|
||||
self.extractedBackgroundNode.frame = CGRect(origin: CGPoint(x: -extractedBackgroundInset, y: floor((height - extractedBackgroundHeight) / 2.0)), size: CGSize(width: width + extractedBackgroundInset * 2.0, height: extractedBackgroundHeight))
|
||||
|
||||
return (width, shortTitleSize.width)
|
||||
return (width, shortTitleSize.width - self.shortTitleNode.insets.left - self.shortTitleNode.insets.right + 5.0)
|
||||
}
|
||||
|
||||
func updateArea(size: CGSize, sideInset: CGFloat, useShortTitle: Bool, transition: ContainedViewLayoutTransition) {
|
||||
transition.updateAlpha(node: self.titleNode, alpha: useShortTitle ? 0.0 : 1.0)
|
||||
transition.updateAlpha(node: self.shortTitleNode, alpha: useShortTitle ? 1.0 : 0.0)
|
||||
transition.updateAlpha(node: self.titleContainer, alpha: useShortTitle ? 0.0 : 1.0)
|
||||
transition.updateAlpha(node: self.shortTitleContainer, alpha: useShortTitle ? 1.0 : 0.0)
|
||||
|
||||
self.buttonNode.frame = CGRect(origin: CGPoint(x: -sideInset, y: 0.0), size: CGSize(width: size.width + sideInset * 2.0, height: size.height))
|
||||
|
||||
@ -272,6 +318,10 @@ private final class ItemNode: ASDisplayNode {
|
||||
self.extractedContainerNode.hitTestSlop = self.hitTestSlop
|
||||
self.extractedContainerNode.contentNode.hitTestSlop = self.hitTestSlop
|
||||
self.containerNode.hitTestSlop = self.hitTestSlop
|
||||
|
||||
let extractedBackgroundHeight: CGFloat = 36.0
|
||||
let extractedBackgroundInset: CGFloat = 14.0
|
||||
self.extractedBackgroundNode.frame = CGRect(origin: CGPoint(x: -extractedBackgroundInset, y: floor((size.height - extractedBackgroundHeight) / 2.0)), size: CGSize(width: size.width + extractedBackgroundInset * 2.0, height: extractedBackgroundHeight))
|
||||
}
|
||||
|
||||
func animateBadgeIn() {
|
||||
@ -397,7 +447,7 @@ final class ChatListFilterTabContainerNode: ASDisplayNode {
|
||||
var tabSelected: ((ChatListFilterTabEntryId) -> Void)?
|
||||
var tabRequestedDeletion: ((ChatListFilterTabEntryId) -> Void)?
|
||||
var addFilter: (() -> Void)?
|
||||
var contextGesture: ((Int32, ContextExtractedContentContainingNode, ContextGesture) -> Void)?
|
||||
var contextGesture: ((Int32?, ContextExtractedContentContainingNode, ContextGesture) -> Void)?
|
||||
|
||||
private var reorderingGesture: ReorderingGestureRecognizer?
|
||||
private var reorderingItem: ChatListFilterTabEntryId?
|
||||
@ -566,7 +616,10 @@ final class ChatListFilterTabContainerNode: ASDisplayNode {
|
||||
self.scrollNode.layer.removeAllAnimations()
|
||||
}
|
||||
|
||||
func update(size: CGSize, sideInset: CGFloat, filters: [ChatListFilterTabEntry], selectedFilter: ChatListFilterTabEntryId?, isReordering: Bool, isEditing: Bool, transitionFraction: CGFloat, presentationData: PresentationData, transition: ContainedViewLayoutTransition) {
|
||||
func update(size: CGSize, sideInset: CGFloat, filters: [ChatListFilterTabEntry], selectedFilter: ChatListFilterTabEntryId?, isReordering: Bool, isEditing: Bool, transitionFraction: CGFloat, presentationData: PresentationData, transition proposedTransition: ContainedViewLayoutTransition) {
|
||||
let isFirstTime = self.currentParams == nil
|
||||
let transition: ContainedViewLayoutTransition = isFirstTime ? .immediate : proposedTransition
|
||||
|
||||
var focusOnSelectedFilter = self.currentParams?.selectedFilter != selectedFilter
|
||||
let previousScrollBounds = self.scrollNode.bounds
|
||||
let previousContentWidth = self.scrollNode.view.contentSize.width
|
||||
@ -624,7 +677,9 @@ final class ChatListFilterTabContainerNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
for filter in reorderedFilters {
|
||||
for i in 0 ..< reorderedFilters.count {
|
||||
let filter = reorderedFilters[i]
|
||||
|
||||
let itemNode: ItemNode
|
||||
var itemNodeTransition = transition
|
||||
var wasAdded = false
|
||||
@ -641,14 +696,14 @@ final class ChatListFilterTabContainerNode: ASDisplayNode {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.scrollNode.view.panGestureRecognizer.isEnabled = false
|
||||
strongSelf.scrollNode.view.panGestureRecognizer.isEnabled = true
|
||||
strongSelf.scrollNode.view.setContentOffset(strongSelf.scrollNode.view.contentOffset, animated: false)
|
||||
switch filter {
|
||||
case let .filter(filter):
|
||||
strongSelf.scrollNode.view.panGestureRecognizer.isEnabled = false
|
||||
strongSelf.scrollNode.view.panGestureRecognizer.isEnabled = true
|
||||
strongSelf.scrollNode.view.setContentOffset(strongSelf.scrollNode.view.contentOffset, animated: false)
|
||||
strongSelf.contextGesture?(filter.id, sourceNode, gesture)
|
||||
default:
|
||||
break
|
||||
strongSelf.contextGesture?(nil, sourceNode, gesture)
|
||||
}
|
||||
})
|
||||
self.itemNodes[filter.id] = itemNode
|
||||
@ -668,7 +723,19 @@ final class ChatListFilterTabContainerNode: ASDisplayNode {
|
||||
if !wasAdded && (itemNode.unreadCount != 0) != (unreadCount != 0) {
|
||||
badgeAnimations[filter.id] = (unreadCount != 0) ? .in : .out
|
||||
}
|
||||
itemNode.updateText(title: filter.title(strings: presentationData.strings), shortTitle: filter.shortTitle(strings: presentationData.strings), unreadCount: unreadCount, unreadHasUnmuted: unreadHasUnmuted, isNoFilter: isNoFilter, isSelected: selectedFilter == filter.id, isEditing: false, isAllChats: isNoFilter, isReordering: isEditing || isReordering, presentationData: presentationData, transition: itemNodeTransition)
|
||||
|
||||
let selectionFraction: CGFloat
|
||||
if selectedFilter == filter.id {
|
||||
selectionFraction = 1.0 - abs(transitionFraction)
|
||||
} else if i != 0 && selectedFilter == reorderedFilters[i - 1].id {
|
||||
selectionFraction = max(0.0, -transitionFraction)
|
||||
} else if i != reorderedFilters.count - 1 && selectedFilter == reorderedFilters[i + 1].id {
|
||||
selectionFraction = max(0.0, transitionFraction)
|
||||
} else {
|
||||
selectionFraction = 0.0
|
||||
}
|
||||
|
||||
itemNode.updateText(title: filter.title(strings: presentationData.strings), shortTitle: filter.shortTitle(strings: presentationData.strings), unreadCount: unreadCount, unreadHasUnmuted: unreadHasUnmuted, isNoFilter: isNoFilter, selectionFraction: selectionFraction, isEditing: false, isAllChats: isNoFilter, isReordering: isEditing || isReordering, presentationData: presentationData, transition: itemNodeTransition)
|
||||
}
|
||||
var removeKeys: [ChatListFilterTabEntryId] = []
|
||||
for (id, _) in self.itemNodes {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -484,7 +484,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
||||
}
|
||||
})
|
||||
case let .message(message, peer, readState, presentationData):
|
||||
return ChatListItem(presentationData: presentationData, context: context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: nil, messageIndex: message.index), content: .peer(message: message, peer: peer, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: true, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: enableHeaders ? ChatListSearchItemHeader(type: .messages, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)
|
||||
return ChatListItem(presentationData: presentationData, context: context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: message.index), content: .peer(message: message, peer: peer, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: true, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: enableHeaders ? ChatListSearchItemHeader(type: .messages, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)
|
||||
case let .addContact(phoneNumber, theme, strings):
|
||||
return ContactsAddItem(theme: theme, strings: strings, phoneNumber: phoneNumber, header: ChatListSearchItemHeader(type: .phoneNumber, theme: theme, strings: strings, actionTitle: nil, action: nil), action: {
|
||||
interaction.addContact(phoneNumber)
|
||||
|
@ -15,23 +15,36 @@ struct ChatListSelectionOptions: Equatable {
|
||||
let delete: Bool
|
||||
}
|
||||
|
||||
func chatListSelectionOptions(postbox: Postbox, peerIds: Set<PeerId>) -> Signal<ChatListSelectionOptions, NoError> {
|
||||
func chatListSelectionOptions(postbox: Postbox, peerIds: Set<PeerId>, filterId: Int32?) -> Signal<ChatListSelectionOptions, NoError> {
|
||||
if peerIds.isEmpty {
|
||||
let key = PostboxViewKey.unreadCounts(items: [.total(nil)])
|
||||
return postbox.combinedView(keys: [key])
|
||||
|> map { view -> ChatListSelectionOptions in
|
||||
var hasUnread = false
|
||||
if let unreadCounts = view.views[key] as? UnreadMessageCountsView, let total = unreadCounts.total() {
|
||||
for (_, counter) in total.1.absoluteCounters {
|
||||
if counter.messageCount != 0 {
|
||||
hasUnread = true
|
||||
break
|
||||
if let filterId = filterId {
|
||||
return chatListFilterItems(postbox: postbox)
|
||||
|> map { filterItems -> ChatListSelectionOptions in
|
||||
for (filter, unreadCount, _) in filterItems.1 {
|
||||
if filter.id == filterId {
|
||||
return ChatListSelectionOptions(read: .all(enabled: unreadCount != 0), delete: false)
|
||||
}
|
||||
}
|
||||
return ChatListSelectionOptions(read: .all(enabled: false), delete: false)
|
||||
}
|
||||
return ChatListSelectionOptions(read: .all(enabled: hasUnread), delete: false)
|
||||
|> distinctUntilChanged
|
||||
} else {
|
||||
let key = PostboxViewKey.unreadCounts(items: [.total(nil)])
|
||||
return postbox.combinedView(keys: [key])
|
||||
|> map { view -> ChatListSelectionOptions in
|
||||
var hasUnread = false
|
||||
if let unreadCounts = view.views[key] as? UnreadMessageCountsView, let total = unreadCounts.total() {
|
||||
for (_, counter) in total.1.absoluteCounters {
|
||||
if counter.messageCount != 0 {
|
||||
hasUnread = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return ChatListSelectionOptions(read: .all(enabled: hasUnread), delete: false)
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
} else {
|
||||
let items: [UnreadMessageCountsItem] = peerIds.map(UnreadMessageCountsItem.peer)
|
||||
let key = PostboxViewKey.unreadCounts(items: items)
|
||||
|
@ -37,7 +37,7 @@ public class ChatListItem: ListViewItem, ChatListSearchItemNeighbour {
|
||||
let presentationData: ChatListPresentationData
|
||||
let context: AccountContext
|
||||
let peerGroupId: PeerGroupId
|
||||
let isInFilter: Bool
|
||||
let filterData: ChatListItemFilterData?
|
||||
let index: ChatListIndex
|
||||
public let content: ChatListItemContent
|
||||
let editing: Bool
|
||||
@ -59,10 +59,10 @@ public class ChatListItem: ListViewItem, ChatListSearchItemNeighbour {
|
||||
return self.index.pinningIndex != nil
|
||||
}
|
||||
|
||||
public init(presentationData: ChatListPresentationData, context: AccountContext, peerGroupId: PeerGroupId, isInFilter: Bool, index: ChatListIndex, content: ChatListItemContent, editing: Bool, hasActiveRevealControls: Bool, selected: Bool, header: ListViewItemHeader?, enableContextActions: Bool, hiddenOffset: Bool, interaction: ChatListNodeInteraction) {
|
||||
public init(presentationData: ChatListPresentationData, context: AccountContext, peerGroupId: PeerGroupId, filterData: ChatListItemFilterData?, index: ChatListIndex, content: ChatListItemContent, editing: Bool, hasActiveRevealControls: Bool, selected: Bool, header: ListViewItemHeader?, enableContextActions: Bool, hiddenOffset: Bool, interaction: ChatListNodeInteraction) {
|
||||
self.presentationData = presentationData
|
||||
self.peerGroupId = peerGroupId
|
||||
self.isInFilter = isInFilter
|
||||
self.filterData = filterData
|
||||
self.context = context
|
||||
self.index = index
|
||||
self.content = content
|
||||
@ -204,7 +204,15 @@ private func canArchivePeer(id: PeerId, accountPeerId: PeerId) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
private func revealOptions(strings: PresentationStrings, theme: PresentationTheme, isPinned: Bool, isMuted: Bool?, groupId: PeerGroupId, peerId: PeerId, accountPeerId: PeerId, canDelete: Bool, isEditing: Bool, isInFilter: Bool) -> [ItemListRevealOption] {
|
||||
public struct ChatListItemFilterData: Equatable {
|
||||
public var excludesArchived: Bool
|
||||
|
||||
public init(excludesArchived: Bool) {
|
||||
self.excludesArchived = excludesArchived
|
||||
}
|
||||
}
|
||||
|
||||
private func revealOptions(strings: PresentationStrings, theme: PresentationTheme, isPinned: Bool, isMuted: Bool?, groupId: PeerGroupId, peerId: PeerId, accountPeerId: PeerId, canDelete: Bool, isEditing: Bool, filterData: ChatListItemFilterData?) -> [ItemListRevealOption] {
|
||||
var options: [ItemListRevealOption] = []
|
||||
if !isEditing {
|
||||
if case .group = groupId {
|
||||
@ -226,7 +234,7 @@ private func revealOptions(strings: PresentationStrings, theme: PresentationThem
|
||||
if canDelete {
|
||||
options.append(ItemListRevealOption(key: RevealOptionKey.delete.rawValue, title: strings.Common_Delete, icon: deleteIcon, color: theme.list.itemDisclosureActions.destructive.fillColor, textColor: theme.list.itemDisclosureActions.destructive.foregroundColor))
|
||||
}
|
||||
if !isEditing && !isInFilter {
|
||||
if !isEditing && filterData == nil {
|
||||
if case .root = groupId {
|
||||
if canArchivePeer(id: peerId, accountPeerId: accountPeerId) {
|
||||
options.append(ItemListRevealOption(key: RevealOptionKey.archive.rawValue, title: strings.ChatList_ArchiveAction, icon: archiveIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, textColor: theme.list.itemDisclosureActions.inactive.foregroundColor))
|
||||
@ -250,7 +258,7 @@ private func groupReferenceRevealOptions(strings: PresentationStrings, theme: Pr
|
||||
return options
|
||||
}
|
||||
|
||||
private func leftRevealOptions(strings: PresentationStrings, theme: PresentationTheme, isUnread: Bool, isEditing: Bool, isPinned: Bool, isSavedMessages: Bool, groupId: PeerGroupId, isInFilter: Bool) -> [ItemListRevealOption] {
|
||||
private func leftRevealOptions(strings: PresentationStrings, theme: PresentationTheme, isUnread: Bool, isEditing: Bool, isPinned: Bool, isSavedMessages: Bool, groupId: PeerGroupId, filterData: ChatListItemFilterData?) -> [ItemListRevealOption] {
|
||||
if case .group = groupId {
|
||||
return []
|
||||
}
|
||||
@ -260,7 +268,7 @@ private func leftRevealOptions(strings: PresentationStrings, theme: Presentation
|
||||
} else {
|
||||
options.append(ItemListRevealOption(key: RevealOptionKey.toggleMarkedUnread.rawValue, title: strings.DialogList_Unread, icon: unreadIcon, color: theme.list.itemDisclosureActions.accent.fillColor, textColor: theme.list.itemDisclosureActions.accent.foregroundColor))
|
||||
}
|
||||
if !isEditing && !isInFilter {
|
||||
if !isEditing {
|
||||
if isPinned {
|
||||
options.append(ItemListRevealOption(key: RevealOptionKey.unpin.rawValue, title: strings.DialogList_Unpin, icon: unpinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor))
|
||||
} else {
|
||||
@ -1169,9 +1177,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
let isPinned = item.index.pinningIndex != nil
|
||||
|
||||
if item.enableContextActions && !isAd {
|
||||
peerRevealOptions = revealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isPinned: isPinned, isMuted: item.context.account.peerId != item.index.messageIndex.id.peerId ? (currentMutedIconImage != nil) : nil, groupId: item.peerGroupId, peerId: renderedPeer.peerId, accountPeerId: item.context.account.peerId, canDelete: true, isEditing: item.editing, isInFilter: item.isInFilter)
|
||||
peerRevealOptions = revealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isPinned: isPinned, isMuted: item.context.account.peerId != item.index.messageIndex.id.peerId ? (currentMutedIconImage != nil) : nil, groupId: item.peerGroupId, peerId: renderedPeer.peerId, accountPeerId: item.context.account.peerId, canDelete: true, isEditing: item.editing, filterData: item.filterData)
|
||||
if case let .chat(itemPeer) = contentPeer {
|
||||
peerLeftRevealOptions = leftRevealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isUnread: unreadCount.unread, isEditing: item.editing, isPinned: isPinned, isSavedMessages: itemPeer.peerId == item.context.account.peerId, groupId: item.peerGroupId, isInFilter: item.isInFilter)
|
||||
peerLeftRevealOptions = leftRevealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isUnread: unreadCount.unread, isEditing: item.editing, isPinned: isPinned, isSavedMessages: itemPeer.peerId == item.context.account.peerId, groupId: item.peerGroupId, filterData: item.filterData)
|
||||
} else {
|
||||
peerLeftRevealOptions = []
|
||||
}
|
||||
@ -1681,7 +1689,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
|
||||
let titleFrame = self.titleNode.frame
|
||||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x + titleOffset, y: titleFrame.origin.y), size: titleFrame.size))
|
||||
transition.updateFrameAdditive(node: self.titleNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x + titleOffset, y: titleFrame.origin.y), size: titleFrame.size))
|
||||
|
||||
let authorFrame = self.authorNode.frame
|
||||
transition.updateFrame(node: self.authorNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x, y: authorFrame.origin.y), size: authorFrame.size))
|
||||
@ -1689,10 +1697,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
transition.updateFrame(node: self.inputActivitiesNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x, y: self.inputActivitiesNode.frame.minY), size: self.inputActivitiesNode.bounds.size))
|
||||
|
||||
var textFrame = self.textNode.frame
|
||||
let textDeltaX = textFrame.origin.x - contentRect.origin.x
|
||||
transition.animatePositionAdditive(node: self.textNode, offset: CGPoint(x: textDeltaX, y: 0.0))
|
||||
textFrame.origin.x = contentRect.origin.x
|
||||
transition.updateFrame(node: textNode, frame: textFrame)
|
||||
transition.updateFrameAdditive(node: self.textNode, frame: textFrame)
|
||||
|
||||
var contentImageFrame = self.contentImageNode.frame
|
||||
contentImageFrame.origin = textFrame.origin.offsetBy(dx: 1.0, dy: 0.0)
|
||||
|
@ -153,7 +153,7 @@ public struct ChatListNodeState: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, peerGroupId: PeerGroupId, isInFilter: Bool, mode: ChatListNodeMode, entries: [ChatListNodeViewTransitionInsertEntry]) -> [ListViewInsertItem] {
|
||||
private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, peerGroupId: PeerGroupId, filterData: ChatListItemFilterData?, mode: ChatListNodeMode, entries: [ChatListNodeViewTransitionInsertEntry]) -> [ListViewInsertItem] {
|
||||
return entries.map { entry -> ListViewInsertItem in
|
||||
switch entry.entry {
|
||||
case .HeaderEntry:
|
||||
@ -172,7 +172,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
case let .PeerEntry(index, presentationData, message, combinedReadState, isRemovedFromTotalUnreadCount, embeddedState, peer, presence, summaryInfo, editing, hasActiveRevealControls, selected, inputActivities, isAd, hasFailedMessages, isContact):
|
||||
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, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, 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)
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, context: context, peerGroupId: peerGroupId, filterData: filterData, index: index, content: .peer(message: message, peer: peer, combinedReadState: combinedReadState, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, 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, isSelecting, _):
|
||||
let itemPeer = peer.chatMainPeer
|
||||
var chatPeer: Peer?
|
||||
@ -266,20 +266,20 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
case let .HoleEntry(_, theme):
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListHoleItem(theme: theme), directionHint: entry.directionHint)
|
||||
case let .GroupReferenceEntry(index, presentationData, groupId, peers, message, editing, unreadState, revealed, hiddenByDefault):
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, context: context, peerGroupId: peerGroupId, isInFilter: isInFilter, index: index, content: .groupReference(groupId: groupId, peers: peers, message: message, unreadState: unreadState, hiddenByDefault: hiddenByDefault), editing: editing, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: true, hiddenOffset: hiddenByDefault && !revealed, interaction: nodeInteraction), directionHint: entry.directionHint)
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, context: context, peerGroupId: peerGroupId, filterData: filterData, index: index, content: .groupReference(groupId: groupId, peers: peers, message: message, unreadState: unreadState, hiddenByDefault: hiddenByDefault), editing: editing, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: true, hiddenOffset: hiddenByDefault && !revealed, interaction: nodeInteraction), directionHint: entry.directionHint)
|
||||
case let .ArchiveIntro(presentationData):
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListArchiveInfoItem(theme: presentationData.theme, strings: presentationData.strings), directionHint: entry.directionHint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, peerGroupId: PeerGroupId, isInFilter: Bool, mode: ChatListNodeMode, entries: [ChatListNodeViewTransitionUpdateEntry]) -> [ListViewUpdateItem] {
|
||||
private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, peerGroupId: PeerGroupId, filterData: ChatListItemFilterData?, mode: ChatListNodeMode, entries: [ChatListNodeViewTransitionUpdateEntry]) -> [ListViewUpdateItem] {
|
||||
return entries.map { entry -> ListViewUpdateItem in
|
||||
switch entry.entry {
|
||||
case let .PeerEntry(index, presentationData, message, combinedReadState, isRemovedFromTotalUnreadCount, embeddedState, peer, presence, summaryInfo, editing, hasActiveRevealControls, selected, inputActivities, isAd, hasFailedMessages, isContact):
|
||||
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, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, 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)
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, context: context, peerGroupId: peerGroupId, filterData: filterData, index: index, content: .peer(message: message, peer: peer, combinedReadState: combinedReadState, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, 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, isSelecting, _):
|
||||
let itemPeer = peer.chatMainPeer
|
||||
var chatPeer: Peer?
|
||||
@ -329,7 +329,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
case let .HoleEntry(_, theme):
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListHoleItem(theme: theme), directionHint: entry.directionHint)
|
||||
case let .GroupReferenceEntry(index, presentationData, groupId, peers, message, editing, unreadState, revealed, hiddenByDefault):
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, context: context, peerGroupId: peerGroupId, isInFilter: isInFilter, index: index, content: .groupReference(groupId: groupId, peers: peers, message: message, unreadState: unreadState, hiddenByDefault: hiddenByDefault), editing: editing, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: true, hiddenOffset: hiddenByDefault && !revealed, interaction: nodeInteraction), directionHint: entry.directionHint)
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(presentationData: presentationData, context: context, peerGroupId: peerGroupId, filterData: filterData, index: index, content: .groupReference(groupId: groupId, peers: peers, message: message, unreadState: unreadState, hiddenByDefault: hiddenByDefault), editing: editing, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: true, hiddenOffset: hiddenByDefault && !revealed, interaction: nodeInteraction), directionHint: entry.directionHint)
|
||||
case let .ArchiveIntro(presentationData):
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListArchiveInfoItem(theme: presentationData.theme, strings: presentationData.strings), directionHint: entry.directionHint)
|
||||
case .HeaderEntry:
|
||||
@ -349,8 +349,8 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
}
|
||||
}
|
||||
|
||||
private func mappedChatListNodeViewListTransition(context: AccountContext, nodeInteraction: ChatListNodeInteraction, peerGroupId: PeerGroupId, isInFilter: Bool, mode: ChatListNodeMode, transition: ChatListNodeViewTransition) -> ChatListNodeListViewTransition {
|
||||
return ChatListNodeListViewTransition(chatListView: transition.chatListView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(context: context, nodeInteraction: nodeInteraction, peerGroupId: peerGroupId, isInFilter: isInFilter, mode: mode, entries: transition.insertEntries), updateItems: mappedUpdateEntries(context: context, nodeInteraction: nodeInteraction, peerGroupId: peerGroupId, isInFilter: isInFilter, mode: mode, entries: transition.updateEntries), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, adjustScrollToFirstItem: transition.adjustScrollToFirstItem, animateCrossfade: transition.animateCrossfade)
|
||||
private func mappedChatListNodeViewListTransition(context: AccountContext, nodeInteraction: ChatListNodeInteraction, peerGroupId: PeerGroupId, filterData: ChatListItemFilterData?, mode: ChatListNodeMode, transition: ChatListNodeViewTransition) -> ChatListNodeListViewTransition {
|
||||
return ChatListNodeListViewTransition(chatListView: transition.chatListView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(context: context, nodeInteraction: nodeInteraction, peerGroupId: peerGroupId, filterData: filterData, mode: mode, entries: transition.insertEntries), updateItems: mappedUpdateEntries(context: context, nodeInteraction: nodeInteraction, peerGroupId: peerGroupId, filterData: filterData, mode: mode, entries: transition.updateEntries), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, adjustScrollToFirstItem: transition.adjustScrollToFirstItem, animateCrossfade: transition.animateCrossfade)
|
||||
}
|
||||
|
||||
private final class ChatListOpaqueTransactionState {
|
||||
@ -487,6 +487,8 @@ public final class ChatListNode: ListView {
|
||||
|
||||
let preloadItems = Promise<[ChatHistoryPreloadItem]>([])
|
||||
|
||||
var didBeginSelectingChats: (() -> Void)?
|
||||
|
||||
public init(context: AccountContext, groupId: PeerGroupId, chatListFilter: ChatListFilter? = nil, previewing: Bool, fillPreloadItems: Bool, mode: ChatListNodeMode, theme: PresentationTheme, fontSize: PresentationFontSize, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, disableAnimations: Bool) {
|
||||
self.context = context
|
||||
self.groupId = groupId
|
||||
@ -525,17 +527,24 @@ public final class ChatListNode: ListView {
|
||||
disabledPeerSelected(peer)
|
||||
}
|
||||
}, togglePeerSelected: { [weak self] peerId in
|
||||
var didBeginSelecting = false
|
||||
self?.updateState { state in
|
||||
var state = state
|
||||
if state.selectedPeerIds.contains(peerId) {
|
||||
state.selectedPeerIds.remove(peerId)
|
||||
} else {
|
||||
if state.selectedPeerIds.count < 100 {
|
||||
if state.selectedPeerIds.isEmpty {
|
||||
didBeginSelecting = true
|
||||
}
|
||||
state.selectedPeerIds.insert(peerId)
|
||||
}
|
||||
}
|
||||
return state
|
||||
}
|
||||
if didBeginSelecting {
|
||||
self?.didBeginSelectingChats?()
|
||||
}
|
||||
}, additionalCategorySelected: { [weak self] id in
|
||||
self?.additionalCategorySelected?(id)
|
||||
}, messageSelected: { [weak self] peer, message, isAd in
|
||||
@ -560,14 +569,26 @@ public final class ChatListNode: ListView {
|
||||
}
|
||||
}
|
||||
}, setItemPinned: { [weak self] itemId, _ in
|
||||
let _ = (toggleItemPinned(postbox: context.account.postbox, groupId: groupId, itemId: itemId)
|
||||
let location: TogglePeerChatPinnedLocation
|
||||
if let chatListFilter = chatListFilter {
|
||||
location = .filter(chatListFilter.id)
|
||||
} else {
|
||||
location = .group(groupId)
|
||||
}
|
||||
let _ = (toggleItemPinned(postbox: context.account.postbox, location: location, itemId: itemId)
|
||||
|> deliverOnMainQueue).start(next: { result in
|
||||
if let strongSelf = self {
|
||||
switch result {
|
||||
case .done:
|
||||
break
|
||||
case let .limitExceeded(maxCount):
|
||||
strongSelf.presentAlert?(strongSelf.currentState.presentationData.strings.DialogList_PinLimitError("\(maxCount)").0)
|
||||
case .done:
|
||||
break
|
||||
case let .limitExceeded(maxCount):
|
||||
let text: String
|
||||
if chatListFilter != nil {
|
||||
text = strongSelf.currentState.presentationData.strings.DialogList_UnknownPinLimitError
|
||||
} else {
|
||||
text = strongSelf.currentState.presentationData.strings.DialogList_PinLimitError("\(maxCount)").0
|
||||
}
|
||||
strongSelf.presentAlert?(text)
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -856,10 +877,12 @@ public final class ChatListNode: ListView {
|
||||
updatedScrollPosition = nil
|
||||
}
|
||||
|
||||
let isInFilter = filter != nil
|
||||
let filterData = filter.flatMap { filter -> ChatListItemFilterData in
|
||||
return ChatListItemFilterData(excludesArchived: filter.data.excludeArchived)
|
||||
}
|
||||
|
||||
return preparedChatListNodeViewTransition(from: previousView, to: processedView, reason: reason, previewing: previewing, disableAnimations: disableAnimations, account: context.account, scrollPosition: updatedScrollPosition, searchMode: searchMode)
|
||||
|> map({ mappedChatListNodeViewListTransition(context: context, nodeInteraction: nodeInteraction, peerGroupId: groupId, isInFilter: isInFilter, mode: mode, transition: $0) })
|
||||
|> map({ mappedChatListNodeViewListTransition(context: context, nodeInteraction: nodeInteraction, peerGroupId: groupId, filterData: filterData, mode: mode, transition: $0) })
|
||||
|> runOn(prepareOnMainQueue ? Queue.mainQueue() : viewProcessingQueue)
|
||||
}
|
||||
|
||||
@ -1033,30 +1056,33 @@ public final class ChatListNode: ListView {
|
||||
var referenceId: PinnedItemId?
|
||||
var beforeAll = false
|
||||
switch toEntry {
|
||||
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, isAd, _, _):
|
||||
if isAd {
|
||||
beforeAll = true
|
||||
} else {
|
||||
referenceId = .peer(index.messageIndex.id.peerId)
|
||||
}
|
||||
/*case let .GroupReferenceEntry(_, _, groupId, _, _, _, _):
|
||||
referenceId = .group(groupId)*/
|
||||
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, isAd, _, _):
|
||||
if isAd {
|
||||
beforeAll = true
|
||||
} else {
|
||||
referenceId = .peer(index.messageIndex.id.peerId)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if case let .index(index) = fromEntry.sortIndex, let _ = index.pinningIndex {
|
||||
return strongSelf.context.account.postbox.transaction { transaction -> Bool in
|
||||
var itemIds = transaction.getPinnedItemIds(groupId: groupId)
|
||||
let location: TogglePeerChatPinnedLocation
|
||||
if let chatListFilter = chatListFilter {
|
||||
location = .filter(chatListFilter.id)
|
||||
} else {
|
||||
location = .group(groupId)
|
||||
}
|
||||
|
||||
var itemIds = getPinnedItemIds(transaction: transaction, location: location)
|
||||
|
||||
var itemId: PinnedItemId?
|
||||
switch fromEntry {
|
||||
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
itemId = .peer(index.messageIndex.id.peerId)
|
||||
/*case let .GroupReferenceEntry(_, _, groupId, _, _, _, _):
|
||||
itemId = .group(groupId)*/
|
||||
default:
|
||||
break
|
||||
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
itemId = .peer(index.messageIndex.id.peerId)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if let itemId = itemId {
|
||||
@ -1082,7 +1108,7 @@ public final class ChatListNode: ListView {
|
||||
} else {
|
||||
itemIds.append(itemId)
|
||||
}
|
||||
return reorderPinnedItemIds(transaction: transaction, groupId: groupId, itemIds: itemIds)
|
||||
return reorderPinnedItemIds(transaction: transaction, location: location, itemIds: itemIds)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
@ -1467,6 +1493,25 @@ public final class ChatListNode: ListView {
|
||||
}
|
||||
}
|
||||
|
||||
var isNavigationInAFinalState: Bool {
|
||||
switch self.visibleContentOffset() {
|
||||
case let .known(value):
|
||||
if value < navigationBarSearchContentHeight - 1.0 {
|
||||
if abs(value - 0.0) < 1.0 {
|
||||
return true
|
||||
}
|
||||
if abs(value - navigationBarSearchContentHeight) < 1.0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func adjustScrollOffsetForNavigation(isNavigationHidden: Bool) {
|
||||
if self.isNavigationHidden == isNavigationHidden {
|
||||
return
|
||||
|
@ -283,6 +283,7 @@ func chatListNodeEntriesForView(_ view: ChatListView, state: ChatListNodeState,
|
||||
if view.laterIndex == nil && savedMessagesPeer == nil {
|
||||
pinnedIndexOffset += UInt16(view.additionalItemEntries.count)
|
||||
}
|
||||
var filterAfterHole = false
|
||||
loop: for entry in view.entries {
|
||||
switch entry {
|
||||
case let .MessageEntry(index, message, combinedReadState, isRemovedFromTotalUnreadCount, embeddedState, peer, peerPresence, summaryInfo, hasFailed, isContact):
|
||||
@ -301,8 +302,9 @@ func chatListNodeEntriesForView(_ view: ChatListView, state: ChatListNodeState,
|
||||
result.append(.PeerEntry(index: offsetPinnedIndex(index, offset: pinnedIndexOffset), presentationData: state.presentationData, message: updatedMessage, readState: updatedCombinedReadState, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: embeddedState, peer: peer, presence: peerPresence, summaryInfo: summaryInfo, editing: state.editing, hasActiveRevealControls: index.messageIndex.id.peerId == state.peerIdWithRevealedOptions, selected: state.selectedPeerIds.contains(index.messageIndex.id.peerId), inputActivities: state.peerInputActivities?.activities[index.messageIndex.id.peerId], isAd: false, hasFailedMessages: hasFailed, isContact: isContact))
|
||||
case let .HoleEntry(hole):
|
||||
if hole.index.timestamp == Int32.max - 1 {
|
||||
return ([.HeaderEntry], true)
|
||||
//return ([.HeaderEntry], true)
|
||||
}
|
||||
filterAfterHole = true
|
||||
result.append(.HoleEntry(hole, theme: state.presentationData.theme))
|
||||
}
|
||||
}
|
||||
@ -351,11 +353,35 @@ func chatListNodeEntriesForView(_ view: ChatListView, state: ChatListNodeState,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var isLoading: Bool = false
|
||||
|
||||
if filterAfterHole {
|
||||
var seenHole = false
|
||||
for i in (0 ..< result.count).reversed() {
|
||||
if seenHole {
|
||||
result.remove(at: i)
|
||||
} else {
|
||||
switch result[i] {
|
||||
case .HeaderEntry:
|
||||
break
|
||||
case .ArchiveIntro, .AdditionalCategory, .GroupReferenceEntry:
|
||||
break
|
||||
case .PeerEntry:
|
||||
break
|
||||
case .HoleEntry:
|
||||
isLoading = true
|
||||
seenHole = true
|
||||
result.remove(at: i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if result.count >= 1, case .HoleEntry = result[result.count - 1] {
|
||||
return ([.HeaderEntry], true)
|
||||
} else if result.count == 1, case .HoleEntry = result[0] {
|
||||
return ([.HeaderEntry], true)
|
||||
}
|
||||
return (result, false)
|
||||
return (result, isLoading)
|
||||
}
|
||||
|
@ -30,8 +30,14 @@ struct ChatListNodeViewUpdate {
|
||||
}
|
||||
|
||||
func chatListFilterPredicate(filter: ChatListFilterData) -> ChatListFilterPredicate {
|
||||
let includePeers = Set(filter.includePeers)
|
||||
let excludePeers = Set(filter.excludePeers)
|
||||
var includePeers = Set(filter.includePeers.peers)
|
||||
var excludePeers = Set(filter.excludePeers)
|
||||
|
||||
if !filter.includePeers.pinnedPeers.isEmpty {
|
||||
includePeers.subtract(filter.includePeers.pinnedPeers)
|
||||
excludePeers.subtract(filter.includePeers.pinnedPeers)
|
||||
}
|
||||
|
||||
var includeAdditionalPeerGroupIds: [PeerGroupId] = []
|
||||
if !filter.excludeArchived {
|
||||
includeAdditionalPeerGroupIds.append(Namespaces.PeerGroup.archive)
|
||||
@ -41,7 +47,7 @@ func chatListFilterPredicate(filter: ChatListFilterData) -> ChatListFilterPredic
|
||||
if filter.excludeRead {
|
||||
messageTagSummary = ChatListMessageTagSummaryResultCalculation(addCount: ChatListMessageTagSummaryResultComponent(tag: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud), subtractCount: ChatListMessageTagActionsSummaryResultComponent(type: PendingMessageActionType.consumeUnseenPersonalMessage, namespace: Namespaces.Message.Cloud))
|
||||
}
|
||||
return ChatListFilterPredicate(includePeerIds: includePeers, excludePeerIds: excludePeers, messageTagSummary: messageTagSummary, includeAdditionalPeerGroupIds: includeAdditionalPeerGroupIds, include: { peer, isMuted, isUnread, isContact, messageTagSummaryResult in
|
||||
return ChatListFilterPredicate(includePeerIds: includePeers, excludePeerIds: excludePeers, pinnedPeerIds: filter.includePeers.pinnedPeers, messageTagSummary: messageTagSummary, includeAdditionalPeerGroupIds: includeAdditionalPeerGroupIds, include: { peer, isMuted, isUnread, isContact, messageTagSummaryResult in
|
||||
if filter.excludeRead {
|
||||
var effectiveUnread = isUnread
|
||||
if let messageTagSummaryResult = messageTagSummaryResult, messageTagSummaryResult {
|
||||
@ -53,7 +59,10 @@ func chatListFilterPredicate(filter: ChatListFilterData) -> ChatListFilterPredic
|
||||
}
|
||||
if filter.excludeMuted {
|
||||
if isMuted {
|
||||
return false
|
||||
if let messageTagSummaryResult = messageTagSummaryResult, messageTagSummaryResult {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
if !filter.categories.contains(.contacts) && isContact {
|
||||
|
@ -188,9 +188,22 @@ func preparedChatListNodeViewTransition(from fromView: ChatListNodeView?, to toV
|
||||
}
|
||||
}
|
||||
} else if fromView.filteredEntries.isEmpty || fromView.filter != toView.filter {
|
||||
options.remove(.AnimateInsertion)
|
||||
options.remove(.AnimateAlpha)
|
||||
fromEmptyView = true
|
||||
var updateEmpty = true
|
||||
if !fromView.filteredEntries.isEmpty, let fromFilter = fromView.filter, let toFilter = toView.filter, fromFilter.data.includePeers.pinnedPeers != toFilter.data.includePeers.pinnedPeers {
|
||||
var fromData = fromFilter.data
|
||||
let toData = toFilter.data
|
||||
fromData.includePeers = toData.includePeers
|
||||
if fromData == toData {
|
||||
options.insert(.AnimateInsertion)
|
||||
updateEmpty = false
|
||||
}
|
||||
}
|
||||
|
||||
if updateEmpty {
|
||||
options.remove(.AnimateInsertion)
|
||||
options.remove(.AnimateAlpha)
|
||||
fromEmptyView = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fromEmptyView = true
|
||||
|
@ -10,8 +10,8 @@ import Postbox
|
||||
import TelegramUIPreferences
|
||||
import TelegramCore
|
||||
|
||||
func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatListFilter, Int, Bool)]), NoError> {
|
||||
return updatedChatListFilters(postbox: context.account.postbox)
|
||||
func chatListFilterItems(postbox: Postbox) -> Signal<(Int, [(ChatListFilter, Int, Bool)]), NoError> {
|
||||
return updatedChatListFilters(postbox: postbox)
|
||||
|> distinctUntilChanged
|
||||
|> mapToSignal { filters -> Signal<(Int, [(ChatListFilter, Int, Bool)]), NoError> in
|
||||
var unreadCountItems: [UnreadMessageCountsItem] = []
|
||||
@ -19,7 +19,7 @@ func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatListFilt
|
||||
var additionalPeerIds = Set<PeerId>()
|
||||
var additionalGroupIds = Set<PeerGroupId>()
|
||||
for filter in filters {
|
||||
additionalPeerIds.formUnion(filter.data.includePeers)
|
||||
additionalPeerIds.formUnion(filter.data.includePeers.peers)
|
||||
additionalPeerIds.formUnion(filter.data.excludePeers)
|
||||
if !filter.data.excludeArchived {
|
||||
additionalGroupIds.insert(Namespaces.PeerGroup.archive)
|
||||
@ -40,8 +40,8 @@ func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatListFilt
|
||||
keys.append(.basicPeer(peerId))
|
||||
}
|
||||
|
||||
return combineLatest(queue: context.account.postbox.queue,
|
||||
context.account.postbox.combinedView(keys: keys),
|
||||
return combineLatest(queue: postbox.queue,
|
||||
postbox.combinedView(keys: keys),
|
||||
Signal<Bool, NoError>.single(true)
|
||||
)
|
||||
|> map { view, _ -> (Int, [(ChatListFilter, Int, Bool)]) in
|
||||
@ -51,7 +51,7 @@ func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatListFilt
|
||||
|
||||
var result: [(ChatListFilter, Int, Bool)] = []
|
||||
|
||||
var peerTagAndCount: [PeerId: (PeerSummaryCounterTags, Int, Bool)] = [:]
|
||||
var peerTagAndCount: [PeerId: (PeerSummaryCounterTags, Int, Bool, PeerGroupId?)] = [:]
|
||||
|
||||
var totalStates: [PeerGroupId: ChatListTotalUnreadState] = [:]
|
||||
for entry in unreadCounts.entries {
|
||||
@ -63,7 +63,7 @@ func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatListFilt
|
||||
case let .peer(peerId, state):
|
||||
if let state = state, state.isUnread {
|
||||
if let peerView = view.views[.basicPeer(peerId)] as? BasicPeerView, let peer = peerView.peer {
|
||||
let tag = context.account.postbox.seedConfiguration.peerSummaryCounterTags(peer, peerView.isContact)
|
||||
let tag = postbox.seedConfiguration.peerSummaryCounterTags(peer, peerView.isContact)
|
||||
|
||||
var peerCount = Int(state.count)
|
||||
if state.isUnread {
|
||||
@ -71,9 +71,9 @@ func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatListFilt
|
||||
}
|
||||
|
||||
if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings, case .muted = notificationSettings.muteState {
|
||||
peerTagAndCount[peerId] = (tag, peerCount, false)
|
||||
peerTagAndCount[peerId] = (tag, peerCount, false, peerView.groupId)
|
||||
} else {
|
||||
peerTagAndCount[peerId] = (tag, peerCount, true)
|
||||
peerTagAndCount[peerId] = (tag, peerCount, true, peerView.groupId)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -146,10 +146,21 @@ func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatListFilt
|
||||
}
|
||||
}
|
||||
}
|
||||
for peerId in filter.data.includePeers {
|
||||
if let (tag, peerCount, hasUnmuted) = peerTagAndCount[peerId] {
|
||||
if !tags.contains(tag) {
|
||||
if peerCount != 0 {
|
||||
for peerId in filter.data.includePeers.peers {
|
||||
if let (tag, peerCount, hasUnmuted, groupId) = peerTagAndCount[peerId] {
|
||||
if let groupId = groupId, !tags.contains(tag) {
|
||||
let matchesGroup: Bool
|
||||
switch groupId {
|
||||
case .root:
|
||||
matchesGroup = true
|
||||
case .group:
|
||||
if groupId == Namespaces.PeerGroup.archive {
|
||||
matchesGroup = !filter.data.excludeArchived
|
||||
} else {
|
||||
matchesGroup = false
|
||||
}
|
||||
}
|
||||
if matchesGroup && peerCount != 0 {
|
||||
count += 1
|
||||
if hasUnmuted {
|
||||
hasUnmutedUnread = true
|
||||
@ -159,9 +170,20 @@ func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatListFilt
|
||||
}
|
||||
}
|
||||
for peerId in filter.data.excludePeers {
|
||||
if let (tag, peerCount, _) = peerTagAndCount[peerId] {
|
||||
if tags.contains(tag) {
|
||||
if peerCount != 0 {
|
||||
if let (tag, peerCount, _, groupId) = peerTagAndCount[peerId] {
|
||||
if let groupId = groupId, tags.contains(tag) {
|
||||
let matchesGroup: Bool
|
||||
switch groupId {
|
||||
case .root:
|
||||
matchesGroup = true
|
||||
case .group:
|
||||
if groupId == Namespaces.PeerGroup.archive {
|
||||
matchesGroup = !filter.data.excludeArchived
|
||||
} else {
|
||||
matchesGroup = false
|
||||
}
|
||||
}
|
||||
if matchesGroup && peerCount != 0 {
|
||||
count -= 1
|
||||
}
|
||||
}
|
||||
|
@ -152,7 +152,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
||||
}
|
||||
}
|
||||
|
||||
func item(context: AccountContext, presentationData: PresentationData, interaction: ContactListNodeInteraction) -> ListViewItem {
|
||||
func item(context: AccountContext, presentationData: PresentationData, interaction: ContactListNodeInteraction, isSearch: Bool) -> ListViewItem {
|
||||
switch self {
|
||||
case let .search(theme, strings):
|
||||
return ChatListSearchItem(theme: theme, placeholder: strings.Contacts_SearchLabel, activate: {
|
||||
@ -177,7 +177,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
||||
case let .option(_, option, header, theme, _):
|
||||
return ContactListActionItem(presentationData: ItemListPresentationData(presentationData), title: option.title, icon: option.icon, clearHighlightAutomatically: false, header: header, action: option.action)
|
||||
case let .peer(_, peer, presence, header, selection, theme, strings, dateTimeFormat, nameSortOrder, nameDisplayOrder, enabled):
|
||||
let status: ContactsPeerItemStatus
|
||||
var status: ContactsPeerItemStatus
|
||||
let itemPeer: ContactsPeerItemPeer
|
||||
var isContextActionEnabled = false
|
||||
switch peer {
|
||||
@ -214,6 +214,9 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
||||
status = .none
|
||||
itemPeer = .deviceContact(stableId: id, contact: contact)
|
||||
}
|
||||
if isSearch {
|
||||
status = .none
|
||||
}
|
||||
var itemContextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
|
||||
if isContextActionEnabled, let contextAction = interaction.contextAction {
|
||||
itemContextAction = { node, gesture in
|
||||
@ -227,7 +230,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
||||
}
|
||||
}
|
||||
}
|
||||
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .peer, peer: itemPeer, status: status, enabled: enabled, selection: selection, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { _ in
|
||||
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: isSearch ? .generalSearch : .peer, peer: itemPeer, status: status, enabled: enabled, selection: selection, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { _ in
|
||||
interaction.openPeer(peer)
|
||||
}, itemHighlighting: interaction.itemHighlighting, contextAction: itemContextAction)
|
||||
}
|
||||
@ -608,12 +611,12 @@ private func contactListNodeEntries(accountPeer: Peer?, peers: [ContactListPeer]
|
||||
return entries
|
||||
}
|
||||
|
||||
private func preparedContactListNodeTransition(context: AccountContext, presentationData: PresentationData, from fromEntries: [ContactListNodeEntry], to toEntries: [ContactListNodeEntry], interaction: ContactListNodeInteraction, firstTime: Bool, isEmpty: Bool, generateIndexSections: Bool, animation: ContactListAnimation) -> ContactsListNodeTransition {
|
||||
private func preparedContactListNodeTransition(context: AccountContext, presentationData: PresentationData, from fromEntries: [ContactListNodeEntry], to toEntries: [ContactListNodeEntry], interaction: ContactListNodeInteraction, firstTime: Bool, isEmpty: Bool, generateIndexSections: Bool, animation: ContactListAnimation, isSearch: Bool) -> ContactsListNodeTransition {
|
||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
||||
|
||||
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
||||
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction), directionHint: nil) }
|
||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction), directionHint: nil) }
|
||||
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction, isSearch: isSearch), directionHint: nil) }
|
||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction, isSearch: isSearch), directionHint: nil) }
|
||||
|
||||
var shouldFixScroll = false
|
||||
var indexSections: [String] = []
|
||||
@ -674,7 +677,7 @@ private struct ContactsListNodeTransition {
|
||||
public enum ContactListPresentation {
|
||||
case orderedByPresence(options: [ContactListAdditionalOption])
|
||||
case natural(options: [ContactListAdditionalOption], includeChatList: Bool)
|
||||
case search(signal: Signal<String, NoError>, searchChatList: Bool, searchDeviceContacts: Bool, searchGroups: Bool, searchChannels: Bool)
|
||||
case search(signal: Signal<String, NoError>, searchChatList: Bool, searchDeviceContacts: Bool, searchGroups: Bool, searchChannels: Bool, globalSearch: Bool)
|
||||
|
||||
public var sortOrder: ContactsSortOrder? {
|
||||
switch self {
|
||||
@ -783,7 +786,7 @@ public final class ContactListNode: ASDisplayNode {
|
||||
private var authorizationNode: PermissionContentNode
|
||||
private let displayPermissionPlaceholder: Bool
|
||||
|
||||
public init(context: AccountContext, presentation: Signal<ContactListPresentation, NoError>, filters: [ContactListFilter] = [.excludeSelf], selectionState: ContactListNodeGroupSelectionState? = nil, displayPermissionPlaceholder: Bool = true, displaySortOptions: Bool = false, contextAction: ((Peer, ASDisplayNode, ContextGesture?) -> Void)? = nil) {
|
||||
public init(context: AccountContext, presentation: Signal<ContactListPresentation, NoError>, filters: [ContactListFilter] = [.excludeSelf], selectionState: ContactListNodeGroupSelectionState? = nil, displayPermissionPlaceholder: Bool = true, displaySortOptions: Bool = false, contextAction: ((Peer, ASDisplayNode, ContextGesture?) -> Void)? = nil, isSearch: Bool = false) {
|
||||
self.context = context
|
||||
self.filters = filters
|
||||
self.displayPermissionPlaceholder = displayPermissionPlaceholder
|
||||
@ -917,7 +920,7 @@ public final class ContactListNode: ASDisplayNode {
|
||||
includeChatList = natural.includeChatList
|
||||
}
|
||||
|
||||
if case let .search(query, searchChatList, searchDeviceContacts, searchGroups, searchChannels) = presentation {
|
||||
if case let .search(query, searchChatList, searchDeviceContacts, searchGroups, searchChannels, globalSearch) = presentation {
|
||||
return query
|
||||
|> mapToSignal { query in
|
||||
let foundLocalContacts: Signal<([FoundPeer], [PeerId: PeerPresence]), NoError>
|
||||
@ -926,6 +929,7 @@ public final class ContactListNode: ASDisplayNode {
|
||||
foundLocalContacts = foundChatListPeers
|
||||
|> mapToSignal { peers -> Signal<([FoundPeer], [PeerId: PeerPresence]), NoError> in
|
||||
var resultPeers: [FoundPeer] = []
|
||||
|
||||
for peer in peers {
|
||||
if searchGroups || searchChannels {
|
||||
let mainPeer = peer.chatMainPeer
|
||||
@ -975,12 +979,15 @@ public final class ContactListNode: ASDisplayNode {
|
||||
return (peers.map({ FoundPeer(peer: $0, subscribers: nil) }), presences)
|
||||
}
|
||||
}
|
||||
let foundRemoteContacts: Signal<([FoundPeer], [FoundPeer]), NoError> = .single(([], []))
|
||||
|> then(
|
||||
searchPeers(account: context.account, query: query)
|
||||
|> map { ($0.0, $0.1) }
|
||||
|> delay(0.2, queue: Queue.concurrentDefaultQueue())
|
||||
)
|
||||
var foundRemoteContacts: Signal<([FoundPeer], [FoundPeer]), NoError> = .single(([], []))
|
||||
if globalSearch {
|
||||
foundRemoteContacts = foundRemoteContacts
|
||||
|> then(
|
||||
searchPeers(account: context.account, query: query)
|
||||
|> map { ($0.0, $0.1) }
|
||||
|> delay(0.2, queue: Queue.concurrentDefaultQueue())
|
||||
)
|
||||
}
|
||||
let foundDeviceContacts: Signal<[DeviceContactStableId: (DeviceContactBasicData, PeerId?)], NoError>
|
||||
if searchDeviceContacts {
|
||||
foundDeviceContacts = context.sharedContext.contactDataManager?.search(query: query) ?? .single([:])
|
||||
@ -988,16 +995,21 @@ public final class ContactListNode: ASDisplayNode {
|
||||
foundDeviceContacts = .single([:])
|
||||
}
|
||||
|
||||
return combineLatest(foundLocalContacts, foundRemoteContacts, foundDeviceContacts, selectionStateSignal, presentationDataPromise.get())
|
||||
|> mapToQueue { localPeersAndStatuses, remotePeers, deviceContacts, selectionState, presentationData -> Signal<ContactsListNodeTransition, NoError> in
|
||||
let accountPeer = context.account.postbox.loadedPeerWithId(context.account.peerId)
|
||||
|> take(1)
|
||||
|
||||
return combineLatest(accountPeer, foundLocalContacts, foundRemoteContacts, foundDeviceContacts, selectionStateSignal, presentationDataPromise.get())
|
||||
|> mapToQueue { accountPeer, localPeersAndStatuses, remotePeers, deviceContacts, selectionState, presentationData -> Signal<ContactsListNodeTransition, NoError> in
|
||||
let signal = deferred { () -> Signal<ContactsListNodeTransition, NoError> in
|
||||
var existingPeerIds = Set<PeerId>()
|
||||
var disabledPeerIds = Set<PeerId>()
|
||||
|
||||
var existingNormalizedPhoneNumbers = Set<DeviceContactNormalizedPhoneNumber>()
|
||||
var excludeSelf = false
|
||||
for filter in filters {
|
||||
switch filter {
|
||||
case .excludeSelf:
|
||||
excludeSelf = true
|
||||
existingPeerIds.insert(context.account.peerId)
|
||||
case let .exclude(peerIds):
|
||||
existingPeerIds = existingPeerIds.union(peerIds)
|
||||
@ -1007,6 +1019,15 @@ public final class ContactListNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
var peers: [ContactListPeer] = []
|
||||
|
||||
if !excludeSelf && !existingPeerIds.contains(accountPeer.id) {
|
||||
let lowercasedQuery = query.lowercased()
|
||||
if presentationData.strings.DialogList_SavedMessages.lowercased().hasPrefix(lowercasedQuery) || "saved messages".hasPrefix(lowercasedQuery) {
|
||||
existingPeerIds.insert(accountPeer.id)
|
||||
peers.append(.peer(peer: accountPeer, isGlobal: false, participantCount: nil))
|
||||
}
|
||||
}
|
||||
|
||||
for peer in localPeersAndStatuses.0 {
|
||||
if !existingPeerIds.contains(peer.peer.id) {
|
||||
existingPeerIds.insert(peer.peer.id)
|
||||
@ -1094,7 +1115,7 @@ public final class ContactListNode: ASDisplayNode {
|
||||
|
||||
let entries = contactListNodeEntries(accountPeer: nil, peers: peers, presences: localPeersAndStatuses.1, presentation: presentation, selectionState: selectionState, theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, disabledPeerIds: disabledPeerIds, authorizationStatus: .allowed, warningSuppressed: (true, true), displaySortOptions: false)
|
||||
let previous = previousEntries.swap(entries)
|
||||
return .single(preparedContactListNodeTransition(context: context, presentationData: presentationData, from: previous ?? [], to: entries, interaction: interaction, firstTime: previous == nil, isEmpty: false, generateIndexSections: generateSections, animation: .none))
|
||||
return .single(preparedContactListNodeTransition(context: context, presentationData: presentationData, from: previous ?? [], to: entries, interaction: interaction, firstTime: previous == nil, isEmpty: false, generateIndexSections: generateSections, animation: .none, isSearch: isSearch))
|
||||
}
|
||||
|
||||
if OSAtomicCompareAndSwap32(1, 0, &firstTime) {
|
||||
@ -1200,7 +1221,7 @@ public final class ContactListNode: ASDisplayNode {
|
||||
animation = .none
|
||||
}
|
||||
|
||||
return .single(preparedContactListNodeTransition(context: context, presentationData: presentationData, from: previous ?? [], to: entries, interaction: interaction, firstTime: previous == nil, isEmpty: isEmpty, generateIndexSections: generateSections, animation: animation))
|
||||
return .single(preparedContactListNodeTransition(context: context, presentationData: presentationData, from: previous ?? [], to: entries, interaction: interaction, firstTime: previous == nil, isEmpty: isEmpty, generateIndexSections: generateSections, animation: animation, isSearch: isSearch))
|
||||
}
|
||||
|
||||
if OSAtomicCompareAndSwap32(1, 0, &firstTime) {
|
||||
|
@ -1424,7 +1424,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
||||
} else if topItemFound {
|
||||
self.scroller.contentSize = CGSize(width: self.visibleSize.width, height: infiniteScrollSize * 2.0)
|
||||
self.lastContentOffset = CGPoint(x: 0.0, y: -topItemEdge)
|
||||
self.scroller.contentOffset = self.lastContentOffset
|
||||
if self.scroller.contentOffset != self.lastContentOffset {
|
||||
self.scroller.contentOffset = self.lastContentOffset
|
||||
}
|
||||
} else if bottomItemFound {
|
||||
self.scroller.contentSize = CGSize(width: self.visibleSize.width, height: infiniteScrollSize * 2.0)
|
||||
self.lastContentOffset = CGPoint(x: 0.0, y: infiniteScrollSize * 2.0 - bottomItemEdge)
|
||||
@ -3693,13 +3695,14 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
||||
var offsetRanges = OffsetRanges()
|
||||
|
||||
if let reorderOffset = self.reorderNode?.currentOffset(), !self.itemNodes.isEmpty {
|
||||
if reorderOffset < self.insets.top + 10.0 {
|
||||
if self.itemNodes[0].apparentFrame.minY < self.insets.top {
|
||||
let effectiveInsets = self.visualInsets ?? self.insets
|
||||
if reorderOffset < effectiveInsets.top + 10.0 {
|
||||
if self.itemNodes[0].apparentFrame.minY < effectiveInsets.top {
|
||||
continueAnimations = true
|
||||
offsetRanges.offset(IndexRange(first: 0, last: Int.max), offset: 6.0)
|
||||
}
|
||||
} else if reorderOffset > self.visibleSize.height - self.insets.bottom - 10.0 {
|
||||
if self.itemNodes[self.itemNodes.count - 1].apparentFrame.maxY > self.visibleSize.height - self.insets.bottom {
|
||||
} else if reorderOffset > self.visibleSize.height - effectiveInsets.bottom - 10.0 {
|
||||
if self.itemNodes[self.itemNodes.count - 1].apparentFrame.maxY > self.visibleSize.height - effectiveInsets.bottom {
|
||||
continueAnimations = true
|
||||
offsetRanges.offset(IndexRange(first: 0, last: Int.max), offset: -6.0)
|
||||
}
|
||||
|
@ -11,9 +11,11 @@ public enum ToolbarActionOption {
|
||||
final class TabBarControllerNode: ASDisplayNode {
|
||||
private var theme: TabBarControllerTheme
|
||||
let tabBarNode: TabBarNode
|
||||
private let disabledOverlayNode: ASDisplayNode
|
||||
private let navigationBar: NavigationBar?
|
||||
private var toolbarNode: ToolbarNode?
|
||||
private let toolbarActionSelected: (ToolbarActionOption) -> Void
|
||||
private let disabledPressed: () -> Void
|
||||
|
||||
var currentControllerNode: ASDisplayNode? {
|
||||
didSet {
|
||||
@ -25,11 +27,15 @@ final class TabBarControllerNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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, disabledPressed: @escaping () -> Void) {
|
||||
self.theme = theme
|
||||
self.navigationBar = navigationBar
|
||||
self.tabBarNode = TabBarNode(theme: theme, itemSelected: itemSelected, contextAction: contextAction, swipeAction: swipeAction)
|
||||
self.disabledOverlayNode = ASDisplayNode()
|
||||
self.disabledOverlayNode.backgroundColor = theme.backgroundColor.withAlphaComponent(0.5)
|
||||
self.disabledOverlayNode.alpha = 0.0
|
||||
self.toolbarActionSelected = toolbarActionSelected
|
||||
self.disabledPressed = disabledPressed
|
||||
|
||||
super.init()
|
||||
|
||||
@ -40,6 +46,19 @@ final class TabBarControllerNode: ASDisplayNode {
|
||||
self.backgroundColor = theme.backgroundColor
|
||||
|
||||
self.addSubnode(self.tabBarNode)
|
||||
self.addSubnode(self.disabledOverlayNode)
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
self.disabledOverlayNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.disabledTapGesture(_:))))
|
||||
}
|
||||
|
||||
@objc private func disabledTapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
self.disabledPressed()
|
||||
}
|
||||
}
|
||||
|
||||
func updateTheme(_ theme: TabBarControllerTheme) {
|
||||
@ -47,9 +66,14 @@ final class TabBarControllerNode: ASDisplayNode {
|
||||
self.backgroundColor = theme.backgroundColor
|
||||
|
||||
self.tabBarNode.updateTheme(theme)
|
||||
self.disabledOverlayNode.backgroundColor = theme.backgroundColor.withAlphaComponent(0.5)
|
||||
self.toolbarNode?.updateTheme(theme)
|
||||
}
|
||||
|
||||
func updateIsTabBarEnabled(_ value: Bool, transition: ContainedViewLayoutTransition) {
|
||||
transition.updateAlpha(node: self.disabledOverlayNode, alpha: value ? 0.0 : 1.0)
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, toolbar: Toolbar?, transition: ContainedViewLayoutTransition) {
|
||||
var tabBarHeight: CGFloat
|
||||
var options: ContainerViewLayoutInsetOptions = []
|
||||
@ -68,6 +92,8 @@ final class TabBarControllerNode: ASDisplayNode {
|
||||
transition.updateFrame(node: self.tabBarNode, frame: tabBarFrame)
|
||||
self.tabBarNode.updateLayout(size: layout.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: bottomInset, transition: transition)
|
||||
|
||||
transition.updateFrame(node: self.disabledOverlayNode, frame: tabBarFrame)
|
||||
|
||||
if let toolbar = toolbar {
|
||||
if let toolbarNode = self.toolbarNode {
|
||||
transition.updateFrame(node: toolbarNode, frame: tabBarFrame)
|
||||
|
@ -181,6 +181,10 @@ open class TabBarController: ViewController {
|
||||
return false
|
||||
}
|
||||
|
||||
public func updateIsTabBarEnabled(_ value: Bool, transition: ContainedViewLayoutTransition) {
|
||||
self.tabBarControllerNode.updateIsTabBarEnabled(value, transition: transition)
|
||||
}
|
||||
|
||||
override open func loadDisplayNode() {
|
||||
self.displayNode = TabBarControllerNode(theme: self.theme, navigationBar: self.navigationBar, itemSelected: { [weak self] index, longTap, itemNodes in
|
||||
if let strongSelf = self {
|
||||
@ -264,6 +268,8 @@ open class TabBarController: ViewController {
|
||||
}
|
||||
}, toolbarActionSelected: { [weak self] action in
|
||||
self?.currentController?.toolbarActionSelected(action: action)
|
||||
}, disabledPressed: { [weak self] in
|
||||
self?.currentController?.tabBarDisabledAction()
|
||||
})
|
||||
|
||||
self.updateSelectedIndex()
|
||||
|
@ -636,6 +636,9 @@ public enum TabBarItemContextActionType {
|
||||
open func tabBarItemContextAction(sourceNode: ContextExtractedContentContainingNode, gesture: ContextGesture) {
|
||||
}
|
||||
|
||||
open func tabBarDisabledAction() {
|
||||
}
|
||||
|
||||
open func tabBarItemSwipeAction(direction: TabBarItemSwipeDirection) {
|
||||
}
|
||||
}
|
||||
|
@ -13,14 +13,21 @@ import Cocoa
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
struct ChartVisibilityItem {
|
||||
var title: String
|
||||
var color: GColor
|
||||
|
||||
static func generateItemsFrames(for chartWidth: CGFloat, items: [ChartVisibilityItem]) -> [CGRect] {
|
||||
public struct ChartVisibilityItem {
|
||||
public var title: String
|
||||
public var color: GColor
|
||||
public init(title: String, color: GColor) {
|
||||
self.title = title
|
||||
self.color = color
|
||||
}
|
||||
public static func generateItemsFrames(for chartWidth: CGFloat, items: [ChartVisibilityItem]) -> [CGRect] {
|
||||
|
||||
if items.count == 1 {
|
||||
return []
|
||||
}
|
||||
|
||||
var previousPoint = CGPoint(x: ChatVisibilityItemConstants.insets.left, y: ChatVisibilityItemConstants.insets.top)
|
||||
var frames: [CGRect] = []
|
||||
|
||||
for item in items {
|
||||
let labelSize = textSize(with: item.title, font: ChatVisibilityItemConstants.textFont)
|
||||
let width = (labelSize.width + ChatVisibilityItemConstants.labelTextApproxInsets).rounded(.up)
|
||||
@ -35,7 +42,7 @@ struct ChartVisibilityItem {
|
||||
}
|
||||
previousPoint.x += width + ChatVisibilityItemConstants.itemSpacing
|
||||
}
|
||||
|
||||
|
||||
return frames
|
||||
}
|
||||
|
||||
|
@ -128,6 +128,7 @@ public class BaseChartController: ChartThemeContainer {
|
||||
return ChartVisibilityItem(title: value.name, color: value.color)
|
||||
}
|
||||
let frames = ChartVisibilityItem.generateItemsFrames(for: width, items: items)
|
||||
|
||||
guard let lastFrame = frames.last else { return height }
|
||||
|
||||
height += lastFrame.maxY
|
||||
|
@ -15,11 +15,11 @@ import UIKit
|
||||
|
||||
class ChartDetailsRenderer: BaseChartRenderer, ChartThemeContainer {
|
||||
private lazy var colorAnimator = AnimationController<CGFloat>(current: 1, refreshClosure: refreshClosure)
|
||||
private var fromColorMode: ChartTheme = ChartTheme.defaultDayTheme
|
||||
private var currentColorMode: ChartTheme = ChartTheme.defaultDayTheme
|
||||
private var fromTheme: ChartTheme = ChartTheme.defaultDayTheme
|
||||
private var currentTheme: ChartTheme = ChartTheme.defaultDayTheme
|
||||
func apply(theme: ChartTheme, animated: Bool) {
|
||||
fromColorMode = currentColorMode
|
||||
currentColorMode = theme
|
||||
fromTheme = currentTheme
|
||||
currentTheme = theme
|
||||
colorAnimator.set(current: 1)
|
||||
}
|
||||
|
||||
@ -91,11 +91,11 @@ class ChartDetailsRenderer: BaseChartRenderer, ChartThemeContainer {
|
||||
|
||||
let totalWidth: CGFloat = max(prefixesWidth + labelsWidth + valuesWidth, titleWidth + iconWidth) + margins * 2
|
||||
let totalHeight: CGFloat = CGFloat(detailsViewModel.values.count + 1) * rowHeight + margins * 2
|
||||
let backgroundColor = GColor.valueBetween(start: fromColorMode.chartDetailsViewColor,
|
||||
end: currentColorMode.chartDetailsViewColor,
|
||||
let backgroundColor = GColor.valueBetween(start: fromTheme.chartDetailsViewColor,
|
||||
end: currentTheme.chartDetailsViewColor,
|
||||
offset: Double(colorAnimator.current))
|
||||
let titleAndTextColor = GColor.valueBetween(start: fromColorMode.chartDetailsTextColor,
|
||||
end: currentColorMode.chartDetailsTextColor,
|
||||
let titleAndTextColor = GColor.valueBetween(start: fromTheme.chartDetailsTextColor,
|
||||
end: currentTheme.chartDetailsTextColor,
|
||||
offset: Double(colorAnimator.current))
|
||||
let detailsViewFrame: CGRect
|
||||
if totalWidth + detailViewTopOffset > detailsViewPosition {
|
||||
|
@ -26,11 +26,13 @@ extension NSAttributedString {
|
||||
}
|
||||
|
||||
func textSize(with string: String, font: NSFont) -> CGSize {
|
||||
|
||||
let attributedString:NSAttributedString = NSAttributedString(string: string, attributes: [.font : font])
|
||||
let layout = LabelNode.layoutText(attributedString, CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude))
|
||||
var size:CGSize = layout.0.size
|
||||
size.width = ceil(size.width)
|
||||
size.height = ceil(size.height)
|
||||
|
||||
return size
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
//
|
||||
// colorMode.swift
|
||||
// theme.swift
|
||||
// GraphTest
|
||||
//
|
||||
// Created by Andrew Solovey on 15/03/2019.
|
||||
@ -66,6 +66,9 @@ public class ChartTheme {
|
||||
|
||||
public static var defaultDayTheme = ChartTheme(chartTitleColor: GColor.black, actionButtonColor: GColor(red: 53/255.0, green: 120/255.0, blue: 246/255.0, alpha: 1.0), tableBackgroundColor: GColor(red: 239/255.0, green: 239/255.0, blue: 244/255.0, alpha: 1.0), chartBackgroundColor: GColor(red: 254/255.0, green: 254/255.0, blue: 254/255.0, alpha: 1.0), tableSeparatorColor: GColor(red: 200/255.0, green: 199/255.0, blue: 204/255.0, alpha: 1.0), chartLabelsColor: GColor(red: 37/255.0, green: 37/255.0, blue: 41/255.0, alpha: 0.5), chartHelperLinesColor: GColor(red: 24/255.0, green: 45/255.0, blue: 59/255.0, alpha: 0.1), chartStrongLinesColor: GColor(red: 24/255.0, green: 45/255.0, blue: 59/255.0, alpha: 0.35), barChartStrongLinesColor: GColor(red: 37/255.0, green: 37/255.0, blue: 41/255.0, alpha: 0.2), chartDetailsTextColor: GColor(red: 109/255.0, green: 109/255.0, blue: 114/255.0, alpha: 1.0), chartDetailsArrowColor: GColor(red: 197/255.0, green: 199/255.0, blue: 205/255.0, alpha: 1.0), chartDetailsViewColor: GColor(red: 245/255.0, green: 245/255.0, blue: 251/255.0, alpha: 1.0), descriptionActionColor: GColor(red: 1/255.0, green: 125/255.0, blue: 229/255.0, alpha: 1.0), rangeViewFrameColor: GColor(red: 202/255.0, green: 212/255.0, blue: 222/255.0, alpha: 1.0), rangeViewTintColor: GColor(red: 239/255.0, green: 239/255.0, blue: 244/255.0, alpha: 0.5), rangeViewMarkerColor: GColor.white)
|
||||
|
||||
|
||||
public static var defaultNightTheme = ChartTheme(chartTitleColor: GColor.white, actionButtonColor: GColor(red: 84/255.0, green: 164/255.0, blue: 247/255.0, alpha: 1.0), tableBackgroundColor: GColor(red: 24/255.0, green: 34/255.0, blue: 45/255.0, alpha: 1.0), chartBackgroundColor: GColor(red: 34/255.0, green: 47/255.0, blue: 63/255.0, alpha: 1.0), tableSeparatorColor: GColor(red: 18/255.0, green: 26/255.0, blue: 35/255.0, alpha: 1.0), chartLabelsColor: GColor(red: 186/255.0, green: 204/255.0, blue: 225/255.0, alpha: 0.6), chartHelperLinesColor: GColor(red: 133/255.0, green: 150/255.0, blue: 171/255.0, alpha: 0.20), chartStrongLinesColor: GColor(red: 186/255.0, green: 204/255.0, blue: 225/255.0, alpha: 0.45), barChartStrongLinesColor: GColor(red: 186/255.0, green: 204/255.0, blue: 225/255.0, alpha: 0.45), chartDetailsTextColor: GColor(red: 254/255.0, green: 254/255.0, blue: 254/255.0, alpha: 1.0), chartDetailsArrowColor: GColor(red: 76/255.0, green: 84/255.0, blue: 96/255.0, alpha: 1.0), chartDetailsViewColor: GColor(red: 25/255.0, green: 35/255.0, blue: 47/255.0, alpha: 1.0), descriptionActionColor: GColor(red: 24/255.0, green: 145/255.0, blue: 255/255.0, alpha: 1.0), rangeViewFrameColor: GColor(red: 53/255.0, green: 70/255.0, blue: 89/255.0, alpha: 1.0), rangeViewTintColor: GColor(red: 24/255.0, green: 34/255.0, blue: 45/255.0, alpha: 0.5), rangeViewMarkerColor: GColor.white)
|
||||
|
||||
|
||||
// public var actionButtonColor: GColor { // Кнопка Zoom Out/ Смена режима день/ночь
|
||||
// switch self {
|
||||
|
@ -174,6 +174,13 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, UIGestureRecognizerD
|
||||
}
|
||||
}
|
||||
|
||||
open func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
/*if gestureRecognizer === self.recognizer && otherGestureRecognizer is InteractiveTransitionGestureRecognizer {
|
||||
return true
|
||||
}*/
|
||||
return false
|
||||
}
|
||||
|
||||
@objc private func revealTapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
self.updateRevealOffsetInternal(offset: 0.0, transition: .animated(duration: 0.3, curve: .spring))
|
||||
|
@ -1051,13 +1051,23 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
|
||||
|
||||
}
|
||||
|
||||
_ = (ApplicationSpecificNotice.getSetPublicChannelLink(accountManager: context.sharedContext.accountManager) |> deliverOnMainQueue).start(next: { showAlert in
|
||||
if showAlert {
|
||||
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Channel_Edit_PrivatePublicLinkAlert, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: invokeAction)]), nil)
|
||||
if peer.isVerified {
|
||||
let alertText: String
|
||||
if case .broadcast = peer.info {
|
||||
alertText = presentationData.strings.SetupUsername_ChangeLinkWarningChannel
|
||||
} else {
|
||||
invokeAction()
|
||||
alertText = presentationData.strings.SetupUsername_ChangeLinkWarningGroup
|
||||
}
|
||||
})
|
||||
presentControllerImpl?(textAlertController(context: context, title: nil, text: alertText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: invokeAction)]), nil)
|
||||
} else {
|
||||
_ = (ApplicationSpecificNotice.getSetPublicChannelLink(accountManager: context.sharedContext.accountManager) |> deliverOnMainQueue).start(next: { showAlert in
|
||||
if showAlert {
|
||||
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Channel_Edit_PrivatePublicLinkAlert, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: invokeAction)]), nil)
|
||||
} else {
|
||||
invokeAction()
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
switch mode {
|
||||
case .initialSetup:
|
||||
|
@ -262,7 +262,9 @@ final class ChatListTable: Table {
|
||||
|
||||
let messageTagSummaryResult = resolveChatListMessageTagSummaryResultCalculation(postbox: postbox, peerId: peer.id, calculation: filterPredicate.messageTagSummary)
|
||||
|
||||
if filterPredicate.includes(peer: peer, groupId: groupId, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, isUnread: isUnread, isContact: isContact, messageTagSummaryResult: messageTagSummaryResult) {
|
||||
if filterPredicate.pinnedPeerIds.contains(peer.id) {
|
||||
passFilter = true
|
||||
} else if filterPredicate.includes(peer: peer, groupId: groupId, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, isUnread: isUnread, isContact: isContact, messageTagSummaryResult: messageTagSummaryResult) {
|
||||
passFilter = true
|
||||
} else {
|
||||
passFilter = false
|
||||
|
@ -253,19 +253,24 @@ private enum ChatListEntryType {
|
||||
public struct ChatListFilterPredicate {
|
||||
public var includePeerIds: Set<PeerId>
|
||||
public var excludePeerIds: Set<PeerId>
|
||||
public var pinnedPeerIds: [PeerId]
|
||||
public var messageTagSummary: ChatListMessageTagSummaryResultCalculation?
|
||||
public var includeAdditionalPeerGroupIds: [PeerGroupId]
|
||||
public var include: (Peer, Bool, Bool, Bool, Bool?) -> Bool
|
||||
|
||||
public init(includePeerIds: Set<PeerId>, excludePeerIds: Set<PeerId>, messageTagSummary: ChatListMessageTagSummaryResultCalculation?, includeAdditionalPeerGroupIds: [PeerGroupId], include: @escaping (Peer, Bool, Bool, Bool, Bool?) -> Bool) {
|
||||
public init(includePeerIds: Set<PeerId>, excludePeerIds: Set<PeerId>, pinnedPeerIds: [PeerId], messageTagSummary: ChatListMessageTagSummaryResultCalculation?, includeAdditionalPeerGroupIds: [PeerGroupId], include: @escaping (Peer, Bool, Bool, Bool, Bool?) -> Bool) {
|
||||
self.includePeerIds = includePeerIds
|
||||
self.excludePeerIds = excludePeerIds
|
||||
self.pinnedPeerIds = pinnedPeerIds
|
||||
self.messageTagSummary = messageTagSummary
|
||||
self.includeAdditionalPeerGroupIds = includeAdditionalPeerGroupIds
|
||||
self.include = include
|
||||
}
|
||||
|
||||
func includes(peer: Peer, groupId: PeerGroupId, isRemovedFromTotalUnreadCount: Bool, isUnread: Bool, isContact: Bool, messageTagSummaryResult: Bool?) -> Bool {
|
||||
if self.pinnedPeerIds.contains(peer.id) {
|
||||
return false
|
||||
}
|
||||
let includePeerId = peer.associatedPeerId ?? peer.id
|
||||
if self.excludePeerIds.contains(includePeerId) {
|
||||
return false
|
||||
@ -293,41 +298,46 @@ final class MutableChatListView {
|
||||
fileprivate var state: ChatListViewState
|
||||
fileprivate var sampledState: ChatListViewSample
|
||||
|
||||
private var additionalItemIds = Set<PeerId>()
|
||||
fileprivate var additionalItemEntries: [MutableChatListEntry] = []
|
||||
|
||||
init(postbox: Postbox, groupId: PeerGroupId, filterPredicate: ChatListFilterPredicate?, aroundIndex: ChatListIndex, count: Int, summaryComponents: ChatListEntrySummaryComponents) {
|
||||
self.groupId = groupId
|
||||
self.filterPredicate = filterPredicate
|
||||
self.summaryComponents = summaryComponents
|
||||
|
||||
var spaces: [ChatListViewSpace] = [
|
||||
.group(groupId: self.groupId, pinned: .notPinned)
|
||||
.group(groupId: self.groupId, pinned: .notPinned, predicate: filterPredicate)
|
||||
]
|
||||
if let filterPredicate = self.filterPredicate {
|
||||
spaces.append(.group(groupId: self.groupId, pinned: .includePinnedAsUnpinned))
|
||||
spaces.append(.group(groupId: self.groupId, pinned: .includePinnedAsUnpinned, predicate: filterPredicate))
|
||||
for additionalGroupId in filterPredicate.includeAdditionalPeerGroupIds {
|
||||
spaces.append(.group(groupId: additionalGroupId, pinned: .notPinned))
|
||||
spaces.append(.group(groupId: additionalGroupId, pinned: .includePinnedAsUnpinned))
|
||||
spaces.append(.group(groupId: additionalGroupId, pinned: .notPinned, predicate: filterPredicate))
|
||||
spaces.append(.group(groupId: additionalGroupId, pinned: .includePinnedAsUnpinned, predicate: filterPredicate))
|
||||
}
|
||||
if !filterPredicate.pinnedPeerIds.isEmpty {
|
||||
spaces.append(.peers(peerIds: filterPredicate.pinnedPeerIds, asPinned: true))
|
||||
}
|
||||
} else {
|
||||
spaces.append(.group(groupId: self.groupId, pinned: .includePinned))
|
||||
spaces.append(.group(groupId: self.groupId, pinned: .includePinned, predicate: filterPredicate))
|
||||
}
|
||||
self.spaces = spaces
|
||||
self.state = ChatListViewState(postbox: postbox, spaces: self.spaces, anchorIndex: aroundIndex, filterPredicate: self.filterPredicate, summaryComponents: self.summaryComponents, halfLimit: count)
|
||||
self.state = ChatListViewState(postbox: postbox, spaces: self.spaces, anchorIndex: aroundIndex, summaryComponents: self.summaryComponents, halfLimit: count)
|
||||
self.sampledState = self.state.sample(postbox: postbox)
|
||||
|
||||
self.count = count
|
||||
|
||||
if case .root = groupId, self.filterPredicate == nil {
|
||||
/*let itemIds = postbox.additionalChatListItemsTable.get()
|
||||
let itemIds = postbox.additionalChatListItemsTable.get()
|
||||
self.additionalItemIds = Set(itemIds)
|
||||
for peerId in itemIds {
|
||||
if let entry = postbox.chatListTable.getStandalone(peerId: peerId, messageHistoryTable: postbox.messageHistoryTable) {
|
||||
self.additionalItemEntries.append(MutableChatListEntry(entry, cachedDataTable: postbox.cachedPeerDataTable, readStateTable: postbox.readStateTable, messageHistoryTable: postbox.messageHistoryTable))
|
||||
}
|
||||
}*/
|
||||
}
|
||||
self.groupEntries = []
|
||||
self.reloadGroups(postbox: postbox)
|
||||
} else {
|
||||
//self.additionalItemIds = Set()
|
||||
self.groupEntries = []
|
||||
}
|
||||
}
|
||||
@ -404,7 +414,7 @@ final class MutableChatListView {
|
||||
func refreshDueToExternalTransaction(postbox: Postbox) -> Bool {
|
||||
var updated = false
|
||||
|
||||
self.state = ChatListViewState(postbox: postbox, spaces: self.spaces, anchorIndex: .absoluteUpperBound, filterPredicate: self.filterPredicate, summaryComponents: self.summaryComponents, halfLimit: self.count)
|
||||
self.state = ChatListViewState(postbox: postbox, spaces: self.spaces, anchorIndex: .absoluteUpperBound, summaryComponents: self.summaryComponents, halfLimit: self.count)
|
||||
self.sampledState = self.state.sample(postbox: postbox)
|
||||
updated = true
|
||||
|
||||
@ -423,7 +433,7 @@ final class MutableChatListView {
|
||||
var hasChanges = false
|
||||
|
||||
if transaction.updatedGlobalNotificationSettings && self.filterPredicate != nil {
|
||||
self.state = ChatListViewState(postbox: postbox, spaces: self.spaces, anchorIndex: .absoluteUpperBound, filterPredicate: self.filterPredicate, summaryComponents: self.summaryComponents, halfLimit: self.count)
|
||||
self.state = ChatListViewState(postbox: postbox, spaces: self.spaces, anchorIndex: .absoluteUpperBound, summaryComponents: self.summaryComponents, halfLimit: self.count)
|
||||
self.sampledState = self.state.sample(postbox: postbox)
|
||||
hasChanges = true
|
||||
} else {
|
||||
@ -469,11 +479,8 @@ final class MutableChatListView {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
|
||||
var updateAdditionalItems = false
|
||||
if let itemIds = transaction.replacedAdditionalChatListItems {
|
||||
if case .root = self.groupId, self.filterPredicate == nil, let itemIds = transaction.replacedAdditionalChatListItems {
|
||||
self.additionalItemIds = Set(itemIds)
|
||||
updateAdditionalItems = true
|
||||
}
|
||||
@ -497,34 +504,6 @@ final class MutableChatListView {
|
||||
}
|
||||
hasChanges = true
|
||||
}
|
||||
var updateAdditionalMixedItems = false
|
||||
for peerId in self.additionalMixedItemIds.union(self.additionalMixedPinnedItemIds) {
|
||||
if transaction.currentOperationsByPeerId[peerId] != nil {
|
||||
updateAdditionalMixedItems = true
|
||||
}
|
||||
if transaction.currentUpdatedPeers[peerId] != nil {
|
||||
updateAdditionalMixedItems = true
|
||||
}
|
||||
if transaction.currentUpdatedChatListInclusions[peerId] != nil {
|
||||
updateAdditionalMixedItems = true
|
||||
}
|
||||
}
|
||||
if updateAdditionalMixedItems {
|
||||
self.additionalMixedItemEntries.removeAll()
|
||||
for peerId in self.additionalMixedItemIds {
|
||||
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))
|
||||
}
|
||||
}
|
||||
self.additionalMixedPinnedEntries.removeAll()
|
||||
for peerId in self.additionalMixedPinnedItemIds {
|
||||
if let entry = postbox.chatListTable.getEntry(peerId: peerId, messageHistoryTable: postbox.messageHistoryTable, peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable) {
|
||||
self.additionalMixedPinnedEntries.append(MutableChatListEntry(entry, cachedDataTable: postbox.cachedPeerDataTable, readStateTable: postbox.readStateTable, messageHistoryTable: postbox.messageHistoryTable))
|
||||
}
|
||||
}
|
||||
|
||||
hasChanges = true
|
||||
}*/
|
||||
return hasChanges
|
||||
}
|
||||
|
||||
@ -536,7 +515,7 @@ final class MutableChatListView {
|
||||
return self.sampledState.hole
|
||||
}
|
||||
|
||||
/*private func renderEntry(_ entry: MutableChatListEntry, postbox: Postbox, renderMessage: (IntermediateMessage) -> Message, getPeer: (PeerId) -> Peer?, getPeerNotificationSettings: (PeerId) -> PeerNotificationSettings?, getPeerPresence: (PeerId) -> PeerPresence?) -> MutableChatListEntry? {
|
||||
private func renderEntry(_ entry: MutableChatListEntry, postbox: Postbox, renderMessage: (IntermediateMessage) -> Message, getPeer: (PeerId) -> Peer?, getPeerNotificationSettings: (PeerId) -> PeerNotificationSettings?, getPeerPresence: (PeerId) -> PeerPresence?) -> MutableChatListEntry? {
|
||||
switch entry {
|
||||
case let .IntermediateMessageEntry(index, messageIndex):
|
||||
let renderedMessage: Message?
|
||||
@ -565,48 +544,21 @@ final class MutableChatListView {
|
||||
}
|
||||
}
|
||||
|
||||
var tagSummaryCount: Int32?
|
||||
var actionsSummaryCount: Int32?
|
||||
let tagSummaryCount: Int32? = nil
|
||||
let actionsSummaryCount: Int32? = nil
|
||||
|
||||
if let tagSummary = self.summaryComponents.tagSummary {
|
||||
let key = MessageHistoryTagsSummaryKey(tag: tagSummary.tag, peerId: index.messageIndex.id.peerId, namespace: tagSummary.namespace)
|
||||
if let summary = postbox.messageHistoryTagsSummaryTable.get(key) {
|
||||
tagSummaryCount = summary.count
|
||||
}
|
||||
}
|
||||
|
||||
if let actionsSummary = self.summaryComponents.actionsSummary {
|
||||
let key = PendingMessageActionsSummaryKey(type: actionsSummary.type, peerId: index.messageIndex.id.peerId, namespace: actionsSummary.namespace)
|
||||
actionsSummaryCount = postbox.pendingMessageActionsMetadataTable.getCount(.peerNamespaceAction(key.peerId, key.namespace, key.type))
|
||||
}
|
||||
|
||||
return .MessageEntry(index: index, message: renderedMessage, readState: postbox.readStateTable.getCombinedState(index.messageIndex.id.peerId), notificationSettings: notificationSettings, embeddedInterfaceState: postbox.peerChatInterfaceStateTable.get(index.messageIndex.id.peerId)?.chatListEmbeddedState, renderedPeer: RenderedPeer(peerId: index.messageIndex.id.peerId, peers: peers), presence: presence, tagSummaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: tagSummaryCount, actionsSummaryCount: actionsSummaryCount), hasFailedMessages: postbox.messageHistoryFailedTable.contains(peerId: index.messageIndex.id.peerId), isContact: isContact)
|
||||
return .MessageEntry(index: index, message: renderedMessage, readState: postbox.readStateTable.getCombinedState(index.messageIndex.id.peerId), notificationSettings: notificationSettings, isRemovedFromTotalUnreadCount: false, embeddedInterfaceState: postbox.peerChatInterfaceStateTable.get(index.messageIndex.id.peerId)?.chatListEmbeddedState, renderedPeer: RenderedPeer(peerId: index.messageIndex.id.peerId, peers: peers), presence: presence, tagSummaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: tagSummaryCount, actionsSummaryCount: actionsSummaryCount), hasFailedMessages: postbox.messageHistoryFailedTable.contains(peerId: index.messageIndex.id.peerId), isContact: isContact)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
func render(postbox: Postbox, renderMessage: (IntermediateMessage) -> Message, getPeer: (PeerId) -> Peer?, getPeerNotificationSettings: (PeerId) -> PeerNotificationSettings?, getPeerPresence: (PeerId) -> PeerPresence?) {
|
||||
/*for i in 0 ..< self.entries.count {
|
||||
if let updatedEntry = self.renderEntry(self.entries[i], postbox: postbox, renderMessage: renderMessage, getPeer: getPeer, getPeerNotificationSettings: getPeerNotificationSettings, getPeerPresence: getPeerPresence) {
|
||||
self.entries[i] = updatedEntry
|
||||
}
|
||||
}
|
||||
for i in 0 ..< self.additionalItemEntries.count {
|
||||
if let updatedEntry = self.renderEntry(self.additionalItemEntries[i], postbox: postbox, renderMessage: renderMessage, getPeer: getPeer, getPeerNotificationSettings: getPeerNotificationSettings, getPeerPresence: getPeerPresence) {
|
||||
self.additionalItemEntries[i] = updatedEntry
|
||||
}
|
||||
}
|
||||
for i in 0 ..< self.additionalMixedItemEntries.count {
|
||||
if let updatedEntry = self.renderEntry(self.additionalMixedItemEntries[i], postbox: postbox, renderMessage: renderMessage, getPeer: getPeer, getPeerNotificationSettings: getPeerNotificationSettings, getPeerPresence: getPeerPresence) {
|
||||
self.additionalMixedItemEntries[i] = updatedEntry
|
||||
}
|
||||
}
|
||||
for i in 0 ..< self.additionalMixedPinnedEntries.count {
|
||||
if let updatedEntry = self.renderEntry(self.additionalMixedPinnedEntries[i], postbox: postbox, renderMessage: renderMessage, getPeer: getPeer, getPeerNotificationSettings: getPeerNotificationSettings, getPeerPresence: getPeerPresence) {
|
||||
self.additionalMixedPinnedEntries[i] = updatedEntry
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
@ -640,16 +592,16 @@ public final class ChatListView {
|
||||
self.groupEntries = mutableView.groupEntries
|
||||
|
||||
var additionalItemEntries: [ChatListEntry] = []
|
||||
/*for entry in mutableView.additionalItemEntries {
|
||||
for entry in mutableView.additionalItemEntries {
|
||||
switch entry {
|
||||
case let .MessageEntry(index, message, combinedReadState, notificationSettings, embeddedState, peer, peerPresence, summaryInfo, hasFailed, isContact):
|
||||
additionalItemEntries.append(.MessageEntry(index, message, combinedReadState, notificationSettings, embeddedState, peer, peerPresence, summaryInfo, hasFailed, isContact))
|
||||
case .HoleEntry:
|
||||
assertionFailure()
|
||||
case .IntermediateMessageEntry:
|
||||
assertionFailure()
|
||||
case let .MessageEntry(index, message, combinedReadState, _, isExcludedFromUnreadCount, embeddedState, peer, peerPresence, summaryInfo, hasFailed, isContact):
|
||||
additionalItemEntries.append(.MessageEntry(index: index, message: message, readState: combinedReadState, isRemovedFromTotalUnreadCount: isExcludedFromUnreadCount, embeddedInterfaceState: embeddedState, renderedPeer: peer, presence: peerPresence, summaryInfo: summaryInfo, hasFailed: hasFailed, isContact: isContact))
|
||||
case .HoleEntry:
|
||||
assertionFailure()
|
||||
case .IntermediateMessageEntry:
|
||||
assertionFailure()
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
self.additionalItemEntries = additionalItemEntries
|
||||
}
|
||||
|
@ -15,7 +15,42 @@ enum ChatListViewSpacePinned {
|
||||
}
|
||||
|
||||
enum ChatListViewSpace: Hashable {
|
||||
case group(groupId: PeerGroupId, pinned: ChatListViewSpacePinned)
|
||||
case group(groupId: PeerGroupId, pinned: ChatListViewSpacePinned, predicate: ChatListFilterPredicate?)
|
||||
case peers(peerIds: [PeerId], asPinned: Bool)
|
||||
|
||||
static func ==(lhs: ChatListViewSpace, rhs: ChatListViewSpace) -> Bool {
|
||||
switch lhs {
|
||||
case let .group(groupId, pinned, _):
|
||||
if case let .group(rhsGroupId, rhsPinned, _) = rhs {
|
||||
if groupId != rhsGroupId {
|
||||
return false
|
||||
}
|
||||
if pinned != rhsPinned {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .peers(peerIds, asPinned):
|
||||
if case .peers(peerIds, asPinned) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
switch self {
|
||||
case let .group(groupId, pinned, _):
|
||||
hasher.combine(groupId)
|
||||
hasher.combine(pinned)
|
||||
case let .peers(peerIds, asPinned):
|
||||
hasher.combine(peerIds)
|
||||
hasher.combine(asPinned)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func mappedChatListFilterPredicate(postbox: Postbox, groupId: PeerGroupId, predicate: ChatListFilterPredicate) -> (ChatListIntermediateEntry) -> Bool {
|
||||
@ -91,16 +126,14 @@ private func updatedRenderedPeer(_ renderedPeer: RenderedPeer, updatedPeers: [Pe
|
||||
private final class ChatListViewSpaceState {
|
||||
private let space: ChatListViewSpace
|
||||
private let anchorIndex: MutableChatListEntryIndex
|
||||
private let filterPredicate: ChatListFilterPredicate?
|
||||
private let summaryComponents: ChatListEntrySummaryComponents
|
||||
private let halfLimit: Int
|
||||
|
||||
var orderedEntries: OrderedChatListViewEntries
|
||||
|
||||
init(postbox: Postbox, space: ChatListViewSpace, anchorIndex: MutableChatListEntryIndex, filterPredicate: ChatListFilterPredicate?, summaryComponents: ChatListEntrySummaryComponents, halfLimit: Int) {
|
||||
init(postbox: Postbox, space: ChatListViewSpace, anchorIndex: MutableChatListEntryIndex, summaryComponents: ChatListEntrySummaryComponents, halfLimit: Int) {
|
||||
self.space = space
|
||||
self.anchorIndex = anchorIndex
|
||||
self.filterPredicate = filterPredicate
|
||||
self.summaryComponents = summaryComponents
|
||||
self.halfLimit = halfLimit
|
||||
self.orderedEntries = OrderedChatListViewEntries(anchorIndex: anchorIndex.index, lowerOrAtAnchor: [], higherThanAnchor: [])
|
||||
@ -109,7 +142,7 @@ private final class ChatListViewSpaceState {
|
||||
|
||||
private func fillSpace(postbox: Postbox) {
|
||||
switch self.space {
|
||||
case let .group(groupId, pinned):
|
||||
case let .group(groupId, pinned, filterPredicate):
|
||||
let lowerBound: MutableChatListEntryIndex
|
||||
let upperBound: MutableChatListEntryIndex
|
||||
if pinned.include {
|
||||
@ -138,15 +171,21 @@ private final class ChatListViewSpaceState {
|
||||
}
|
||||
|
||||
if case .includePinnedAsUnpinned = pinned {
|
||||
let unpinnedLowerBound: MutableChatListEntryIndex
|
||||
let unpinnedUpperBound: MutableChatListEntryIndex
|
||||
unpinnedUpperBound = .absoluteUpperBound
|
||||
unpinnedLowerBound = MutableChatListEntryIndex(index: ChatListIndex.absoluteLowerBound, isMessage: true)
|
||||
let resolvedUnpinnedAnchorIndex = min(unpinnedUpperBound, max(self.anchorIndex, unpinnedLowerBound))
|
||||
|
||||
if lowerOrAtAnchorMessages.count < self.halfLimit || higherThanAnchorMessages.count < self.halfLimit {
|
||||
let loadedMessages = postbox.chatListTable.entries(groupId: groupId, from: (ChatListIndex.pinnedLowerBound, true), to: (ChatListIndex.absoluteUpperBound, true), peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable, count: self.halfLimit * 2, predicate: self.filterPredicate.flatMap { mappedChatListFilterPredicate(postbox: postbox, groupId: groupId, predicate: $0) }).map(mapEntry).sorted(by: { $0.entryIndex < $1.entryIndex })
|
||||
let loadedMessages = postbox.chatListTable.entries(groupId: groupId, from: (ChatListIndex.pinnedLowerBound, true), to: (ChatListIndex.absoluteUpperBound, true), peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable, count: self.halfLimit * 2, predicate: filterPredicate.flatMap { mappedChatListFilterPredicate(postbox: postbox, groupId: groupId, predicate: $0) }).map(mapEntry).sorted(by: { $0.entryIndex < $1.entryIndex })
|
||||
|
||||
if lowerOrAtAnchorMessages.count < self.halfLimit {
|
||||
var nextLowerIndex: MutableChatListEntryIndex
|
||||
if let lastMessage = lowerOrAtAnchorMessages.min(by: { $0.entryIndex < $1.entryIndex }) {
|
||||
nextLowerIndex = lastMessage.entryIndex.predecessor
|
||||
} else {
|
||||
nextLowerIndex = min(resolvedAnchorIndex, self.anchorIndex)
|
||||
nextLowerIndex = min(resolvedUnpinnedAnchorIndex, self.anchorIndex)
|
||||
}
|
||||
var loadedLowerMessages = Array(loadedMessages.filter({ $0.entryIndex <= nextLowerIndex }).reversed())
|
||||
let lowerLimit = self.halfLimit - lowerOrAtAnchorMessages.count
|
||||
@ -160,7 +199,7 @@ private final class ChatListViewSpaceState {
|
||||
if let lastMessage = higherThanAnchorMessages.max(by: { $0.entryIndex < $1.entryIndex }) {
|
||||
nextHigherIndex = lastMessage.entryIndex.successor
|
||||
} else {
|
||||
nextHigherIndex = max(resolvedAnchorIndex, self.anchorIndex.successor)
|
||||
nextHigherIndex = max(resolvedUnpinnedAnchorIndex, self.anchorIndex.successor)
|
||||
}
|
||||
var loadedHigherMessages = loadedMessages.filter({ $0.entryIndex > nextHigherIndex })
|
||||
let higherLimit = self.halfLimit - higherThanAnchorMessages.count
|
||||
@ -176,9 +215,9 @@ private final class ChatListViewSpaceState {
|
||||
if let lastMessage = lowerOrAtAnchorMessages.min(by: { $0.entryIndex < $1.entryIndex }) {
|
||||
nextLowerIndex = lastMessage.entryIndex.predecessor
|
||||
} else {
|
||||
nextLowerIndex = resolvedAnchorIndex
|
||||
nextLowerIndex = resolvedAnchorIndex.predecessor
|
||||
}
|
||||
let loadedLowerMessages = postbox.chatListTable.entries(groupId: groupId, from: (nextLowerIndex.index, nextLowerIndex.isMessage), to: (lowerBound.index, lowerBound.isMessage), peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable, count: self.halfLimit - lowerOrAtAnchorMessages.count, predicate: self.filterPredicate.flatMap { mappedChatListFilterPredicate(postbox: postbox, groupId: groupId, predicate: $0) }).map(mapEntry)
|
||||
let loadedLowerMessages = postbox.chatListTable.entries(groupId: groupId, from: (nextLowerIndex.index, nextLowerIndex.isMessage), to: (lowerBound.index, lowerBound.isMessage), peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable, count: self.halfLimit - lowerOrAtAnchorMessages.count, predicate: filterPredicate.flatMap { mappedChatListFilterPredicate(postbox: postbox, groupId: groupId, predicate: $0) }).map(mapEntry)
|
||||
lowerOrAtAnchorMessages.append(contentsOf: loadedLowerMessages)
|
||||
}
|
||||
if higherThanAnchorMessages.count < self.halfLimit {
|
||||
@ -188,7 +227,7 @@ private final class ChatListViewSpaceState {
|
||||
} else {
|
||||
nextHigherIndex = resolvedAnchorIndex
|
||||
}
|
||||
let loadedHigherMessages = postbox.chatListTable.entries(groupId: groupId, from: (nextHigherIndex.index, nextHigherIndex.isMessage), to: (upperBound.index, upperBound.isMessage), peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable, count: self.halfLimit - higherThanAnchorMessages.count, predicate: self.filterPredicate.flatMap { mappedChatListFilterPredicate(postbox: postbox, groupId: groupId, predicate: $0) }).map(mapEntry)
|
||||
let loadedHigherMessages = postbox.chatListTable.entries(groupId: groupId, from: (nextHigherIndex.index, nextHigherIndex.isMessage), to: (upperBound.index, upperBound.isMessage), peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable, count: self.halfLimit - higherThanAnchorMessages.count, predicate: filterPredicate.flatMap { mappedChatListFilterPredicate(postbox: postbox, groupId: groupId, predicate: $0) }).map(mapEntry)
|
||||
higherThanAnchorMessages.append(contentsOf: loadedHigherMessages)
|
||||
}
|
||||
}
|
||||
@ -204,6 +243,78 @@ private final class ChatListViewSpaceState {
|
||||
|
||||
let entries = OrderedChatListViewEntries(anchorIndex: self.anchorIndex.index, lowerOrAtAnchor: lowerOrAtAnchorMessages, higherThanAnchor: higherThanAnchorMessages)
|
||||
self.orderedEntries = entries
|
||||
case let .peers(peerIds, asPinned):
|
||||
var lowerOrAtAnchorMessages: [MutableChatListEntry] = self.orderedEntries.lowerOrAtAnchor.reversed()
|
||||
var higherThanAnchorMessages: [MutableChatListEntry] = self.orderedEntries.higherThanAnchor
|
||||
|
||||
let unpinnedLowerBound: MutableChatListEntryIndex
|
||||
let unpinnedUpperBound: MutableChatListEntryIndex
|
||||
unpinnedUpperBound = .absoluteUpperBound
|
||||
unpinnedLowerBound = MutableChatListEntryIndex(index: ChatListIndex.absoluteLowerBound, isMessage: true)
|
||||
let resolvedUnpinnedAnchorIndex = min(unpinnedUpperBound, max(self.anchorIndex, unpinnedLowerBound))
|
||||
|
||||
if lowerOrAtAnchorMessages.count < self.halfLimit || higherThanAnchorMessages.count < self.halfLimit {
|
||||
func mapEntry(_ entry: ChatListIntermediateEntry, pinningIndex: UInt16?) -> MutableChatListEntry {
|
||||
switch entry {
|
||||
case let .message(index, messageIndex):
|
||||
var updatedIndex = index
|
||||
updatedIndex = ChatListIndex(pinningIndex: pinningIndex, messageIndex: index.messageIndex)
|
||||
return .IntermediateMessageEntry(index: updatedIndex, messageIndex: messageIndex)
|
||||
case let .hole(hole):
|
||||
return .HoleEntry(hole)
|
||||
}
|
||||
}
|
||||
|
||||
var loadedMessages: [MutableChatListEntry] = []
|
||||
for i in 0 ..< peerIds.count {
|
||||
let peerId = peerIds[i]
|
||||
if let entry = postbox.chatListTable.getEntry(peerId: peerId, messageHistoryTable: postbox.messageHistoryTable, peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable) {
|
||||
loadedMessages.append(mapEntry(entry, pinningIndex: asPinned ? UInt16(i) : nil))
|
||||
}
|
||||
}
|
||||
loadedMessages.sort(by: { $0.entryIndex < $1.entryIndex })
|
||||
|
||||
if lowerOrAtAnchorMessages.count < self.halfLimit {
|
||||
var nextLowerIndex: MutableChatListEntryIndex
|
||||
if let lastMessage = lowerOrAtAnchorMessages.min(by: { $0.entryIndex < $1.entryIndex }) {
|
||||
nextLowerIndex = lastMessage.entryIndex.predecessor
|
||||
} else {
|
||||
nextLowerIndex = min(resolvedUnpinnedAnchorIndex, self.anchorIndex)
|
||||
}
|
||||
var loadedLowerMessages = Array(loadedMessages.filter({ $0.entryIndex <= nextLowerIndex }).reversed())
|
||||
let lowerLimit = self.halfLimit - lowerOrAtAnchorMessages.count
|
||||
if loadedLowerMessages.count > lowerLimit {
|
||||
loadedLowerMessages.removeLast(loadedLowerMessages.count - lowerLimit)
|
||||
}
|
||||
lowerOrAtAnchorMessages.append(contentsOf: loadedLowerMessages)
|
||||
}
|
||||
if higherThanAnchorMessages.count < self.halfLimit {
|
||||
var nextHigherIndex: MutableChatListEntryIndex
|
||||
if let lastMessage = higherThanAnchorMessages.max(by: { $0.entryIndex < $1.entryIndex }) {
|
||||
nextHigherIndex = lastMessage.entryIndex.successor
|
||||
} else {
|
||||
nextHigherIndex = max(resolvedUnpinnedAnchorIndex, self.anchorIndex.successor)
|
||||
}
|
||||
var loadedHigherMessages = loadedMessages.filter({ $0.entryIndex > nextHigherIndex })
|
||||
let higherLimit = self.halfLimit - higherThanAnchorMessages.count
|
||||
if loadedHigherMessages.count > higherLimit {
|
||||
loadedHigherMessages.removeLast(loadedHigherMessages.count - higherLimit)
|
||||
}
|
||||
higherThanAnchorMessages.append(contentsOf: loadedHigherMessages)
|
||||
}
|
||||
|
||||
lowerOrAtAnchorMessages.reverse()
|
||||
|
||||
assert(lowerOrAtAnchorMessages.count <= self.halfLimit)
|
||||
assert(higherThanAnchorMessages.count <= self.halfLimit)
|
||||
|
||||
let allIndices = (lowerOrAtAnchorMessages + higherThanAnchorMessages).map { $0.entryIndex }
|
||||
assert(Set(allIndices).count == allIndices.count)
|
||||
assert(allIndices.sorted() == allIndices)
|
||||
|
||||
let entries = OrderedChatListViewEntries(anchorIndex: self.anchorIndex.index, lowerOrAtAnchor: lowerOrAtAnchorMessages, higherThanAnchor: higherThanAnchorMessages)
|
||||
self.orderedEntries = entries
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -212,27 +323,21 @@ private final class ChatListViewSpaceState {
|
||||
var hadRemovals = false
|
||||
var globalNotificationSettings: PostboxGlobalNotificationSettings?
|
||||
for (groupId, operations) in transaction.chatListOperations {
|
||||
let matchesSpace: Bool
|
||||
switch self.space {
|
||||
case .group(groupId, _):
|
||||
matchesSpace = true
|
||||
default:
|
||||
matchesSpace = false
|
||||
}
|
||||
if !matchesSpace {
|
||||
continue
|
||||
}
|
||||
|
||||
inner: for operation in operations {
|
||||
switch operation {
|
||||
case let .InsertEntry(index, messageIndex):
|
||||
switch self.space {
|
||||
case let .group(_, pinned) where (index.pinningIndex != nil) == pinned.include:
|
||||
case let .group(spaceGroupId, pinned, filterPredicate):
|
||||
let matchesGroup = groupId == spaceGroupId && (index.pinningIndex != nil) == pinned.include
|
||||
if !matchesGroup {
|
||||
continue inner
|
||||
}
|
||||
|
||||
var updatedIndex = index
|
||||
if case .includePinnedAsUnpinned = pinned {
|
||||
updatedIndex = ChatListIndex(pinningIndex: nil, messageIndex: index.messageIndex)
|
||||
}
|
||||
if let filterPredicate = self.filterPredicate {
|
||||
if let filterPredicate = filterPredicate {
|
||||
if let peer = postbox.peerTable.get(updatedIndex.messageIndex.id.peerId) {
|
||||
let notificationsPeerId = peer.notificationSettingsPeerId ?? peer.id
|
||||
let globalNotificationSettingsValue: PostboxGlobalNotificationSettings
|
||||
@ -257,42 +362,80 @@ private final class ChatListViewSpaceState {
|
||||
if self.add(entry: .IntermediateMessageEntry(index: updatedIndex, messageIndex: messageIndex)) {
|
||||
hasUpdates = true
|
||||
}
|
||||
default:
|
||||
break
|
||||
case let .peers(peerIds, asPinned):
|
||||
if let peerIndex = peerIds.firstIndex(of: index.messageIndex.id.peerId) {
|
||||
var updatedIndex = index
|
||||
if asPinned {
|
||||
updatedIndex = ChatListIndex(pinningIndex: UInt16(peerIndex), messageIndex: index.messageIndex)
|
||||
}
|
||||
if self.add(entry: .IntermediateMessageEntry(index: updatedIndex, messageIndex: messageIndex)) {
|
||||
hasUpdates = true
|
||||
}
|
||||
} else {
|
||||
continue inner
|
||||
}
|
||||
}
|
||||
case let .InsertHole(hole):
|
||||
switch self.space {
|
||||
case let .group(_, pinned) where !pinned.include:
|
||||
if self.add(entry: .HoleEntry(hole)) {
|
||||
hasUpdates = true
|
||||
case let .group(spaceGroupId, pinned, _):
|
||||
if spaceGroupId == groupId && !pinned.include {
|
||||
if self.add(entry: .HoleEntry(hole)) {
|
||||
hasUpdates = true
|
||||
}
|
||||
}
|
||||
default:
|
||||
case .peers:
|
||||
break
|
||||
}
|
||||
case let .RemoveEntry(indices):
|
||||
for index in indices {
|
||||
var updatedIndex = index
|
||||
if case .group(_, .includePinnedAsUnpinned) = self.space {
|
||||
updatedIndex = ChatListIndex(pinningIndex: nil, messageIndex: index.messageIndex)
|
||||
switch self.space {
|
||||
case let .group(spaceGroupId, pinned, _):
|
||||
if spaceGroupId == groupId {
|
||||
for index in indices {
|
||||
var updatedIndex = index
|
||||
if case .includePinnedAsUnpinned = pinned {
|
||||
updatedIndex = ChatListIndex(pinningIndex: nil, messageIndex: index.messageIndex)
|
||||
}
|
||||
|
||||
if self.orderedEntries.remove(index: MutableChatListEntryIndex(index: updatedIndex, isMessage: true)) {
|
||||
hasUpdates = true
|
||||
hadRemovals = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.orderedEntries.remove(index: MutableChatListEntryIndex(index: updatedIndex, isMessage: true)) {
|
||||
hasUpdates = true
|
||||
hadRemovals = true
|
||||
case let .peers(peerIds, asPinned):
|
||||
for index in indices {
|
||||
if let peerIndex = peerIds.firstIndex(of: index.messageIndex.id.peerId) {
|
||||
var updatedIndex = index
|
||||
if asPinned {
|
||||
updatedIndex = ChatListIndex(pinningIndex: UInt16(peerIndex), messageIndex: index.messageIndex)
|
||||
}
|
||||
|
||||
if self.orderedEntries.remove(index: MutableChatListEntryIndex(index: updatedIndex, isMessage: true)) {
|
||||
hasUpdates = true
|
||||
hadRemovals = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case let .RemoveHoles(indices):
|
||||
for index in indices {
|
||||
if self.orderedEntries.remove(index: MutableChatListEntryIndex(index: index, isMessage: false)) {
|
||||
hasUpdates = true
|
||||
hadRemovals = true
|
||||
switch self.space {
|
||||
case let .group(spaceGroupId, pinned, _):
|
||||
if spaceGroupId == groupId && !pinned.include {
|
||||
for index in indices {
|
||||
if self.orderedEntries.remove(index: MutableChatListEntryIndex(index: index, isMessage: false)) {
|
||||
hasUpdates = true
|
||||
hadRemovals = true
|
||||
}
|
||||
}
|
||||
}
|
||||
case .peers:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !transaction.currentUpdatedPeerNotificationSettings.isEmpty, let filterPredicate = self.filterPredicate, case let .group(groupId, pinned) = self.space {
|
||||
if !transaction.currentUpdatedPeerNotificationSettings.isEmpty, case let .group(groupId, pinned, maybeFilterPredicate) = self.space, let filterPredicate = maybeFilterPredicate {
|
||||
var removeEntryIndices: [MutableChatListEntryIndex] = []
|
||||
let _ = self.orderedEntries.mutableScan { entry in
|
||||
let entryPeer: Peer
|
||||
@ -479,7 +622,7 @@ private final class ChatListViewSpaceState {
|
||||
}
|
||||
}
|
||||
|
||||
if !transaction.currentUpdatedMessageTagSummaries.isEmpty || !transaction.currentUpdatedMessageActionsSummaries.isEmpty, let filterPredicate = self.filterPredicate, let filterMessageTagSummary = filterPredicate.messageTagSummary, case let .group(groupId, pinned) = self.space {
|
||||
if !transaction.currentUpdatedMessageTagSummaries.isEmpty || !transaction.currentUpdatedMessageActionsSummaries.isEmpty, case let .group(groupId, pinned, maybeFilterPredicate) = self.space, let filterPredicate = maybeFilterPredicate, let filterMessageTagSummary = filterPredicate.messageTagSummary {
|
||||
var removeEntryIndices: [MutableChatListEntryIndex] = []
|
||||
let _ = self.orderedEntries.mutableScan { entry in
|
||||
let entryPeer: Peer
|
||||
@ -948,19 +1091,17 @@ final class ChatListViewSample {
|
||||
|
||||
struct ChatListViewState {
|
||||
private let anchorIndex: MutableChatListEntryIndex
|
||||
private let filterPredicate: ChatListFilterPredicate?
|
||||
private let summaryComponents: ChatListEntrySummaryComponents
|
||||
private let halfLimit: Int
|
||||
private var stateBySpace: [ChatListViewSpace: ChatListViewSpaceState] = [:]
|
||||
|
||||
init(postbox: Postbox, spaces: [ChatListViewSpace], anchorIndex: ChatListIndex, filterPredicate: ChatListFilterPredicate?, summaryComponents: ChatListEntrySummaryComponents, halfLimit: Int) {
|
||||
init(postbox: Postbox, spaces: [ChatListViewSpace], anchorIndex: ChatListIndex, summaryComponents: ChatListEntrySummaryComponents, halfLimit: Int) {
|
||||
self.anchorIndex = MutableChatListEntryIndex(index: anchorIndex, isMessage: true)
|
||||
self.filterPredicate = filterPredicate
|
||||
self.summaryComponents = summaryComponents
|
||||
self.halfLimit = halfLimit
|
||||
|
||||
for space in spaces {
|
||||
self.stateBySpace[space] = ChatListViewSpaceState(postbox: postbox, space: space, anchorIndex: self.anchorIndex, filterPredicate: self.filterPredicate, summaryComponents: summaryComponents, halfLimit: halfLimit)
|
||||
self.stateBySpace[space] = ChatListViewSpaceState(postbox: postbox, space: space, anchorIndex: self.anchorIndex, summaryComponents: summaryComponents, halfLimit: halfLimit)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1209,8 +1350,10 @@ struct ChatListViewState {
|
||||
|
||||
return ChatListViewSample(entries: result.map { $0.1 }, lower: lower, upper: upper, anchorIndex: self.anchorIndex.index, hole: sampledHole.flatMap { space, hole in
|
||||
switch space {
|
||||
case let .group(groupId, _):
|
||||
case let .group(groupId, _, _):
|
||||
return (groupId, hole)
|
||||
case .peers:
|
||||
return nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -29,30 +29,41 @@ private func generateBackground(foregroundColor: UIColor, diameter: CGFloat) ->
|
||||
private class SearchBarTextField: UITextField {
|
||||
public var didDeleteBackwardWhileEmpty: (() -> Void)?
|
||||
|
||||
let placeholderLabel: ASTextNode
|
||||
let placeholderLabel: ImmediateTextNode
|
||||
var placeholderString: NSAttributedString? {
|
||||
didSet {
|
||||
self.placeholderLabel.attributedText = self.placeholderString
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
}
|
||||
|
||||
let prefixLabel: ASTextNode
|
||||
private let measurePrefixLabel: ImmediateTextNode
|
||||
let prefixLabel: ImmediateTextNode
|
||||
var prefixString: NSAttributedString? {
|
||||
didSet {
|
||||
self.measurePrefixLabel.attributedText = self.prefixString
|
||||
self.prefixLabel.attributedText = self.prefixString
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.placeholderLabel = ASTextNode()
|
||||
self.placeholderLabel = ImmediateTextNode()
|
||||
self.placeholderLabel.isUserInteractionEnabled = false
|
||||
self.placeholderLabel.displaysAsynchronously = false
|
||||
self.placeholderLabel.maximumNumberOfLines = 1
|
||||
self.placeholderLabel.truncationMode = .byTruncatingTail
|
||||
|
||||
self.prefixLabel = ASTextNode()
|
||||
self.measurePrefixLabel = ImmediateTextNode()
|
||||
self.measurePrefixLabel.isUserInteractionEnabled = false
|
||||
self.measurePrefixLabel.displaysAsynchronously = false
|
||||
self.measurePrefixLabel.maximumNumberOfLines = 1
|
||||
self.measurePrefixLabel.truncationMode = .byTruncatingTail
|
||||
|
||||
self.prefixLabel = ImmediateTextNode()
|
||||
self.prefixLabel.isUserInteractionEnabled = false
|
||||
self.prefixLabel.displaysAsynchronously = false
|
||||
self.prefixLabel.maximumNumberOfLines = 1
|
||||
self.prefixLabel.truncationMode = .byTruncatingTail
|
||||
|
||||
super.init(frame: frame)
|
||||
@ -87,9 +98,9 @@ private class SearchBarTextField: UITextField {
|
||||
}
|
||||
var rect = bounds.insetBy(dx: 4.0, dy: 4.0)
|
||||
|
||||
let prefixSize = self.prefixLabel.measure(CGSize(width: floor(bounds.size.width * 0.7), height: bounds.size.height))
|
||||
let prefixSize = self.measurePrefixLabel.updateLayout(CGSize(width: floor(bounds.size.width * 0.7), height: bounds.size.height))
|
||||
if !prefixSize.width.isZero {
|
||||
let prefixOffset = prefixSize.width
|
||||
let prefixOffset = prefixSize.width + 3.0
|
||||
rect.origin.x += prefixOffset
|
||||
rect.size.width -= prefixOffset
|
||||
}
|
||||
@ -115,10 +126,10 @@ private class SearchBarTextField: UITextField {
|
||||
}
|
||||
|
||||
let textRect = self.textRect(forBounds: bounds)
|
||||
let labelSize = self.placeholderLabel.measure(textRect.size)
|
||||
let labelSize = self.placeholderLabel.updateLayout(textRect.size)
|
||||
self.placeholderLabel.frame = CGRect(origin: CGPoint(x: textRect.minX, y: textRect.minY + textOffset), size: labelSize)
|
||||
|
||||
let prefixSize = self.prefixLabel.measure(CGSize(width: floor(bounds.size.width * 0.7), height: bounds.size.height))
|
||||
let prefixSize = self.prefixLabel.updateLayout(CGSize(width: floor(bounds.size.width * 0.7), height: bounds.size.height))
|
||||
let prefixBounds = bounds.insetBy(dx: 4.0, dy: 4.0)
|
||||
self.prefixLabel.frame = CGRect(origin: CGPoint(x: prefixBounds.minX, y: prefixBounds.minY + textOffset), size: prefixSize)
|
||||
}
|
||||
|
@ -68,6 +68,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
case optimizeDatabase(PresentationTheme)
|
||||
case photoPreview(PresentationTheme, Bool)
|
||||
case knockoutWallpaper(PresentationTheme, Bool)
|
||||
case alternativeFolderTabs(Bool)
|
||||
case hostInfo(PresentationTheme, String)
|
||||
case versionInfo(PresentationTheme)
|
||||
|
||||
@ -81,7 +82,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
return DebugControllerSection.logging.rawValue
|
||||
case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries:
|
||||
return DebugControllerSection.experiments.rawValue
|
||||
case .clearTips, .reimport, .resetData, .resetDatabase, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper:
|
||||
case .clearTips, .reimport, .resetData, .resetDatabase, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .alternativeFolderTabs:
|
||||
return DebugControllerSection.experiments.rawValue
|
||||
case .hostInfo, .versionInfo:
|
||||
return DebugControllerSection.info.rawValue
|
||||
@ -134,6 +135,8 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
return 21
|
||||
case .knockoutWallpaper:
|
||||
return 22
|
||||
case .alternativeFolderTabs:
|
||||
return 23
|
||||
case .hostInfo:
|
||||
return 24
|
||||
case .versionInfo:
|
||||
@ -448,7 +451,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
})
|
||||
])])
|
||||
])])
|
||||
arguments.presentController(actionSheet, nil)
|
||||
})
|
||||
case let .resetHoles(theme):
|
||||
@ -523,6 +526,16 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
})
|
||||
}).start()
|
||||
})
|
||||
case let .alternativeFolderTabs(value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: "Alternative Tabs", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
|
||||
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
|
||||
var settings = settings as? ExperimentalUISettings ?? ExperimentalUISettings.defaultSettings
|
||||
settings.foldersTabAtBottom = value
|
||||
return settings
|
||||
})
|
||||
}).start()
|
||||
})
|
||||
case let .hostInfo(theme, string):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(string), sectionId: self.section)
|
||||
case let .versionInfo(theme):
|
||||
@ -565,6 +578,7 @@ private func debugControllerEntries(presentationData: PresentationData, loggingS
|
||||
entries.append(.optimizeDatabase(presentationData.theme))
|
||||
entries.append(.photoPreview(presentationData.theme, experimentalSettings.chatListPhotos))
|
||||
entries.append(.knockoutWallpaper(presentationData.theme, experimentalSettings.knockoutWallpaper))
|
||||
entries.append(.alternativeFolderTabs(experimentalSettings.foldersTabAtBottom))
|
||||
|
||||
if let backupHostOverride = networkSettings?.backupHostOverride {
|
||||
entries.append(.hostInfo(presentationData.theme, "Host: \(backupHostOverride)"))
|
||||
|
@ -234,22 +234,22 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView
|
||||
let timestamp = self.referenceTimestamp
|
||||
|
||||
let timestamp1 = timestamp + 120
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: selfPeer, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: selfPeer, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
|
||||
let presenceTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + 60 * 60)
|
||||
let timestamp2 = timestamp + 3660
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: "", attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer2), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: TelegramUserPresence(status: .present(until: presenceTimestamp), lastActivity: presenceTimestamp), summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: [(peer2, .typingText)], isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: "", attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer2), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: TelegramUserPresence(status: .present(until: presenceTimestamp), lastActivity: presenceTimestamp), summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: [(peer2, .typingText)], isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
|
||||
let timestamp3 = timestamp + 3200
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer3.id, namespace: 0, id: 0), timestamp: timestamp3)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer3.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp3, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer3Author, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer3), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer3.id, namespace: 0, id: 0), timestamp: timestamp3)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer3.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp3, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer3Author, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer3), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
|
||||
let timestamp4 = timestamp + 3000
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer4.id, namespace: 0, id: 0), timestamp: timestamp4)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp4, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer4, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer4), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer4.id, namespace: 0, id: 0), timestamp: timestamp4)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp4, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer4, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer4), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
|
||||
let timestamp5 = timestamp + 1000
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer5.id, namespace: 0, id: 0), timestamp: timestamp5)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp5, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer5, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer5), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer5.id, namespace: 0, id: 0), timestamp: timestamp5)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp5, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer5, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer5), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer6.id, namespace: 0, id: 0), timestamp: timestamp - 360)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer6.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp - 360, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer6), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer6.id, namespace: 0, id: 0), timestamp: timestamp - 360)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer6.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp - 360, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer6), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
|
||||
let width: CGFloat
|
||||
if case .regular = layout.metrics.widthClass {
|
||||
|
@ -785,17 +785,17 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
|
||||
let timestamp = self.referenceTimestamp
|
||||
|
||||
let timestamp1 = timestamp + 120
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: selfPeer, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: selfPeer, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
|
||||
let presenceTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + 60 * 60)
|
||||
let timestamp2 = timestamp + 3660
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer2), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: TelegramUserPresence(status: .present(until: presenceTimestamp), lastActivity: presenceTimestamp), summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer2), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: TelegramUserPresence(status: .present(until: presenceTimestamp), lastActivity: presenceTimestamp), summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
|
||||
let timestamp3 = timestamp + 3200
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer3.id, namespace: 0, id: 0), timestamp: timestamp3)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer3.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp3, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer3Author, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer3), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer3.id, namespace: 0, id: 0), timestamp: timestamp3)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer3.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp3, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer3Author, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer3), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
|
||||
let timestamp4 = timestamp + 3000
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer4.id, namespace: 0, id: 0), timestamp: timestamp4)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp4, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer4, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer4), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer4.id, namespace: 0, id: 0), timestamp: timestamp4)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp4, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer4, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer4), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
|
||||
let params = ListViewItemLayoutParams(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, availableHeight: layout.size.height)
|
||||
if let chatNodes = self.chatNodes {
|
||||
|
@ -372,24 +372,24 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
let timestamp = self.referenceTimestamp
|
||||
|
||||
let timestamp1 = timestamp + 120
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: selfPeer, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: selfPeer, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
|
||||
let presenceTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + 60 * 60)
|
||||
let timestamp2 = timestamp + 3660
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: "", attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer2), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: TelegramUserPresence(status: .present(until: presenceTimestamp), lastActivity: presenceTimestamp), summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: [(peer2, .typingText)], isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer2.id, namespace: 0, id: 0), timestamp: timestamp2)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer2.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp2, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer2, text: "", attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer2), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: TelegramUserPresence(status: .present(until: presenceTimestamp), lastActivity: presenceTimestamp), summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: [(peer2, .typingText)], isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
|
||||
let timestamp3 = timestamp + 3200
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer3.id, namespace: 0, id: 0), timestamp: timestamp3)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer3.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp3, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer3Author, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer3), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer3.id, namespace: 0, id: 0), timestamp: timestamp3)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer3.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp3, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer3Author, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer3), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
|
||||
let timestamp4 = timestamp + 3000
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer4.id, namespace: 0, id: 0), timestamp: timestamp4)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp4, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer4, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer4), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer4.id, namespace: 0, id: 0), timestamp: timestamp4)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp4, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer4, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer4), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
|
||||
let timestamp5 = timestamp + 1000
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer5.id, namespace: 0, id: 0), timestamp: timestamp5)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp5, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer5, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer5), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer5.id, namespace: 0, id: 0), timestamp: timestamp5)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer4.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp5, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer5, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer5), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer6.id, namespace: 0, id: 0), timestamp: timestamp - 360)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer6.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp - 360, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer6), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer6.id, namespace: 0, id: 0), timestamp: timestamp - 360)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer6.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp - 360, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer6), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 1, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer7.id, namespace: 0, id: 0), timestamp: timestamp - 420)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer7.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp - 420, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_7_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer7), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
items.append(ChatListItem(presentationData: chatListPresentationData, context: self.context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: peer7.id, namespace: 0, id: 0), timestamp: timestamp - 420)), content: .peer(message: Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer7.id, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp - 420, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer6, text: self.presentationData.strings.Appearance_ThemePreview_ChatList_7_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: []), peer: RenderedPeer(peer: peer7), combinedReadState: nil, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction))
|
||||
|
||||
let width: CGFloat
|
||||
if case .regular = layout.metrics.widthClass {
|
||||
|
@ -605,7 +605,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-1551583367] = { return Api.ReceivedNotifyMessage.parse_receivedNotifyMessage($0) }
|
||||
dict[-57668565] = { return Api.ChatParticipants.parse_chatParticipantsForbidden($0) }
|
||||
dict[1061556205] = { return Api.ChatParticipants.parse_chatParticipants($0) }
|
||||
dict[1687327098] = { return Api.DialogFilter.parse_dialogFilter($0) }
|
||||
dict[1949890536] = { return Api.DialogFilter.parse_dialogFilter($0) }
|
||||
dict[-1056001329] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentialsSaved($0) }
|
||||
dict[873977640] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentials($0) }
|
||||
dict[178373535] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentialsApplePay($0) }
|
||||
|
@ -17276,17 +17276,23 @@ public extension Api {
|
||||
|
||||
}
|
||||
public enum DialogFilter: TypeConstructorDescription {
|
||||
case dialogFilter(flags: Int32, id: Int32, title: String, includePeers: [Api.InputPeer], excludePeers: [Api.InputPeer])
|
||||
case dialogFilter(flags: Int32, id: Int32, title: String, emoticon: String?, pinnedPeers: [Api.InputPeer], includePeers: [Api.InputPeer], excludePeers: [Api.InputPeer])
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .dialogFilter(let flags, let id, let title, let includePeers, let excludePeers):
|
||||
case .dialogFilter(let flags, let id, let title, let emoticon, let pinnedPeers, let includePeers, let excludePeers):
|
||||
if boxed {
|
||||
buffer.appendInt32(1687327098)
|
||||
buffer.appendInt32(1949890536)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt32(id, buffer: buffer, boxed: false)
|
||||
serializeString(title, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 25) != 0 {serializeString(emoticon!, buffer: buffer, boxed: false)}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(pinnedPeers.count))
|
||||
for item in pinnedPeers {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(includePeers.count))
|
||||
for item in includePeers {
|
||||
@ -17303,8 +17309,8 @@ public extension Api {
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .dialogFilter(let flags, let id, let title, let includePeers, let excludePeers):
|
||||
return ("dialogFilter", [("flags", flags), ("id", id), ("title", title), ("includePeers", includePeers), ("excludePeers", excludePeers)])
|
||||
case .dialogFilter(let flags, let id, let title, let emoticon, let pinnedPeers, let includePeers, let excludePeers):
|
||||
return ("dialogFilter", [("flags", flags), ("id", id), ("title", title), ("emoticon", emoticon), ("pinnedPeers", pinnedPeers), ("includePeers", includePeers), ("excludePeers", excludePeers)])
|
||||
}
|
||||
}
|
||||
|
||||
@ -17315,21 +17321,29 @@ public extension Api {
|
||||
_2 = reader.readInt32()
|
||||
var _3: String?
|
||||
_3 = parseString(reader)
|
||||
var _4: [Api.InputPeer]?
|
||||
if let _ = reader.readInt32() {
|
||||
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputPeer.self)
|
||||
}
|
||||
var _4: String?
|
||||
if Int(_1!) & Int(1 << 25) != 0 {_4 = parseString(reader) }
|
||||
var _5: [Api.InputPeer]?
|
||||
if let _ = reader.readInt32() {
|
||||
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputPeer.self)
|
||||
}
|
||||
var _6: [Api.InputPeer]?
|
||||
if let _ = reader.readInt32() {
|
||||
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputPeer.self)
|
||||
}
|
||||
var _7: [Api.InputPeer]?
|
||||
if let _ = reader.readInt32() {
|
||||
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputPeer.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
let _c4 = (Int(_1!) & Int(1 << 25) == 0) || _4 != nil
|
||||
let _c5 = _5 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 {
|
||||
return Api.DialogFilter.dialogFilter(flags: _1!, id: _2!, title: _3!, includePeers: _4!, excludePeers: _5!)
|
||||
let _c6 = _6 != nil
|
||||
let _c7 = _7 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
|
||||
return Api.DialogFilter.dialogFilter(flags: _1!, id: _2!, title: _3!, emoticon: _4, pinnedPeers: _5!, includePeers: _6!, excludePeers: _7!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
@ -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, accountPeerId: self.peerId).start())
|
||||
|
||||
let importantBackgroundOperations: [Signal<AccountRunningImportantTasks, NoError>] = [
|
||||
managedSynchronizeChatInputStateOperations(postbox: self.postbox, network: self.network) |> map { $0 ? AccountRunningImportantTasks.other : [] },
|
||||
|
@ -1488,7 +1488,7 @@ private func resolveMissingPeerChatInfos(network: Network, state: AccountMutable
|
||||
|
||||
var updatedState = state
|
||||
switch result {
|
||||
case let .peerDialogs(dialogs, messages, chats, users, state):
|
||||
case let .peerDialogs(dialogs, messages, chats, users, _):
|
||||
updatedState.mergeChats(chats)
|
||||
updatedState.mergeUsers(users)
|
||||
|
||||
@ -1496,7 +1496,7 @@ private func resolveMissingPeerChatInfos(network: Network, state: AccountMutable
|
||||
|
||||
for dialog in dialogs {
|
||||
switch dialog {
|
||||
case let .dialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, notifySettings, pts, draft, folderId):
|
||||
case let .dialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, notifySettings, pts, _, folderId):
|
||||
let peerId = peer.peerId
|
||||
|
||||
updatedState.setNeedsHoleFromPreviousState(peerId: peerId, namespace: Namespaces.Message.Cloud)
|
||||
|
@ -286,7 +286,7 @@ public final class AccountViewTracker {
|
||||
|
||||
self.historyViewStateValidationContexts = HistoryViewStateValidationContexts(queue: self.queue, postbox: account.postbox, network: account.network, accountPeerId: account.peerId)
|
||||
|
||||
self.chatHistoryPreloadManager = ChatHistoryPreloadManager(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, networkState: account.networkState, preloadItemsSignal: self.chatListPreloadItems.get())
|
||||
self.chatHistoryPreloadManager = ChatHistoryPreloadManager(postbox: account.postbox, network: account.network, accountPeerId: account.peerId, networkState: account.networkState, preloadItemsSignal: self.chatListPreloadItems.get() |> distinctUntilChanged)
|
||||
|
||||
self.externallyUpdatedPeerIdDisposable.set((account.stateManager.externallyUpdatedPeerIds
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] peerIds in
|
||||
|
@ -234,7 +234,7 @@ private final class AdditionalPreloadPeerIdsContext {
|
||||
}
|
||||
}
|
||||
|
||||
public struct ChatHistoryPreloadItem {
|
||||
public struct ChatHistoryPreloadItem : Equatable {
|
||||
public let index: ChatListIndex
|
||||
public let isMuted: Bool
|
||||
public let hasUnread: Bool
|
||||
|
@ -96,12 +96,69 @@ extension ChatListFilterPeerCategories {
|
||||
}
|
||||
}
|
||||
|
||||
public struct ChatListFilterIncludePeers: Equatable, Hashable {
|
||||
public private(set) var peers: [PeerId]
|
||||
public private(set) var pinnedPeers: [PeerId]
|
||||
|
||||
public init() {
|
||||
self.peers = []
|
||||
self.pinnedPeers = []
|
||||
}
|
||||
|
||||
init(peers: [PeerId], pinnedPeers: [PeerId]) {
|
||||
self.peers = peers
|
||||
self.pinnedPeers = pinnedPeers
|
||||
}
|
||||
|
||||
public mutating func reorderPinnedPeers(_ pinnedPeers: [PeerId]) {
|
||||
if Set(self.pinnedPeers) == Set(pinnedPeers) {
|
||||
self.pinnedPeers = pinnedPeers
|
||||
}
|
||||
}
|
||||
|
||||
public mutating func addPinnedPeer(_ peerId: PeerId) -> Bool {
|
||||
if self.pinnedPeers.contains(peerId) {
|
||||
return false
|
||||
}
|
||||
if self.peers.contains(peerId) {
|
||||
self.pinnedPeers.insert(peerId, at: 0)
|
||||
return true
|
||||
} else {
|
||||
if self.peers.count < 100 {
|
||||
self.peers.insert(peerId, at: 0)
|
||||
self.pinnedPeers.insert(peerId, at: 0)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public mutating func removePinnedPeer(_ peerId: PeerId) {
|
||||
if self.pinnedPeers.contains(peerId) {
|
||||
self.pinnedPeers.removeAll(where: { $0 == peerId })
|
||||
}
|
||||
}
|
||||
|
||||
public mutating func setPeers(_ peers: [PeerId]) {
|
||||
self.peers = peers
|
||||
self.pinnedPeers = self.pinnedPeers.filter { peers.contains($0) }
|
||||
}
|
||||
}
|
||||
|
||||
extension ChatListFilterIncludePeers {
|
||||
init(rawPeers: [PeerId], rawPinnedPeers: [PeerId]) {
|
||||
self.peers = rawPinnedPeers + rawPeers.filter { !rawPinnedPeers.contains($0) }
|
||||
self.pinnedPeers = rawPinnedPeers
|
||||
}
|
||||
}
|
||||
|
||||
public struct ChatListFilterData: Equatable, Hashable {
|
||||
public var categories: ChatListFilterPeerCategories
|
||||
public var excludeMuted: Bool
|
||||
public var excludeRead: Bool
|
||||
public var excludeArchived: Bool
|
||||
public var includePeers: [PeerId]
|
||||
public var includePeers: ChatListFilterIncludePeers
|
||||
public var excludePeers: [PeerId]
|
||||
|
||||
public init(
|
||||
@ -109,7 +166,7 @@ public struct ChatListFilterData: Equatable, Hashable {
|
||||
excludeMuted: Bool,
|
||||
excludeRead: Bool,
|
||||
excludeArchived: Bool,
|
||||
includePeers: [PeerId],
|
||||
includePeers: ChatListFilterIncludePeers,
|
||||
excludePeers: [PeerId]
|
||||
) {
|
||||
self.categories = categories
|
||||
@ -124,27 +181,31 @@ public struct ChatListFilterData: Equatable, Hashable {
|
||||
public struct ChatListFilter: PostboxCoding, Equatable {
|
||||
public var id: Int32
|
||||
public var title: String
|
||||
public var emoticon: String?
|
||||
public var data: ChatListFilterData
|
||||
|
||||
public init(
|
||||
id: Int32,
|
||||
title: String,
|
||||
emoticon: String?,
|
||||
data: ChatListFilterData
|
||||
) {
|
||||
self.id = id
|
||||
self.title = title
|
||||
self.emoticon = emoticon
|
||||
self.data = data
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.id = decoder.decodeInt32ForKey("id", orElse: 0)
|
||||
self.title = decoder.decodeStringForKey("title", orElse: "")
|
||||
self.emoticon = decoder.decodeOptionalStringForKey("emoticon")
|
||||
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,
|
||||
excludeArchived: decoder.decodeInt32ForKey("excludeArchived", orElse: 0) != 0,
|
||||
includePeers: decoder.decodeInt64ArrayForKey("includePeers").map(PeerId.init),
|
||||
includePeers: ChatListFilterIncludePeers(peers: decoder.decodeInt64ArrayForKey("includePeers").map(PeerId.init), pinnedPeers: decoder.decodeInt64ArrayForKey("pinnedPeers").map(PeerId.init)),
|
||||
excludePeers: decoder.decodeInt64ArrayForKey("excludePeers").map(PeerId.init)
|
||||
)
|
||||
}
|
||||
@ -152,11 +213,17 @@ public struct ChatListFilter: PostboxCoding, Equatable {
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt32(self.id, forKey: "id")
|
||||
encoder.encodeString(self.title, forKey: "title")
|
||||
if let emoticon = self.emoticon {
|
||||
encoder.encodeString(emoticon, forKey: "emoticon")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "emoticon")
|
||||
}
|
||||
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.encodeInt32(self.data.excludeArchived ? 1 : 0, forKey: "excludeArchived")
|
||||
encoder.encodeInt64Array(self.data.includePeers.map { $0.toInt64() }, forKey: "includePeers")
|
||||
encoder.encodeInt64Array(self.data.includePeers.peers.map { $0.toInt64() }, forKey: "includePeers")
|
||||
encoder.encodeInt64Array(self.data.includePeers.pinnedPeers.map { $0.toInt64() }, forKey: "pinnedPeers")
|
||||
encoder.encodeInt64Array(self.data.excludePeers.map { $0.toInt64() }, forKey: "excludePeers")
|
||||
}
|
||||
}
|
||||
@ -164,16 +231,17 @@ public struct ChatListFilter: PostboxCoding, Equatable {
|
||||
extension ChatListFilter {
|
||||
init(apiFilter: Api.DialogFilter) {
|
||||
switch apiFilter {
|
||||
case let .dialogFilter(flags, id, title, includePeers, excludePeers):
|
||||
case let .dialogFilter(flags, id, title, emoticon, pinnedPeers, includePeers, excludePeers):
|
||||
self.init(
|
||||
id: id,
|
||||
title: title,
|
||||
emoticon: emoticon,
|
||||
data: ChatListFilterData(
|
||||
categories: ChatListFilterPeerCategories(apiFlags: flags),
|
||||
excludeMuted: (flags & (1 << 11)) != 0,
|
||||
excludeRead: (flags & (1 << 12)) != 0,
|
||||
excludeArchived: (flags & (1 << 13)) != 0,
|
||||
includePeers: includePeers.compactMap { peer -> PeerId? in
|
||||
includePeers: ChatListFilterIncludePeers(rawPeers: includePeers.compactMap { peer -> PeerId? in
|
||||
switch peer {
|
||||
case let .inputPeerUser(userId, _):
|
||||
return PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
|
||||
@ -184,7 +252,18 @@ extension ChatListFilter {
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
},
|
||||
}, rawPinnedPeers: pinnedPeers.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
|
||||
}
|
||||
}),
|
||||
excludePeers: excludePeers.compactMap { peer -> PeerId? in
|
||||
switch peer {
|
||||
case let .inputPeerUser(userId, _):
|
||||
@ -214,7 +293,15 @@ extension ChatListFilter {
|
||||
flags |= 1 << 13
|
||||
}
|
||||
flags |= self.data.categories.apiFlags
|
||||
return .dialogFilter(flags: flags, id: self.id, title: self.title, includePeers: self.data.includePeers.compactMap { peerId -> Api.InputPeer? in
|
||||
if self.emoticon != nil {
|
||||
flags |= 1 << 25
|
||||
}
|
||||
return .dialogFilter(flags: flags, id: self.id, title: self.title, emoticon: self.emoticon, pinnedPeers: self.data.includePeers.pinnedPeers.compactMap { peerId -> Api.InputPeer? in
|
||||
return transaction.getPeer(peerId).flatMap(apiInputPeer)
|
||||
}, includePeers: self.data.includePeers.peers.compactMap { peerId -> Api.InputPeer? in
|
||||
if self.data.includePeers.pinnedPeers.contains(peerId) {
|
||||
return nil
|
||||
}
|
||||
return transaction.getPeer(peerId).flatMap(apiInputPeer)
|
||||
}, excludePeers: self.data.excludePeers.compactMap { peerId -> Api.InputPeer? in
|
||||
return transaction.getPeer(peerId).flatMap(apiInputPeer)
|
||||
@ -264,22 +351,24 @@ private enum RequestChatListFiltersError {
|
||||
case generic
|
||||
}
|
||||
|
||||
private func requestChatListFilters(postbox: Postbox, network: Network) -> Signal<[ChatListFilter], RequestChatListFiltersError> {
|
||||
private func requestChatListFilters(accountPeerId: PeerId, postbox: Postbox, network: Network) -> Signal<[ChatListFilter], RequestChatListFiltersError> {
|
||||
return network.request(Api.functions.messages.getDialogFilters())
|
||||
|> mapError { _ -> RequestChatListFiltersError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { result -> Signal<[ChatListFilter], RequestChatListFiltersError> in
|
||||
return postbox.transaction { transaction -> ([ChatListFilter], [Api.InputPeer]) in
|
||||
return postbox.transaction { transaction -> ([ChatListFilter], [Api.InputPeer], [Api.InputPeer]) in
|
||||
var filters: [ChatListFilter] = []
|
||||
var missingPeers: [Api.InputPeer] = []
|
||||
var missingChats: [Api.InputPeer] = []
|
||||
var missingPeerIds = Set<PeerId>()
|
||||
var missingChatIds = Set<PeerId>()
|
||||
for apiFilter in result {
|
||||
let filter = ChatListFilter(apiFilter: apiFilter)
|
||||
filters.append(filter)
|
||||
switch apiFilter {
|
||||
case let .dialogFilter(_, _, _, includePeers, excludePeers):
|
||||
for peer in includePeers {
|
||||
case let .dialogFilter(_, _, _, _, pinnedPeers, includePeers, excludePeers):
|
||||
for peer in pinnedPeers + includePeers + excludePeers {
|
||||
var peerId: PeerId?
|
||||
switch peer {
|
||||
case let .inputPeerUser(userId, _):
|
||||
@ -298,7 +387,8 @@ private func requestChatListFilters(postbox: Postbox, network: Network) -> Signa
|
||||
}
|
||||
}
|
||||
}
|
||||
for peer in excludePeers {
|
||||
|
||||
for peer in pinnedPeers {
|
||||
var peerId: PeerId?
|
||||
switch peer {
|
||||
case let .inputPeerUser(userId, _):
|
||||
@ -310,20 +400,20 @@ private func requestChatListFilters(postbox: Postbox, network: Network) -> Signa
|
||||
default:
|
||||
break
|
||||
}
|
||||
if let peerId = peerId {
|
||||
if transaction.getPeer(peerId) == nil && !missingPeerIds.contains(peerId) {
|
||||
missingPeerIds.insert(peerId)
|
||||
missingPeers.append(peer)
|
||||
if let peerId = peerId, !missingChatIds.contains(peerId) {
|
||||
if transaction.getPeerChatListIndex(peerId) == nil {
|
||||
missingChatIds.insert(peerId)
|
||||
missingChats.append(peer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return (filters, missingPeers)
|
||||
return (filters, missingPeers, missingChats)
|
||||
}
|
||||
|> castError(RequestChatListFiltersError.self)
|
||||
|> mapToSignal { filtersAndMissingPeers -> Signal<[ChatListFilter], RequestChatListFiltersError> in
|
||||
let (filters, missingPeers) = filtersAndMissingPeers
|
||||
let (filters, missingPeers, missingChats) = filtersAndMissingPeers
|
||||
|
||||
var missingUsers: [Api.InputUser] = []
|
||||
var missingChannels: [Api.InputChannel] = []
|
||||
@ -425,6 +515,13 @@ private func requestChatListFilters(postbox: Postbox, network: Network) -> Signa
|
||||
resolveMissingGroups = .complete()
|
||||
}
|
||||
|
||||
let loadMissingChats: Signal<Never, NoError>
|
||||
if !missingChats.isEmpty {
|
||||
loadMissingChats = loadAndStorePeerChatInfos(accountPeerId: accountPeerId, postbox: postbox, network: network, peers: missingChats)
|
||||
} else {
|
||||
loadMissingChats = .complete()
|
||||
}
|
||||
|
||||
return (
|
||||
resolveMissingUsers
|
||||
)
|
||||
@ -434,9 +531,14 @@ private func requestChatListFilters(postbox: Postbox, network: Network) -> Signa
|
||||
|> then(
|
||||
resolveMissingGroups
|
||||
)
|
||||
|> then(
|
||||
loadMissingChats
|
||||
)
|
||||
|> castError(RequestChatListFiltersError.self)
|
||||
|> mapToSignal { _ -> Signal<[ChatListFilter], RequestChatListFiltersError> in
|
||||
#if swift(<5.1)
|
||||
return .complete()
|
||||
#endif
|
||||
}
|
||||
|> then(
|
||||
.single(filters)
|
||||
@ -445,6 +547,147 @@ private func requestChatListFilters(postbox: Postbox, network: Network) -> Signa
|
||||
}
|
||||
}
|
||||
|
||||
private func loadAndStorePeerChatInfos(accountPeerId: PeerId, postbox: Postbox, network: Network, peers: [Api.InputPeer]) -> Signal<Never, NoError> {
|
||||
let signal = network.request(Api.functions.messages.getPeerDialogs(peers: peers.map(Api.InputDialogPeer.inputDialogPeer(peer:))))
|
||||
|> map(Optional.init)
|
||||
|
||||
return signal
|
||||
|> `catch` { _ -> Signal<Api.messages.PeerDialogs?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { result -> Signal<Never, NoError> in
|
||||
guard let result = result else {
|
||||
return .complete()
|
||||
}
|
||||
|
||||
return postbox.transaction { transaction -> Void in
|
||||
var peers: [Peer] = []
|
||||
var peerPresences: [PeerId: PeerPresence] = [:]
|
||||
var notificationSettings: [PeerId: PeerNotificationSettings] = [:]
|
||||
var channelStates: [PeerId: ChannelState] = [:]
|
||||
|
||||
switch result {
|
||||
case let .peerDialogs(dialogs, messages, chats, users, _):
|
||||
for chat in chats {
|
||||
if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) {
|
||||
peers.append(groupOrChannel)
|
||||
}
|
||||
}
|
||||
for user in users {
|
||||
let telegramUser = TelegramUser(user: user)
|
||||
peers.append(telegramUser)
|
||||
if let presence = TelegramUserPresence(apiUser: user) {
|
||||
peerPresences[telegramUser.id] = presence
|
||||
}
|
||||
}
|
||||
|
||||
var topMessageIds = Set<MessageId>()
|
||||
|
||||
for dialog in dialogs {
|
||||
switch dialog {
|
||||
case let .dialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, notifySettings, pts, _, folderId):
|
||||
let peerId = peer.peerId
|
||||
|
||||
if topMessage != 0 {
|
||||
topMessageIds.insert(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: topMessage))
|
||||
}
|
||||
|
||||
var isExcludedFromChatList = false
|
||||
for chat in chats {
|
||||
if chat.peerId == peerId {
|
||||
if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) {
|
||||
if let group = groupOrChannel as? TelegramGroup {
|
||||
if group.flags.contains(.deactivated) {
|
||||
isExcludedFromChatList = true
|
||||
} else {
|
||||
switch group.membership {
|
||||
case .Member:
|
||||
break
|
||||
default:
|
||||
isExcludedFromChatList = true
|
||||
}
|
||||
}
|
||||
} else if let channel = groupOrChannel as? TelegramChannel {
|
||||
switch channel.participationStatus {
|
||||
case .member:
|
||||
break
|
||||
default:
|
||||
isExcludedFromChatList = true
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !isExcludedFromChatList {
|
||||
let groupId = PeerGroupId(rawValue: folderId ?? 0)
|
||||
let currentInclusion = transaction.getPeerChatListInclusion(peerId)
|
||||
var currentPinningIndex: UInt16?
|
||||
var currentMinTimestamp: Int32?
|
||||
switch currentInclusion {
|
||||
case let .ifHasMessagesOrOneOf(currentGroupId, pinningIndex, minTimestamp):
|
||||
if currentGroupId == groupId {
|
||||
currentPinningIndex = pinningIndex
|
||||
}
|
||||
currentMinTimestamp = minTimestamp
|
||||
default:
|
||||
break
|
||||
}
|
||||
transaction.updatePeerChatListInclusion(peerId, inclusion: .ifHasMessagesOrOneOf(groupId: groupId, pinningIndex: currentPinningIndex, minTimestamp: currentMinTimestamp))
|
||||
}
|
||||
|
||||
notificationSettings[peer.peerId] = TelegramPeerNotificationSettings(apiSettings: notifySettings)
|
||||
|
||||
transaction.resetIncomingReadStates([peerId: [Namespaces.Message.Cloud: .idBased(maxIncomingReadId: readInboxMaxId, maxOutgoingReadId: readOutboxMaxId, maxKnownId: topMessage, count: unreadCount, markedUnread: false)]])
|
||||
|
||||
transaction.replaceMessageTagSummary(peerId: peerId, tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud, count: unreadMentionsCount, maxId: topMessage)
|
||||
|
||||
if let pts = pts {
|
||||
let channelState = ChannelState(pts: pts, invalidatedPts: pts)
|
||||
transaction.setPeerChatState(peerId, state: channelState)
|
||||
channelStates[peer.peerId] = channelState
|
||||
}
|
||||
case .dialogFolder:
|
||||
assertionFailure()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var storeMessages: [StoreMessage] = []
|
||||
for message in messages {
|
||||
if let storeMessage = StoreMessage(apiMessage: message) {
|
||||
var updatedStoreMessage = storeMessage
|
||||
if case let .Id(id) = storeMessage.id {
|
||||
if let channelState = channelStates[id.peerId] {
|
||||
var updatedAttributes = storeMessage.attributes
|
||||
updatedAttributes.append(ChannelMessageStateVersionAttribute(pts: channelState.pts))
|
||||
updatedStoreMessage = updatedStoreMessage.withUpdatedAttributes(updatedAttributes)
|
||||
}
|
||||
}
|
||||
storeMessages.append(updatedStoreMessage)
|
||||
}
|
||||
}
|
||||
|
||||
for message in storeMessages {
|
||||
if case let .Id(id) = message.id {
|
||||
let _ = transaction.addMessages([message], location: topMessageIds.contains(id) ? .UpperHistoryBlock : .Random)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
|
||||
return updated
|
||||
})
|
||||
|
||||
updatePeerPresences(transaction: transaction, accountPeerId: accountPeerId, peerPresences: peerPresences)
|
||||
|
||||
transaction.updateCurrentPeerNotificationSettings(notificationSettings)
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
}
|
||||
|
||||
struct ChatListFiltersState: PreferencesEntry, Equatable {
|
||||
var filters: [ChatListFilter]
|
||||
var remoteFilters: [ChatListFilter]?
|
||||
@ -509,6 +752,23 @@ public func updateChatListFiltersInteractively(postbox: Postbox, _ f: @escaping
|
||||
}
|
||||
}
|
||||
|
||||
public func updateChatListFiltersInteractively(transaction: Transaction, _ f: ([ChatListFilter]) -> [ChatListFilter]) {
|
||||
var hasUpdates = false
|
||||
transaction.updatePreferencesEntry(key: PreferencesKeys.chatListFilters, { entry in
|
||||
var state = entry as? ChatListFiltersState ?? ChatListFiltersState.default
|
||||
let updatedFilters = f(state.filters)
|
||||
if updatedFilters != state.filters {
|
||||
state.filters = updatedFilters
|
||||
hasUpdates = true
|
||||
}
|
||||
return state
|
||||
})
|
||||
if hasUpdates {
|
||||
requestChatListFiltersSync(transaction: transaction)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func updatedChatListFilters(postbox: Postbox) -> Signal<[ChatListFilter], NoError> {
|
||||
return postbox.preferencesView(keys: [PreferencesKeys.chatListFilters])
|
||||
|> map { preferences -> [ChatListFilter] in
|
||||
@ -576,7 +836,7 @@ public struct ChatListFeaturedFilter: PostboxCoding, Equatable {
|
||||
excludeMuted: decoder.decodeInt32ForKey("excludeMuted", orElse: 0) != 0,
|
||||
excludeRead: decoder.decodeInt32ForKey("excludeRead", orElse: 0) != 0,
|
||||
excludeArchived: decoder.decodeInt32ForKey("excludeArchived", orElse: 0) != 0,
|
||||
includePeers: decoder.decodeInt64ArrayForKey("includePeers").map(PeerId.init),
|
||||
includePeers: ChatListFilterIncludePeers(peers: decoder.decodeInt64ArrayForKey("includePeers").map(PeerId.init), pinnedPeers: decoder.decodeInt64ArrayForKey("pinnedPeers").map(PeerId.init)),
|
||||
excludePeers: decoder.decodeInt64ArrayForKey("excludePeers").map(PeerId.init)
|
||||
)
|
||||
}
|
||||
@ -588,7 +848,8 @@ public struct ChatListFeaturedFilter: PostboxCoding, Equatable {
|
||||
encoder.encodeInt32(self.data.excludeMuted ? 1 : 0, forKey: "excludeMuted")
|
||||
encoder.encodeInt32(self.data.excludeRead ? 1 : 0, forKey: "excludeRead")
|
||||
encoder.encodeInt32(self.data.excludeArchived ? 1 : 0, forKey: "excludeArchived")
|
||||
encoder.encodeInt64Array(self.data.includePeers.map { $0.toInt64() }, forKey: "includePeers")
|
||||
encoder.encodeInt64Array(self.data.includePeers.peers.map { $0.toInt64() }, forKey: "includePeers")
|
||||
encoder.encodeInt64Array(self.data.includePeers.pinnedPeers.map { $0.toInt64() }, forKey: "pinnedPeers")
|
||||
encoder.encodeInt64Array(self.data.excludePeers.map { $0.toInt64() }, forKey: "excludePeers")
|
||||
}
|
||||
}
|
||||
@ -668,8 +929,6 @@ public func updateChatListFeaturedFilters(postbox: Postbox, network: Network) ->
|
||||
}
|
||||
|
||||
private enum SynchronizeChatListFiltersOperationContentType: Int32 {
|
||||
case add
|
||||
case remove
|
||||
case sync
|
||||
}
|
||||
|
||||
@ -681,7 +940,7 @@ private enum SynchronizeChatListFiltersOperationContent: PostboxCoding {
|
||||
case SynchronizeChatListFiltersOperationContentType.sync.rawValue:
|
||||
self = .sync
|
||||
default:
|
||||
assertionFailure()
|
||||
//assertionFailure()
|
||||
self = .sync
|
||||
}
|
||||
}
|
||||
@ -791,7 +1050,7 @@ func requestChatListFiltersSync(transaction: Transaction) {
|
||||
transaction.operationLogAddEntry(peerId: peerId, tag: tag, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SynchronizeChatListFiltersOperation(content: .sync))
|
||||
}
|
||||
|
||||
func managedChatListFilters(postbox: Postbox, network: Network) -> Signal<Void, NoError> {
|
||||
func managedChatListFilters(postbox: Postbox, network: Network, accountPeerId: PeerId) -> Signal<Void, NoError> {
|
||||
return Signal { _ in
|
||||
let updateFeaturedDisposable = updateChatListFeaturedFilters(postbox: postbox, network: network).start()
|
||||
let _ = postbox.transaction({ transaction in
|
||||
@ -815,7 +1074,7 @@ func managedChatListFilters(postbox: Postbox, network: Network) -> Signal<Void,
|
||||
let signal = withTakenOperation(postbox: postbox, peerId: entry.peerId, tag: tag, tagLocalIndex: entry.tagLocalIndex, { transaction, entry -> Signal<Never, NoError> in
|
||||
if let entry = entry {
|
||||
if let operation = entry.contents as? SynchronizeChatListFiltersOperation {
|
||||
return synchronizeChatListFilters(transaction: transaction, postbox: postbox, network: network, operation: operation)
|
||||
return synchronizeChatListFilters(transaction: transaction, accountPeerId: accountPeerId, postbox: postbox, network: network, operation: operation)
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
@ -847,14 +1106,14 @@ func managedChatListFilters(postbox: Postbox, network: Network) -> Signal<Void,
|
||||
}
|
||||
}
|
||||
|
||||
private func synchronizeChatListFilters(transaction: Transaction, postbox: Postbox, network: Network, operation: SynchronizeChatListFiltersOperation) -> Signal<Never, NoError> {
|
||||
private func synchronizeChatListFilters(transaction: Transaction, accountPeerId: PeerId, postbox: Postbox, network: Network, operation: SynchronizeChatListFiltersOperation) -> Signal<Never, NoError> {
|
||||
switch operation.content {
|
||||
case .sync:
|
||||
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default
|
||||
let localFilters = settings.filters
|
||||
let locallyKnownRemoteFilters = settings.remoteFilters ?? []
|
||||
|
||||
return requestChatListFilters(postbox: postbox, network: network)
|
||||
return requestChatListFilters(accountPeerId: accountPeerId, postbox: postbox, network: network)
|
||||
|> `catch` { _ -> Signal<[ChatListFilter], NoError> in
|
||||
return .complete()
|
||||
}
|
||||
|
@ -2,66 +2,38 @@ import Foundation
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
import SyncCore
|
||||
import TelegramApi
|
||||
|
||||
private final class ManagedChatListHolesState {
|
||||
private var holeDisposables: [ChatListHolesEntry: Disposable] = [:]
|
||||
private var additionalLatestHoleDisposable: (ChatListHole, Disposable)?
|
||||
private var additionalLatestArchiveHoleDisposable: (ChatListHole, Disposable)?
|
||||
private var currentHole: (ChatListHolesEntry, Disposable)?
|
||||
|
||||
func clearDisposables() -> [Disposable] {
|
||||
let disposables = Array(self.holeDisposables.values)
|
||||
self.holeDisposables.removeAll()
|
||||
return disposables
|
||||
if let (_, disposable) = self.currentHole {
|
||||
self.currentHole = nil
|
||||
return [disposable]
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
func update(entries: Set<ChatListHolesEntry>, additionalLatestHole: ChatListHole?, additionalLatestArchiveHole: ChatListHole?) -> (removed: [Disposable], added: [ChatListHolesEntry: MetaDisposable], addedAdditionalLatestHole: (ChatListHole, MetaDisposable)?, addedAdditionalLatestArchiveHole: (ChatListHole, MetaDisposable)?) {
|
||||
func update(entries: [ChatListHolesEntry]) -> (removed: [Disposable], added: [ChatListHolesEntry: MetaDisposable]) {
|
||||
var removed: [Disposable] = []
|
||||
var added: [ChatListHolesEntry: MetaDisposable] = [:]
|
||||
|
||||
for (entry, disposable) in self.holeDisposables {
|
||||
if let (entry, disposable) = self.currentHole {
|
||||
if !entries.contains(entry) {
|
||||
removed.append(disposable)
|
||||
self.holeDisposables.removeValue(forKey: entry)
|
||||
self.currentHole = nil
|
||||
}
|
||||
}
|
||||
|
||||
for entry in entries {
|
||||
if self.holeDisposables[entry] == nil {
|
||||
let disposable = MetaDisposable()
|
||||
self.holeDisposables[entry] = disposable
|
||||
added[entry] = disposable
|
||||
}
|
||||
if self.currentHole == nil, let entry = entries.first {
|
||||
let disposable = MetaDisposable()
|
||||
self.currentHole = (entry, disposable)
|
||||
added[entry] = disposable
|
||||
}
|
||||
|
||||
var addedAdditionalLatestHole: (ChatListHole, MetaDisposable)?
|
||||
var addedAdditionalLatestArchiveHole: (ChatListHole, MetaDisposable)?
|
||||
if self.holeDisposables.isEmpty {
|
||||
if self.additionalLatestHoleDisposable?.0 != additionalLatestHole {
|
||||
if let (_, disposable) = self.additionalLatestHoleDisposable {
|
||||
removed.append(disposable)
|
||||
}
|
||||
if let additionalLatestHole = additionalLatestHole {
|
||||
let disposable = MetaDisposable()
|
||||
self.additionalLatestHoleDisposable = (additionalLatestHole, disposable)
|
||||
addedAdditionalLatestHole = (additionalLatestHole, disposable)
|
||||
}
|
||||
}
|
||||
|
||||
if additionalLatestHole == nil {
|
||||
if self.additionalLatestArchiveHoleDisposable?.0 != additionalLatestArchiveHole {
|
||||
if let (_, disposable) = self.additionalLatestArchiveHoleDisposable {
|
||||
removed.append(disposable)
|
||||
}
|
||||
if let additionalLatestArchiveHole = additionalLatestArchiveHole {
|
||||
let disposable = MetaDisposable()
|
||||
self.additionalLatestArchiveHoleDisposable = (additionalLatestArchiveHole, disposable)
|
||||
addedAdditionalLatestArchiveHole = (additionalLatestArchiveHole, disposable)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (removed, added, addedAdditionalLatestHole, addedAdditionalLatestArchiveHole)
|
||||
return (removed, added)
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,24 +47,29 @@ func managedChatListHoles(network: Network, postbox: Postbox, accountPeerId: Pee
|
||||
let combinedView = postbox.combinedView(keys: [topRootHoleKey, topArchiveHoleKey, filtersKey])
|
||||
|
||||
let disposable = combineLatest(postbox.chatListHolesView(), combinedView).start(next: { view, combinedView in
|
||||
var additionalLatestHole: ChatListHole?
|
||||
var additionalLatestArchiveHole: ChatListHole?
|
||||
var entries = Array(view.entries).sorted(by: { lhs, rhs in
|
||||
return lhs.hole.index > rhs.hole.index
|
||||
})
|
||||
|
||||
if let preferencesView = combinedView.views[filtersKey] as? PreferencesView, let filtersState = preferencesView.values[PreferencesKeys.chatListFilters] as? ChatListFiltersState, !filtersState.filters.isEmpty {
|
||||
if let topRootHole = combinedView.views[topRootHoleKey] as? AllChatListHolesView, let hole = topRootHole.latestHole {
|
||||
if !view.entries.contains(ChatListHolesEntry(groupId: .root, hole: hole)) {
|
||||
additionalLatestHole = hole
|
||||
let entry = ChatListHolesEntry(groupId: .root, hole: hole)
|
||||
if !entries.contains(entry) {
|
||||
entries.append(entry)
|
||||
}
|
||||
}
|
||||
if let topArchiveHole = combinedView.views[topArchiveHoleKey] as? AllChatListHolesView, let hole = topArchiveHole.latestHole {
|
||||
if !view.entries.contains(ChatListHolesEntry(groupId: Namespaces.PeerGroup.archive, hole: hole)) {
|
||||
additionalLatestArchiveHole = hole
|
||||
let entry = ChatListHolesEntry(groupId: Namespaces.PeerGroup.archive, hole: hole)
|
||||
if !entries.contains(entry) {
|
||||
entries.append(entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (removed, added, addedAdditionalLatestHole, addedAdditionalLatestArchiveHole) = state.with { state in
|
||||
return state.update(entries: view.entries, additionalLatestHole: additionalLatestHole, additionalLatestArchiveHole: additionalLatestArchiveHole)
|
||||
let (removed, added) = state.with { state in
|
||||
return state.update(entries: entries)
|
||||
}
|
||||
|
||||
for disposable in removed {
|
||||
@ -102,14 +79,6 @@ func managedChatListHoles(network: Network, postbox: Postbox, accountPeerId: Pee
|
||||
for (entry, disposable) in added {
|
||||
disposable.set(fetchChatListHole(postbox: postbox, network: network, accountPeerId: accountPeerId, groupId: entry.groupId, hole: entry.hole).start())
|
||||
}
|
||||
|
||||
if let (hole, disposable) = addedAdditionalLatestHole {
|
||||
disposable.set(fetchChatListHole(postbox: postbox, network: network, accountPeerId: accountPeerId, groupId: .root, hole: hole).start())
|
||||
}
|
||||
|
||||
if let (hole, disposable) = addedAdditionalLatestArchiveHole {
|
||||
disposable.set(fetchChatListHole(postbox: postbox, network: network, accountPeerId: accountPeerId, groupId: Namespaces.PeerGroup.archive, hole: hole).start())
|
||||
}
|
||||
})
|
||||
|
||||
return ActionDisposable {
|
||||
|
@ -34,6 +34,18 @@ public func removePeerChat(account: Account, transaction: Transaction, mediaBox:
|
||||
}
|
||||
})
|
||||
}
|
||||
updateChatListFiltersInteractively(transaction: transaction, { filters in
|
||||
var filters = filters
|
||||
for i in 0 ..< filters.count {
|
||||
if filters[i].data.includePeers.peers.contains(peerId) {
|
||||
filters[i].data.includePeers.setPeers(filters[i].data.includePeers.peers.filter { $0 != peerId })
|
||||
}
|
||||
if filters[i].data.excludePeers.contains(peerId) {
|
||||
filters[i].data.excludePeers = filters[i].data.excludePeers.filter { $0 != peerId }
|
||||
}
|
||||
}
|
||||
return filters
|
||||
})
|
||||
if peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
if let state = transaction.getPeerChatState(peerId) as? SecretChatState, state.embeddedState != .terminated {
|
||||
let updatedState = addSecretChatOutgoingOperation(transaction: transaction, peerId: peerId, operation: SecretChatOutgoingOperationContents.terminate(reportSpam: reportChatSpam), state: state).withUpdatedEmbeddedState(.terminated)
|
||||
|
@ -4,62 +4,130 @@ import SwiftSignalKit
|
||||
|
||||
import SyncCore
|
||||
|
||||
public enum TogglePeerChatPinnedLocation {
|
||||
case group(PeerGroupId)
|
||||
case filter(Int32)
|
||||
}
|
||||
|
||||
public enum TogglePeerChatPinnedResult {
|
||||
case done
|
||||
case limitExceeded(Int)
|
||||
}
|
||||
|
||||
public func toggleItemPinned(postbox: Postbox, groupId: PeerGroupId, itemId: PinnedItemId) -> Signal<TogglePeerChatPinnedResult, NoError> {
|
||||
public func toggleItemPinned(postbox: Postbox, location: TogglePeerChatPinnedLocation, itemId: PinnedItemId) -> Signal<TogglePeerChatPinnedResult, NoError> {
|
||||
return postbox.transaction { transaction -> TogglePeerChatPinnedResult in
|
||||
var itemIds = transaction.getPinnedItemIds(groupId: groupId)
|
||||
let sameKind = itemIds.filter { item in
|
||||
switch itemId {
|
||||
case let .peer(lhsPeerId):
|
||||
if case let .peer(rhsPeerId) = item {
|
||||
return (lhsPeerId.namespace == Namespaces.Peer.SecretChat) == (rhsPeerId.namespace == Namespaces.Peer.SecretChat) && lhsPeerId != rhsPeerId
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
switch location {
|
||||
case let .group(groupId):
|
||||
var itemIds = transaction.getPinnedItemIds(groupId: groupId)
|
||||
let sameKind = itemIds.filter { item in
|
||||
switch itemId {
|
||||
case let .peer(lhsPeerId):
|
||||
if case let .peer(rhsPeerId) = item {
|
||||
return (lhsPeerId.namespace == Namespaces.Peer.SecretChat) == (rhsPeerId.namespace == Namespaces.Peer.SecretChat) && lhsPeerId != rhsPeerId
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let additionalCount: Int
|
||||
if let _ = itemIds.firstIndex(of: itemId) {
|
||||
additionalCount = -1
|
||||
} else {
|
||||
additionalCount = 1
|
||||
}
|
||||
|
||||
let limitsConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.limitsConfiguration) as? LimitsConfiguration ?? LimitsConfiguration.defaultValue
|
||||
let limitCount: Int
|
||||
if case .root = groupId {
|
||||
limitCount = Int(limitsConfiguration.maxPinnedChatCount)
|
||||
} else {
|
||||
limitCount = Int(limitsConfiguration.maxArchivedPinnedChatCount)
|
||||
}
|
||||
|
||||
if sameKind.count + additionalCount > limitCount {
|
||||
return .limitExceeded(limitCount)
|
||||
} else {
|
||||
if let index = itemIds.firstIndex(of: itemId) {
|
||||
itemIds.remove(at: index)
|
||||
let additionalCount: Int
|
||||
if let _ = itemIds.firstIndex(of: itemId) {
|
||||
additionalCount = -1
|
||||
} else {
|
||||
itemIds.insert(itemId, at: 0)
|
||||
additionalCount = 1
|
||||
}
|
||||
addSynchronizePinnedChatsOperation(transaction: transaction, groupId: groupId)
|
||||
transaction.setPinnedItemIds(groupId: groupId, itemIds: itemIds)
|
||||
return .done
|
||||
|
||||
let limitsConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.limitsConfiguration) as? LimitsConfiguration ?? LimitsConfiguration.defaultValue
|
||||
let limitCount: Int
|
||||
if case .root = groupId {
|
||||
limitCount = Int(limitsConfiguration.maxPinnedChatCount)
|
||||
} else {
|
||||
limitCount = Int(limitsConfiguration.maxArchivedPinnedChatCount)
|
||||
}
|
||||
|
||||
if sameKind.count + additionalCount > limitCount {
|
||||
return .limitExceeded(limitCount)
|
||||
} else {
|
||||
if let index = itemIds.firstIndex(of: itemId) {
|
||||
itemIds.remove(at: index)
|
||||
} else {
|
||||
itemIds.insert(itemId, at: 0)
|
||||
}
|
||||
addSynchronizePinnedChatsOperation(transaction: transaction, groupId: groupId)
|
||||
transaction.setPinnedItemIds(groupId: groupId, itemIds: itemIds)
|
||||
return .done
|
||||
}
|
||||
case let .filter(filterId):
|
||||
var result: TogglePeerChatPinnedResult = .done
|
||||
updateChatListFiltersInteractively(transaction: transaction, { filters in
|
||||
var filters = filters
|
||||
if let index = filters.firstIndex(where: { $0.id == filterId }) {
|
||||
switch itemId {
|
||||
case let .peer(peerId):
|
||||
if filters[index].data.includePeers.pinnedPeers.contains(peerId) {
|
||||
filters[index].data.includePeers.removePinnedPeer(peerId)
|
||||
} else {
|
||||
if !filters[index].data.includePeers.addPinnedPeer(peerId) {
|
||||
result = .limitExceeded(100)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return filters
|
||||
})
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func reorderPinnedItemIds(transaction: Transaction, groupId: PeerGroupId, itemIds: [PinnedItemId]) -> Bool {
|
||||
if transaction.getPinnedItemIds(groupId: groupId) != itemIds {
|
||||
transaction.setPinnedItemIds(groupId: groupId, itemIds: itemIds)
|
||||
addSynchronizePinnedChatsOperation(transaction: transaction, groupId: groupId)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
public func getPinnedItemIds(transaction: Transaction, location: TogglePeerChatPinnedLocation) -> [PinnedItemId] {
|
||||
switch location {
|
||||
case let .group(groupId):
|
||||
return transaction.getPinnedItemIds(groupId: groupId)
|
||||
case let .filter(filterId):
|
||||
var itemIds: [PinnedItemId] = []
|
||||
let _ = updateChatListFiltersInteractively(transaction: transaction, { filters in
|
||||
if let index = filters.firstIndex(where: { $0.id == filterId }) {
|
||||
itemIds = filters[index].data.includePeers.pinnedPeers.map { peerId in
|
||||
return .peer(peerId)
|
||||
}
|
||||
}
|
||||
return filters
|
||||
})
|
||||
return itemIds
|
||||
}
|
||||
}
|
||||
|
||||
public func reorderPinnedItemIds(transaction: Transaction, location: TogglePeerChatPinnedLocation, itemIds: [PinnedItemId]) -> Bool {
|
||||
switch location {
|
||||
case let .group(groupId):
|
||||
if transaction.getPinnedItemIds(groupId: groupId) != itemIds {
|
||||
transaction.setPinnedItemIds(groupId: groupId, itemIds: itemIds)
|
||||
addSynchronizePinnedChatsOperation(transaction: transaction, groupId: groupId)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .filter(filterId):
|
||||
var result: Bool = false
|
||||
updateChatListFiltersInteractively(transaction: transaction, { filters in
|
||||
var filters = filters
|
||||
if let index = filters.firstIndex(where: { $0.id == filterId }) {
|
||||
let peerIds: [PeerId] = itemIds.map { itemId -> PeerId in
|
||||
switch itemId {
|
||||
case let .peer(peerId):
|
||||
return peerId
|
||||
}
|
||||
}
|
||||
|
||||
if filters[index].data.includePeers.pinnedPeers != peerIds {
|
||||
filters[index].data.includePeers.reorderPinnedPeers(peerIds)
|
||||
result = true
|
||||
}
|
||||
}
|
||||
return filters
|
||||
})
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
@ -5,18 +5,16 @@ import AsyncDisplayKit
|
||||
final class ChatControllerTitlePanelNodeContainer: ASDisplayNode {
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if self.bounds.contains(point) {
|
||||
var foundHit = false
|
||||
if let subnodes = self.subnodes {
|
||||
for subnode in subnodes {
|
||||
if subnode.frame.contains(point) {
|
||||
foundHit = true
|
||||
break
|
||||
if let result = subnode.view.hitTest(self.view.convert(point, to: subnode.view), with: event) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !foundHit {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
|
@ -57,6 +57,7 @@ final class ChatSearchInputPanelNode: ChatInputPanelNode {
|
||||
self.calendarButton = HighlightableButtonNode()
|
||||
self.membersButton = HighlightableButtonNode()
|
||||
self.measureResultsLabel = TextNode()
|
||||
self.measureResultsLabel.displaysAsynchronously = false
|
||||
self.resultsButton = HighlightableButtonNode()
|
||||
self.activityIndicator = ActivityIndicator(type: .navigationAccent(theme.rootController.navigationBar.buttonColor))
|
||||
self.activityIndicator.isHidden = true
|
||||
@ -68,6 +69,7 @@ final class ChatSearchInputPanelNode: ChatInputPanelNode {
|
||||
self.addSubnode(self.calendarButton)
|
||||
self.addSubnode(self.membersButton)
|
||||
self.addSubnode(self.resultsButton)
|
||||
self.resultsButton.addSubnode(self.measureResultsLabel)
|
||||
self.addSubnode(self.activityIndicator)
|
||||
|
||||
self.upButton.addTarget(self, action: #selector(self.upPressed), forControlEvents: [.touchUpInside])
|
||||
@ -190,11 +192,11 @@ final class ChatSearchInputPanelNode: ChatInputPanelNode {
|
||||
self.membersButton.isHidden = (!(interfaceState.search?.query.isEmpty ?? true)) || self.displayActivity || !canSearchMembers
|
||||
|
||||
let resultsEnabled = (resultCount ?? 0) > 0
|
||||
self.resultsButton.setTitle(resultsText ?? "", with: labelFont, with: resultsEnabled ? interfaceState.theme.chat.inputPanel.panelControlAccentColor : interfaceState.theme.chat.inputPanel.primaryTextColor, for: .normal)
|
||||
//self.resultsButton.setTitle(resultsText ?? "", with: labelFont, with: resultsEnabled ? interfaceState.theme.chat.inputPanel.panelControlAccentColor : interfaceState.theme.chat.inputPanel.primaryTextColor, for: .normal)
|
||||
self.resultsButton.isUserInteractionEnabled = resultsEnabled
|
||||
|
||||
let makeLabelLayout = TextNode.asyncLayout(self.measureResultsLabel)
|
||||
let (labelSize, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: resultsText ?? "", font: labelFont, textColor: .black, paragraphAlignment: .left), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - leftInset - rightInset - 50.0, height: 100.0), alignment: .left, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (labelSize, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: resultsText ?? "", font: labelFont, textColor: resultsEnabled ? interfaceState.theme.chat.inputPanel.panelControlAccentColor : interfaceState.theme.chat.inputPanel.primaryTextColor, paragraphAlignment: .left), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - leftInset - rightInset - 50.0, height: 100.0), alignment: .left, cutout: nil, insets: UIEdgeInsets()))
|
||||
let _ = labelApply()
|
||||
|
||||
var resultsOffset: CGFloat = 16.0
|
||||
@ -202,6 +204,7 @@ final class ChatSearchInputPanelNode: ChatInputPanelNode {
|
||||
resultsOffset += 48.0
|
||||
}
|
||||
self.resultsButton.frame = CGRect(origin: CGPoint(x: leftInset + resultsOffset, y: floor((panelHeight - labelSize.size.height) / 2.0)), size: labelSize.size)
|
||||
self.measureResultsLabel.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: labelSize.size)
|
||||
|
||||
let indicatorSize = self.activityIndicator.measure(CGSize(width: 22.0, height: 22.0))
|
||||
self.activityIndicator.frame = CGRect(origin: CGPoint(x: width - rightInset - 41.0, y: floor((panelHeight - indicatorSize.height) / 2.0)), size: indicatorSize)
|
||||
|
@ -78,7 +78,7 @@ private enum ChatListSearchEntry: Comparable, Identifiable {
|
||||
public func item(context: AccountContext, interaction: ChatListNodeInteraction) -> ListViewItem {
|
||||
switch self {
|
||||
case let .message(message, peer, readState, presentationData):
|
||||
return ChatListItem(presentationData: presentationData, context: context, peerGroupId: .root, isInFilter: false, index: ChatListIndex(pinningIndex: nil, messageIndex: message.index), content: .peer(message: message, peer: peer, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: true, displayAsMessage: true, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)
|
||||
return ChatListItem(presentationData: presentationData, context: context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: message.index), content: .peer(message: message, peer: peer, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: true, displayAsMessage: true, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import SyncCore
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import ProgressNavigationButtonNode
|
||||
import AccountContext
|
||||
import AlertUI
|
||||
@ -14,6 +15,14 @@ import PresentationDataUtils
|
||||
import ContactListUI
|
||||
import CounterContollerTitleView
|
||||
|
||||
private func peerTokenTitle(accountPeerId: PeerId, peer: Peer, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder) -> String {
|
||||
if peer.id == accountPeerId {
|
||||
return strings.DialogList_SavedMessages
|
||||
} else {
|
||||
return peer.displayTitle(strings: strings, displayOrder: nameDisplayOrder)
|
||||
}
|
||||
}
|
||||
|
||||
class ContactMultiselectionControllerImpl: ViewController, ContactMultiselectionController {
|
||||
private let params: ContactMultiselectionControllerParams
|
||||
private let context: AccountContext
|
||||
@ -132,7 +141,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
||||
}
|
||||
}
|
||||
strongSelf.contactsNode.editableTokens.append(contentsOf: peers.map { peer -> EditableTokenListToken in
|
||||
return EditableTokenListToken(id: peer.id, title: peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder), fixedPosition: nil)
|
||||
return EditableTokenListToken(id: peer.id, title: peerTokenTitle(accountPeerId: params.context.account.peerId, peer: peer, strings: strongSelf.presentationData.strings, nameDisplayOrder: strongSelf.presentationData.nameDisplayOrder), fixedPosition: nil)
|
||||
})
|
||||
strongSelf._peersReady.set(.single(true))
|
||||
if strongSelf.isNodeLoaded {
|
||||
@ -203,7 +212,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
||||
}
|
||||
|
||||
override func loadDisplayNode() {
|
||||
self.displayNode = ContactMultiselectionControllerNode(context: self.context, mode: self.mode, options: self.options, filters: filters)
|
||||
self.displayNode = ContactMultiselectionControllerNode(context: self.context, mode: self.mode, options: self.options, filters: self.filters)
|
||||
switch self.contactsNode.contentNode {
|
||||
case let .contacts(contactsNode):
|
||||
self._listReady.set(contactsNode.ready)
|
||||
@ -211,6 +220,8 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
||||
self._listReady.set(chatsNode.ready)
|
||||
}
|
||||
|
||||
let accountPeerId = self.context.account.peerId
|
||||
|
||||
self.contactsNode.dismiss = { [weak self] in
|
||||
self?.presentingViewController?.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
@ -237,7 +248,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
||||
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), fixedPosition: nil)
|
||||
addedToken = EditableTokenListToken(id: peer.id, title: peerTokenTitle(accountPeerId: accountPeerId, peer: peer, strings: strongSelf.presentationData.strings, nameDisplayOrder: strongSelf.presentationData.nameDisplayOrder), fixedPosition: nil)
|
||||
}
|
||||
}
|
||||
updatedCount = updatedState.selectedPeerIndices.count
|
||||
@ -254,7 +265,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
||||
state.selectedPeerIds.remove(peer.id)
|
||||
removedTokenId = peer.id
|
||||
} else {
|
||||
addedToken = EditableTokenListToken(id: peer.id, title: peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder), fixedPosition: nil)
|
||||
addedToken = EditableTokenListToken(id: peer.id, title: peerTokenTitle(accountPeerId: accountPeerId, peer: peer, strings: strongSelf.presentationData.strings, nameDisplayOrder: strongSelf.presentationData.nameDisplayOrder), fixedPosition: nil)
|
||||
state.selectedPeerIds.insert(peer.id)
|
||||
}
|
||||
updatedCount = state.selectedPeerIds.count
|
||||
|
@ -86,7 +86,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
||||
|
||||
if case let .chatSelection(_, selectedChats, additionalCategories) = mode {
|
||||
placeholder = self.presentationData.strings.ChatListFilter_AddChatsTitle
|
||||
let chatListNode = ChatListNode(context: context, groupId: .root, previewing: false, fillPreloadItems: false, mode: .peers(filter: [.excludeSavedMessages], isSelecting: true, additionalCategories: additionalCategories?.categories ?? []), 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)
|
||||
let chatListNode = ChatListNode(context: context, groupId: .root, previewing: false, fillPreloadItems: false, mode: .peers(filter: [], isSelecting: true, additionalCategories: additionalCategories?.categories ?? []), 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)
|
||||
chatListNode.updateState { state in
|
||||
var state = state
|
||||
for peerId in selectedChats {
|
||||
@ -161,22 +161,28 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
||||
selectionState = state
|
||||
return state
|
||||
}
|
||||
case .chats:
|
||||
break
|
||||
case let .chats(chatsNode):
|
||||
selectionState = ContactListNodeGroupSelectionState()
|
||||
for peerId in chatsNode.currentState.selectedPeerIds {
|
||||
selectionState = selectionState?.withToggledPeerId(.peer(peerId))
|
||||
}
|
||||
}
|
||||
var searchChatList = false
|
||||
var searchGroups = false
|
||||
var searchChannels = false
|
||||
var globalSearch = false
|
||||
if case let .peerSelection(peerSelection) = mode {
|
||||
searchChatList = peerSelection.searchChatList
|
||||
searchGroups = peerSelection.searchGroups
|
||||
searchChannels = peerSelection.searchChannels
|
||||
globalSearch = true
|
||||
} else if case .chatSelection = mode {
|
||||
searchChatList = true
|
||||
searchGroups = true
|
||||
searchChannels = true
|
||||
globalSearch = false
|
||||
}
|
||||
let searchResultsNode = ContactListNode(context: context, presentation: .single(.search(signal: searchText.get(), searchChatList: searchChatList, searchDeviceContacts: false, searchGroups: searchGroups, searchChannels: searchChannels)), filters: filters, selectionState: selectionState)
|
||||
let searchResultsNode = ContactListNode(context: context, presentation: .single(.search(signal: searchText.get(), searchChatList: searchChatList, searchDeviceContacts: false, searchGroups: searchGroups, searchChannels: searchChannels, globalSearch: globalSearch)), filters: filters, selectionState: selectionState, isSearch: true)
|
||||
searchResultsNode.openPeer = { peer in
|
||||
self?.tokenListNode.setText("")
|
||||
self?.openPeer?(peer)
|
||||
|
@ -278,7 +278,7 @@ final class EditableTokenListNode: ASDisplayNode, UITextFieldDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
if width - currentOffset.x < 200.0 {
|
||||
if width - currentOffset.x < 90.0 {
|
||||
currentOffset.y += 28.0
|
||||
currentOffset.x = sideInset
|
||||
}
|
||||
|
@ -510,7 +510,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
|
||||
super.didLoad()
|
||||
|
||||
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 {
|
||||
guard let strongSelf = self, let currentPaneKey = strongSelf.currentPaneKey, let availablePanes = strongSelf.currentParams?.data?.availablePanes, let index = availablePanes.firstIndex(of: currentPaneKey) else {
|
||||
return []
|
||||
}
|
||||
if index == 0 {
|
||||
|
@ -792,7 +792,6 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
|
||||
if let data = data, let notificationSettings = data.notificationSettings {
|
||||
let notificationsLabel: String
|
||||
let soundLabel: String
|
||||
let notificationSettings = notificationSettings as? TelegramPeerNotificationSettings ?? TelegramPeerNotificationSettings.defaultSettings
|
||||
if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) {
|
||||
if until < Int32.max - 1 {
|
||||
notificationsLabel = stringForRemainingMuteInterval(strings: presentationData.strings, muteInterval: until)
|
||||
@ -1840,52 +1839,70 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
let title = strongSelf.headerNode.editingContentNode.editingTextForKey(.title) ?? ""
|
||||
let description = strongSelf.headerNode.editingContentNode.editingTextForKey(.description) ?? ""
|
||||
|
||||
if title.isEmpty {
|
||||
strongSelf.headerNode.editingContentNode.shakeTextForKey(.title)
|
||||
let proceed: () -> Void = {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
if title.isEmpty {
|
||||
strongSelf.headerNode.editingContentNode.shakeTextForKey(.title)
|
||||
} else {
|
||||
var updateDataSignals: [Signal<Never, Void>] = []
|
||||
|
||||
if title != channel.title {
|
||||
updateDataSignals.append(
|
||||
updatePeerTitle(account: strongSelf.context.account, peerId: channel.id, title: title)
|
||||
|> ignoreValues
|
||||
|> mapError { _ in return Void() }
|
||||
)
|
||||
}
|
||||
if description != (data.cachedData as? CachedChannelData)?.about {
|
||||
updateDataSignals.append(
|
||||
updatePeerDescription(account: strongSelf.context.account, peerId: channel.id, description: description.isEmpty ? nil : description)
|
||||
|> ignoreValues
|
||||
|> mapError { _ in return Void() }
|
||||
)
|
||||
}
|
||||
|
||||
var dismissStatus: (() -> Void)?
|
||||
let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: {
|
||||
dismissStatus?()
|
||||
}))
|
||||
dismissStatus = { [weak statusController] in
|
||||
self?.activeActionDisposable.set(nil)
|
||||
statusController?.dismiss()
|
||||
}
|
||||
strongSelf.controller?.present(statusController, in: .window(.root))
|
||||
|
||||
strongSelf.activeActionDisposable.set((combineLatest(updateDataSignals)
|
||||
|> deliverOnMainQueue).start(error: { _ in
|
||||
dismissStatus?()
|
||||
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel)
|
||||
}, completed: {
|
||||
dismissStatus?()
|
||||
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
if channel.isVerified && title != channel.title {
|
||||
let alertText: String
|
||||
if case .broadcast = channel.info {
|
||||
alertText = strongSelf.presentationData.strings.SetupUsername_ChangeNameWarningChannel
|
||||
} else {
|
||||
alertText = strongSelf.presentationData.strings.SetupUsername_ChangeNameWarningGroup
|
||||
}
|
||||
strongSelf.controller?.present(textAlertController(context: context, title: nil, text: alertText, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: proceed)]), in: .window(.root))
|
||||
} else {
|
||||
var updateDataSignals: [Signal<Never, Void>] = []
|
||||
|
||||
if title != channel.title {
|
||||
updateDataSignals.append(
|
||||
updatePeerTitle(account: strongSelf.context.account, peerId: channel.id, title: title)
|
||||
|> ignoreValues
|
||||
|> mapError { _ in return Void() }
|
||||
)
|
||||
}
|
||||
if description != (data.cachedData as? CachedChannelData)?.about {
|
||||
updateDataSignals.append(
|
||||
updatePeerDescription(account: strongSelf.context.account, peerId: channel.id, description: description.isEmpty ? nil : description)
|
||||
|> ignoreValues
|
||||
|> mapError { _ in return Void() }
|
||||
)
|
||||
}
|
||||
|
||||
var dismissStatus: (() -> Void)?
|
||||
let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: {
|
||||
dismissStatus?()
|
||||
}))
|
||||
dismissStatus = { [weak statusController] in
|
||||
self?.activeActionDisposable.set(nil)
|
||||
statusController?.dismiss()
|
||||
}
|
||||
strongSelf.controller?.present(statusController, in: .window(.root))
|
||||
|
||||
strongSelf.activeActionDisposable.set((combineLatest(updateDataSignals)
|
||||
|> deliverOnMainQueue).start(error: { _ in
|
||||
dismissStatus?()
|
||||
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel)
|
||||
}, completed: {
|
||||
dismissStatus?()
|
||||
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel)
|
||||
}))
|
||||
proceed()
|
||||
}
|
||||
} else {
|
||||
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel)
|
||||
@ -2130,13 +2147,40 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
case .call:
|
||||
self.requestCall()
|
||||
case .mute:
|
||||
let muteInterval: Int32?
|
||||
if let notificationSettings = self.data?.notificationSettings, case .muted = notificationSettings.muteState {
|
||||
muteInterval = nil
|
||||
let _ = updatePeerMuteSetting(account: self.context.account, peerId: self.peerId, muteInterval: nil).start()
|
||||
} else {
|
||||
muteInterval = Int32.max
|
||||
let actionSheet = ActionSheetController(presentationData: self.presentationData)
|
||||
let dismissAction: () -> Void = { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
}
|
||||
var items: [ActionSheetItem] = []
|
||||
let muteValues: [Int32] = [
|
||||
1 * 60 * 60,
|
||||
24 * 60 * 60,
|
||||
Int32.max
|
||||
]
|
||||
for delay in muteValues {
|
||||
let title: String
|
||||
if delay == Int32.max {
|
||||
title = self.presentationData.strings.MuteFor_Forever
|
||||
} else {
|
||||
title = muteForIntervalString(strings: self.presentationData.strings, value: delay)
|
||||
}
|
||||
items.append(ActionSheetButtonItem(title: title, action: {
|
||||
dismissAction()
|
||||
|
||||
let _ = updatePeerMuteSetting(account: self.context.account, peerId: self.peerId, muteInterval: delay).start()
|
||||
}))
|
||||
}
|
||||
|
||||
actionSheet.setItemGroups([
|
||||
ActionSheetItemGroup(items: items),
|
||||
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
||||
])
|
||||
self.view.endEditing(true)
|
||||
controller.present(actionSheet, in: .window(.root))
|
||||
}
|
||||
let _ = updatePeerMuteSetting(account: self.context.account, peerId: self.peerId, muteInterval: muteInterval).start()
|
||||
case .more:
|
||||
guard let data = self.data, let peer = data.peer else {
|
||||
return
|
||||
|
@ -8,19 +8,19 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry {
|
||||
public var crashOnLongQueries: Bool
|
||||
public var chatListPhotos: Bool
|
||||
public var knockoutWallpaper: Bool
|
||||
public var wallets: Bool
|
||||
public var foldersTabAtBottom: Bool
|
||||
|
||||
public static var defaultSettings: ExperimentalUISettings {
|
||||
return ExperimentalUISettings(keepChatNavigationStack: false, skipReadHistory: false, crashOnLongQueries: false, chatListPhotos: false, knockoutWallpaper: false, wallets: false)
|
||||
return ExperimentalUISettings(keepChatNavigationStack: false, skipReadHistory: false, crashOnLongQueries: false, chatListPhotos: false, knockoutWallpaper: false, foldersTabAtBottom: false)
|
||||
}
|
||||
|
||||
public init(keepChatNavigationStack: Bool, skipReadHistory: Bool, crashOnLongQueries: Bool, chatListPhotos: Bool, knockoutWallpaper: Bool, wallets: Bool) {
|
||||
public init(keepChatNavigationStack: Bool, skipReadHistory: Bool, crashOnLongQueries: Bool, chatListPhotos: Bool, knockoutWallpaper: Bool, foldersTabAtBottom: Bool) {
|
||||
self.keepChatNavigationStack = keepChatNavigationStack
|
||||
self.skipReadHistory = skipReadHistory
|
||||
self.crashOnLongQueries = crashOnLongQueries
|
||||
self.chatListPhotos = chatListPhotos
|
||||
self.knockoutWallpaper = knockoutWallpaper
|
||||
self.wallets = wallets
|
||||
self.foldersTabAtBottom = foldersTabAtBottom
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
@ -29,7 +29,7 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry {
|
||||
self.crashOnLongQueries = decoder.decodeInt32ForKey("crashOnLongQueries", orElse: 0) != 0
|
||||
self.chatListPhotos = decoder.decodeInt32ForKey("chatListPhotos", orElse: 0) != 0
|
||||
self.knockoutWallpaper = decoder.decodeInt32ForKey("knockoutWallpaper", orElse: 0) != 0
|
||||
self.wallets = decoder.decodeInt32ForKey("wallets", orElse: 0) != 0
|
||||
self.foldersTabAtBottom = decoder.decodeInt32ForKey("foldersTabAtBottom", orElse: 0) != 0
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
@ -38,7 +38,7 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry {
|
||||
encoder.encodeInt32(self.crashOnLongQueries ? 1 : 0, forKey: "crashOnLongQueries")
|
||||
encoder.encodeInt32(self.chatListPhotos ? 1 : 0, forKey: "chatListPhotos")
|
||||
encoder.encodeInt32(self.knockoutWallpaper ? 1 : 0, forKey: "knockoutWallpaper")
|
||||
encoder.encodeInt32(self.wallets ? 1 : 0, forKey: "wallets")
|
||||
encoder.encodeInt32(self.foldersTabAtBottom ? 1 : 0, forKey: "foldersTabAtBottom")
|
||||
}
|
||||
|
||||
public func isEqual(to: PreferencesEntry) -> Bool {
|
||||
|
@ -17,6 +17,7 @@ static_library(
|
||||
compiler_flags = [
|
||||
"-Dpixman_region_selfcheck(x)=1",
|
||||
"-DLOTTIE_DISABLE_ARM_NEON=1",
|
||||
"-DLOTTIE_IMAGE_MODULE_DISABLED=1",
|
||||
],
|
||||
headers = glob([
|
||||
"rlottie/src/**/*.h",
|
||||
|
@ -24,6 +24,7 @@ objc_library(
|
||||
copts = [
|
||||
"-Dpixman_region_selfcheck(x)=1",
|
||||
"-DLOTTIE_DISABLE_ARM_NEON=1",
|
||||
"-DLOTTIE_IMAGE_MODULE_DISABLED=1",
|
||||
"-I{}".format(package_name()),
|
||||
"-I{}/rlottie/inc".format(package_name()),
|
||||
"-I{}/rlottie/src/vector".format(package_name()),
|
||||
|
Loading…
x
Reference in New Issue
Block a user