Folder improvements

This commit is contained in:
Ali 2020-03-17 18:31:16 +04:00
parent d12b0d895f
commit da4c5a70e5
19 changed files with 5638 additions and 4462 deletions

View File

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

View File

@ -147,22 +147,23 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, source: ChatC
}
if case let .chatList(filter) = source {
let isPinned = index.pinningIndex != nil
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 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, 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)?

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

View File

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

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

View File

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

View File

@ -59,9 +59,12 @@ func chatListFilterPredicate(filter: ChatListFilterData) -> ChatListFilterPredic
}
if filter.excludeMuted {
if isMuted {
if let messageTagSummaryResult = messageTagSummaryResult, messageTagSummaryResult {
} else {
return false
}
}
}
if !filter.categories.contains(.contacts) && isContact {
if let user = peer as? TelegramUser {
if user.botInfo == nil {

View File

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

View File

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

View File

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

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

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

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

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 {