mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Folder improvements
This commit is contained in:
parent
d12b0d895f
commit
da4c5a70e5
@ -5349,7 +5349,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";
|
||||
|
@ -147,22 +147,23 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, source: ChatC
|
||||
}
|
||||
|
||||
if case let .chatList(filter) = source {
|
||||
let isPinned = index.pinningIndex != nil
|
||||
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 location: TogglePeerChatPinnedLocation
|
||||
if let filter = filter {
|
||||
location = .filter(filter.id)
|
||||
} else {
|
||||
location = .group(group)
|
||||
}
|
||||
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)
|
||||
})
|
||||
@ -183,7 +184,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, source: ChatC
|
||||
}
|
||||
} else {
|
||||
if case .search = source {
|
||||
if let channel = peer as? TelegramChannel {
|
||||
if let _ = peer as? TelegramChannel {
|
||||
items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_JoinChannel, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor) }, action: { _, f in
|
||||
var createSignal = context.peerChannelMemberCategoriesContextsManager.join(account: context.account, peerId: peerId)
|
||||
var cancelImpl: (() -> Void)?
|
||||
|
@ -143,7 +143,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
private var searchContentNode: NavigationBarSearchContentNode?
|
||||
|
||||
private let tabContainerNode: ChatListFilterTabContainerNode
|
||||
private var tabContainerData: [ChatListFilterTabEntry]?
|
||||
private var tabContainerData: ([ChatListFilterTabEntry], Bool)?
|
||||
|
||||
public override func updateNavigationCustomData(_ data: Any?, progress: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
if self.isNodeLoaded {
|
||||
@ -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)
|
||||
@ -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.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)
|
||||
})
|
||||
})))
|
||||
|
||||
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 {
|
||||
@ -1007,6 +863,134 @@ 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] = []
|
||||
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)
|
||||
})
|
||||
})))
|
||||
|
||||
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, 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()
|
||||
@ -1229,7 +1213,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, 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, 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)
|
||||
}
|
||||
@ -1288,7 +1278,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 {
|
||||
@ -1328,15 +1337,24 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
let preferencesKey: PostboxViewKey = .preferences(keys: Set([
|
||||
ApplicationSpecificPreferencesKeys.chatListFilterSettings
|
||||
]))
|
||||
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(context: self.context)
|
||||
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 +1372,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 +1382,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 +1398,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 +1406,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 +1421,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 +1431,41 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
}
|
||||
|
||||
if resetCurrentEntry {
|
||||
strongSelf.tabContainerNode.tabSelected?(selectedEntryId)
|
||||
strongSelf.selectTab(id: selectedEntryId)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
private func selectTab(id: ChatListFilterTabEntryId) {
|
||||
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
|
||||
}
|
||||
}
|
||||
strongSelf.chatListDisplayNode.containerNode.switchToFilter(id: updatedFilter.flatMap { .filter($0.id) } ?? .all)
|
||||
})
|
||||
}
|
||||
|
||||
private func askForFilterRemoval(id: Int32) {
|
||||
let actionSheet = ActionSheetController(presentationData: self.presentationData)
|
||||
|
||||
@ -2352,7 +2401,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.tabContainerNode.tabSelected?(.all)
|
||||
strongSelf.selectTab(id: .all)
|
||||
})))
|
||||
}
|
||||
|
||||
@ -2393,7 +2442,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 +2451,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 +2475,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? {
|
||||
|
@ -556,6 +556,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 +785,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 +958,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?
|
||||
@ -995,6 +1000,8 @@ final class ChatListControllerNode: ASDisplayNode {
|
||||
filterEmptyAction?(filter)
|
||||
})
|
||||
|
||||
self.inlineTabContainerNode = ChatListFilterTabInlineContainerNode()
|
||||
|
||||
self.controller = controller
|
||||
|
||||
super.init()
|
||||
@ -1006,6 +1013,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 +1118,8 @@ final class ChatListControllerNode: ASDisplayNode {
|
||||
transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
self.containerNode.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, isReorderingFilters: self.isReorderingFilters, isEditing: self.isEditing, transition: transition)
|
||||
|
||||
transition.updateFrame(node: self.inlineTabContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - layout.intrinsicInsets.bottom - 8.0 - 40.0), size: CGSize(width: layout.size.width, height: 40.0)))
|
||||
|
||||
self.tapRecognizer?.isEnabled = self.isReorderingFilters
|
||||
|
||||
if let searchDisplayController = self.searchDisplayController {
|
||||
|
@ -9,11 +9,15 @@ import SolidRoundedButtonNode
|
||||
import ActivityIndicator
|
||||
|
||||
final class ChatListEmptyNode: ASDisplayNode {
|
||||
private let action: () -> Void
|
||||
|
||||
let isFilter: Bool
|
||||
private(set) var isLoading: Bool
|
||||
private let textNode: ImmediateTextNode
|
||||
private let descriptionNode: ImmediateTextNode
|
||||
private let animationNode: AnimatedStickerNode
|
||||
private let buttonNode: SolidRoundedButtonNode
|
||||
private let buttonTextNode: ImmediateTextNode
|
||||
private let buttonNode: HighlightTrackingButtonNode
|
||||
private let activityIndicator: ActivityIndicator
|
||||
|
||||
private var animationSize: CGSize = CGSize()
|
||||
@ -21,6 +25,7 @@ final class ChatListEmptyNode: ASDisplayNode {
|
||||
private var validLayout: CGSize?
|
||||
|
||||
init(isFilter: Bool, isLoading: Bool, theme: PresentationTheme, strings: PresentationStrings, action: @escaping () -> Void) {
|
||||
self.action = action
|
||||
self.isFilter = isFilter
|
||||
self.isLoading = isLoading
|
||||
|
||||
@ -33,7 +38,17 @@ final class ChatListEmptyNode: ASDisplayNode {
|
||||
self.textNode.textAlignment = .center
|
||||
self.textNode.lineSpacing = 0.1
|
||||
|
||||
self.buttonNode = SolidRoundedButtonNode(title: isFilter ? strings.ChatList_EmptyChatListEditFilter : strings.ChatList_EmptyChatListNewMessage, theme: SolidRoundedButtonTheme(backgroundColor: theme.list.itemCheckColors.fillColor, foregroundColor: theme.list.itemCheckColors.foregroundColor), height: 50.0, cornerRadius: 10.0, gloss: false)
|
||||
self.descriptionNode = ImmediateTextNode()
|
||||
self.descriptionNode.displaysAsynchronously = false
|
||||
self.descriptionNode.maximumNumberOfLines = 0
|
||||
self.descriptionNode.isUserInteractionEnabled = false
|
||||
self.descriptionNode.textAlignment = .center
|
||||
self.descriptionNode.lineSpacing = 0.1
|
||||
|
||||
self.buttonNode = HighlightTrackingButtonNode()
|
||||
|
||||
self.buttonTextNode = ImmediateTextNode()
|
||||
self.buttonTextNode.displaysAsynchronously = false
|
||||
|
||||
self.activityIndicator = ActivityIndicator(type: .custom(theme.list.itemAccentColor, 22.0, 1.0, false))
|
||||
|
||||
@ -41,6 +56,8 @@ final class ChatListEmptyNode: ASDisplayNode {
|
||||
|
||||
self.addSubnode(self.animationNode)
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.descriptionNode)
|
||||
self.addSubnode(self.buttonTextNode)
|
||||
self.addSubnode(self.buttonNode)
|
||||
self.addSubnode(self.activityIndicator)
|
||||
|
||||
@ -56,27 +73,50 @@ 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)
|
||||
}
|
||||
|
||||
@objc private func buttonPressed() {
|
||||
self.action()
|
||||
}
|
||||
|
||||
func restartAnimation() {
|
||||
self.animationNode.play()
|
||||
}
|
||||
|
||||
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.medium(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 +132,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 +144,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 +167,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,9 +176,13 @@ 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? {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1478,6 +1478,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
|
||||
|
@ -59,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 {
|
||||
|
@ -674,7 +674,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 {
|
||||
@ -917,7 +917,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>
|
||||
@ -975,12 +975,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([:])
|
||||
|
@ -3693,13 +3693,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)
|
||||
}
|
||||
|
@ -215,7 +215,7 @@ 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: filterPredicate.flatMap { mappedChatListFilterPredicate(postbox: postbox, groupId: groupId, predicate: $0) }).map(mapEntry)
|
||||
lowerOrAtAnchorMessages.append(contentsOf: loadedLowerMessages)
|
||||
|
@ -29,30 +29,41 @@ private func generateBackground(foregroundColor: UIColor, diameter: CGFloat) ->
|
||||
private class SearchBarTextField: UITextField {
|
||||
public var didDeleteBackwardWhileEmpty: (() -> Void)?
|
||||
|
||||
let placeholderLabel: ASTextNode
|
||||
let placeholderLabel: ImmediateTextNode
|
||||
var placeholderString: NSAttributedString? {
|
||||
didSet {
|
||||
self.placeholderLabel.attributedText = self.placeholderString
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
}
|
||||
|
||||
let prefixLabel: ASTextNode
|
||||
private let measurePrefixLabel: ImmediateTextNode
|
||||
let prefixLabel: ImmediateTextNode
|
||||
var prefixString: NSAttributedString? {
|
||||
didSet {
|
||||
self.measurePrefixLabel.attributedText = self.prefixString
|
||||
self.prefixLabel.attributedText = self.prefixString
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.placeholderLabel = ASTextNode()
|
||||
self.placeholderLabel = ImmediateTextNode()
|
||||
self.placeholderLabel.isUserInteractionEnabled = false
|
||||
self.placeholderLabel.displaysAsynchronously = false
|
||||
self.placeholderLabel.maximumNumberOfLines = 1
|
||||
self.placeholderLabel.truncationMode = .byTruncatingTail
|
||||
|
||||
self.prefixLabel = ASTextNode()
|
||||
self.measurePrefixLabel = ImmediateTextNode()
|
||||
self.measurePrefixLabel.isUserInteractionEnabled = false
|
||||
self.measurePrefixLabel.displaysAsynchronously = false
|
||||
self.measurePrefixLabel.maximumNumberOfLines = 1
|
||||
self.measurePrefixLabel.truncationMode = .byTruncatingTail
|
||||
|
||||
self.prefixLabel = ImmediateTextNode()
|
||||
self.prefixLabel.isUserInteractionEnabled = false
|
||||
self.prefixLabel.displaysAsynchronously = false
|
||||
self.prefixLabel.maximumNumberOfLines = 1
|
||||
self.prefixLabel.truncationMode = .byTruncatingTail
|
||||
|
||||
super.init(frame: frame)
|
||||
@ -87,9 +98,9 @@ private class SearchBarTextField: UITextField {
|
||||
}
|
||||
var rect = bounds.insetBy(dx: 4.0, dy: 4.0)
|
||||
|
||||
let prefixSize = self.prefixLabel.measure(CGSize(width: floor(bounds.size.width * 0.7), height: bounds.size.height))
|
||||
let prefixSize = self.measurePrefixLabel.updateLayout(CGSize(width: floor(bounds.size.width * 0.7), height: bounds.size.height))
|
||||
if !prefixSize.width.isZero {
|
||||
let prefixOffset = prefixSize.width
|
||||
let prefixOffset = prefixSize.width + 3.0
|
||||
rect.origin.x += prefixOffset
|
||||
rect.size.width -= prefixOffset
|
||||
}
|
||||
@ -115,10 +126,10 @@ private class SearchBarTextField: UITextField {
|
||||
}
|
||||
|
||||
let textRect = self.textRect(forBounds: bounds)
|
||||
let labelSize = self.placeholderLabel.measure(textRect.size)
|
||||
let labelSize = self.placeholderLabel.updateLayout(textRect.size)
|
||||
self.placeholderLabel.frame = CGRect(origin: CGPoint(x: textRect.minX, y: textRect.minY + textOffset), size: labelSize)
|
||||
|
||||
let prefixSize = self.prefixLabel.measure(CGSize(width: floor(bounds.size.width * 0.7), height: bounds.size.height))
|
||||
let prefixSize = self.prefixLabel.updateLayout(CGSize(width: floor(bounds.size.width * 0.7), height: bounds.size.height))
|
||||
let prefixBounds = bounds.insetBy(dx: 4.0, dy: 4.0)
|
||||
self.prefixLabel.frame = CGRect(origin: CGPoint(x: prefixBounds.minX, y: prefixBounds.minY + textOffset), size: prefixSize)
|
||||
}
|
||||
|
@ -68,6 +68,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
case optimizeDatabase(PresentationTheme)
|
||||
case photoPreview(PresentationTheme, Bool)
|
||||
case knockoutWallpaper(PresentationTheme, Bool)
|
||||
case alternativeFolderTabs(Bool)
|
||||
case hostInfo(PresentationTheme, String)
|
||||
case versionInfo(PresentationTheme)
|
||||
|
||||
@ -81,7 +82,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
return DebugControllerSection.logging.rawValue
|
||||
case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries:
|
||||
return DebugControllerSection.experiments.rawValue
|
||||
case .clearTips, .reimport, .resetData, .resetDatabase, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper:
|
||||
case .clearTips, .reimport, .resetData, .resetDatabase, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .alternativeFolderTabs:
|
||||
return DebugControllerSection.experiments.rawValue
|
||||
case .hostInfo, .versionInfo:
|
||||
return DebugControllerSection.info.rawValue
|
||||
@ -134,6 +135,8 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
return 21
|
||||
case .knockoutWallpaper:
|
||||
return 22
|
||||
case .alternativeFolderTabs:
|
||||
return 23
|
||||
case .hostInfo:
|
||||
return 24
|
||||
case .versionInfo:
|
||||
@ -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)"))
|
||||
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
@ -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)
|
||||
|
@ -167,16 +167,19 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
||||
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)
|
||||
searchResultsNode.openPeer = { peer in
|
||||
self?.tokenListNode.setText("")
|
||||
self?.openPeer?(peer)
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user