Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2020-03-21 03:21:34 +04:00
commit 5d505ff41a
64 changed files with 7223 additions and 5067 deletions

View File

@ -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

View File

@ -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";

View File

@ -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)?

View File

@ -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? {

View File

@ -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 {

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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()
}

View File

@ -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

View File

@ -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()
}
}
}

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)
}

View File

@ -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 {

View File

@ -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

View File

@ -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
}
}

View File

@ -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) {

View File

@ -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)
}

View File

@ -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)

View File

@ -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()

View File

@ -636,6 +636,9 @@ public enum TabBarItemContextActionType {
open func tabBarItemContextAction(sourceNode: ContextExtractedContentContainingNode, gesture: ContextGesture) {
}
open func tabBarDisabledAction() {
}
open func tabBarItemSwipeAction(direction: TabBarItemSwipeDirection) {
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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 {

View File

@ -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
}

View File

@ -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 {

View File

@ -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))

View File

@ -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:

View File

@ -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

View File

@ -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
}

View File

@ -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
}
})
}

View File

@ -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)
}

View File

@ -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)"))

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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) }

View File

@ -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

View File

@ -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 : [] },

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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()
}

View File

@ -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 {

View File

@ -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)

View File

@ -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
}
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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)
}
}
}

View File

@ -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

View File

@ -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)

View File

@ -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
}

View File

@ -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 {

View File

@ -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

View File

@ -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 {

View File

@ -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",

View File

@ -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()),