mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
305 lines
14 KiB
Swift
305 lines
14 KiB
Swift
import AsyncDisplayKit
|
|
import Display
|
|
import TelegramCore
|
|
import SwiftSignalKit
|
|
import Postbox
|
|
import TelegramPresentationData
|
|
import AccountContext
|
|
import TelegramStringFormatting
|
|
import ComponentFlow
|
|
import TelegramUIPreferences
|
|
import AppBundle
|
|
import PeerInfoPaneNode
|
|
import ContextUI
|
|
|
|
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 PeerInfoChatPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScrollViewDelegate, ASGestureRecognizerDelegate {
|
|
private let context: AccountContext
|
|
private let peerId: EnginePeer.Id
|
|
private let navigationController: () -> NavigationController?
|
|
|
|
private let chatController: ChatController
|
|
|
|
private let coveringView: UIView
|
|
|
|
public weak var parentController: ViewController? {
|
|
didSet {
|
|
if self.parentController !== oldValue {
|
|
if let parentController = self.parentController {
|
|
self.chatController.willMove(toParent: parentController)
|
|
parentController.addChild(self.chatController)
|
|
self.chatController.didMove(toParent: parentController)
|
|
} else {
|
|
self.chatController.willMove(toParent: nil)
|
|
self.chatController.removeFromParent()
|
|
self.chatController.didMove(toParent: nil)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private var currentParams: (size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData)?
|
|
|
|
private let ready = Promise<Bool>()
|
|
private var didSetReady: Bool = false
|
|
public var isReady: Signal<Bool, NoError> {
|
|
return self.ready.get()
|
|
}
|
|
|
|
private let statusPromise = Promise<PeerInfoStatusData?>(nil)
|
|
public var status: Signal<PeerInfoStatusData?, NoError> {
|
|
self.statusPromise.get()
|
|
}
|
|
|
|
public var tabBarOffsetUpdated: ((ContainedViewLayoutTransition) -> Void)?
|
|
public var tabBarOffset: CGFloat {
|
|
return 0.0
|
|
}
|
|
|
|
private var searchNavigationContentNode: SearchNavigationContentNode?
|
|
public var navigationContentNode: PeerInfoPanelNodeNavigationContentNode? {
|
|
return self.searchNavigationContentNode
|
|
}
|
|
public var externalDataUpdated: ((ContainedViewLayoutTransition) -> Void)?
|
|
|
|
private var presentationData: PresentationData
|
|
private var presentationDataDisposable: Disposable?
|
|
|
|
public init(context: AccountContext, peerId: EnginePeer.Id, navigationController: @escaping () -> NavigationController?) {
|
|
self.context = context
|
|
self.peerId = peerId
|
|
self.navigationController = navigationController
|
|
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
|
|
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()
|
|
|
|
self.clipsToBounds = true
|
|
|
|
self.presentationDataDisposable = (self.context.sharedContext.presentationData
|
|
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
|
guard let self else {
|
|
return
|
|
}
|
|
self.presentationData = presentationData
|
|
})
|
|
|
|
let strings = self.presentationData.strings
|
|
self.statusPromise.set(self.context.engine.data.subscribe(
|
|
TelegramEngine.EngineData.Item.Messages.MessageCount(peerId: self.context.account.peerId, threadId: peerId.toInt64(), tag: [])
|
|
)
|
|
|> map { count in
|
|
if let count {
|
|
return PeerInfoStatusData(text: strings.Conversation_Messages(Int32(count)), isActivity: false, key: .savedMessages)
|
|
} else {
|
|
return nil
|
|
}
|
|
})
|
|
|
|
self.ready.set(self.chatController.ready.get())
|
|
|
|
self.addSubnode(self.chatController.displayNode)
|
|
self.chatController.displayNode.clipsToBounds = true
|
|
|
|
self.view.addSubview(self.coveringView)
|
|
|
|
self.chatController.stateUpdated = { [weak self] transition in
|
|
guard let self else {
|
|
return
|
|
}
|
|
if let contentNode = self.chatController.customNavigationBarContentNode {
|
|
if self.searchNavigationContentNode?.contentNode !== contentNode {
|
|
self.searchNavigationContentNode = SearchNavigationContentNode(chatController: self.chatController, contentNode: contentNode)
|
|
self.searchNavigationContentNode?.panelNode = self.chatController.customNavigationPanelNode
|
|
self.externalDataUpdated?(transition)
|
|
} else if self.searchNavigationContentNode?.panelNode !== self.chatController.customNavigationPanelNode {
|
|
self.searchNavigationContentNode?.panelNode = self.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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
deinit {
|
|
self.presentationDataDisposable?.dispose()
|
|
}
|
|
|
|
public func ensureMessageIsVisible(id: MessageId) {
|
|
}
|
|
|
|
public func scrollToTop() -> Bool {
|
|
return self.chatController.performScrollToTop()
|
|
}
|
|
|
|
public func hitTestResultForScrolling() -> UIView? {
|
|
return nil
|
|
}
|
|
|
|
public func brieflyDisableTouchActions() {
|
|
}
|
|
|
|
public func findLoadedMessage(id: MessageId) -> Message? {
|
|
return nil
|
|
}
|
|
|
|
public func updateHiddenMedia() {
|
|
}
|
|
|
|
public func transferVelocity(_ velocity: CGFloat) {
|
|
if velocity > 0.0 {
|
|
self.chatController.transferScrollingVelocity(velocity)
|
|
}
|
|
}
|
|
|
|
public func cancelPreviewGestures() {
|
|
}
|
|
|
|
public func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? {
|
|
return nil
|
|
}
|
|
|
|
public func addToTransitionSurface(view: UIView) {
|
|
}
|
|
|
|
override public func didLoad() {
|
|
super.didLoad()
|
|
}
|
|
|
|
public func activateSearch() {
|
|
self.chatController.activateSearch(domain: .everything, query: "")
|
|
}
|
|
|
|
override public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
|
return true
|
|
}
|
|
|
|
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
|
if gestureRecognizer.state != .failed, let otherGestureRecognizer = otherGestureRecognizer as? UIPanGestureRecognizer {
|
|
let _ = otherGestureRecognizer
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
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)
|
|
|
|
let fullHeight = navigationHeight + size.height
|
|
|
|
let chatFrame = CGRect(origin: CGPoint(x: 0.0, y: -navigationHeight), size: CGSize(width: size.width, height: fullHeight))
|
|
|
|
if !self.chatController.displayNode.bounds.isEmpty {
|
|
if let contextController = self.chatController.visibleContextController as? ContextController {
|
|
let deltaY = chatFrame.minY - self.chatController.displayNode.frame.minY
|
|
contextController.addRelativeContentOffset(CGPoint(x: 0.0, y: -deltaY * 0.0), transition: transition)
|
|
}
|
|
}
|
|
|
|
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 combinedBottomInset = bottomInset
|
|
transition.updateFrame(node: self.chatController.displayNode, frame: chatFrame)
|
|
self.chatController.updateIsScrollingLockedAtTop(isScrollingLockedAtTop: isScrollingLockedAtTop)
|
|
self.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)
|
|
}
|
|
|
|
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
|
guard let result = super.hitTest(point, with: event) else {
|
|
return nil
|
|
}
|
|
return result
|
|
}
|
|
}
|