diff --git a/submodules/ChatListSearchRecentPeersNode/Sources/ChatListSearchRecentPeersNode.swift b/submodules/ChatListSearchRecentPeersNode/Sources/ChatListSearchRecentPeersNode.swift index 382b02bd16..e879ce151f 100644 --- a/submodules/ChatListSearchRecentPeersNode/Sources/ChatListSearchRecentPeersNode.swift +++ b/submodules/ChatListSearchRecentPeersNode/Sources/ChatListSearchRecentPeersNode.swift @@ -226,6 +226,8 @@ public final class ChatListSearchRecentPeersNode: ASDisplayNode { var options = ListViewDeleteAndInsertOptions() if transition.firstTime { + options.insert(.PreferSynchronousResourceLoading) + options.insert(.PreferSynchronousDrawing) options.insert(.Synchronous) options.insert(.LowLatency) } else if transition.animated { diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 6d3c3c47fa..e670713562 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1703,8 +1703,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, public func deactivateSearch(animated: Bool) { if !self.displayNavigationBar { + var completion: (() -> Void)? if let searchContentNode = self.searchContentNode { - self.chatListDisplayNode.deactivateSearch(placeholderNode: searchContentNode.placeholderNode, animated: animated) + completion = self.chatListDisplayNode.deactivateSearch(placeholderNode: searchContentNode.placeholderNode, animated: animated) } let filtersIsEmpty: Bool @@ -1722,6 +1723,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.4, curve: .spring) : .immediate self.setDisplayNavigationBar(true, transition: transition) + completion?() + (self.parent as? TabBarController)?.updateIsTabBarHidden(false, transition: .animated(duration: 0.4, curve: .spring)) } } diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 9ec789289e..1341eecf9e 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -1088,7 +1088,6 @@ final class ChatListControllerNode: ASDisplayNode { var insets = layout.insets(options: [.input]) insets.top += navigationBarHeight - insets.left += layout.safeInsets.left insets.right += layout.safeInsets.right @@ -1195,11 +1194,19 @@ final class ChatListControllerNode: ASDisplayNode { }) } - func deactivateSearch(placeholderNode: SearchBarPlaceholderNode, animated: Bool) { + func deactivateSearch(placeholderNode: SearchBarPlaceholderNode, animated: Bool) -> (() -> Void)? { if let searchDisplayController = self.searchDisplayController { searchDisplayController.deactivate(placeholder: placeholderNode, animated: animated) self.searchDisplayController = nil self.containerNode.accessibilityElementsHidden = false + + return { [weak self] in + if let strongSelf = self, let (layout, _, _, cleanNavigationBarHeight) = strongSelf.containerLayout { + searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: cleanNavigationBarHeight, transition: .animated(duration: 0.4, curve: .spring)) + } + } + } else { + return nil } } diff --git a/submodules/ChatListUI/Sources/ChatListRecentPeersListItem.swift b/submodules/ChatListUI/Sources/ChatListRecentPeersListItem.swift index b8908d7e35..32a8478310 100644 --- a/submodules/ChatListUI/Sources/ChatListRecentPeersListItem.swift +++ b/submodules/ChatListUI/Sources/ChatListRecentPeersListItem.swift @@ -39,7 +39,9 @@ class ChatListRecentPeersListItem: ListViewItem { node.contentSize = nodeLayout.contentSize node.insets = nodeLayout.insets - completion(node, nodeApply) + completion(node, { + return (nil, { _ in nodeApply(synchronousLoads) }) + }) } } @@ -50,8 +52,8 @@ class ChatListRecentPeersListItem: ListViewItem { async { let (nodeLayout, apply) = layout(self, params, nextItem != nil) Queue.mainQueue().async { - completion(nodeLayout, { info in - apply().1(info) + completion(nodeLayout, { _ in + apply(false) }) } } @@ -86,56 +88,54 @@ class ChatListRecentPeersListItemNode: ListViewItemNode { let (nodeLayout, nodeApply) = makeLayout(item, params, nextItem == nil) self.contentSize = nodeLayout.contentSize self.insets = nodeLayout.insets - let _ = nodeApply() + let _ = nodeApply(false) } } - func asyncLayout() -> (_ item: ChatListRecentPeersListItem, _ params: ListViewItemLayoutParams, _ last: Bool) -> (ListViewItemNodeLayout, () -> (Signal?, (ListViewItemApply) -> Void)) { + func asyncLayout() -> (_ item: ChatListRecentPeersListItem, _ params: ListViewItemLayoutParams, _ last: Bool) -> (ListViewItemNodeLayout, (Bool) -> Void) { let currentItem = self.item return { [weak self] item, params, last in let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 96.0), insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)) - return (nodeLayout, { [weak self] in - var updatedTheme: PresentationTheme? - if currentItem?.theme !== item.theme { - updatedTheme = item.theme - } - - return (nil, { _ in - if let strongSelf = self { - strongSelf.item = item - - if let _ = updatedTheme { - strongSelf.separatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor - strongSelf.backgroundNode.backgroundColor = item.theme.list.plainBackgroundColor - } - - let peersNode: ChatListSearchRecentPeersNode - if let currentPeersNode = strongSelf.peersNode { - peersNode = currentPeersNode - peersNode.updateThemeAndStrings(theme: item.theme, strings: item.strings) - } else { - peersNode = ChatListSearchRecentPeersNode(context: item.context, theme: item.theme, mode: .list, strings: item.strings, peerSelected: { peer in - self?.item?.peerSelected(peer) - }, peerContextAction: { peer, node, gesture in - self?.item?.peerContextAction(peer, node, gesture) - }, isPeerSelected: { _ in - return false - }) - strongSelf.peersNode = peersNode - strongSelf.addSubnode(peersNode) - } - - peersNode.frame = CGRect(origin: CGPoint(), size: nodeLayout.contentSize) - peersNode.updateLayout(size: nodeLayout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset) - - let separatorHeight = UIScreenPixel - strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: nodeLayout.contentSize.width, height: nodeLayout.contentSize.height)) - strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width, height: separatorHeight)) - strongSelf.separatorNode.isHidden = true + var updatedTheme: PresentationTheme? + if currentItem?.theme !== item.theme { + updatedTheme = item.theme + } + + return (nodeLayout, { [weak self] synchronousLoads in + if let strongSelf = self { + strongSelf.item = item + + if let _ = updatedTheme { + strongSelf.separatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor + strongSelf.backgroundNode.backgroundColor = item.theme.list.plainBackgroundColor } - }) + + let peersNode: ChatListSearchRecentPeersNode + if let currentPeersNode = strongSelf.peersNode { + peersNode = currentPeersNode + peersNode.updateThemeAndStrings(theme: item.theme, strings: item.strings) + } else { + peersNode = ChatListSearchRecentPeersNode(context: item.context, theme: item.theme, mode: .list, strings: item.strings, peerSelected: { peer in + self?.item?.peerSelected(peer) + }, peerContextAction: { peer, node, gesture in + self?.item?.peerContextAction(peer, node, gesture) + }, isPeerSelected: { _ in + return false + }) + strongSelf.peersNode = peersNode + strongSelf.addSubnode(peersNode) + } + + peersNode.frame = CGRect(origin: CGPoint(), size: nodeLayout.contentSize) + peersNode.updateLayout(size: nodeLayout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset) + + let separatorHeight = UIScreenPixel + strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: nodeLayout.contentSize.width, height: nodeLayout.contentSize.height)) + strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width, height: separatorHeight)) + strongSelf.separatorNode.isHidden = true + } }) } } diff --git a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift index 1f191a7ffa..3b470bbff6 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift @@ -112,6 +112,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo private let statePromise = ValuePromise() private var selectedFilterKey: ChatListSearchFilterEntryId? = .filter(ChatListSearchFilter.chats.id) + private var selectedFilterKeyPromise = Promise(.filter(ChatListSearchFilter.chats.id)) private var transitionFraction: CGFloat = 0.0 private var didSetReady: Bool = false @@ -133,7 +134,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo self.presentInGlobalOverlay = presentInGlobalOverlay self.filterContainerNode = ChatListSearchFiltersContainerNode() - self.paneContainerNode = ChatListSearchPaneContainerNode(context: context, peersFilter: self.peersFilter, searchQuery: self.searchQuery.get(), searchOptions: self.searchOptions.get(), navigationController: navigationController) + self.paneContainerNode = ChatListSearchPaneContainerNode(context: context, peersFilter: self.peersFilter, groupId: groupId, searchQuery: self.searchQuery.get(), searchOptions: self.searchOptions.get(), navigationController: navigationController) self.paneContainerNode.clipsToBounds = true super.init() @@ -216,7 +217,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo }, dismissInput: { [weak self] in self?.dismissInput() }, updateSuggestedPeers: { [weak self] peers, key in - if let strongSelf = self, strongSelf.paneContainerNode.currentPaneKey == key { + if let strongSelf = self, key == .chats { strongSelf.suggestedPeers.set(.single(peers)) } }, getSelectedMessageIds: { [weak self] () -> Set? in @@ -248,6 +249,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo } } strongSelf.selectedFilterKey = filterKey.flatMap { .filter($0.id) } + strongSelf.selectedFilterKeyPromise.set(.single(strongSelf.selectedFilterKey)) strongSelf.transitionFraction = transitionFraction if let (layout, _) = strongSelf.validLayout { @@ -257,7 +259,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo } else { filters = [.chats, .media, .links, .files, .music, .voice] } - strongSelf.filterContainerNode.update(size: CGSize(width: layout.size.width, height: 38.0), sideInset: layout.safeInsets.left, filters: filters.map { .filter($0) }, selectedFilter: strongSelf.selectedFilterKey, transitionFraction: strongSelf.transitionFraction, presentationData: strongSelf.presentationData, transition: transition) + strongSelf.filterContainerNode.update(size: CGSize(width: layout.size.width - 40.0, height: 38.0), sideInset: layout.safeInsets.left - 20.0, filters: filters.map { .filter($0) }, selectedFilter: strongSelf.selectedFilterKey, transitionFraction: strongSelf.transitionFraction, presentationData: strongSelf.presentationData, transition: transition) } } } @@ -297,15 +299,15 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo } } - self.suggestedFiltersDisposable.set((combineLatest(self.suggestedPeers.get(), self.suggestedDates.get()) - |> mapToSignal { peers, dates -> Signal<([Peer], [(Date, String?)]), NoError> in + self.suggestedFiltersDisposable.set((combineLatest(self.suggestedPeers.get(), self.suggestedDates.get(), self.selectedFilterKeyPromise.get()) + |> mapToSignal { peers, dates, selectedFilter -> Signal<([Peer], [(Date, String?)], ChatListSearchFilterEntryId?), NoError> in if (peers.isEmpty && dates.isEmpty) || peers.isEmpty { - return .single((peers, dates)) + return .single((peers, dates, selectedFilter)) } else { return (.complete() |> delay(0.2, queue: Queue.mainQueue())) - |> then(.single((peers, dates))) + |> then(.single((peers, dates, selectedFilter))) } - } |> map { peers, dates -> [ChatListSearchFilter] in + } |> map { peers, dates, selectedFilter -> [ChatListSearchFilter] in var suggestedFilters: [ChatListSearchFilter] = [] if !dates.isEmpty { let formatter = DateFormatter() @@ -317,7 +319,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo suggestedFilters.append(.date(Int32(date.timeIntervalSince1970), title)) } } - if !peers.isEmpty { + if !peers.isEmpty && selectedFilter != .filter(ChatListSearchFilter.chats.id) { for peer in peers { let isGroup: Bool if let channel = peer as? TelegramChannel, case .group = channel.info { @@ -355,6 +357,18 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo } })) + self.presentationDataDisposable = (context.sharedContext.presentationData + |> deliverOnMainQueue).start(next: { [weak self] presentationData in + if let strongSelf = self { + let previousTheme = strongSelf.presentationData.theme + strongSelf.presentationData = presentationData + + if previousTheme !== presentationData.theme { + strongSelf.updateTheme(theme: presentationData.theme) + } + } + }) + self._ready.set(self.paneContainerNode.isReady.get() |> map { _ in Void() }) } @@ -429,29 +443,16 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo } } -// private func updateTheme(theme: PresentationTheme) { -// self.backgroundColor = self.peersFilter.contains(.excludeRecent) ? nil : theme.chatList.backgroundColor -// self.dimNode.backgroundColor = self.peersFilter.contains(.excludeRecent) ? UIColor.black.withAlphaComponent(0.5) : theme.chatList.backgroundColor -// self.recentListNode.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor -// self.listNode.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor -// -// self.listNode.forEachItemHeaderNode({ itemHeaderNode in -// if let itemHeaderNode = itemHeaderNode as? ChatListSearchItemHeaderNode { -// itemHeaderNode.updateTheme(theme: theme) -// } else if let itemHeaderNode = itemHeaderNode as? ListMessageDateHeaderNode { -// itemHeaderNode.updateThemeAndStrings(theme: theme, strings: self.presentationData.strings) -// } -// }) -// self.recentListNode.forEachItemHeaderNode({ itemHeaderNode in -// if let itemHeaderNode = itemHeaderNode as? ChatListSearchItemHeaderNode { -// itemHeaderNode.updateTheme(theme: theme) -// } -// }) -// } + private func updateTheme(theme: PresentationTheme) { + self.backgroundColor = self.peersFilter.contains(.excludeRecent) ? nil : theme.chatList.backgroundColor + + if let (layout, navigationBarHeight) = self.validLayout { + self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) + } + } override public func searchTextUpdated(text: String) { let searchQuery: String? = !text.isEmpty ? text : nil -// self.interaction?.searchTextHighightState = searchQuery self.searchQuery.set(.single(searchQuery)) self.searchQueryValue = searchQuery @@ -463,11 +464,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo self.validLayout = (layout, navigationBarHeight) - var topInset = navigationBarHeight - var topPanelHeight: CGFloat = 0.0 - - topInset += topPanelHeight - + let topInset = navigationBarHeight transition.updateFrame(node: self.filterContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight + 6.0), size: CGSize(width: layout.size.width, height: 38.0))) let filters: [ChatListSearchFilter] @@ -477,7 +474,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo filters = [.chats, .media, .links, .files, .music, .voice] } - self.filterContainerNode.update(size: CGSize(width: layout.size.width, height: 38.0), sideInset: layout.safeInsets.left, filters: filters.map { .filter($0) }, selectedFilter: self.selectedFilterKey, transitionFraction: self.transitionFraction, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring)) + self.filterContainerNode.update(size: CGSize(width: layout.size.width, height: 38.0), sideInset: layout.safeInsets.left - 20.0, filters: filters.map { .filter($0) }, selectedFilter: self.selectedFilterKey, transitionFraction: self.transitionFraction, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring)) if let selectedMessageIds = self.stateValue.selectedMessageIds { var wasAdded = false @@ -546,7 +543,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo }) } - transition.updateFrame(node: self.paneContainerNode, frame: CGRect(x: 0.0, y: topInset - 48.0, width: layout.size.width, height: layout.size.height - topInset + 48)) + transition.updateFrame(node: self.paneContainerNode, frame: CGRect(x: 0.0, y: topInset, width: layout.size.width, height: layout.size.height - topInset)) self.paneContainerNode.update(size: CGSize(width: layout.size.width, height: layout.size.height - topInset), sideInset: layout.safeInsets.left, bottomInset: layout.inputHeight ?? 0.0, visibleHeight: layout.size.height - topInset, presentationData: self.presentationData, transition: transition) } diff --git a/submodules/ChatListUI/Sources/ChatListSearchFiltersContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchFiltersContainerNode.swift index 1a85fa03dd..9d75cb8946 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchFiltersContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchFiltersContainerNode.swift @@ -367,7 +367,7 @@ final class ChatListSearchFiltersContainerNode: ASDisplayNode { } else { if self.bounds.intersects(self.scrollNode.convert(paneFrame, to: self)) { itemNodeTransition.updateFrameAdditive(node: paneNode, frame: paneFrame) - } else { + } else if paneNode.frame != paneFrame { paneNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.4) { [weak paneNode] _ in paneNode?.frame = paneFrame } diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 304b8e228a..e6458be46f 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -24,6 +24,7 @@ import PhoneNumberFormat import InstantPageUI import GalleryData import AppBundle +import ShimmerEffect private enum ChatListRecentEntryStableId: Hashable { case topPeers @@ -365,7 +366,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { } } - public func item(context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, tagMask: MessageTags?, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (Peer) -> Void, searchResults: [Message], searchOptions: ChatListSearchOptions?, messageContextAction: ((Message, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void)?) -> ListViewItem { + public func item(context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, tagMask: MessageTags?, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (Peer) -> Void, searchQuery: String?, searchOptions: ChatListSearchOptions?, messageContextAction: ((Message, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void)?) -> ListViewItem { switch self { case let .localPeer(peer, associatedPeer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, expandType): let primaryPeer: Peer @@ -503,10 +504,10 @@ public enum ChatListSearchEntry: Comparable, Identifiable { case let .message(message, peer, readState, presentationData, totalCount, selected, displayCustomHeader): let header = ChatListSearchItemHeader(type: .messages(totalCount), theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) let selection: ChatHistoryMessageSelection = selected.flatMap { .selectable(selected: $0) } ?? .none - if let tagMask = tagMask, tagMask != .photoOrVideo { + if let tagMask = tagMask, tagMask != .photoOrVideo && (searchQuery?.isEmpty ?? true) { return ListMessageItem(presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: .builtin(WallpaperSettings())), fontSize: presentationData.fontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: presentationData.disableAnimations, largeEmoji: false, chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0.0, auxiliaryRadius: 0.0, mergeBubbleCorners: false)), context: context, chatLocation: .peer(peer.peerId), interaction: listInteraction, message: message, selection: selection, displayHeader: enableHeaders && !displayCustomHeader, customHeader: nil, hintIsLink: tagMask == .webPage, isGlobalSearchResult: true) } else { - return ChatListItem(presentationData: presentationData, context: context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: message.index), content: .peer(messages: [message], peer: peer, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: true, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: header, enableContextActions: false, hiddenOffset: false, interaction: interaction) + return ChatListItem(presentationData: presentationData, context: context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: message.index), content: .peer(messages: [message], peer: peer, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: true, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: tagMask == nil ? header : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) } case let .addContact(phoneNumber, theme, strings): return ContactsAddItem(theme: theme, strings: strings, phoneNumber: phoneNumber, header: ChatListSearchItemHeader(type: .phoneNumber, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { @@ -529,10 +530,10 @@ public struct ChatListSearchContainerTransition { public let displayingResults: Bool public let isEmpty: Bool public let isLoading: Bool - public let query: String + public let query: String? public let animated: Bool - public init(deletions: [ListViewDeleteItem], insertions: [ListViewInsertItem], updates: [ListViewUpdateItem], displayingResults: Bool, isEmpty: Bool, isLoading: Bool, query: String, animated: Bool) { + public init(deletions: [ListViewDeleteItem], insertions: [ListViewInsertItem], updates: [ListViewUpdateItem], displayingResults: Bool, isEmpty: Bool, isLoading: Bool, query: String?, animated: Bool) { self.deletions = deletions self.insertions = insertions self.updates = updates @@ -554,12 +555,12 @@ private func chatListSearchContainerPreparedRecentTransition(from fromEntries: [ return ChatListSearchContainerRecentTransition(deletions: deletions, insertions: insertions, updates: updates) } -public func chatListSearchContainerPreparedTransition(from fromEntries: [ChatListSearchEntry], to toEntries: [ChatListSearchEntry], displayingResults: Bool, isEmpty: Bool, isLoading: Bool, animated: Bool, searchQuery: String, context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, tagMask: MessageTags?, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (Peer) -> Void, searchResults: [Message], searchOptions: ChatListSearchOptions?, messageContextAction: ((Message, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void)?) -> ChatListSearchContainerTransition { +public func chatListSearchContainerPreparedTransition(from fromEntries: [ChatListSearchEntry], to toEntries: [ChatListSearchEntry], displayingResults: Bool, isEmpty: Bool, isLoading: Bool, animated: Bool, context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, tagMask: MessageTags?, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (Peer) -> Void, searchQuery: String?, searchOptions: ChatListSearchOptions?, messageContextAction: ((Message, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void)?) -> ChatListSearchContainerTransition { let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } - let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, tagMask: tagMask, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchResults: searchResults, searchOptions: searchOptions, messageContextAction: messageContextAction), directionHint: nil) } - let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, tagMask: tagMask, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchResults: searchResults, searchOptions: searchOptions, messageContextAction: messageContextAction), directionHint: nil) } + let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, tagMask: tagMask, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchQuery: searchQuery, searchOptions: searchOptions, messageContextAction: messageContextAction), directionHint: nil) } + let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, tagMask: tagMask, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchQuery: searchQuery, searchOptions: searchOptions, messageContextAction: messageContextAction), directionHint: nil) } return ChatListSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, displayingResults: displayingResults, isEmpty: isEmpty, isLoading: isLoading, query: searchQuery, animated: animated) } @@ -636,10 +637,11 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { private var presentationData: PresentationData private let key: ChatListSearchPaneKey private let tagMask: MessageTags? + private let groupId: PeerGroupId? private let navigationController: NavigationController? private let recentListNode: ListView - private let loadingNode: ASImageNode + private let shimmerNode: ChatListSearchShimmerNode private let listNode: ListView private let mediaNode: ChatListSearchMediaNode private var enqueuedRecentTransitions: [(ChatListSearchContainerRecentTransition, Bool)] = [] @@ -696,11 +698,12 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { private var hiddenMediaDisposable: Disposable? - init(context: AccountContext, interaction: ChatListSearchInteraction, key: ChatListSearchPaneKey, peersFilter: ChatListNodePeersFilter, searchQuery: Signal, searchOptions: Signal, navigationController: NavigationController?) { + init(context: AccountContext, interaction: ChatListSearchInteraction, key: ChatListSearchPaneKey, peersFilter: ChatListNodePeersFilter, groupId: PeerGroupId?, searchQuery: Signal, searchOptions: Signal, navigationController: NavigationController?) { self.context = context self.interaction = interaction self.key = key self.peersFilter = peersFilter + self.groupId = groupId self.navigationController = navigationController let tagMask: MessageTags? @@ -730,8 +733,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { self.recentListNode = ListView() self.recentListNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor - self.loadingNode = ASImageNode() - + self.shimmerNode = ChatListSearchShimmerNode(key: key) + self.shimmerNode.isUserInteractionEnabled = false + self.listNode = ListView() self.listNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor @@ -775,7 +779,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { self.addSubnode(self.recentListNode) self.addSubnode(self.listNode) self.addSubnode(self.mediaNode) - self.addSubnode(self.loadingNode) + if key != .chats { + self.addSubnode(self.shimmerNode) + } self.addSubnode(self.mediaAccessoryPanelContainer) self.addSubnode(self.emptyResultsAnimationNode) @@ -871,11 +877,18 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { if let (peerId, _, _) = options.peer { location = .peer(peerId: peerId, fromId: nil, tags: self.tagMask, topMsgId: nil, minDate: options.minDate?.0, maxDate: options.maxDate?.0) } else { - - location = .general(tags: self.tagMask, minDate: options.minDate?.0, maxDate: options.maxDate?.0) + if let groupId = groupId { + location = .group(groupId: groupId, tags: self.tagMask, minDate: options.minDate?.0, maxDate: options.maxDate?.0) + } else { + location = .general(tags: self.tagMask, minDate: options.minDate?.0, maxDate: options.maxDate?.0) + } } } else { - location = .general(tags: self.tagMask, minDate: nil, maxDate: nil) + if let groupId = groupId { + location = .group(groupId: groupId, tags: self.tagMask, minDate: nil, maxDate: nil) + } else { + location = .general(tags: self.tagMask, minDate: nil, maxDate: nil) + } } let finalQuery = query ?? "" @@ -1272,8 +1285,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { let previousSelectedMessages = Atomic?>(value: nil) let _ = (searchQuery - |> deliverOnMainQueue).start(next: { [weak self] query in + |> deliverOnMainQueue).start(next: { [weak self, weak chatListInteraction] query in self?.searchQueryValue = query + chatListInteraction?.searchTextHighightState = query }) let _ = (searchOptions @@ -1329,7 +1343,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { let animated = (previousSelectedMessageIds == nil) != (strongSelf.selectedMessages == nil) let firstTime = previousEntries == nil - let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: newEntries, displayingResults: entriesAndFlags?.0 != nil, isEmpty: !isSearching && (entriesAndFlags?.0.isEmpty ?? false), isLoading: isSearching, animated: animated, searchQuery: strongSelf.searchQueryValue ?? "", context: context, presentationData: strongSelf.presentationData, enableHeaders: true, filter: peersFilter, tagMask: tagMask, interaction: chatListInteraction, listInteraction: listInteraction, peerContextAction: { _, _, _, _ in }, toggleExpandLocalResults: { + let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: newEntries, displayingResults: entriesAndFlags?.0 != nil, isEmpty: !isSearching && (entriesAndFlags?.0.isEmpty ?? false), isLoading: isSearching, animated: animated, context: context, presentationData: strongSelf.presentationData, enableHeaders: true, filter: peersFilter, tagMask: tagMask, interaction: chatListInteraction, listInteraction: listInteraction, peerContextAction: { message, node, rect, gesture in + interaction.peerContextAction?(message, node, rect, gesture) + }, toggleExpandLocalResults: { guard let strongSelf = self else { return } @@ -1348,13 +1364,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { return state } }, searchPeer: { peer in - }, searchResults: newEntries.compactMap { entry -> Message? in - if case let .message(message, _, _, _, _, _, _) = entry { - return message - } else { - return nil - } - }, searchOptions: strongSelf.searchOptionsValue, messageContextAction: { message, node, rect, gesture in + }, searchQuery: strongSelf.searchQueryValue, searchOptions: strongSelf.searchOptionsValue, messageContextAction: { message, node, rect, gesture in interaction.messageContextAction(message, node, rect, gesture) }) strongSelf.enqueueTransition(transition, firstTime: firstTime) @@ -1370,7 +1380,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { if !strongSelf.didSetReady { strongSelf.ready.set(.single(true)) strongSelf.didSetReady = true - } else if tagMask != nil { + } else if tagMask == nil { interaction.updateSuggestedPeers(Array(peers.prefix(8)), strongSelf.key) } } @@ -1485,13 +1495,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { self.presentationDataDisposable = (context.sharedContext.presentationData |> deliverOnMainQueue).start(next: { [weak self] presentationData in if let strongSelf = self { - let previousTheme = strongSelf.presentationData.theme strongSelf.presentationData = presentationData strongSelf.presentationDataPromise.set(.single(ChatListPresentationData(theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: presentationData.disableAnimations))) - - if previousTheme !== presentationData.theme { -// strongSelf.updateTheme(theme: presentationData.theme) - } } }) @@ -1600,18 +1605,21 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { } func scrollToTop() -> Bool { + if !self.mediaNode.isHidden { + return self.mediaNode.scrollToTop() + } let offset = self.listNode.visibleContentOffset() switch offset { case let .known(value) where value <= CGFloat.ulpOfOne: return false default: -// self.listNode.scrollto + self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) return true } } func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { - var hadValidLayout = self.currentParams != nil + let hadValidLayout = self.currentParams != nil self.currentParams = (size, sideInset, bottomInset, visibleHeight, presentationData) var topPanelHeight: CGFloat = 0.0 @@ -1814,11 +1822,12 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { transition.updateFrame(node: self.mediaAccessoryPanelContainer, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: MediaNavigationAccessoryHeaderNode.minimizedHeight))) - var topInset: CGFloat = 0.0 - let navigationBarHeight: CGFloat = 0.0 + let topInset: CGFloat = topPanelHeight + let overflowInset: CGFloat = 20.0 let insets = UIEdgeInsets(top: topPanelHeight, left: sideInset, bottom: bottomInset, right: sideInset) - self.loadingNode.frame = CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: size.width, height: 422.0)) + self.shimmerNode.frame = CGRect(origin: CGPoint(x: overflowInset, y: topInset), size: CGSize(width: size.width - overflowInset * 2.0, height: size.height)) + self.shimmerNode.update(context: self.context, size: CGSize(width: size.width - overflowInset * 2.0, height: size.height), presentationData: self.presentationData, key: (self.searchQueryValue?.isEmpty ?? true) ? self.key : .chats, transition: transition) let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) self.recentListNode.frame = CGRect(origin: CGPoint(), size: size) @@ -1842,7 +1851,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { // } let emptyTextSpacing: CGFloat = 8.0 let emptyTotalHeight = emptyAnimationHeight + emptyAnimationSpacing + emptyTitleSize.height + emptyTextSize.height + emptyTextSpacing - let emptyAnimationY = navigationBarHeight + floorToScreenPixels((visibleHeight - navigationBarHeight - bottomInset - emptyTotalHeight) / 2.0) + let emptyAnimationY = topInset + floorToScreenPixels((visibleHeight - topInset - bottomInset - emptyTotalHeight) / 2.0) let textTransition = ContainedViewLayoutTransition.immediate textTransition.updateFrame(node: self.emptyResultsAnimationNode, frame: CGRect(origin: CGPoint(x: sideInset + padding + (size.width - sideInset * 2.0 - padding * 2.0 - self.animationSize.width) / 2.0, y: emptyAnimationY), size: self.animationSize)) @@ -1909,6 +1918,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { var options = ListViewDeleteAndInsertOptions() if firstTime { + options.insert(.PreferSynchronousResourceLoading) options.insert(.PreferSynchronousDrawing) } else { options.insert(.AnimateInsertion) @@ -1957,9 +1967,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { if emptyResults { let emptyResultsTitle: String let emptyResultsText: String - if !transition.query.isEmpty { + if let query = transition.query, !query.isEmpty { emptyResultsTitle = strongSelf.presentationData.strings.ChatList_Search_NoResults - emptyResultsText = strongSelf.presentationData.strings.ChatList_Search_NoResultsQueryDescription(transition.query).0 + emptyResultsText = strongSelf.presentationData.strings.ChatList_Search_NoResultsQueryDescription(query).0 } else { if let searchOptions = searchOptions, searchOptions.minDate == nil && searchOptions.maxDate == nil && searchOptions.peer == nil { emptyResultsTitle = strongSelf.presentationData.strings.ChatList_Search_NoResultsFilter @@ -1994,16 +2004,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { strongSelf.emptyResultsTitleNode.isHidden = !emptyResults strongSelf.emptyResultsTextNode.isHidden = !emptyResults strongSelf.emptyResultsAnimationNode.visibility = emptyResults - - if strongSelf.tagMask == .webPage { - strongSelf.loadingNode.image = UIImage(bundleImageName: "Chat List/Search/M_Links") - } else if strongSelf.tagMask == .file { - strongSelf.loadingNode.image = UIImage(bundleImageName: "Chat List/Search/M_Files") - } else if strongSelf.tagMask == .music || strongSelf.tagMask == .voiceOrInstantVideo { - strongSelf.loadingNode.image = UIImage(bundleImageName: "Chat List/Search/M_Music") - } - - strongSelf.loadingNode.isHidden = !transition.isLoading + + ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut).updateAlpha(node: strongSelf.shimmerNode, alpha: transition.isLoading ? 1.0 : 0.0) strongSelf.recentListNode.isHidden = displayingResults || strongSelf.peersFilter.contains(.excludeRecent) // strongSelf.dimNode.isHidden = displayingResults @@ -2058,3 +2060,313 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { return nil } } + +private final class ShimmerEffectNode: ASDisplayNode { + private var currentBackgroundColor: UIColor? + private var currentForegroundColor: UIColor? + private let imageNodeContainer: ASDisplayNode + private let imageNode: ASImageNode + + private var absoluteLocation: (CGRect, CGSize)? + private var isCurrentlyInHierarchy = false + private var shouldBeAnimating = false + + override init() { + self.imageNodeContainer = ASDisplayNode() + self.imageNodeContainer.isLayerBacked = true + + self.imageNode = ASImageNode() + self.imageNode.isLayerBacked = true + self.imageNode.displaysAsynchronously = false + self.imageNode.displayWithoutProcessing = true + self.imageNode.contentMode = .scaleToFill + + super.init() + + self.isLayerBacked = true + self.clipsToBounds = true + + self.imageNodeContainer.addSubnode(self.imageNode) + self.addSubnode(self.imageNodeContainer) + } + + override func didEnterHierarchy() { + super.didEnterHierarchy() + + self.isCurrentlyInHierarchy = true + self.updateAnimation() + } + + override func didExitHierarchy() { + super.didExitHierarchy() + + self.isCurrentlyInHierarchy = false + self.updateAnimation() + } + + func update(backgroundColor: UIColor, foregroundColor: UIColor) { + if let currentBackgroundColor = self.currentBackgroundColor, currentBackgroundColor.isEqual(backgroundColor), let currentForegroundColor = self.currentForegroundColor, currentForegroundColor.isEqual(foregroundColor) { + return + } + self.currentBackgroundColor = backgroundColor + self.currentForegroundColor = foregroundColor + + self.imageNode.image = generateImage(CGSize(width: 4.0, height: 320.0), opaque: true, scale: 1.0, rotatedContext: { size, context in + context.setFillColor(backgroundColor.cgColor) + context.fill(CGRect(origin: CGPoint(), size: size)) + + context.clip(to: CGRect(origin: CGPoint(), size: size)) + + let transparentColor = foregroundColor.withAlphaComponent(0.0).cgColor + let peakColor = foregroundColor.cgColor + + var locations: [CGFloat] = [0.0, 0.5, 1.0] + let colors: [CGColor] = [transparentColor, peakColor, transparentColor] + + let colorSpace = CGColorSpaceCreateDeviceRGB() + let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)! + + context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions()) + }) + } + + func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + if let absoluteLocation = self.absoluteLocation, absoluteLocation.0 == rect && absoluteLocation.1 == containerSize { + return + } + let sizeUpdated = self.absoluteLocation?.1 != containerSize + let frameUpdated = self.absoluteLocation?.0 != rect + self.absoluteLocation = (rect, containerSize) + + if sizeUpdated { + if self.shouldBeAnimating { + self.imageNode.layer.removeAnimation(forKey: "shimmer") + self.addImageAnimation() + } + } + + if frameUpdated { + self.imageNodeContainer.frame = CGRect(origin: CGPoint(x: -rect.minX, y: -rect.minY), size: containerSize) + } + + self.updateAnimation() + } + + private func updateAnimation() { + let shouldBeAnimating = self.isCurrentlyInHierarchy && self.absoluteLocation != nil + if shouldBeAnimating != self.shouldBeAnimating { + self.shouldBeAnimating = shouldBeAnimating + if shouldBeAnimating { + self.addImageAnimation() + } else { + self.imageNode.layer.removeAnimation(forKey: "shimmer") + } + } + } + + private func addImageAnimation() { + guard let containerSize = self.absoluteLocation?.1 else { + return + } + let gradientHeight: CGFloat = 250.0 + self.imageNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -gradientHeight), size: CGSize(width: containerSize.width, height: gradientHeight)) + let animation = self.imageNode.layer.makeAnimation(from: 0.0 as NSNumber, to: (containerSize.height + gradientHeight) as NSNumber, keyPath: "position.y", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 1.3 * 1.0, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true) + animation.repeatCount = Float.infinity + animation.beginTime = 1.0 + self.imageNode.layer.add(animation, forKey: "shimmer") + } +} + +private final class ChatListSearchShimmerNode: ASDisplayNode { + private let backgroundColorNode: ASDisplayNode + private let effectNode: ShimmerEffectNode + private let maskNode: ASImageNode + private var currentParams: (size: CGSize, presentationData: PresentationData, key: ChatListSearchPaneKey)? + + init(key: ChatListSearchPaneKey) { + self.backgroundColorNode = ASDisplayNode() + self.effectNode = ShimmerEffectNode() + self.maskNode = ASImageNode() + + super.init() + + self.isUserInteractionEnabled = false + + self.addSubnode(self.backgroundColorNode) + self.addSubnode(self.effectNode) + self.addSubnode(self.maskNode) + } + + func update(context: AccountContext, size: CGSize, presentationData: PresentationData, key: ChatListSearchPaneKey, transition: ContainedViewLayoutTransition) { + if self.currentParams?.size != size || self.currentParams?.presentationData !== presentationData || self.currentParams?.key != key { + self.currentParams = (size, presentationData, key) + + let chatListPresentationData = ChatListPresentationData(theme: presentationData.theme, fontSize: presentationData.chatFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: true) + + let peer1 = TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: 1), accessHash: nil, firstName: "FirstName", lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) + let timestamp1: Int32 = 100000 + let peers = SimpleDictionary() + let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, additionalCategorySelected: { _ in + }, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in }, activateChatPreview: { _, _, gesture in + gesture?.cancel() + }, present: { _ in }) + + let items = (0 ..< 1).compactMap { _ -> ListViewItem? in + switch key { + case .chats: + return ChatListItem(presentationData: chatListPresentationData, context: context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer1, text: "Text", attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])], peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) + case .media: + return nil + case .links: + var media: [Media] = [] + media.append(TelegramMediaWebpage(webpageId: MediaId(namespace: 0, id: 0), content: .Loaded(TelegramMediaWebpageLoadedContent(url: "https://telegram.org", displayUrl: "https://telegram.org", hash: 0, type: nil, websiteName: "Telegram", title: "Telegram Telegram", text: "Telegram", embedUrl: nil, embedType: nil, embedSize: nil, duration: nil, author: nil, image: nil, file: nil, attributes: [], instantPage: nil)))) + let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer1, text: "Text", attributes: [], media: media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + + return ListMessageItem(presentationData: ChatPresentationData(presentationData: presentationData), context: context, chatLocation: .peer(peer1.id), interaction: ListMessageItemInteraction.default, message: message, selection: .none, displayHeader: false, customHeader: nil, hintIsLink: true, isGlobalSearchResult: true) + case .files: + var media: [Media] = [] + media.append(TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: [.Audio(isVoice: false, duration: 0, title: nil, performer: nil, waveform: MemoryBuffer(data: Data()))])) + let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer1, text: "Text", attributes: [], media: media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + + return ListMessageItem(presentationData: ChatPresentationData(presentationData: presentationData), context: context, chatLocation: .peer(peer1.id), interaction: ListMessageItemInteraction.default, message: message, selection: .none, displayHeader: false, customHeader: nil, hintIsLink: false, isGlobalSearchResult: true) + case .music: + var media: [Media] = [] + media.append(TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: [.Audio(isVoice: false, duration: 0, title: nil, performer: nil, waveform: MemoryBuffer(data: Data()))])) + let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer1, text: "Text", attributes: [], media: media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + + return ListMessageItem(presentationData: ChatPresentationData(presentationData: presentationData), context: context, chatLocation: .peer(peer1.id), interaction: ListMessageItemInteraction.default, message: message, selection: .none, displayHeader: false, customHeader: nil, hintIsLink: false, isGlobalSearchResult: true) + case .voice: + var media: [Media] = [] + media.append(TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: [.Audio(isVoice: true, duration: 0, title: nil, performer: nil, waveform: MemoryBuffer(data: Data()))])) + let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer1, text: "Text", attributes: [], media: media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + + return ListMessageItem(presentationData: ChatPresentationData(presentationData: presentationData), context: context, chatLocation: .peer(peer1.id), interaction: ListMessageItemInteraction.default, message: message, selection: .none, displayHeader: false, customHeader: nil, hintIsLink: false, isGlobalSearchResult: true) + } + } + + var itemNodes: [ListViewItemNode] = [] + for i in 0 ..< items.count { + items[i].nodeConfiguredForParams(async: { f in f() }, params: ListViewItemLayoutParams(width: size.width, leftInset: 0.0, rightInset: 0.0, availableHeight: 100.0), synchronousLoads: false, previousItem: i == 0 ? nil : items[i - 1], nextItem: (i == items.count - 1) ? nil : items[i + 1], completion: { node, apply in + itemNodes.append(node) + apply().1(ListViewItemApply(isOnScreen: true)) + }) + } + + self.backgroundColorNode.backgroundColor = presentationData.theme.list.mediaPlaceholderColor + + self.maskNode.image = generateImage(size, rotatedContext: { size, context in + context.setFillColor(presentationData.theme.chatList.backgroundColor.cgColor) + context.fill(CGRect(origin: CGPoint(), size: size)) + + if key == .media { + var currentY: CGFloat = 0.0 + var rowIndex: Int = 0 + + let itemSpacing: CGFloat = 1.0 + let itemsInRow = max(3, min(6, Int(size.width / 140.0))) + let itemSize: CGFloat = floor(size.width / CGFloat(itemsInRow)) + + context.setBlendMode(.copy) + context.setFillColor(UIColor.clear.cgColor) + + while currentY < size.height { + for i in 0 ..< itemsInRow { + let itemOrigin = CGPoint(x: CGFloat(i) * (itemSize + itemSpacing), y: itemSpacing + CGFloat(rowIndex) * (itemSize + itemSpacing)) + context.fill(CGRect(origin: itemOrigin, size: CGSize(width: itemSize, height: itemSize))) + } + currentY += itemSize + rowIndex += 1 + } + } else { + var currentY: CGFloat = 0.0 + let fakeLabelPlaceholderHeight: CGFloat = 8.0 + + func fillLabelPlaceholderRect(origin: CGPoint, width: CGFloat) { + let startPoint = origin + let diameter = fakeLabelPlaceholderHeight + context.fillEllipse(in: CGRect(origin: startPoint, size: CGSize(width: diameter, height: diameter))) + context.fillEllipse(in: CGRect(origin: CGPoint(x: startPoint.x + width - diameter, y: startPoint.y), size: CGSize(width: diameter, height: diameter))) + context.fill(CGRect(origin: CGPoint(x: startPoint.x + diameter / 2.0, y: startPoint.y), size: CGSize(width: width - diameter, height: diameter))) + } + + while currentY < size.height { + let sampleIndex = 0 + let itemHeight: CGFloat = itemNodes[sampleIndex].contentSize.height + + context.setBlendMode(.copy) + context.setFillColor(UIColor.clear.cgColor) + + if let itemNode = itemNodes[sampleIndex] as? ChatListItemNode { + context.fillEllipse(in: itemNode.avatarNode.frame.offsetBy(dx: 0.0, dy: currentY)) + let titleFrame = itemNode.titleNode.frame.offsetBy(dx: 0.0, dy: currentY) + fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: floor(titleFrame.midY - fakeLabelPlaceholderHeight / 2.0)), width: 60.0) + + fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: currentY + itemHeight - floor(itemNode.titleNode.frame.midY - fakeLabelPlaceholderHeight / 2.0) - fakeLabelPlaceholderHeight), width: 60.0) + + fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: currentY + floor((itemHeight - fakeLabelPlaceholderHeight) / 2.0)), width: 120.0) + fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX + 120.0 + 10.0, y: currentY + floor((itemHeight - fakeLabelPlaceholderHeight) / 2.0)), width: 60.0) + + let dateFrame = itemNode.dateNode.frame.offsetBy(dx: 0.0, dy: currentY) + fillLabelPlaceholderRect(origin: CGPoint(x: dateFrame.maxX - 30.0, y: dateFrame.minY), width: 30.0) + + context.setBlendMode(.normal) + context.setFillColor(presentationData.theme.chatList.itemSeparatorColor.cgColor) + context.fill(itemNode.separatorNode.frame.offsetBy(dx: 0.0, dy: currentY)) + } else if let itemNode = itemNodes[sampleIndex] as? ListMessageFileItemNode { + if let media = itemNode.currentMedia as? TelegramMediaFile { + if media.isMusic || media.isVoice { + context.fillEllipse(in: CGRect(x: 12.0, y: currentY + 12.0, width: 40.0, height: 40.0)) + } else { + let path = UIBezierPath(roundedRect: CGRect(x: 12.0, y: currentY + 12.0, width: 40.0, height: 40.0), cornerRadius: 6.0) + context.addPath(path.cgPath) + context.fillPath() + } + } + + let titleFrame = itemNode.titleNode.frame.offsetBy(dx: 0.0, dy: currentY) + fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: floor(titleFrame.midY - fakeLabelPlaceholderHeight / 2.0)), width: 60.0) + + let descriptionFrame = itemNode.descriptionNode.frame.offsetBy(dx: 0.0, dy: currentY) + fillLabelPlaceholderRect(origin: CGPoint(x: descriptionFrame.minX, y: floor(descriptionFrame.midY - fakeLabelPlaceholderHeight / 2.0)), width: 240.0) + + let dateFrame = itemNode.dateNode.frame.offsetBy(dx: 0.0, dy: currentY) + fillLabelPlaceholderRect(origin: CGPoint(x: dateFrame.maxX - 30.0, y: dateFrame.minY), width: 30.0) + + context.setBlendMode(.normal) + context.setFillColor(presentationData.theme.chatList.itemSeparatorColor.cgColor) + context.fill(itemNode.separatorNode.frame.offsetBy(dx: 0.0, dy: currentY)) + } else if let itemNode = itemNodes[sampleIndex] as? ListMessageSnippetItemNode { + let path = UIBezierPath(roundedRect: CGRect(x: 12.0, y: currentY + 12.0, width: 40.0, height: 40.0), cornerRadius: 6.0) + context.addPath(path.cgPath) + context.fillPath() + + let titleFrame = itemNode.titleNode.frame.offsetBy(dx: 0.0, dy: currentY) + fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: floor(titleFrame.midY - fakeLabelPlaceholderHeight / 2.0)), width: 120.0) + + let linkFrame = itemNode.linkNode.frame.offsetBy(dx: 0.0, dy: currentY) + fillLabelPlaceholderRect(origin: CGPoint(x: linkFrame.minX, y: floor(linkFrame.midY - fakeLabelPlaceholderHeight / 2.0)), width: 240.0) + + let authorFrame = itemNode.authorNode.frame.offsetBy(dx: 0.0, dy: currentY) + fillLabelPlaceholderRect(origin: CGPoint(x: authorFrame.minX, y: floor(authorFrame.midY - fakeLabelPlaceholderHeight / 2.0)), width: 60.0) + + let dateFrame = itemNode.dateNode.frame.offsetBy(dx: 0.0, dy: currentY) + fillLabelPlaceholderRect(origin: CGPoint(x: dateFrame.maxX - 30.0, y: dateFrame.minY), width: 30.0) + + context.setBlendMode(.normal) + context.setFillColor(presentationData.theme.chatList.itemSeparatorColor.cgColor) + context.fill(itemNode.separatorNode.frame.offsetBy(dx: 0.0, dy: currentY)) + } + + currentY += itemHeight + } + } + }) + + self.effectNode.update(backgroundColor: presentationData.theme.list.mediaPlaceholderColor, foregroundColor: presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4)) + self.effectNode.updateAbsoluteRect(CGRect(origin: CGPoint(), size: size), within: size) + } + transition.updateFrame(node: self.backgroundColorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size)) + transition.updateFrame(node: self.maskNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size)) + transition.updateFrame(node: self.effectNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size)) + } +} diff --git a/submodules/ChatListUI/Sources/ChatListSearchMediaNode.swift b/submodules/ChatListUI/Sources/ChatListSearchMediaNode.swift index f8fa5b5eee..78c8c2381a 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchMediaNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchMediaNode.swift @@ -13,7 +13,6 @@ import TelegramStringFormatting import UniversalMediaPlayer import ListMessageItem import ChatMessageInteractiveMediaBadge -import ShimmerEffect import GridMessageSelectionNode private let mediaBadgeBackgroundColor = UIColor(white: 0.0, alpha: 0.6) @@ -49,7 +48,6 @@ private final class VisualMediaItemNode: ASDisplayNode { private let imageNode: TransformImageNode private var statusNode: RadialStatusNode private let mediaBadgeNode: ChatMessageInteractiveMediaBadge - private var placeholderNode: ShimmerEffectNode? private var selectionNode: GridMessageSelectionNode? private let fetchStatusDisposable = MetaDisposable() @@ -179,7 +177,6 @@ private final class VisualMediaItemNode: ASDisplayNode { } func updateAbsoluteRect(_ absoluteRect: CGRect, within containerSize: CGSize) { - self.placeholderNode?.updateAbsoluteRect(absoluteRect, within: containerSize) } func update(size: CGSize, item: VisualMediaItem, theme: PresentationTheme, synchronousLoad: Bool) { @@ -302,20 +299,9 @@ private final class VisualMediaItemNode: ASDisplayNode { self.selectionNode?.frame = CGRect(origin: CGPoint(), size: size) self.updateHiddenMedia() - - if let placeholderNode = self.placeholderNode { - self.placeholderNode = nil - placeholderNode.removeFromSupernode() - } - } else if item.isEmpty, self.placeholderNode == nil { - let placeholderNode = ShimmerEffectNode() - placeholderNode.update(backgroundColor: theme.list.itemBlocksBackgroundColor, foregroundColor: theme.list.mediaPlaceholderColor, shimmeringColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: [.rect(rect: CGRect(origin: CGPoint(), size: size))], size: size) - self.addSubnode(placeholderNode) - self.placeholderNode = placeholderNode } let imageFrame = CGRect(origin: CGPoint(), size: size) - self.placeholderNode?.frame = imageFrame if let (item, media, _, mediaDimensions) = self.item { self.item = (item, media, size, mediaDimensions) @@ -435,15 +421,6 @@ private final class VisualMediaItem { let message: Message? let dimensions: CGSize let aspectRatio: CGFloat - let isEmpty: Bool - - init(index: UInt32) { - self.index = index - self.message = nil - self.dimensions = CGSize(width: 100.0, height: 100.0) - self.aspectRatio = 1.0 - self.isEmpty = true - } init(message: Message) { self.index = nil @@ -461,7 +438,6 @@ private final class VisualMediaItem { } self.aspectRatio = aspectRatio self.dimensions = dimensions - self.isEmpty = false } var stableId: UInt32 { @@ -741,23 +717,16 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate { break default: self.mediaItems.removeAll() - let loading: Bool - if let entries = entries { - loading = false + + if let entries = entries { for entry in entries { if case let .message(message, _, _, _, _, _, _) = entry { self.mediaItems.append(VisualMediaItem(message: message)) } } - } else { - loading = true - for i in 0 ..< 21 { - self.mediaItems.append(VisualMediaItem(index: UInt32(i))) - } } self.itemsLayout = nil - let wasInitialized = self.initialized self.initialized = true if let (size, sideInset, bottomInset, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) = self.currentParams { diff --git a/submodules/ChatListUI/Sources/ChatListSearchPaneContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchPaneContainerNode.swift index dada51b135..da9d2922ae 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchPaneContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchPaneContainerNode.swift @@ -36,7 +36,7 @@ final class ChatListSearchPaneWrapper { } func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { - if let (currentSize, currentSideInset, currentBottomInset, visibleHeight, currentPresentationData) = self.appliedParams { + if let (currentSize, currentSideInset, currentBottomInset, _, currentPresentationData) = self.appliedParams { if currentSize == size && currentSideInset == sideInset && currentBottomInset == bottomInset && currentPresentationData === presentationData { return } @@ -76,12 +76,13 @@ private final class ChatListSearchPendingPane { interaction: ChatListSearchInteraction, navigationController: NavigationController?, peersFilter: ChatListNodePeersFilter, + groupId: PeerGroupId, searchQuery: Signal, searchOptions: Signal, key: ChatListSearchPaneKey, hasBecomeReady: @escaping (ChatListSearchPaneKey) -> Void ) { - let paneNode = ChatListSearchListPaneNode(context: context, interaction: interaction, key: key, peersFilter: key == .chats ? peersFilter : [], searchQuery: searchQuery, searchOptions: searchOptions, navigationController: navigationController) + let paneNode = ChatListSearchListPaneNode(context: context, interaction: interaction, key: key, peersFilter: key == .chats ? peersFilter : [], groupId: groupId, searchQuery: searchQuery, searchOptions: searchOptions, navigationController: navigationController) self.pane = ChatListSearchPaneWrapper(key: key, node: paneNode) self.disposable = (paneNode.isReady @@ -100,14 +101,12 @@ private final class ChatListSearchPendingPane { final class ChatListSearchPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { private let context: AccountContext private let peersFilter: ChatListNodePeersFilter + private let groupId: PeerGroupId private let searchQuery: Signal private let searchOptions: Signal private let navigationController: NavigationController? var interaction: ChatListSearchInteraction? - - private let coveringBackgroundNode: ASDisplayNode - private let separatorNode: ASDisplayNode - + let isReady = Promise() var didSetIsReady = false @@ -138,23 +137,15 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, UIGestureRecognizerD private var currentAvailablePanes: [ChatListSearchPaneKey]? - init(context: AccountContext, peersFilter: ChatListNodePeersFilter, searchQuery: Signal, searchOptions: Signal, navigationController: NavigationController?) { + init(context: AccountContext, peersFilter: ChatListNodePeersFilter, groupId: PeerGroupId, searchQuery: Signal, searchOptions: Signal, navigationController: NavigationController?) { self.context = context self.peersFilter = peersFilter + self.groupId = groupId self.searchQuery = searchQuery self.searchOptions = searchOptions self.navigationController = navigationController - self.separatorNode = ASDisplayNode() - self.separatorNode.isLayerBacked = true - - self.coveringBackgroundNode = ASDisplayNode() - self.coveringBackgroundNode.isLayerBacked = true - super.init() - - self.addSubnode(self.separatorNode) - self.addSubnode(self.coveringBackgroundNode) } func requestSelectPane(_ key: ChatListSearchPaneKey) { @@ -256,7 +247,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, UIGestureRecognizerD directionIsToRight = translation.x > size.width / 2.0 } } - var updated = false + if let directionIsToRight = directionIsToRight { var updatedIndex = currentIndex if directionIsToRight { @@ -267,7 +258,6 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, UIGestureRecognizerD let switchToKey = availablePanes[updatedIndex] if switchToKey != self.currentPaneKey && self.currentPanes[switchToKey] != nil{ self.currentPaneKey = switchToKey - updated = true } } self.transitionFraction = 0.0 @@ -338,19 +328,10 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, UIGestureRecognizerD } self.currentParams = (size, sideInset, bottomInset, visibleHeight, presentationData) - - transition.updateAlpha(node: self.coveringBackgroundNode, alpha: 0.0) - + self.backgroundColor = presentationData.theme.list.itemBlocksBackgroundColor - self.coveringBackgroundNode.backgroundColor = presentationData.theme.rootController.navigationBar.backgroundColor - self.separatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor - let tabsHeight: CGFloat = 48.0 - - transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: size.width, height: UIScreenPixel))) - transition.updateFrame(node: self.coveringBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: size.width, height: tabsHeight + UIScreenPixel))) - - let paneFrame = CGRect(origin: CGPoint(x: 0.0, y: tabsHeight), size: CGSize(width: size.width, height: size.height - tabsHeight)) + let paneFrame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height)) var visiblePaneIndices: [Int] = [] var requiredPendingKeys: [ChatListSearchPaneKey] = [] @@ -386,6 +367,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, UIGestureRecognizerD interaction: self.interaction!, navigationController: self.navigationController, peersFilter: self.peersFilter, + groupId: self.groupId, searchQuery: self.searchQuery, searchOptions: self.searchOptions, key: key, @@ -429,8 +411,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, UIGestureRecognizerD var paneSwitchAnimationOffset: CGFloat = 0.0 var updatedCurrentIndex = currentIndex - var animatePaneTransitionOffset: CGFloat? - if let pendingSwitchToPaneKey = self.pendingSwitchToPaneKey, let pane = self.currentPanes[pendingSwitchToPaneKey] { + if let pendingSwitchToPaneKey = self.pendingSwitchToPaneKey, let _ = self.currentPanes[pendingSwitchToPaneKey] { self.pendingSwitchToPaneKey = nil previousPaneKey = self.currentPaneKey self.currentPaneKey = pendingSwitchToPaneKey @@ -463,8 +444,8 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, UIGestureRecognizerD return } pane.isAnimatingOut = false - if let (size, sideInset, bottomInset, visibleHeight, presentationData) = strongSelf.currentParams { - if let currentPaneKey = strongSelf.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey), let paneIndex = availablePanes.firstIndex(of: key), abs(paneIndex - currentIndex) <= 1 { + if let _ = strongSelf.currentParams { + if let currentPaneKey = strongSelf.currentPaneKey, let currentIndex = availablePanes.firstIndex(of: currentPaneKey), let paneIndex = availablePanes.firstIndex(of: key), paneIndex == 0 || abs(paneIndex - currentIndex) <= 1 { } else { if let pane = strongSelf.currentPanes.removeValue(forKey: key) { pane.node.removeFromSupernode() diff --git a/submodules/Display/Source/NavigationBar.swift b/submodules/Display/Source/NavigationBar.swift index 58eb59e1c9..86882093e0 100644 --- a/submodules/Display/Source/NavigationBar.swift +++ b/submodules/Display/Source/NavigationBar.swift @@ -836,10 +836,6 @@ open class NavigationBar: ASDisplayNode { self.validLayout = (size, defaultHeight, additionalHeight, leftInset, rightInset, appearsHidden) - if let secondaryContentNode = self.secondaryContentNode { -// transition.updateAlpha(node: secondaryContentNode, alpha: appearsHidden ? 0.0 : 1.0) - } - let apparentAdditionalHeight: CGFloat = self.secondaryContentNode != nil ? NavigationBar.defaultSecondaryContentHeight : 0.0 let leftButtonInset: CGFloat = leftInset + 16.0 @@ -1217,8 +1213,8 @@ open class NavigationBar: ASDisplayNode { public func setSecondaryContentNode(_ secondaryContentNode: ASDisplayNode?, animated: Bool = false) { if self.secondaryContentNode !== secondaryContentNode { - if let previous = self.secondaryContentNode { - if animated && previous.supernode === self.clippingNode { + if let previous = self.secondaryContentNode, previous.supernode === self.clippingNode { + if animated { previous.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak previous] finished in if finished { previous?.removeFromSupernode() diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift index 661e505d73..e433841dd1 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift @@ -96,11 +96,10 @@ public final class HashtagSearchController: TelegramBaseController { }) let firstTime = previousEntries == nil - let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: entries, displayingResults: true, isEmpty: entries.isEmpty, isLoading: false, animated: false, searchQuery: "", context: strongSelf.context, presentationData: strongSelf.presentationData, enableHeaders: false, filter: [], tagMask: nil, interaction: interaction, listInteraction: listInteraction, peerContextAction: nil, toggleExpandLocalResults: { + let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: entries, displayingResults: true, isEmpty: entries.isEmpty, isLoading: false, animated: false, context: strongSelf.context, presentationData: strongSelf.presentationData, enableHeaders: false, filter: [], tagMask: nil, interaction: interaction, listInteraction: listInteraction, peerContextAction: nil, toggleExpandLocalResults: { }, toggleExpandGlobalResults: { }, searchPeer: { _ in - - }, searchResults: [], searchOptions: nil, messageContextAction: nil) + }, searchQuery: "", searchOptions: nil, messageContextAction: nil) strongSelf.controllerNode.enqueueTransition(transition, firstTime: firstTime) } }) diff --git a/submodules/HorizontalPeerItem/Sources/HorizontalPeerItem.swift b/submodules/HorizontalPeerItem/Sources/HorizontalPeerItem.swift index bbff42ee43..9c9c87aeba 100644 --- a/submodules/HorizontalPeerItem/Sources/HorizontalPeerItem.swift +++ b/submodules/HorizontalPeerItem/Sources/HorizontalPeerItem.swift @@ -57,7 +57,7 @@ public final class HorizontalPeerItem: ListViewItem { Queue.mainQueue().async { completion(node, { return (nil, { _ in - apply(false) + apply(false, synchronousLoads) }) }) } @@ -73,7 +73,7 @@ public final class HorizontalPeerItem: ListViewItem { let (nodeLayout, apply) = layout(self, params) Queue.mainQueue().async { completion(nodeLayout, { _ in - apply(animation.isAnimated) + apply(animation.isAnimated, false) }) } } @@ -126,7 +126,7 @@ public final class HorizontalPeerItemNode: ListViewItemNode { self.layer.sublayerTransform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0) } - public func asyncLayout() -> (HorizontalPeerItem, ListViewItemLayoutParams) -> (ListViewItemNodeLayout, (Bool) -> Void) { + public func asyncLayout() -> (HorizontalPeerItem, ListViewItemLayoutParams) -> (ListViewItemNodeLayout, (Bool, Bool) -> Void) { let badgeTextLayout = TextNode.asyncLayout(self.badgeTextNode) let onlineLayout = self.onlineNode.asyncLayout() @@ -184,11 +184,11 @@ public final class HorizontalPeerItemNode: ListViewItemNode { animateContent = true } - return (itemLayout, { animated in + return (itemLayout, { animated, synchronousLoads in if let strongSelf = self { strongSelf.item = item strongSelf.peerNode.theme = itemTheme - strongSelf.peerNode.setup(context: item.context, theme: item.theme, strings: item.strings, peer: RenderedPeer(peer: item.peer), numberOfLines: 1, synchronousLoad: false) + strongSelf.peerNode.setup(context: item.context, theme: item.theme, strings: item.strings, peer: RenderedPeer(peer: item.peer), numberOfLines: 1, synchronousLoad: synchronousLoads) strongSelf.peerNode.frame = CGRect(origin: CGPoint(), size: itemLayout.size) strongSelf.peerNode.updateSelection(selected: item.isPeerSelected(item.peer.id), animated: false) diff --git a/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift b/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift index e40d65541f..f21fb4ca97 100644 --- a/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift +++ b/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift @@ -134,14 +134,14 @@ public final class ListMessageFileItemNode: ListMessageNode { private let offsetContainerNode: ASDisplayNode private let highlightedBackgroundNode: ASDisplayNode - private let separatorNode: ASDisplayNode + public let separatorNode: ASDisplayNode private var selectionNode: ItemListSelectableControlNode? - private let titleNode: TextNode - private let descriptionNode: TextNode + public let titleNode: TextNode + public let descriptionNode: TextNode private let descriptionProgressNode: ImmediateTextNode - private let dateNode: TextNode + public let dateNode: TextNode private let extensionIconNode: ASImageNode private let extensionIconText: TextNode @@ -149,7 +149,7 @@ public final class ListMessageFileItemNode: ListMessageNode { private let iconStatusNode: SemanticStatusNode private var currentIconImage: FileIconImage? - private var currentMedia: Media? + public var currentMedia: Media? private let statusDisposable = MetaDisposable() private let fetchControls = Atomic(value: nil) diff --git a/submodules/ListMessageItem/Sources/ListMessageItem.swift b/submodules/ListMessageItem/Sources/ListMessageItem.swift index dc75279a93..74beffea41 100644 --- a/submodules/ListMessageItem/Sources/ListMessageItem.swift +++ b/submodules/ListMessageItem/Sources/ListMessageItem.swift @@ -28,6 +28,17 @@ public final class ListMessageItemInteraction { self.longTap = longTap self.getHiddenMedia = getHiddenMedia } + + public static var `default`: ListMessageItemInteraction = ListMessageItemInteraction(openMessage: { _, _ in + return false + }, openMessageContextMenu: { _, _, _, _, _ in + }, toggleMessagesSelection: { _, _ in + }, openUrl: { _, _, _, _ in + }, openInstantPage: { _, _ in + }, longTap: { _, _ in + }, getHiddenMedia: { () -> [MessageId : [Media]] in + return [:] + }) } public final class ListMessageItem: ListViewItem { diff --git a/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift b/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift index 946f8f7ecf..d473654286 100644 --- a/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift +++ b/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift @@ -31,17 +31,17 @@ public final class ListMessageSnippetItemNode: ListMessageNode { private var nonExtractedRect: CGRect? private let highlightedBackgroundNode: ASDisplayNode - private let separatorNode: ASDisplayNode + public let separatorNode: ASDisplayNode private var selectionNode: ItemListSelectableControlNode? - private let titleNode: TextNode - private let descriptionNode: TextNode - private let dateNode: TextNode + public let titleNode: TextNode + let descriptionNode: TextNode + public let dateNode: TextNode private let instantViewIconNode: ASImageNode - private let linkNode: TextNode + public let linkNode: TextNode private var linkHighlightingNode: LinkHighlightingNode? - private let authorNode: TextNode + public let authorNode: TextNode private let iconTextBackgroundNode: ASImageNode private let iconTextNode: TextNode diff --git a/submodules/SearchUI/Sources/SearchDisplayController.swift b/submodules/SearchUI/Sources/SearchDisplayController.swift index b6a05ca6d6..d896010a7b 100644 --- a/submodules/SearchUI/Sources/SearchDisplayController.swift +++ b/submodules/SearchUI/Sources/SearchDisplayController.swift @@ -126,8 +126,16 @@ public final class SearchDisplayController { let bounds = CGRect(origin: CGPoint(), size: layout.size) transition.updateFrame(node: self.backgroundNode, frame: bounds.insetBy(dx: -20.0, dy: -20.0)) - transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(x: 20.0, y: 20.0), size: layout.size)) - self.contentNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: LayoutMetrics(), deviceMetrics: layout.deviceMetrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: nil, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), navigationBarHeight: navigationBarHeight, transition: transition) + + var size = layout.size + size.width += 20.0 * 2.0 + transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 20.0), size: size)) + + var safeInsets = layout.safeInsets + safeInsets.left += 20.0 + safeInsets.right += 20.0 + + self.contentNode.containerLayoutUpdated(ContainerViewLayout(size: size, metrics: LayoutMetrics(), deviceMetrics: layout.deviceMetrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: safeInsets, statusBarHeight: nil, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), navigationBarHeight: navigationBarHeight, transition: transition) } public func activate(insertSubnode: (ASDisplayNode, Bool) -> Void, placeholder: SearchBarPlaceholderNode?) { @@ -142,28 +150,35 @@ public final class SearchDisplayController { self.backgroundNode.backgroundColor = .clear } - self.contentNode.frame = CGRect(origin: CGPoint(x: 20.0, y: 20.0), size: layout.size) + var size = layout.size + size.width += 20.0 * 2.0 - self.contentNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: LayoutMetrics(), deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: layout.safeInsets, statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), navigationBarHeight: navigationBarHeight, transition: .immediate) + self.contentNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 20.0), size: size) + + var safeInsets = layout.safeInsets + safeInsets.left += 20.0 + safeInsets.right += 20.0 + self.contentNode.containerLayoutUpdated(ContainerViewLayout(size: size, metrics: LayoutMetrics(), deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: safeInsets, statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), navigationBarHeight: navigationBarHeight, transition: .immediate) var contentNavigationBarHeight = navigationBarHeight if layout.statusBarHeight == nil { contentNavigationBarHeight += 28.0 } - - if let placeholder = placeholder { - let initialTextBackgroundFrame = placeholder.convert(placeholder.backgroundNode.frame, to: nil) + + self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue) + if !self.contentNode.hasDim { + self.backgroundNode.layer.animateScale(from: 0.85, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) - let contentNodePosition = self.backgroundNode.layer.position - - - self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue) - if !self.contentNode.hasDim { - self.backgroundNode.layer.animateScale(from: 0.85, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring) - } else { - self.backgroundNode.layer.animatePosition(from: CGPoint(x: contentNodePosition.x, y: contentNodePosition.y + (initialTextBackgroundFrame.maxY + 8.0 - contentNavigationBarHeight)), to: contentNodePosition, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) + if let placeholder = placeholder { + self.searchBar.placeholderString = placeholder.placeholderString + } + } else { + if let placeholder = placeholder { + let initialTextBackgroundFrame = placeholder.convert(placeholder.backgroundNode.frame, to: nil) + let contentNodePosition = self.backgroundNode.layer.position + self.backgroundNode.layer.animatePosition(from: CGPoint(x: contentNodePosition.x, y: contentNodePosition.y + (initialTextBackgroundFrame.maxY + 8.0 - contentNavigationBarHeight)), to: contentNodePosition, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) + self.searchBar.placeholderString = placeholder.placeholderString } - self.searchBar.placeholderString = placeholder.placeholderString } let navigationBarFrame: CGRect diff --git a/submodules/SelectablePeerNode/Sources/SelectablePeerNode.swift b/submodules/SelectablePeerNode/Sources/SelectablePeerNode.swift index 77c5462bd3..6f4c470742 100644 --- a/submodules/SelectablePeerNode/Sources/SelectablePeerNode.swift +++ b/submodules/SelectablePeerNode/Sources/SelectablePeerNode.swift @@ -114,7 +114,7 @@ public final class SelectablePeerNode: ASDisplayNode { self.textNode = ASTextNode() self.textNode.isUserInteractionEnabled = false - self.textNode.displaysAsynchronously = true + self.textNode.displaysAsynchronously = false self.onlineNode = PeerOnlineMarkerNode() diff --git a/submodules/SettingsUI/Sources/Themes/ThemeGridSearchContentNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeGridSearchContentNode.swift index 86012e78f0..9b4f57668a 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeGridSearchContentNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeGridSearchContentNode.swift @@ -371,11 +371,7 @@ final class ThemeGridSearchContentNode: SearchDisplayControllerContentNode { override var isSearching: Signal { return self._isSearching.get() } - - public override var hasDim: Bool { - return true - } - + init(context: AccountContext, openResult: @escaping (ChatContextResult) -> Void) { self.context = context self.queryPromise = Promise(self.queryValue) diff --git a/submodules/TelegramCore/Sources/SearchMessages.swift b/submodules/TelegramCore/Sources/SearchMessages.swift index ce30fb2c06..19eb12abc5 100644 --- a/submodules/TelegramCore/Sources/SearchMessages.swift +++ b/submodules/TelegramCore/Sources/SearchMessages.swift @@ -8,7 +8,7 @@ import SyncCore public enum SearchMessagesLocation: Equatable { case general(tags: MessageTags?, minDate: Int32?, maxDate: Int32?) - case group(PeerGroupId) + case group(groupId: PeerGroupId, tags: MessageTags?, minDate: Int32?, maxDate: Int32?) case peer(peerId: PeerId, fromId: PeerId?, tags: MessageTags?, topMsgId: MessageId?, minDate: Int32?, maxDate: Int32?) case publicForwards(messageId: MessageId, datacenterId: Int?) } @@ -270,9 +270,15 @@ public func searchMessages(account: Account, location: SearchMessagesLocation, q } return combineLatest(peerMessages, additionalPeerMessages) } - case .group: - remoteSearchResult = .single((nil, nil)) - case let .general(tags, minDate, maxDate): + case let .general(tags, minDate, maxDate), let .group(_, tags, minDate, maxDate): + var flags: Int32 = 0 + let folderId: Int32? + if case let .group(groupId, _, _, _) = location { + folderId = groupId.rawValue + flags |= (1 << 0) + } else { + folderId = nil + } let filter: Api.MessagesFilter = tags.flatMap { messageFilterForTagMask($0) } ?? .inputMessagesFilterEmpty remoteSearchResult = account.postbox.transaction { transaction -> (Int32, MessageIndex?, Api.InputPeer) in var lowerBound: MessageIndex? @@ -286,7 +292,7 @@ public func searchMessages(account: Account, location: SearchMessagesLocation, q } } |> mapToSignal { (nextRate, lowerBound, inputPeer) in - return account.network.request(Api.functions.messages.searchGlobal(flags: 0, folderId: nil, q: query, filter: filter, minDate: minDate ?? 0, maxDate: maxDate ?? (Int32.max - 1), offsetRate: nextRate, offsetPeer: inputPeer, offsetId: lowerBound?.id.id ?? 0, limit: limit), automaticFloodWait: false) + return account.network.request(Api.functions.messages.searchGlobal(flags: flags, folderId: folderId, q: query, filter: filter, minDate: minDate ?? 0, maxDate: maxDate ?? (Int32.max - 1), offsetRate: nextRate, offsetPeer: inputPeer, offsetId: lowerBound?.id.id ?? 0, limit: limit), automaticFloodWait: false) |> map { result -> (Api.messages.Messages?, Api.messages.Messages?) in return (result, nil) } diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Files.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Files.imageset/Contents.json deleted file mode 100644 index e4bf34c537..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Files.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "Files.png", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Files.imageset/Files.png b/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Files.imageset/Files.png deleted file mode 100644 index 9785f3ba7c..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Files.imageset/Files.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Links.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Links.imageset/Contents.json deleted file mode 100644 index 1169595a34..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Links.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "Links.png", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Links.imageset/Links.png b/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Links.imageset/Links.png deleted file mode 100644 index cd6782f0c9..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Links.imageset/Links.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Music.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Music.imageset/Contents.json deleted file mode 100644 index 573448e1ce..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Music.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "Music.png", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Music.imageset/Music.png b/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Music.imageset/Music.png deleted file mode 100644 index 7e65a1657c..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Chat List/Search/M_Music.imageset/Music.png and /dev/null differ diff --git a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoGroupsInCommonPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoGroupsInCommonPaneNode.swift index 79489ea9a3..862b391038 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoGroupsInCommonPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoGroupsInCommonPaneNode.swift @@ -122,7 +122,7 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode { func scrollToTop() -> Bool { if !self.listNode.scrollToOffsetFromTop(0.0) { - self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Spring(duration: 0.4), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) return true } else { return false diff --git a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoMembersPane.swift b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoMembersPane.swift index c84ffdf10a..2b41898196 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoMembersPane.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoMembersPane.swift @@ -165,7 +165,7 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode { func scrollToTop() -> Bool { if !self.listNode.scrollToOffsetFromTop(0.0) { - self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Spring(duration: 0.4), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) return true } else { return false diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 44ae82c9a5..600426bbdb 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -4841,11 +4841,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD } private func activateSearch() { - guard let (layout, navigationBarHeight) = self.validLayout else { - return - } - - if let _ = self.searchDisplayController { + guard let (layout, navigationBarHeight) = self.validLayout, self.searchDisplayController == nil else { return } @@ -4912,16 +4908,14 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD transition.updateAlpha(node: navigationBar, alpha: 0.0) } - self.searchDisplayController?.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) + self.searchDisplayController?.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight + 10.0, transition: .immediate) self.searchDisplayController?.activate(insertSubnode: { [weak self] subnode, isSearchBar in if let strongSelf = self, let navigationBar = strongSelf.controller?.navigationBar { strongSelf.insertSubnode(subnode, belowSubnode: navigationBar) } }, placeholder: nil) - if let (layout, navigationHeight) = self.validLayout { - self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate) - } + self.containerLayoutUpdated(layout: layout, navigationHeight: navigationBarHeight, transition: .immediate) } private func deactivateSearch() { @@ -4958,7 +4952,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD } if let searchDisplayController = self.searchDisplayController { - searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: transition) + searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight + 10.0, transition: transition) if !searchDisplayController.isDeactivating { //vanillaInsets.top += (layout.statusBarHeight ?? 0.0) - navigationBarHeightDelta }