From d3bfd683865a18eb25a1412e55e08f6edc0593f2 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Tue, 30 Jan 2024 10:08:46 +0100 Subject: [PATCH] Add inline search results --- .../Sources/ChatController.swift | 2 + .../BUILD | 1 + ...ChatInlineSearchResultsListComponent.swift | 173 +++++++++---- .../Sources/PeerInfoChatListPaneNode.swift | 228 +++++++++++++++++- .../Sources/PeerInfoChatPaneNode.swift | 1 + .../Sources/PeerInfoScreen.swift | 4 + .../StoryQualityUpgradeSheetScreen.swift | 8 +- .../Animations/StoryUpgradeSheet.tgs | Bin 0 -> 26344 bytes ...UpdateChatPresentationInterfaceState.swift | 2 +- .../TelegramUI/Sources/ChatController.swift | 12 +- .../Sources/ChatControllerNode.swift | 50 +++- .../ChatControllerOpenCalendarSearch.swift | 4 + .../Sources/ChatTagSearchInputPanelNode.swift | 18 +- 13 files changed, 429 insertions(+), 74 deletions(-) create mode 100644 submodules/TelegramUI/Resources/Animations/StoryUpgradeSheet.tgs diff --git a/submodules/AccountContext/Sources/ChatController.swift b/submodules/AccountContext/Sources/ChatController.swift index 3ee7cc641d..875857ad41 100644 --- a/submodules/AccountContext/Sources/ChatController.swift +++ b/submodules/AccountContext/Sources/ChatController.swift @@ -947,6 +947,8 @@ public protocol ChatController: ViewController { var visibleContextController: ViewController? { get } + var alwaysShowSearchResultsAsList: Bool { get set } + func updatePresentationMode(_ mode: ChatControllerPresentationMode) func beginMessageSearch(_ query: String) func displayPromoAnnouncement(text: String) diff --git a/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/BUILD b/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/BUILD index 904cf18eb8..a43fd25787 100644 --- a/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/BUILD @@ -23,6 +23,7 @@ swift_library( "//submodules/TelegramPresentationData", "//submodules/TelegramUIPreferences", "//submodules/UIKitRuntimeUtils", + "//submodules/ChatPresentationInterfaceState", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift b/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift index 4ff623eef4..ac06ac2a05 100644 --- a/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift +++ b/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift @@ -12,6 +12,7 @@ import TelegramPresentationData import SwiftSignalKit import TelegramUIPreferences import UIKitRuntimeUtils +import ChatPresentationInterfaceState public final class ChatInlineSearchResultsListComponent: Component { public struct Presentation: Equatable { @@ -62,7 +63,9 @@ public final class ChatInlineSearchResultsListComponent: Component { } public enum Contents: Equatable { + case empty case tag(MemoryBuffer) + case search } public let context: AccountContext @@ -72,6 +75,7 @@ public final class ChatInlineSearchResultsListComponent: Component { public let insets: UIEdgeInsets public let messageSelected: (EngineMessage) -> Void public let loadTagMessages: (MemoryBuffer, MessageIndex?) -> Signal? + public let getSearchResult: () -> Signal? public init( context: AccountContext, @@ -80,7 +84,8 @@ public final class ChatInlineSearchResultsListComponent: Component { contents: Contents, insets: UIEdgeInsets, messageSelected: @escaping (EngineMessage) -> Void, - loadTagMessages: @escaping (MemoryBuffer, MessageIndex?) -> Signal? + loadTagMessages: @escaping (MemoryBuffer, MessageIndex?) -> Signal?, + getSearchResult: @escaping () -> Signal? ) { self.context = context self.presentation = presentation @@ -89,6 +94,7 @@ public final class ChatInlineSearchResultsListComponent: Component { self.insets = insets self.messageSelected = messageSelected self.loadTagMessages = loadTagMessages + self.getSearchResult = getSearchResult } public static func ==(lhs: ChatInlineSearchResultsListComponent, rhs: ChatInlineSearchResultsListComponent) -> Bool { @@ -111,15 +117,21 @@ public final class ChatInlineSearchResultsListComponent: Component { } private struct ContentsState: Equatable { + enum ContentId: Equatable { + case empty + case tag(MemoryBuffer) + case search + } + var id: Int - var tag: MemoryBuffer? + var contentId: ContentId var entries: [EngineMessage] var hasEarlier: Bool var hasLater: Bool - init(id: Int, tag: MemoryBuffer?, entries: [EngineMessage], hasEarlier: Bool, hasLater: Bool) { + init(id: Int, contentId: ContentId, entries: [EngineMessage], hasEarlier: Bool, hasLater: Bool) { self.id = id - self.tag = tag + self.contentId = contentId self.entries = entries self.hasEarlier = hasEarlier self.hasLater = hasLater @@ -134,6 +146,7 @@ public final class ChatInlineSearchResultsListComponent: Component { private let listNode: ListView private var tagContents: (index: MessageIndex?, disposable: Disposable?)? + private var searchContents: (index: MessageIndex?, disposable: Disposable?)? private var nextContentsId: Int = 0 private var contentsState: ContentsState? @@ -162,6 +175,7 @@ public final class ChatInlineSearchResultsListComponent: Component { deinit { self.tagContents?.disposable?.dispose() + self.searchContents?.disposable?.dispose() } public func animateIn() { @@ -228,9 +242,6 @@ public final class ChatInlineSearchResultsListComponent: Component { guard let contentsState = self.contentsState, contentsState.id == stateId else { return } - guard let (currentIndex, disposable) = self.tagContents else { - return - } guard let visibleRange = displayedRange.visibleRange else { return } @@ -245,49 +256,85 @@ public final class ChatInlineSearchResultsListComponent: Component { } } - if let loadAroundIndex, loadAroundIndex != currentIndex { - switch component.contents { - case let .tag(tag): - disposable?.dispose() - let updatedDisposable = MetaDisposable() - self.tagContents = (loadAroundIndex, updatedDisposable) - - if let historySignal = component.loadTagMessages(tag, self.tagContents?.index) { - updatedDisposable.set((historySignal - |> deliverOnMainQueue).startStrict(next: { [weak self] view in - guard let self else { - return - } - - let contentsId = self.nextContentsId - self.nextContentsId += 1 - self.contentsState = ContentsState( - id: contentsId, - tag: tag, - entries: view.entries.reversed().map { entry in - return EngineMessage(entry.message) - }, - hasEarlier: view.earlierId != nil, - hasLater: view.laterId != nil - ) - if !self.isUpdating { - self.state?.updated(transition: .immediate) - } - - if !self.didSetReady { - self.didSetReady = true - self.isReadyPromise.set(.single(true)) - } - })) + if let (currentIndex, disposable) = self.tagContents { + if let loadAroundIndex, loadAroundIndex != currentIndex { + switch component.contents { + case .empty: + break + case let .tag(tag): + disposable?.dispose() + let updatedDisposable = MetaDisposable() + self.tagContents = (loadAroundIndex, updatedDisposable) + + if let historySignal = component.loadTagMessages(tag, self.tagContents?.index) { + updatedDisposable.set((historySignal + |> deliverOnMainQueue).startStrict(next: { [weak self] view in + guard let self else { + return + } + + let contentsId = self.nextContentsId + self.nextContentsId += 1 + self.contentsState = ContentsState( + id: contentsId, + contentId: .tag(tag), + entries: view.entries.reversed().map { entry in + return EngineMessage(entry.message) + }, + hasEarlier: view.earlierId != nil, + hasLater: view.laterId != nil + ) + if !self.isUpdating { + self.state?.updated(transition: .immediate) + } + + if !self.didSetReady { + self.didSetReady = true + self.isReadyPromise.set(.single(true)) + } + })) + } + case .search: + break } } } } switch component.contents { + case .empty: + if previousComponent?.contents != component.contents { + self.tagContents?.disposable?.dispose() + self.tagContents = nil + + self.searchContents?.disposable?.dispose() + self.searchContents = nil + + let contentsId = self.nextContentsId + self.nextContentsId += 1 + self.contentsState = ContentsState( + id: contentsId, + contentId: .empty, + entries: [], + hasEarlier: false, + hasLater: false + ) + if !self.isUpdating { + self.state?.updated(transition: .immediate) + } + + if !self.didSetReady { + self.didSetReady = true + self.isReadyPromise.set(.single(true)) + } + } case let .tag(tag): if previousComponent?.contents != component.contents { self.tagContents?.disposable?.dispose() + self.tagContents = nil + + self.searchContents?.disposable?.dispose() + self.searchContents = nil let disposable = MetaDisposable() self.tagContents = (nil, disposable) @@ -303,7 +350,7 @@ public final class ChatInlineSearchResultsListComponent: Component { self.nextContentsId += 1 self.contentsState = ContentsState( id: contentsId, - tag: tag, + contentId: .tag(tag), entries: view.entries.reversed().map { entry in return EngineMessage(entry.message) }, @@ -314,6 +361,46 @@ public final class ChatInlineSearchResultsListComponent: Component { self.state?.updated(transition: .immediate) } + if !self.didSetReady { + self.didSetReady = true + self.isReadyPromise.set(.single(true)) + } + })) + } + } + case .search: + if previousComponent?.contents != component.contents { + self.tagContents?.disposable?.dispose() + self.tagContents = nil + + self.searchContents?.disposable?.dispose() + self.searchContents = nil + + let disposable = MetaDisposable() + self.searchContents = (nil, disposable) + + if let historySignal = component.getSearchResult() { + disposable.set((historySignal + |> deliverOnMainQueue).startStrict(next: { [weak self] result in + guard let self else { + return + } + + let contentsId = self.nextContentsId + self.nextContentsId += 1 + self.contentsState = ContentsState( + id: contentsId, + contentId: .search, + entries: result?.messages.map { entry in + return EngineMessage(entry) + } ?? [], + hasEarlier: false, + hasLater: false + ) + if !self.isUpdating { + self.state?.updated(transition: .immediate) + } + if !self.didSetReady { self.didSetReady = true self.isReadyPromise.set(.single(true)) @@ -521,7 +608,7 @@ public final class ChatInlineSearchResultsListComponent: Component { } var scrollToItem: ListViewScrollToItem? - if previousContentsState?.tag != contentsState.tag && !contentsState.entries.isEmpty { + if previousContentsState?.contentId != contentsState.contentId && !contentsState.entries.isEmpty { scrollToItem = ListViewScrollToItem( index: 0, position: .top(0.0), diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/Sources/PeerInfoChatListPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/Sources/PeerInfoChatListPaneNode.swift index 9678cd3744..80df1b1ffd 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/Sources/PeerInfoChatListPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/Sources/PeerInfoChatListPaneNode.swift @@ -17,6 +17,83 @@ import ChatListUI import DeleteChatPeerActionSheetItem import UndoUI +private final class SearchNavigationContentNode: ASDisplayNode, PeerInfoPanelNodeNavigationContentNode { + private struct Params: Equatable { + var width: CGFloat + var defaultHeight: CGFloat + var insets: UIEdgeInsets + + init(width: CGFloat, defaultHeight: CGFloat, insets: UIEdgeInsets) { + self.width = width + self.defaultHeight = defaultHeight + self.insets = insets + } + } + + weak var chatController: ChatController? + let contentNode: NavigationBarContentNode + + var panelNode: ChatControllerCustomNavigationPanelNode? + private var appliedPanelNode: ChatControllerCustomNavigationPanelNode? + + private var params: Params? + + init(chatController: ChatController, contentNode: NavigationBarContentNode) { + self.chatController = chatController + self.contentNode = contentNode + + super.init() + + self.addSubnode(self.contentNode) + } + + func update(transition: ContainedViewLayoutTransition) { + if let params = self.params { + let _ = self.update(width: params.width, defaultHeight: params.defaultHeight, insets: params.insets, transition: transition) + } + } + + func update(width: CGFloat, defaultHeight: CGFloat, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) -> CGFloat { + self.params = Params(width: width, defaultHeight: defaultHeight, insets: insets) + + let size = CGSize(width: width, height: defaultHeight) + transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 10.0), size: size)) + self.contentNode.updateLayout(size: size, leftInset: insets.left, rightInset: insets.right, transition: transition) + + var contentHeight: CGFloat = size.height + 10.0 + + if self.appliedPanelNode !== self.panelNode { + if let previous = self.appliedPanelNode { + transition.updateAlpha(node: previous, alpha: 0.0, completion: { [weak previous] _ in + previous?.removeFromSupernode() + }) + } + + self.appliedPanelNode = self.panelNode + if let panelNode = self.panelNode, let chatController = self.chatController { + self.addSubnode(panelNode) + let panelLayout = panelNode.updateLayout(width: width, leftInset: insets.left, rightInset: insets.right, transition: .immediate, chatController: chatController) + let panelHeight = panelLayout.backgroundHeight + let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: width, height: panelHeight)) + panelNode.frame = panelFrame + panelNode.alpha = 0.0 + transition.updateAlpha(node: panelNode, alpha: 1.0) + + contentHeight += panelHeight - 1.0 + } + } else if let panelNode = self.panelNode, let chatController = self.chatController { + let panelLayout = panelNode.updateLayout(width: width, leftInset: insets.left, rightInset: insets.right, transition: transition, chatController: chatController) + let panelHeight = panelLayout.backgroundHeight + let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: width, height: panelHeight)) + transition.updateFrame(node: panelNode, frame: panelFrame) + + contentHeight += panelHeight - 1.0 + } + + return contentHeight + } +} + public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDelegate, UIGestureRecognizerDelegate { private let context: AccountContext @@ -24,7 +101,7 @@ public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, UI public weak var parentController: ViewController? - private var currentParams: (size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData)? + private var currentParams: (size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData)? private let ready = Promise() private var didSetReady: Bool = false @@ -50,6 +127,16 @@ public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, UI private var emptyShimmerEffectNode: ChatListShimmerNode? private var shimmerNodeOffset: CGFloat = 0.0 private var floatingHeaderOffset: CGFloat? + + private let coveringView: UIView + private var chatController: ChatController? + private var removeChatWhenNotSearching: Bool = false + + private var searchNavigationContentNode: SearchNavigationContentNode? + public var navigationContentNode: PeerInfoPanelNodeNavigationContentNode? { + return self.searchNavigationContentNode + } + public var externalDataUpdated: ((ContainedViewLayoutTransition) -> Void)? public init(context: AccountContext, navigationController: @escaping () -> NavigationController?) { self.context = context @@ -58,6 +145,8 @@ public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, UI self.presentationData = presentationData let strings = presentationData.strings + self.coveringView = UIView() + self.chatListNode = ChatListNode( context: self.context, location: .savedMessagesChats, @@ -83,8 +172,12 @@ public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, UI super.init() + self.clipsToBounds = true + self.addSubnode(self.chatListNode) + self.view.addSubview(self.coveringView) + self.presentationDataDisposable = (self.context.sharedContext.presentationData |> deliverOnMainQueue).start(next: { [weak self] presentationData in guard let self else { @@ -293,12 +386,89 @@ public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, UI deinit { self.presentationDataDisposable?.dispose() } + + public func activateSearch() { + if self.chatController == nil { + let chatController = self.context.sharedContext.makeChatController(context: self.context, chatLocation: .peer(id: self.context.account.peerId), subject: nil, botStart: nil, mode: .standard(.embedded(invertDirection: false))) + chatController.alwaysShowSearchResultsAsList = true + + self.chatController = chatController + chatController.navigation_setNavigationController(self.navigationController()) + + self.insertSubnode(chatController.displayNode, aboveSubnode: self.chatListNode) + chatController.displayNode.alpha = 0.0 + chatController.displayNode.clipsToBounds = true + + self.updateChatController(transition: .immediate) + + let _ = (chatController.ready.get() + |> filter { $0 } + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self, weak chatController] _ in + guard let self, let chatController, self.chatController === chatController else { + return + } + + chatController.stateUpdated = { [weak self] transition in + guard let self, let chatController = self.chatController else { + return + } + if let contentNode = chatController.customNavigationBarContentNode { + self.removeChatWhenNotSearching = true + + chatController.displayNode.layer.allowsGroupOpacity = true + if transition.isAnimated { + Transition.easeInOut(duration: 0.2).setAlpha(layer: chatController.displayNode.layer, alpha: 1.0) + } + + if self.searchNavigationContentNode?.contentNode !== contentNode { + self.searchNavigationContentNode = SearchNavigationContentNode(chatController: chatController, contentNode: contentNode) + self.searchNavigationContentNode?.panelNode = chatController.customNavigationPanelNode + self.externalDataUpdated?(transition) + } else if self.searchNavigationContentNode?.panelNode !== chatController.customNavigationPanelNode { + self.searchNavigationContentNode?.panelNode = chatController.customNavigationPanelNode + self.externalDataUpdated?(transition.isAnimated ? transition : .animated(duration: 0.4, curve: .spring)) + } else { + self.searchNavigationContentNode?.update(transition: transition) + } + } else { + if self.searchNavigationContentNode !== nil { + self.searchNavigationContentNode = nil + self.externalDataUpdated?(transition) + } + + if self.removeChatWhenNotSearching { + self.removeChatController() + } + } + } + + chatController.activateSearch(domain: .everything, query: "") + }) + } + } + + private func removeChatController() { + if let chatController = self.chatController { + self.chatController = nil + + let displayNode = chatController.displayNode + chatController.displayNode.layer.allowsGroupOpacity = true + chatController.displayNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak displayNode] _ in + displayNode?.removeFromSupernode() + }) + } + } public func ensureMessageIsVisible(id: MessageId) { } public func scrollToTop() -> Bool { - self.chatListNode.scrollToPosition(.top(adjustForTempInset: false)) + if let chatController = self.chatController { + let _ = chatController.performScrollToTop() + } else { + self.chatListNode.scrollToPosition(.top(adjustForTempInset: false)) + } return false } @@ -356,25 +526,67 @@ public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, UI public func updateSelectedMessages(animated: Bool) { } - public func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { - self.currentParams = (size, topInset, sideInset, bottomInset, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) + private func updateChatController(transition: ContainedViewLayoutTransition) { + guard let chatController = self.chatController else { + return + } + guard let currentParams = self.currentParams else { + return + } - transition.updateFrame(node: self.chatListNode, frame: CGRect(origin: CGPoint(), size: size)) + let size = currentParams.size + let topInset = currentParams.topInset + let sideInset = currentParams.sideInset + let bottomInset = currentParams.bottomInset + let navigationHeight = currentParams.navigationHeight + let deviceMetrics = currentParams.deviceMetrics + let isScrollingLockedAtTop = currentParams.isScrollingLockedAtTop + + let fullHeight = navigationHeight + size.height + + let chatFrame = CGRect(origin: CGPoint(x: 0.0, y: -navigationHeight), size: CGSize(width: size.width, height: fullHeight)) + + if !chatController.displayNode.bounds.isEmpty { + if let contextController = chatController.visibleContextController as? ContextController { + let deltaY = chatFrame.minY - chatController.displayNode.frame.minY + contextController.addRelativeContentOffset(CGPoint(x: 0.0, y: -deltaY * 0.0), transition: transition) + } + } + + let combinedBottomInset = bottomInset + transition.updateFrame(node: chatController.displayNode, frame: chatFrame) + chatController.updateIsScrollingLockedAtTop(isScrollingLockedAtTop: isScrollingLockedAtTop) + chatController.containerLayoutUpdated(ContainerViewLayout(size: chatFrame.size, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact, orientation: nil), deviceMetrics: deviceMetrics, intrinsicInsets: UIEdgeInsets(top: topInset + navigationHeight, left: sideInset, bottom: combinedBottomInset, right: sideInset), safeInsets: UIEdgeInsets(top: navigationHeight + topInset + 4.0, left: sideInset, bottom: combinedBottomInset, right: sideInset), additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: transition) + } + + public func update(size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) { + self.currentParams = (size, topInset, sideInset, bottomInset, deviceMetrics: deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) + + self.coveringView.backgroundColor = presentationData.theme.list.itemBlocksBackgroundColor + transition.updateFrame(view: self.coveringView, frame: CGRect(origin: CGPoint(x: 0.0, y: -1.0), size: CGSize(width: size.width, height: topInset + 1.0))) + + let fullHeight = navigationHeight + size.height + let chatFrame = CGRect(origin: CGPoint(x: 0.0, y: -navigationHeight), size: CGSize(width: size.width, height: fullHeight)) + let combinedBottomInset = bottomInset + + transition.updateFrame(node: self.chatListNode, frame: chatFrame) let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) self.chatListNode.updateLayout( transition: transition, updateSizeAndInsets: ListViewUpdateSizeAndInsets( size: size, - insets: UIEdgeInsets(top: topInset, left: sideInset, bottom: bottomInset, right: sideInset), + insets: UIEdgeInsets(top: topInset + navigationHeight, left: sideInset, bottom: combinedBottomInset, right: sideInset), duration: duration, curve: curve ), - visibleTopInset: topInset, - originalTopInset: topInset, + visibleTopInset: topInset + navigationHeight, + originalTopInset: topInset + navigationHeight, storiesInset: 0.0, inlineNavigationLocation: nil, inlineNavigationTransitionFraction: 0.0 ) + + self.updateChatController(transition: transition) } override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/Sources/PeerInfoChatPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/Sources/PeerInfoChatPaneNode.swift index ba7225d538..5fa729736d 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/Sources/PeerInfoChatPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/Sources/PeerInfoChatPaneNode.swift @@ -150,6 +150,7 @@ public final class PeerInfoChatPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro self.coveringView = UIView() self.chatController = context.sharedContext.makeChatController(context: context, chatLocation: .replyThread(message: ChatReplyThreadMessage(peerId: context.account.peerId, threadId: peerId.toInt64(), channelMessageId: nil, isChannelPost: false, isForumPost: false, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false)), subject: nil, botStart: nil, mode: .standard(.embedded(invertDirection: true))) + self.chatController.navigation_setNavigationController(navigationController()) super.init() diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index e5ab3ec5f5..e0edabee79 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -103,6 +103,7 @@ import MediaPickerUI import AttachmentUI import BoostLevelIconComponent import PeerInfoChatPaneNode +import PeerInfoChatListPaneNode public enum PeerInfoAvatarEditingMode { case generic @@ -9289,6 +9290,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro if let currentPaneKey = self.paneContainerNode.currentPaneKey, case .savedMessages = currentPaneKey, let paneNode = self.paneContainerNode.currentPane?.node as? PeerInfoChatPaneNode { paneNode.activateSearch() return + } else if let currentPaneKey = self.paneContainerNode.currentPaneKey, case .savedMessagesChats = currentPaneKey, let paneNode = self.paneContainerNode.currentPane?.node as? PeerInfoChatListPaneNode { + paneNode.activateSearch() + return } self.headerNode.navigationButtonContainer.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue) diff --git a/submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen/Sources/StoryQualityUpgradeSheetScreen.swift b/submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen/Sources/StoryQualityUpgradeSheetScreen.swift index ec3434059f..6c61b3ac2e 100644 --- a/submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen/Sources/StoryQualityUpgradeSheetScreen.swift +++ b/submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen/Sources/StoryQualityUpgradeSheetScreen.swift @@ -149,19 +149,19 @@ private final class StoryQualityUpgradeSheetContentComponent: Component { let iconSize = self.icon.update( transition: transition, component: AnyComponent(LottieComponent( - content: LottieComponent.AppBundleContent(name: "ChatListNoResults"), + content: LottieComponent.AppBundleContent(name: "StoryUpgradeSheet"), color: nil, startingPosition: .begin, - size: CGSize(width: 120.0, height: 120.0) + size: CGSize(width: 100.0, height: 100.0) )), environment: {}, - containerSize: CGSize(width: 120.0, height: 120.0) + containerSize: CGSize(width: 100.0, height: 100.0) ) if let iconView = self.icon.view { if iconView.superview == nil { self.addSubview(iconView) } - transition.setFrame(view: iconView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize.width) * 0.5), y: 25.0), size: iconSize)) + transition.setFrame(view: iconView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize.width) * 0.5), y: 42.0), size: iconSize)) } contentHeight += 138.0 diff --git a/submodules/TelegramUI/Resources/Animations/StoryUpgradeSheet.tgs b/submodules/TelegramUI/Resources/Animations/StoryUpgradeSheet.tgs new file mode 100644 index 0000000000000000000000000000000000000000..d57e811cc8e42f691310a7cc96b73a7c57583f46 GIT binary patch literal 26344 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 M>yk#h3`mgw2bOc&6#xJL literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift b/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift index 3bdee209cc..4ae3d58e7e 100644 --- a/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift +++ b/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift @@ -398,7 +398,7 @@ func updateChatPresentationInterfaceStateImpl( } if updatedChatPresentationInterfaceState.displayHistoryFilterAsList { - if updatedChatPresentationInterfaceState.search == nil || updatedChatPresentationInterfaceState.historyFilter == nil { + if updatedChatPresentationInterfaceState.search?.resultsState == nil && updatedChatPresentationInterfaceState.historyFilter == nil && !selfController.alwaysShowSearchResultsAsList { updatedChatPresentationInterfaceState = updatedChatPresentationInterfaceState.updatedDisplayHistoryFilterAsList(false) } } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 1e37226b40..9cb9ce92a2 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -581,6 +581,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var performTextSelectionAction: ((Message?, Bool, NSAttributedString, TextSelectionAction) -> Void)? var performOpenURL: ((Message?, String, Promise?) -> Void)? + public var alwaysShowSearchResultsAsList: Bool = false { + didSet { + self.presentationInterfaceState = self.presentationInterfaceState.updatedDisplayHistoryFilterAsList(self.alwaysShowSearchResultsAsList) + self.chatDisplayNode.alwaysShowSearchResultsAsList = self.alwaysShowSearchResultsAsList + } + } + public init(context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic = Atomic(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, attachBotStart: ChatControllerInitialAttachBotStart? = nil, botAppStart: ChatControllerInitialBotAppStart? = nil, mode: ChatControllerPresentationMode = .standard(.default), peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, chatListFilter: Int32? = nil, chatNavigationStack: [ChatNavigationStackItem] = []) { let _ = ChatControllerCount.modify { value in return value + 1 @@ -10958,11 +10965,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } - if let search = self.presentationInterfaceState.search, !search.query.isEmpty { - self.interfaceInteraction?.openSearchResults() - return - } - self.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in return state.updatedDisplayHistoryFilterAsList(displayAsList) }) diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 3fd70d1b20..c9bee43e42 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -135,6 +135,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { let loadingNode: ChatLoadingNode private(set) var loadingPlaceholderNode: ChatLoadingPlaceholderNode? + var alwaysShowSearchResultsAsList: Bool = false + private var skippedShowSearchResultsAsListAnimationOnce: Bool = false var inlineSearchResults: ComponentView? private var inlineSearchResultsReadyDisposable: Disposable? private var inlineSearchResultsReady: Bool = false @@ -2430,7 +2432,17 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.updatePlainInputSeparator(transition: transition) - if self.chatPresentationInterfaceState.displayHistoryFilterAsList, let peerId = self.chatPresentationInterfaceState.chatLocation.peerId, let historyFilter = self.chatPresentationInterfaceState.historyFilter { + var displayInlineSearch = false + if self.chatPresentationInterfaceState.displayHistoryFilterAsList { + if self.chatPresentationInterfaceState.historyFilter != nil || self.chatPresentationInterfaceState.search?.resultsState != nil { + displayInlineSearch = true + } + if self.alwaysShowSearchResultsAsList { + displayInlineSearch = true + } + } + + if let peerId = self.chatPresentationInterfaceState.chatLocation.peerId, displayInlineSearch { let inlineSearchResults: ComponentView var inlineSearchResultsTransition = Transition(transition) if let current = self.inlineSearchResults { @@ -2441,6 +2453,15 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.inlineSearchResults = inlineSearchResults } + let mappedContents: ChatInlineSearchResultsListComponent.Contents + if let _ = self.chatPresentationInterfaceState.search?.resultsState { + mappedContents = .search + } else if let historyFilter = self.chatPresentationInterfaceState.historyFilter { + mappedContents = .tag(historyFilter.customTag) + } else { + mappedContents = .empty + } + let context = self.context let _ = inlineSearchResults.update( transition: inlineSearchResultsTransition, @@ -2455,7 +2476,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { nameDisplayOrder: self.chatPresentationInterfaceState.nameDisplayOrder ), peerId: peerId, - contents: .tag(historyFilter.customTag), + contents: mappedContents, insets: childContentInsets, messageSelected: { [weak self] message in guard let self else { @@ -2512,6 +2533,15 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { return .single(view) } } + }, + getSearchResult: { [weak self] in + guard let self, let controller = self.controller else { + return nil + } + return controller.searchResult.get() + |> map { result in + return result?.0 + } } )), environment: {}, @@ -2521,7 +2551,10 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { var animateIn = false if inlineSearchResultsView.superview == nil { animateIn = true - inlineSearchResultsView.alpha = 0.0 + if !self.alwaysShowSearchResultsAsList || self.skippedShowSearchResultsAsListAnimationOnce { + inlineSearchResultsView.alpha = 0.0 + } + self.skippedShowSearchResultsAsListAnimationOnce = true inlineSearchResultsView.layer.allowsGroupOpacity = true self.contentContainerNode.view.insertSubview(inlineSearchResultsView, aboveSubview: self.historyNodeContainer.view) } @@ -2538,11 +2571,14 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { guard let inlineSearchResultsView = self.inlineSearchResults?.view as? ChatInlineSearchResultsListComponent.View else { return } - inlineSearchResultsView.alpha = 1.0 - inlineSearchResultsView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - inlineSearchResultsView.animateIn() + if inlineSearchResultsView.alpha == 0.0 { + inlineSearchResultsView.alpha = 1.0 - transition.updateSublayerTransformScale(node: self.historyNodeContainer, scale: CGPoint(x: 0.95, y: 0.95)) + inlineSearchResultsView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + inlineSearchResultsView.animateIn() + + transition.updateSublayerTransformScale(node: self.historyNodeContainer, scale: CGPoint(x: 0.95, y: 0.95)) + } }) } } diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenCalendarSearch.swift b/submodules/TelegramUI/Sources/ChatControllerOpenCalendarSearch.swift index d4d7b60a20..0d6dd32c6b 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenCalendarSearch.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenCalendarSearch.swift @@ -39,6 +39,10 @@ extension ChatControllerImpl { c.dismiss() return } + + strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: false, { state in + return state.updatedDisplayHistoryFilterAsList(false) + }) c.dismiss() diff --git a/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift index 40a403c964..96eaa3add0 100644 --- a/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift @@ -168,12 +168,18 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode { if let currentId = results.currentId, let index = results.messageIndices.firstIndex(where: { $0.id == currentId }) { canChangeListMode = true - let adjustedIndex = results.messageIndices.count - 1 - index - - //TODO:localize - resultsTextString.append(AnimatedTextComponent.Item(id: AnyHashable("search_n"), content: .number(adjustedIndex + 1, minDigits: 1))) - resultsTextString.append(AnimatedTextComponent.Item(id: AnyHashable("search_of"), isUnbreakable: true, content: .text(" of "))) - resultsTextString.append(AnimatedTextComponent.Item(id: AnyHashable("search_m"), content: .number(displayTotalCount, minDigits: 1))) + if params.interfaceState.displayHistoryFilterAsList { + //TODO:localize + resultsTextString.append(AnimatedTextComponent.Item(id: AnyHashable("count_n"), content: .number(displayTotalCount, minDigits: 1))) + resultsTextString.append(AnimatedTextComponent.Item(id: AnyHashable("count_message"), isUnbreakable: true, content: .text(displayTotalCount == 1 ? " message" : " messages"))) + } else { + let adjustedIndex = results.messageIndices.count - 1 - index + + //TODO:localize + resultsTextString.append(AnimatedTextComponent.Item(id: AnyHashable("search_n"), content: .number(adjustedIndex + 1, minDigits: 1))) + resultsTextString.append(AnimatedTextComponent.Item(id: AnyHashable("search_of"), isUnbreakable: true, content: .text(" of "))) + resultsTextString.append(AnimatedTextComponent.Item(id: AnyHashable("search_m"), content: .number(displayTotalCount, minDigits: 1))) + } } else { canChangeListMode = false