mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
650 lines
31 KiB
Swift
650 lines
31 KiB
Swift
import AsyncDisplayKit
|
|
import Display
|
|
import TelegramCore
|
|
import SwiftSignalKit
|
|
import Postbox
|
|
import TelegramPresentationData
|
|
import AccountContext
|
|
import ContextUI
|
|
import TelegramStringFormatting
|
|
import ShimmerEffect
|
|
import ComponentFlow
|
|
import TelegramNotices
|
|
import TelegramUIPreferences
|
|
import AppBundle
|
|
import PeerInfoPaneNode
|
|
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, ASScrollViewDelegate, ASGestureRecognizerDelegate {
|
|
private let context: AccountContext
|
|
|
|
private let navigationController: () -> NavigationController?
|
|
|
|
public weak var parentController: ViewController?
|
|
|
|
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 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 presentationData: PresentationData
|
|
private var presentationDataDisposable: Disposable?
|
|
|
|
private let chatListNode: ChatListNode
|
|
|
|
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
|
|
self.navigationController = navigationController
|
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
self.presentationData = presentationData
|
|
let strings = presentationData.strings
|
|
|
|
self.coveringView = UIView()
|
|
|
|
self.chatListNode = ChatListNode(
|
|
context: self.context,
|
|
location: .savedMessagesChats,
|
|
chatListFilter: nil,
|
|
previewing: false,
|
|
fillPreloadItems: false,
|
|
mode: .chatList(appendContacts: false),
|
|
isPeerEnabled: nil,
|
|
theme: self.presentationData.theme,
|
|
fontSize: self.presentationData.listsFontSize,
|
|
strings: self.presentationData.strings,
|
|
dateTimeFormat: self.presentationData.dateTimeFormat,
|
|
nameSortOrder: self.presentationData.nameSortOrder,
|
|
nameDisplayOrder: self.presentationData.nameDisplayOrder,
|
|
animationCache: self.context.animationCache,
|
|
animationRenderer: self.context.animationRenderer,
|
|
disableAnimations: false,
|
|
isInlineMode: false,
|
|
autoSetReady: false,
|
|
isMainTab: nil
|
|
)
|
|
self.chatListNode.synchronousDrawingWhenNotAnimated = true
|
|
|
|
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 {
|
|
return
|
|
}
|
|
self.presentationData = presentationData
|
|
})
|
|
|
|
self.ready.set(self.chatListNode.ready)
|
|
|
|
self.statusPromise.set(self.context.engine.messages.savedMessagesPeersStats()
|
|
|> map { count in
|
|
if let count {
|
|
return PeerInfoStatusData(text: strings.Notifications_Exceptions(Int32(count)), isActivity: false, key: .savedMessagesChats)
|
|
} else {
|
|
return PeerInfoStatusData(text: strings.Channel_NotificationLoading.lowercased(), isActivity: false, key: .savedMessagesChats)
|
|
}
|
|
})
|
|
|
|
self.chatListNode.peerSelected = { [weak self] peer, _, _, _, _ in
|
|
guard let self, let navigationController = self.navigationController() else {
|
|
return
|
|
}
|
|
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(
|
|
navigationController: navigationController,
|
|
context: self.context,
|
|
chatLocation: .replyThread(ChatReplyThreadMessage(
|
|
peerId: self.context.account.peerId,
|
|
threadId: peer.id.toInt64(),
|
|
channelMessageId: nil,
|
|
isChannelPost: false,
|
|
isForumPost: false,
|
|
maxMessage: nil,
|
|
maxReadIncomingMessageId: nil,
|
|
maxReadOutgoingMessageId: nil,
|
|
unreadCount: 0,
|
|
initialFilledHoles: IndexSet(),
|
|
initialAnchor: .automatic,
|
|
isNotAvailable: false
|
|
)),
|
|
subject: nil,
|
|
keepStack: .always
|
|
))
|
|
self.chatListNode.clearHighlightAnimated(true)
|
|
}
|
|
|
|
self.chatListNode.isEmptyUpdated = { [weak self] isEmptyState, _, transition in
|
|
guard let self else {
|
|
return
|
|
}
|
|
var needsShimmerNode = false
|
|
let shimmerNodeOffset: CGFloat = 0.0
|
|
|
|
switch isEmptyState {
|
|
case let .empty(isLoadingValue, _):
|
|
if isLoadingValue {
|
|
needsShimmerNode = true
|
|
}
|
|
case .notEmpty:
|
|
break
|
|
}
|
|
|
|
if needsShimmerNode {
|
|
self.shimmerNodeOffset = shimmerNodeOffset
|
|
if self.emptyShimmerEffectNode == nil {
|
|
let emptyShimmerEffectNode = ChatListShimmerNode()
|
|
self.emptyShimmerEffectNode = emptyShimmerEffectNode
|
|
self.insertSubnode(emptyShimmerEffectNode, belowSubnode: self.chatListNode)
|
|
if let currentParams = self.currentParams, let offset = self.floatingHeaderOffset {
|
|
self.layoutEmptyShimmerEffectNode(node: emptyShimmerEffectNode, size: currentParams.size, insets: UIEdgeInsets(top: currentParams.topInset, left: currentParams.sideInset, bottom: currentParams.bottomInset, right: currentParams.sideInset), verticalOffset: offset + self.shimmerNodeOffset, transition: .immediate)
|
|
}
|
|
}
|
|
} else if let emptyShimmerEffectNode = self.emptyShimmerEffectNode {
|
|
self.emptyShimmerEffectNode = nil
|
|
let emptyNodeTransition = transition.isAnimated ? transition : .animated(duration: 0.3, curve: .easeInOut)
|
|
emptyNodeTransition.updateAlpha(node: emptyShimmerEffectNode, alpha: 0.0, completion: { [weak emptyShimmerEffectNode] _ in
|
|
emptyShimmerEffectNode?.removeFromSupernode()
|
|
})
|
|
self.chatListNode.alpha = 0.0
|
|
emptyNodeTransition.updateAlpha(node: self.chatListNode, alpha: 1.0)
|
|
}
|
|
}
|
|
|
|
self.chatListNode.updateFloatingHeaderOffset = { [weak self] offset, transition in
|
|
guard let self else {
|
|
return
|
|
}
|
|
self.floatingHeaderOffset = offset
|
|
if let currentParams = self.currentParams, let emptyShimmerEffectNode = self.emptyShimmerEffectNode {
|
|
self.layoutEmptyShimmerEffectNode(node: emptyShimmerEffectNode, size: currentParams.size, insets: UIEdgeInsets(top: currentParams.topInset, left: currentParams.sideInset, bottom: currentParams.bottomInset, right: currentParams.sideInset), verticalOffset: offset + self.shimmerNodeOffset, transition: transition)
|
|
}
|
|
}
|
|
|
|
self.chatListNode.push = { [weak self] c in
|
|
guard let self else {
|
|
return
|
|
}
|
|
self.parentController?.push(c)
|
|
}
|
|
|
|
self.chatListNode.present = { [weak self] c in
|
|
guard let self else {
|
|
return
|
|
}
|
|
self.parentController?.present(c, in: .window(.root))
|
|
}
|
|
|
|
self.chatListNode.deletePeerChat = { [weak self] peerId, _ in
|
|
guard let self else {
|
|
return
|
|
}
|
|
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|
|
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
|
guard let self, let peer else {
|
|
return
|
|
}
|
|
|
|
self.view.window?.endEditing(true)
|
|
|
|
let actionSheet = ActionSheetController(presentationData: self.presentationData)
|
|
var items: [ActionSheetItem] = []
|
|
items.append(DeleteChatPeerActionSheetItem(context: self.context, peer: peer, chatPeer: peer, action: .deleteSavedPeer, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder, balancedLayout: true))
|
|
items.append(ActionSheetButtonItem(title: self.presentationData.strings.Common_Delete, color: .destructive, action: { [weak self, weak actionSheet] in
|
|
actionSheet?.dismissAnimated()
|
|
|
|
guard let self else {
|
|
return
|
|
}
|
|
|
|
self.chatListNode.updateState({ state in
|
|
var state = state
|
|
state.pendingRemovalItemIds.insert(ChatListNodeState.ItemId(peerId: peer.id, threadId: nil))
|
|
return state
|
|
})
|
|
self.parentController?.forEachController({ controller in
|
|
if let controller = controller as? UndoOverlayController {
|
|
controller.dismissWithCommitActionAndReplacementAnimation()
|
|
}
|
|
return true
|
|
})
|
|
|
|
if self.chatListNode.entryPeerIds.count == 0 || self.chatListNode.entryPeerIds == [peer.id] {
|
|
let _ = context.engine.messages.clearHistoryInteractively(peerId: self.context.account.peerId, threadId: peer.id.toInt64(), type: .forLocalPeer).startStandalone(completed: {
|
|
})
|
|
context.engine.peers.updateSavedMessagesViewAsTopics(value: false)
|
|
|
|
self.parentController?.dismiss()
|
|
|
|
return
|
|
}
|
|
|
|
let context = self.context
|
|
let undoController = UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(title: self.presentationData.strings.SavedMessages_SubChatDeleted, text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] value in
|
|
if value == .commit {
|
|
let _ = context.engine.messages.clearHistoryInteractively(peerId: context.account.peerId, threadId: peer.id.toInt64(), type: .forLocalPeer).startStandalone(completed: {
|
|
guard let self else {
|
|
return
|
|
}
|
|
self.chatListNode.updateState({ state in
|
|
var state = state
|
|
state.pendingRemovalItemIds.remove(ChatListNodeState.ItemId(peerId: peer.id, threadId: nil))
|
|
return state
|
|
})
|
|
})
|
|
return true
|
|
} else if value == .undo {
|
|
if let self {
|
|
self.chatListNode.updateState({ state in
|
|
var state = state
|
|
state.pendingRemovalItemIds.remove(ChatListNodeState.ItemId(peerId: peer.id, threadId: nil))
|
|
return state
|
|
})
|
|
}
|
|
return true
|
|
}
|
|
return false
|
|
})
|
|
self.parentController?.present(undoController, in: .window(.root))
|
|
}))
|
|
|
|
actionSheet.setItemGroups([ActionSheetItemGroup(items: items),
|
|
ActionSheetItemGroup(items: [
|
|
ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
|
|
actionSheet?.dismissAnimated()
|
|
})
|
|
])
|
|
])
|
|
self.parentController?.present(actionSheet, in: .window(.root))
|
|
})
|
|
}
|
|
|
|
self.chatListNode.activateChatPreview = { [weak self] item, _, node, gesture, location in
|
|
guard let self, let parentController = self.parentController else {
|
|
gesture?.cancel()
|
|
return
|
|
}
|
|
|
|
if case let .peer(peerData) = item.content {
|
|
let threadId = peerData.peer.peerId.toInt64()
|
|
let chatController = self.context.sharedContext.makeChatController(context: self.context, chatLocation: .replyThread(message: ChatReplyThreadMessage(
|
|
peerId: self.context.account.peerId, threadId: threadId, 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(.previewing))
|
|
chatController.canReadHistory.set(false)
|
|
let source: ContextContentSource = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: parentController.navigationController as? NavigationController))
|
|
|
|
let contextController = ContextController(presentationData: self.presentationData, source: source, items: savedMessagesPeerMenuItems(context: self.context, threadId: threadId, parentController: parentController, deletePeerChat: { [weak self] peerId in
|
|
guard let self else {
|
|
return
|
|
}
|
|
self.chatListNode.deletePeerChat?(peerId, false)
|
|
}) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
|
|
parentController.presentInGlobalOverlay(contextController)
|
|
}
|
|
}
|
|
}
|
|
|
|
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.customDismissSearch = { [weak self] in
|
|
guard let self else {
|
|
return
|
|
}
|
|
if self.searchNavigationContentNode !== nil {
|
|
self.searchNavigationContentNode = nil
|
|
self.externalDataUpdated?(.animated(duration: 0.4, curve: .spring))
|
|
}
|
|
|
|
self.removeChatController()
|
|
}
|
|
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 {
|
|
if let chatController = self.chatController {
|
|
let _ = chatController.performScrollToTop()
|
|
} else {
|
|
self.chatListNode.scrollToPosition(.top(adjustForTempInset: false))
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
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) {
|
|
}
|
|
|
|
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()
|
|
}
|
|
|
|
|
|
override public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
|
return true
|
|
}
|
|
|
|
private func layoutEmptyShimmerEffectNode(node: ChatListShimmerNode, size: CGSize, insets: UIEdgeInsets, verticalOffset: CGFloat, transition: ContainedViewLayoutTransition) {
|
|
node.update(context: self.context, animationCache: self.context.animationCache, animationRenderer: self.context.animationRenderer, size: size, isInlineMode: false, presentationData: self.presentationData, transition: .immediate)
|
|
transition.updateFrameAdditive(node: node, frame: CGRect(origin: CGPoint(x: 0.0, y: verticalOffset), size: size))
|
|
}
|
|
|
|
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) {
|
|
}
|
|
|
|
private func updateChatController(transition: ContainedViewLayoutTransition) {
|
|
guard let chatController = self.chatController else {
|
|
return
|
|
}
|
|
guard let currentParams = self.currentParams else {
|
|
return
|
|
}
|
|
|
|
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 + navigationHeight, left: sideInset, bottom: combinedBottomInset, right: sideInset),
|
|
duration: duration,
|
|
curve: curve
|
|
),
|
|
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? {
|
|
guard let result = super.hitTest(point, with: event) else {
|
|
return nil
|
|
}
|
|
return result
|
|
}
|
|
}
|
|
|
|
private final class ContextControllerContentSourceImpl: ContextControllerContentSource {
|
|
let controller: ViewController
|
|
weak var sourceNode: ASDisplayNode?
|
|
|
|
let navigationController: NavigationController?
|
|
|
|
let passthroughTouches: Bool = true
|
|
|
|
init(controller: ViewController, sourceNode: ASDisplayNode?, navigationController: NavigationController?) {
|
|
self.controller = controller
|
|
self.sourceNode = sourceNode
|
|
self.navigationController = navigationController
|
|
}
|
|
|
|
func transitionInfo() -> ContextControllerTakeControllerInfo? {
|
|
let sourceNode = self.sourceNode
|
|
return ContextControllerTakeControllerInfo(contentAreaInScreenSpace: CGRect(origin: CGPoint(), size: CGSize(width: 10.0, height: 10.0)), sourceNode: { [weak sourceNode] in
|
|
if let sourceNode = sourceNode {
|
|
return (sourceNode.view, sourceNode.bounds)
|
|
} else {
|
|
return nil
|
|
}
|
|
})
|
|
}
|
|
|
|
func animatedIn() {
|
|
}
|
|
}
|