2024-01-26 15:33:01 +01:00

249 lines
12 KiB
Swift

import Foundation
import UIKit
import AsyncDisplayKit
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
import TelegramUIPreferences
import ComponentFlow
import AudioTranscriptionButtonComponent
import ChatMessageDateAndStatusNode
import ChatMessageBubbleContentNode
import ChatMessageItemCommon
import ChatMessageInteractiveFileNode
import ChatControllerInteraction
public class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode {
public let interactiveFileNode: ChatMessageInteractiveFileNode
override public var visibility: ListViewItemNodeVisibility {
didSet {
var wasVisible = false
if case .visible = oldValue {
wasVisible = true
}
var isVisible = false
if case .visible = self.visibility {
isVisible = true
}
if wasVisible != isVisible {
self.interactiveFileNode.visibility = isVisible
}
}
}
required public init() {
self.interactiveFileNode = ChatMessageInteractiveFileNode()
super.init()
self.addSubnode(self.interactiveFileNode)
self.interactiveFileNode.toggleSelection = { [weak self] value in
if let strongSelf = self, let item = strongSelf.item {
item.controllerInteraction.toggleMessagesSelection([item.message.id], value)
}
}
self.interactiveFileNode.activateLocalContent = { [weak self] in
if let strongSelf = self, let item = strongSelf.item {
let _ = item.controllerInteraction.openMessage(item.message, OpenMessageParams(mode: .default))
}
}
self.interactiveFileNode.requestUpdateLayout = { [weak self] _ in
if let strongSelf = self, let item = strongSelf.item {
let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, false)
}
}
self.interactiveFileNode.displayImportedTooltip = { [weak self] sourceNode in
if let strongSelf = self, let item = strongSelf.item {
let _ = item.controllerInteraction.displayImportedMessageTooltip(sourceNode)
}
}
self.interactiveFileNode.dateAndStatusNode.reactionSelected = { [weak self] _, value in
guard let strongSelf = self, let item = strongSelf.item else {
return
}
item.controllerInteraction.updateMessageReaction(item.message, .reaction(value), false)
}
self.interactiveFileNode.dateAndStatusNode.openReactionPreview = { [weak self] gesture, sourceNode, value in
guard let strongSelf = self, let item = strongSelf.item else {
gesture?.cancel()
return
}
item.controllerInteraction.openMessageReactionContextMenu(item.topMessage, sourceNode, gesture, value)
}
self.interactiveFileNode.updateIsTextSelectionActive = { [weak self] value in
self?.updateIsTextSelectionActive?(value)
}
}
override public func accessibilityActivate() -> Bool {
if let item = self.item {
let _ = item.controllerInteraction.openMessage(item.message, OpenMessageParams(mode: .default))
}
return true
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
let interactiveFileLayout = self.interactiveFileNode.asyncLayout()
return { item, layoutConstants, preparePosition, selection, constrainedSize, _ in
var selectedFile: TelegramMediaFile?
for media in item.message.media {
if let telegramFile = media as? TelegramMediaFile {
selectedFile = telegramFile
}
}
var incoming = item.message.effectivelyIncoming(item.context.account.peerId)
if let subject = item.associatedData.subject, case let .messageOptions(_, _, info) = subject, case .forward = info {
incoming = false
}
let statusType: ChatMessageDateAndStatusType?
switch preparePosition {
case .linear(_, .None), .linear(_, .Neighbour(true, _, _)):
if incoming {
statusType = .BubbleIncoming
} else {
if item.message.flags.contains(.Failed) {
statusType = .BubbleOutgoing(.Failed)
} else if (item.message.flags.isSending && !item.message.isSentOrAcknowledged) || item.attributes.updatingMedia != nil {
statusType = .BubbleOutgoing(.Sending)
} else {
statusType = .BubbleOutgoing(.Sent(read: item.read))
}
}
default:
statusType = nil
}
let automaticDownload = shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, authorPeerId: item.message.author?.id, contactsPeerIds: item.associatedData.contactsPeerIds, media: selectedFile!)
let (initialWidth, refineLayout) = interactiveFileLayout(ChatMessageInteractiveFileNode.Arguments(
context: item.context,
presentationData: item.presentationData,
customTintColor: nil,
message: item.message,
topMessage: item.topMessage,
associatedData: item.associatedData,
chatLocation: item.chatLocation,
attributes: item.attributes,
isPinned: item.isItemPinned,
forcedIsEdited: item.isItemEdited,
file: selectedFile!,
automaticDownload: automaticDownload,
incoming: incoming,
isRecentActions: item.associatedData.isRecentActions,
forcedResourceStatus: item.associatedData.forcedResourceStatus,
dateAndStatusType: statusType,
displayReactions: true,
messageSelection: item.message.groupingKey != nil ? selection : nil,
isAttachedContentBlock: false,
layoutConstants: layoutConstants,
constrainedSize: CGSize(width: constrainedSize.width - layoutConstants.file.bubbleInsets.left - layoutConstants.file.bubbleInsets.right, height: constrainedSize.height),
controllerInteraction: item.controllerInteraction
))
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
return (contentProperties, nil, initialWidth + layoutConstants.file.bubbleInsets.left + layoutConstants.file.bubbleInsets.right, { constrainedSize, position in
let (refinedWidth, finishLayout) = refineLayout(CGSize(width: constrainedSize.width - layoutConstants.file.bubbleInsets.left - layoutConstants.file.bubbleInsets.right, height: constrainedSize.height))
return (refinedWidth + layoutConstants.file.bubbleInsets.left + layoutConstants.file.bubbleInsets.right, { boundingWidth in
let (fileSize, fileApply) = finishLayout(boundingWidth - layoutConstants.file.bubbleInsets.left - layoutConstants.file.bubbleInsets.right)
var bottomInset = layoutConstants.file.bubbleInsets.bottom
if case let .linear(_, bottom) = position {
if case .Neighbour(_, _, .condensed) = bottom {
if selectedFile?.isMusic ?? false {
bottomInset -= 14.0
} else {
bottomInset -= 7.0
}
}
}
return (CGSize(width: fileSize.width + layoutConstants.file.bubbleInsets.left + layoutConstants.file.bubbleInsets.right, height: fileSize.height + layoutConstants.file.bubbleInsets.top + bottomInset), { [weak self] animation, synchronousLoads, applyInfo in
if let strongSelf = self {
strongSelf.item = item
strongSelf.interactiveFileNode.frame = CGRect(origin: CGPoint(x: layoutConstants.file.bubbleInsets.left, y: layoutConstants.file.bubbleInsets.top), size: fileSize)
fileApply(synchronousLoads, animation, applyInfo)
}
})
})
})
}
}
override public func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? {
if self.item?.message.id == messageId {
return self.interactiveFileNode.transitionNode(media: media)
} else {
return nil
}
}
override public func updateHiddenMedia(_ media: [Media]?) -> Bool {
return self.interactiveFileNode.updateHiddenMedia(media)
}
override public func animateInsertion(_ currentTimestamp: Double, duration: Double) {
self.interactiveFileNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
override public func animateAdded(_ currentTimestamp: Double, duration: Double) {
self.interactiveFileNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
override public func animateRemoved(_ currentTimestamp: Double, duration: Double) {
self.interactiveFileNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
}
override public func willUpdateIsExtractedToContextPreview(_ value: Bool) {
self.interactiveFileNode.willUpdateIsExtractedToContextPreview(value)
}
override public func updateIsExtractedToContextPreview(_ value: Bool) {
self.interactiveFileNode.updateIsExtractedToContextPreview(value)
}
override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
if self.interactiveFileNode.dateAndStatusNode.supernode != nil, let _ = self.interactiveFileNode.dateAndStatusNode.hitTest(self.view.convert(point, to: self.interactiveFileNode.dateAndStatusNode.view), with: nil) {
return ChatMessageBubbleContentTapAction(content: .ignore)
}
if self.interactiveFileNode.hasTapAction(at: self.view.convert(point, to: self.interactiveFileNode.view)) {
return ChatMessageBubbleContentTapAction(content: .ignore)
}
return super.tapActionAtPoint(point, gesture: gesture, isEstimating: isEstimating)
}
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let result = self.interactiveFileNode.hitTest(self.view.convert(point, to: self.interactiveFileNode.view), with: event) {
return result
}
return super.hitTest(point, with: event)
}
override public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? {
if !self.interactiveFileNode.dateAndStatusNode.isHidden {
return self.interactiveFileNode.dateAndStatusNode.reactionView(value: value)
}
return nil
}
}