mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Add inline search results
This commit is contained in:
parent
4ffc6367bb
commit
d3bfd68386
@ -947,6 +947,8 @@ public protocol ChatController: ViewController {
|
|||||||
|
|
||||||
var visibleContextController: ViewController? { get }
|
var visibleContextController: ViewController? { get }
|
||||||
|
|
||||||
|
var alwaysShowSearchResultsAsList: Bool { get set }
|
||||||
|
|
||||||
func updatePresentationMode(_ mode: ChatControllerPresentationMode)
|
func updatePresentationMode(_ mode: ChatControllerPresentationMode)
|
||||||
func beginMessageSearch(_ query: String)
|
func beginMessageSearch(_ query: String)
|
||||||
func displayPromoAnnouncement(text: String)
|
func displayPromoAnnouncement(text: String)
|
||||||
|
@ -23,6 +23,7 @@ swift_library(
|
|||||||
"//submodules/TelegramPresentationData",
|
"//submodules/TelegramPresentationData",
|
||||||
"//submodules/TelegramUIPreferences",
|
"//submodules/TelegramUIPreferences",
|
||||||
"//submodules/UIKitRuntimeUtils",
|
"//submodules/UIKitRuntimeUtils",
|
||||||
|
"//submodules/ChatPresentationInterfaceState",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -12,6 +12,7 @@ import TelegramPresentationData
|
|||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
import UIKitRuntimeUtils
|
import UIKitRuntimeUtils
|
||||||
|
import ChatPresentationInterfaceState
|
||||||
|
|
||||||
public final class ChatInlineSearchResultsListComponent: Component {
|
public final class ChatInlineSearchResultsListComponent: Component {
|
||||||
public struct Presentation: Equatable {
|
public struct Presentation: Equatable {
|
||||||
@ -62,7 +63,9 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public enum Contents: Equatable {
|
public enum Contents: Equatable {
|
||||||
|
case empty
|
||||||
case tag(MemoryBuffer)
|
case tag(MemoryBuffer)
|
||||||
|
case search
|
||||||
}
|
}
|
||||||
|
|
||||||
public let context: AccountContext
|
public let context: AccountContext
|
||||||
@ -72,6 +75,7 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
|||||||
public let insets: UIEdgeInsets
|
public let insets: UIEdgeInsets
|
||||||
public let messageSelected: (EngineMessage) -> Void
|
public let messageSelected: (EngineMessage) -> Void
|
||||||
public let loadTagMessages: (MemoryBuffer, MessageIndex?) -> Signal<MessageHistoryView, NoError>?
|
public let loadTagMessages: (MemoryBuffer, MessageIndex?) -> Signal<MessageHistoryView, NoError>?
|
||||||
|
public let getSearchResult: () -> Signal<SearchMessagesResult?, NoError>?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
@ -80,7 +84,8 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
|||||||
contents: Contents,
|
contents: Contents,
|
||||||
insets: UIEdgeInsets,
|
insets: UIEdgeInsets,
|
||||||
messageSelected: @escaping (EngineMessage) -> Void,
|
messageSelected: @escaping (EngineMessage) -> Void,
|
||||||
loadTagMessages: @escaping (MemoryBuffer, MessageIndex?) -> Signal<MessageHistoryView, NoError>?
|
loadTagMessages: @escaping (MemoryBuffer, MessageIndex?) -> Signal<MessageHistoryView, NoError>?,
|
||||||
|
getSearchResult: @escaping () -> Signal<SearchMessagesResult?, NoError>?
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.presentation = presentation
|
self.presentation = presentation
|
||||||
@ -89,6 +94,7 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
|||||||
self.insets = insets
|
self.insets = insets
|
||||||
self.messageSelected = messageSelected
|
self.messageSelected = messageSelected
|
||||||
self.loadTagMessages = loadTagMessages
|
self.loadTagMessages = loadTagMessages
|
||||||
|
self.getSearchResult = getSearchResult
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: ChatInlineSearchResultsListComponent, rhs: ChatInlineSearchResultsListComponent) -> Bool {
|
public static func ==(lhs: ChatInlineSearchResultsListComponent, rhs: ChatInlineSearchResultsListComponent) -> Bool {
|
||||||
@ -111,15 +117,21 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private struct ContentsState: Equatable {
|
private struct ContentsState: Equatable {
|
||||||
|
enum ContentId: Equatable {
|
||||||
|
case empty
|
||||||
|
case tag(MemoryBuffer)
|
||||||
|
case search
|
||||||
|
}
|
||||||
|
|
||||||
var id: Int
|
var id: Int
|
||||||
var tag: MemoryBuffer?
|
var contentId: ContentId
|
||||||
var entries: [EngineMessage]
|
var entries: [EngineMessage]
|
||||||
var hasEarlier: Bool
|
var hasEarlier: Bool
|
||||||
var hasLater: 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.id = id
|
||||||
self.tag = tag
|
self.contentId = contentId
|
||||||
self.entries = entries
|
self.entries = entries
|
||||||
self.hasEarlier = hasEarlier
|
self.hasEarlier = hasEarlier
|
||||||
self.hasLater = hasLater
|
self.hasLater = hasLater
|
||||||
@ -134,6 +146,7 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
|||||||
private let listNode: ListView
|
private let listNode: ListView
|
||||||
|
|
||||||
private var tagContents: (index: MessageIndex?, disposable: Disposable?)?
|
private var tagContents: (index: MessageIndex?, disposable: Disposable?)?
|
||||||
|
private var searchContents: (index: MessageIndex?, disposable: Disposable?)?
|
||||||
|
|
||||||
private var nextContentsId: Int = 0
|
private var nextContentsId: Int = 0
|
||||||
private var contentsState: ContentsState?
|
private var contentsState: ContentsState?
|
||||||
@ -162,6 +175,7 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
|||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
self.tagContents?.disposable?.dispose()
|
self.tagContents?.disposable?.dispose()
|
||||||
|
self.searchContents?.disposable?.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func animateIn() {
|
public func animateIn() {
|
||||||
@ -228,9 +242,6 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
|||||||
guard let contentsState = self.contentsState, contentsState.id == stateId else {
|
guard let contentsState = self.contentsState, contentsState.id == stateId else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard let (currentIndex, disposable) = self.tagContents else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard let visibleRange = displayedRange.visibleRange else {
|
guard let visibleRange = displayedRange.visibleRange else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -245,49 +256,85 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let loadAroundIndex, loadAroundIndex != currentIndex {
|
if let (currentIndex, disposable) = self.tagContents {
|
||||||
switch component.contents {
|
if let loadAroundIndex, loadAroundIndex != currentIndex {
|
||||||
case let .tag(tag):
|
switch component.contents {
|
||||||
disposable?.dispose()
|
case .empty:
|
||||||
let updatedDisposable = MetaDisposable()
|
break
|
||||||
self.tagContents = (loadAroundIndex, updatedDisposable)
|
case let .tag(tag):
|
||||||
|
disposable?.dispose()
|
||||||
if let historySignal = component.loadTagMessages(tag, self.tagContents?.index) {
|
let updatedDisposable = MetaDisposable()
|
||||||
updatedDisposable.set((historySignal
|
self.tagContents = (loadAroundIndex, updatedDisposable)
|
||||||
|> deliverOnMainQueue).startStrict(next: { [weak self] view in
|
|
||||||
guard let self else {
|
if let historySignal = component.loadTagMessages(tag, self.tagContents?.index) {
|
||||||
return
|
updatedDisposable.set((historySignal
|
||||||
}
|
|> deliverOnMainQueue).startStrict(next: { [weak self] view in
|
||||||
|
guard let self else {
|
||||||
let contentsId = self.nextContentsId
|
return
|
||||||
self.nextContentsId += 1
|
}
|
||||||
self.contentsState = ContentsState(
|
|
||||||
id: contentsId,
|
let contentsId = self.nextContentsId
|
||||||
tag: tag,
|
self.nextContentsId += 1
|
||||||
entries: view.entries.reversed().map { entry in
|
self.contentsState = ContentsState(
|
||||||
return EngineMessage(entry.message)
|
id: contentsId,
|
||||||
},
|
contentId: .tag(tag),
|
||||||
hasEarlier: view.earlierId != nil,
|
entries: view.entries.reversed().map { entry in
|
||||||
hasLater: view.laterId != nil
|
return EngineMessage(entry.message)
|
||||||
)
|
},
|
||||||
if !self.isUpdating {
|
hasEarlier: view.earlierId != nil,
|
||||||
self.state?.updated(transition: .immediate)
|
hasLater: view.laterId != nil
|
||||||
}
|
)
|
||||||
|
if !self.isUpdating {
|
||||||
if !self.didSetReady {
|
self.state?.updated(transition: .immediate)
|
||||||
self.didSetReady = true
|
}
|
||||||
self.isReadyPromise.set(.single(true))
|
|
||||||
}
|
if !self.didSetReady {
|
||||||
}))
|
self.didSetReady = true
|
||||||
|
self.isReadyPromise.set(.single(true))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
case .search:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch component.contents {
|
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):
|
case let .tag(tag):
|
||||||
if previousComponent?.contents != component.contents {
|
if previousComponent?.contents != component.contents {
|
||||||
self.tagContents?.disposable?.dispose()
|
self.tagContents?.disposable?.dispose()
|
||||||
|
self.tagContents = nil
|
||||||
|
|
||||||
|
self.searchContents?.disposable?.dispose()
|
||||||
|
self.searchContents = nil
|
||||||
|
|
||||||
let disposable = MetaDisposable()
|
let disposable = MetaDisposable()
|
||||||
self.tagContents = (nil, disposable)
|
self.tagContents = (nil, disposable)
|
||||||
@ -303,7 +350,7 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
|||||||
self.nextContentsId += 1
|
self.nextContentsId += 1
|
||||||
self.contentsState = ContentsState(
|
self.contentsState = ContentsState(
|
||||||
id: contentsId,
|
id: contentsId,
|
||||||
tag: tag,
|
contentId: .tag(tag),
|
||||||
entries: view.entries.reversed().map { entry in
|
entries: view.entries.reversed().map { entry in
|
||||||
return EngineMessage(entry.message)
|
return EngineMessage(entry.message)
|
||||||
},
|
},
|
||||||
@ -314,6 +361,46 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
|||||||
self.state?.updated(transition: .immediate)
|
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 {
|
if !self.didSetReady {
|
||||||
self.didSetReady = true
|
self.didSetReady = true
|
||||||
self.isReadyPromise.set(.single(true))
|
self.isReadyPromise.set(.single(true))
|
||||||
@ -521,7 +608,7 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var scrollToItem: ListViewScrollToItem?
|
var scrollToItem: ListViewScrollToItem?
|
||||||
if previousContentsState?.tag != contentsState.tag && !contentsState.entries.isEmpty {
|
if previousContentsState?.contentId != contentsState.contentId && !contentsState.entries.isEmpty {
|
||||||
scrollToItem = ListViewScrollToItem(
|
scrollToItem = ListViewScrollToItem(
|
||||||
index: 0,
|
index: 0,
|
||||||
position: .top(0.0),
|
position: .top(0.0),
|
||||||
|
@ -17,6 +17,83 @@ import ChatListUI
|
|||||||
import DeleteChatPeerActionSheetItem
|
import DeleteChatPeerActionSheetItem
|
||||||
import UndoUI
|
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 {
|
public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDelegate, UIGestureRecognizerDelegate {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
|
|
||||||
@ -24,7 +101,7 @@ public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, UI
|
|||||||
|
|
||||||
public weak var parentController: ViewController?
|
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<Bool>()
|
private let ready = Promise<Bool>()
|
||||||
private var didSetReady: Bool = false
|
private var didSetReady: Bool = false
|
||||||
@ -50,6 +127,16 @@ public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, UI
|
|||||||
private var emptyShimmerEffectNode: ChatListShimmerNode?
|
private var emptyShimmerEffectNode: ChatListShimmerNode?
|
||||||
private var shimmerNodeOffset: CGFloat = 0.0
|
private var shimmerNodeOffset: CGFloat = 0.0
|
||||||
private var floatingHeaderOffset: CGFloat?
|
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?) {
|
public init(context: AccountContext, navigationController: @escaping () -> NavigationController?) {
|
||||||
self.context = context
|
self.context = context
|
||||||
@ -58,6 +145,8 @@ public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, UI
|
|||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
let strings = presentationData.strings
|
let strings = presentationData.strings
|
||||||
|
|
||||||
|
self.coveringView = UIView()
|
||||||
|
|
||||||
self.chatListNode = ChatListNode(
|
self.chatListNode = ChatListNode(
|
||||||
context: self.context,
|
context: self.context,
|
||||||
location: .savedMessagesChats,
|
location: .savedMessagesChats,
|
||||||
@ -83,8 +172,12 @@ public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, UI
|
|||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
self.clipsToBounds = true
|
||||||
|
|
||||||
self.addSubnode(self.chatListNode)
|
self.addSubnode(self.chatListNode)
|
||||||
|
|
||||||
|
self.view.addSubview(self.coveringView)
|
||||||
|
|
||||||
self.presentationDataDisposable = (self.context.sharedContext.presentationData
|
self.presentationDataDisposable = (self.context.sharedContext.presentationData
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
@ -293,12 +386,89 @@ public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, UI
|
|||||||
deinit {
|
deinit {
|
||||||
self.presentationDataDisposable?.dispose()
|
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 ensureMessageIsVisible(id: MessageId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func scrollToTop() -> Bool {
|
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
|
return false
|
||||||
}
|
}
|
||||||
@ -356,25 +526,67 @@ public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, UI
|
|||||||
public func updateSelectedMessages(animated: Bool) {
|
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) {
|
private func updateChatController(transition: ContainedViewLayoutTransition) {
|
||||||
self.currentParams = (size, topInset, sideInset, bottomInset, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData)
|
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)
|
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
|
||||||
self.chatListNode.updateLayout(
|
self.chatListNode.updateLayout(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
updateSizeAndInsets: ListViewUpdateSizeAndInsets(
|
updateSizeAndInsets: ListViewUpdateSizeAndInsets(
|
||||||
size: size,
|
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,
|
duration: duration,
|
||||||
curve: curve
|
curve: curve
|
||||||
),
|
),
|
||||||
visibleTopInset: topInset,
|
visibleTopInset: topInset + navigationHeight,
|
||||||
originalTopInset: topInset,
|
originalTopInset: topInset + navigationHeight,
|
||||||
storiesInset: 0.0,
|
storiesInset: 0.0,
|
||||||
inlineNavigationLocation: nil,
|
inlineNavigationLocation: nil,
|
||||||
inlineNavigationTransitionFraction: 0.0
|
inlineNavigationTransitionFraction: 0.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.updateChatController(transition: transition)
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
|
@ -150,6 +150,7 @@ public final class PeerInfoChatPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
|||||||
self.coveringView = UIView()
|
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 = 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()
|
super.init()
|
||||||
|
|
||||||
|
@ -103,6 +103,7 @@ import MediaPickerUI
|
|||||||
import AttachmentUI
|
import AttachmentUI
|
||||||
import BoostLevelIconComponent
|
import BoostLevelIconComponent
|
||||||
import PeerInfoChatPaneNode
|
import PeerInfoChatPaneNode
|
||||||
|
import PeerInfoChatListPaneNode
|
||||||
|
|
||||||
public enum PeerInfoAvatarEditingMode {
|
public enum PeerInfoAvatarEditingMode {
|
||||||
case generic
|
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 {
|
if let currentPaneKey = self.paneContainerNode.currentPaneKey, case .savedMessages = currentPaneKey, let paneNode = self.paneContainerNode.currentPane?.node as? PeerInfoChatPaneNode {
|
||||||
paneNode.activateSearch()
|
paneNode.activateSearch()
|
||||||
return
|
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)
|
self.headerNode.navigationButtonContainer.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
|
||||||
|
@ -149,19 +149,19 @@ private final class StoryQualityUpgradeSheetContentComponent: Component {
|
|||||||
let iconSize = self.icon.update(
|
let iconSize = self.icon.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(LottieComponent(
|
component: AnyComponent(LottieComponent(
|
||||||
content: LottieComponent.AppBundleContent(name: "ChatListNoResults"),
|
content: LottieComponent.AppBundleContent(name: "StoryUpgradeSheet"),
|
||||||
color: nil,
|
color: nil,
|
||||||
startingPosition: .begin,
|
startingPosition: .begin,
|
||||||
size: CGSize(width: 120.0, height: 120.0)
|
size: CGSize(width: 100.0, height: 100.0)
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: 120.0, height: 120.0)
|
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||||
)
|
)
|
||||||
if let iconView = self.icon.view {
|
if let iconView = self.icon.view {
|
||||||
if iconView.superview == nil {
|
if iconView.superview == nil {
|
||||||
self.addSubview(iconView)
|
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
|
contentHeight += 138.0
|
||||||
|
BIN
submodules/TelegramUI/Resources/Animations/StoryUpgradeSheet.tgs
Normal file
BIN
submodules/TelegramUI/Resources/Animations/StoryUpgradeSheet.tgs
Normal file
Binary file not shown.
@ -398,7 +398,7 @@ func updateChatPresentationInterfaceStateImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if updatedChatPresentationInterfaceState.displayHistoryFilterAsList {
|
if updatedChatPresentationInterfaceState.displayHistoryFilterAsList {
|
||||||
if updatedChatPresentationInterfaceState.search == nil || updatedChatPresentationInterfaceState.historyFilter == nil {
|
if updatedChatPresentationInterfaceState.search?.resultsState == nil && updatedChatPresentationInterfaceState.historyFilter == nil && !selfController.alwaysShowSearchResultsAsList {
|
||||||
updatedChatPresentationInterfaceState = updatedChatPresentationInterfaceState.updatedDisplayHistoryFilterAsList(false)
|
updatedChatPresentationInterfaceState = updatedChatPresentationInterfaceState.updatedDisplayHistoryFilterAsList(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -581,6 +581,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
var performTextSelectionAction: ((Message?, Bool, NSAttributedString, TextSelectionAction) -> Void)?
|
var performTextSelectionAction: ((Message?, Bool, NSAttributedString, TextSelectionAction) -> Void)?
|
||||||
var performOpenURL: ((Message?, String, Promise<Bool>?) -> Void)?
|
var performOpenURL: ((Message?, String, Promise<Bool>?) -> 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<ChatLocationContextHolder?> = Atomic<ChatLocationContextHolder?>(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] = []) {
|
public init(context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?> = Atomic<ChatLocationContextHolder?>(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
|
let _ = ChatControllerCount.modify { value in
|
||||||
return value + 1
|
return value + 1
|
||||||
@ -10958,11 +10965,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if let search = self.presentationInterfaceState.search, !search.query.isEmpty {
|
|
||||||
self.interfaceInteraction?.openSearchResults()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
self.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in
|
self.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in
|
||||||
return state.updatedDisplayHistoryFilterAsList(displayAsList)
|
return state.updatedDisplayHistoryFilterAsList(displayAsList)
|
||||||
})
|
})
|
||||||
|
@ -135,6 +135,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
let loadingNode: ChatLoadingNode
|
let loadingNode: ChatLoadingNode
|
||||||
private(set) var loadingPlaceholderNode: ChatLoadingPlaceholderNode?
|
private(set) var loadingPlaceholderNode: ChatLoadingPlaceholderNode?
|
||||||
|
|
||||||
|
var alwaysShowSearchResultsAsList: Bool = false
|
||||||
|
private var skippedShowSearchResultsAsListAnimationOnce: Bool = false
|
||||||
var inlineSearchResults: ComponentView<Empty>?
|
var inlineSearchResults: ComponentView<Empty>?
|
||||||
private var inlineSearchResultsReadyDisposable: Disposable?
|
private var inlineSearchResultsReadyDisposable: Disposable?
|
||||||
private var inlineSearchResultsReady: Bool = false
|
private var inlineSearchResultsReady: Bool = false
|
||||||
@ -2430,7 +2432,17 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
self.updatePlainInputSeparator(transition: transition)
|
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<Empty>
|
let inlineSearchResults: ComponentView<Empty>
|
||||||
var inlineSearchResultsTransition = Transition(transition)
|
var inlineSearchResultsTransition = Transition(transition)
|
||||||
if let current = self.inlineSearchResults {
|
if let current = self.inlineSearchResults {
|
||||||
@ -2441,6 +2453,15 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
self.inlineSearchResults = inlineSearchResults
|
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 context = self.context
|
||||||
let _ = inlineSearchResults.update(
|
let _ = inlineSearchResults.update(
|
||||||
transition: inlineSearchResultsTransition,
|
transition: inlineSearchResultsTransition,
|
||||||
@ -2455,7 +2476,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
nameDisplayOrder: self.chatPresentationInterfaceState.nameDisplayOrder
|
nameDisplayOrder: self.chatPresentationInterfaceState.nameDisplayOrder
|
||||||
),
|
),
|
||||||
peerId: peerId,
|
peerId: peerId,
|
||||||
contents: .tag(historyFilter.customTag),
|
contents: mappedContents,
|
||||||
insets: childContentInsets,
|
insets: childContentInsets,
|
||||||
messageSelected: { [weak self] message in
|
messageSelected: { [weak self] message in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
@ -2512,6 +2533,15 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
return .single(view)
|
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: {},
|
environment: {},
|
||||||
@ -2521,7 +2551,10 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
var animateIn = false
|
var animateIn = false
|
||||||
if inlineSearchResultsView.superview == nil {
|
if inlineSearchResultsView.superview == nil {
|
||||||
animateIn = true
|
animateIn = true
|
||||||
inlineSearchResultsView.alpha = 0.0
|
if !self.alwaysShowSearchResultsAsList || self.skippedShowSearchResultsAsListAnimationOnce {
|
||||||
|
inlineSearchResultsView.alpha = 0.0
|
||||||
|
}
|
||||||
|
self.skippedShowSearchResultsAsListAnimationOnce = true
|
||||||
inlineSearchResultsView.layer.allowsGroupOpacity = true
|
inlineSearchResultsView.layer.allowsGroupOpacity = true
|
||||||
self.contentContainerNode.view.insertSubview(inlineSearchResultsView, aboveSubview: self.historyNodeContainer.view)
|
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 {
|
guard let inlineSearchResultsView = self.inlineSearchResults?.view as? ChatInlineSearchResultsListComponent.View else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
inlineSearchResultsView.alpha = 1.0
|
if inlineSearchResultsView.alpha == 0.0 {
|
||||||
inlineSearchResultsView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
inlineSearchResultsView.alpha = 1.0
|
||||||
inlineSearchResultsView.animateIn()
|
|
||||||
|
|
||||||
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))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,10 @@ extension ChatControllerImpl {
|
|||||||
c.dismiss()
|
c.dismiss()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: false, { state in
|
||||||
|
return state.updatedDisplayHistoryFilterAsList(false)
|
||||||
|
})
|
||||||
|
|
||||||
c.dismiss()
|
c.dismiss()
|
||||||
|
|
||||||
|
@ -168,12 +168,18 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
|
|||||||
if let currentId = results.currentId, let index = results.messageIndices.firstIndex(where: { $0.id == currentId }) {
|
if let currentId = results.currentId, let index = results.messageIndices.firstIndex(where: { $0.id == currentId }) {
|
||||||
canChangeListMode = true
|
canChangeListMode = true
|
||||||
|
|
||||||
let adjustedIndex = results.messageIndices.count - 1 - index
|
if params.interfaceState.displayHistoryFilterAsList {
|
||||||
|
//TODO:localize
|
||||||
//TODO:localize
|
resultsTextString.append(AnimatedTextComponent.Item(id: AnyHashable("count_n"), content: .number(displayTotalCount, minDigits: 1)))
|
||||||
resultsTextString.append(AnimatedTextComponent.Item(id: AnyHashable("search_n"), content: .number(adjustedIndex + 1, minDigits: 1)))
|
resultsTextString.append(AnimatedTextComponent.Item(id: AnyHashable("count_message"), isUnbreakable: true, content: .text(displayTotalCount == 1 ? " message" : " messages")))
|
||||||
resultsTextString.append(AnimatedTextComponent.Item(id: AnyHashable("search_of"), isUnbreakable: true, content: .text(" of ")))
|
} else {
|
||||||
resultsTextString.append(AnimatedTextComponent.Item(id: AnyHashable("search_m"), content: .number(displayTotalCount, minDigits: 1)))
|
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 {
|
} else {
|
||||||
canChangeListMode = false
|
canChangeListMode = false
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user