mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
338 lines
17 KiB
Swift
338 lines
17 KiB
Swift
import Foundation
|
|
import AsyncDisplayKit
|
|
import Display
|
|
import SwiftSignalKit
|
|
import Postbox
|
|
import TelegramCore
|
|
|
|
class ChatMessageStickerItemNode: ChatMessageItemView {
|
|
let imageNode: TransformImageNode
|
|
var progressNode: RadialProgressNode?
|
|
var tapRecognizer: UITapGestureRecognizer?
|
|
|
|
private var selectionNode: ChatMessageSelectionNode?
|
|
|
|
var telegramFile: TelegramMediaFile?
|
|
|
|
private let fetchDisposable = MetaDisposable()
|
|
|
|
private var replyInfoNode: ChatMessageReplyInfoNode?
|
|
private var replyBackgroundNode: ASImageNode?
|
|
|
|
private var highlightedState: Bool = false
|
|
|
|
required init() {
|
|
self.imageNode = TransformImageNode()
|
|
|
|
super.init(layerBacked: false)
|
|
|
|
self.imageNode.displaysAsynchronously = false
|
|
self.addSubnode(self.imageNode)
|
|
}
|
|
|
|
required init?(coder aDecoder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
deinit {
|
|
self.fetchDisposable.dispose()
|
|
}
|
|
|
|
override func didLoad() {
|
|
super.didLoad()
|
|
|
|
let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:)))
|
|
recognizer.tapActionAtPoint = { _ in
|
|
return .waitForSingleTap
|
|
}
|
|
self.view.addGestureRecognizer(recognizer)
|
|
}
|
|
|
|
override func setupItem(_ item: ChatMessageItem) {
|
|
super.setupItem(item)
|
|
|
|
for media in item.message.media {
|
|
if let telegramFile = media as? TelegramMediaFile {
|
|
if self.telegramFile != telegramFile {
|
|
self.telegramFile = telegramFile
|
|
|
|
let signal = chatMessageSticker(account: item.account, file: telegramFile, small: false)
|
|
self.imageNode.setSignal(account: item.account, signal: signal)
|
|
self.fetchDisposable.set(fileInteractiveFetched(account: item.account, file: telegramFile).start())
|
|
}
|
|
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
override func asyncLayout() -> (_ item: ChatMessageItem, _ width: CGFloat, _ mergedTop: Bool, _ mergedBottom: Bool, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) {
|
|
let displaySize = CGSize(width: 200.0, height: 200.0)
|
|
let telegramFile = self.telegramFile
|
|
let layoutConstants = self.layoutConstants
|
|
let imageLayout = self.imageNode.asyncLayout()
|
|
|
|
let makeReplyInfoLayout = ChatMessageReplyInfoNode.asyncLayout(self.replyInfoNode)
|
|
let currentReplyBackgroundNode = self.replyBackgroundNode
|
|
|
|
return { item, width, mergedTop, mergedBottom, dateHeaderAtBottom in
|
|
let incoming = item.message.effectivelyIncoming
|
|
var imageSize: CGSize = CGSize(width: 100.0, height: 100.0)
|
|
if let telegramFile = telegramFile {
|
|
if let dimensions = telegramFile.dimensions {
|
|
imageSize = dimensions.aspectFitted(displaySize)
|
|
} else if let thumbnailSize = telegramFile.previewRepresentations.first?.dimensions {
|
|
imageSize = thumbnailSize.aspectFitted(displaySize)
|
|
}
|
|
}
|
|
|
|
let avatarInset: CGFloat
|
|
var hasAvatar = false
|
|
|
|
if item.peerId.isGroupOrChannel && item.message.author != nil {
|
|
var isBroadcastChannel = false
|
|
if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = peer.info {
|
|
isBroadcastChannel = true
|
|
}
|
|
|
|
if !isBroadcastChannel {
|
|
hasAvatar = true
|
|
}
|
|
}
|
|
|
|
if hasAvatar {
|
|
avatarInset = layoutConstants.avatarDiameter
|
|
} else {
|
|
avatarInset = 0.0
|
|
}
|
|
|
|
var layoutInsets = UIEdgeInsets(top: mergedTop ? layoutConstants.bubble.mergedSpacing : layoutConstants.bubble.defaultSpacing, left: 0.0, bottom: mergedBottom ? layoutConstants.bubble.mergedSpacing : layoutConstants.bubble.defaultSpacing, right: 0.0)
|
|
if dateHeaderAtBottom {
|
|
layoutInsets.top += layoutConstants.timestampHeaderHeight
|
|
}
|
|
|
|
let imageFrame = CGRect(origin: CGPoint(x: (incoming ? (layoutConstants.bubble.edgeInset + avatarInset + layoutConstants.bubble.contentInsets.left) : (width - imageSize.width - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left)), y: 0.0), size: imageSize)
|
|
|
|
let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: imageFrame.size, boundingSize: imageFrame.size, intrinsicInsets: UIEdgeInsets())
|
|
|
|
let imageApply = imageLayout(arguments)
|
|
|
|
var replyInfoApply: (CGSize, () -> ChatMessageReplyInfoNode)?
|
|
var updatedReplyBackgroundNode: ASImageNode?
|
|
var replyBackgroundImage: UIImage?
|
|
for attribute in item.message.attributes {
|
|
if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] {
|
|
let availableWidth = max(60.0, width - imageSize.width - 20.0 - layoutConstants.bubble.edgeInset * 2.0 - avatarInset - layoutConstants.bubble.contentInsets.left)
|
|
replyInfoApply = makeReplyInfoLayout(item.theme, item.account, .standalone, replyMessage, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude))
|
|
|
|
if let currentReplyBackgroundNode = currentReplyBackgroundNode {
|
|
updatedReplyBackgroundNode = currentReplyBackgroundNode
|
|
} else {
|
|
updatedReplyBackgroundNode = ASImageNode()
|
|
}
|
|
replyBackgroundImage = PresentationResourcesChat.chatFreeformContentAdditionalInfoBackgroundImage(item.theme)
|
|
break
|
|
}
|
|
}
|
|
|
|
return (ListViewItemNodeLayout(contentSize: CGSize(width: width, height: imageSize.height), insets: layoutInsets), { [weak self] animation in
|
|
if let strongSelf = self {
|
|
strongSelf.imageNode.frame = imageFrame
|
|
strongSelf.progressNode?.position = strongSelf.imageNode.position
|
|
imageApply()
|
|
|
|
if let updatedReplyBackgroundNode = updatedReplyBackgroundNode {
|
|
if strongSelf.replyBackgroundNode == nil {
|
|
strongSelf.replyBackgroundNode = updatedReplyBackgroundNode
|
|
strongSelf.addSubnode(updatedReplyBackgroundNode)
|
|
updatedReplyBackgroundNode.image = replyBackgroundImage
|
|
}
|
|
} else if let replyBackgroundNode = strongSelf.replyBackgroundNode {
|
|
replyBackgroundNode.removeFromSupernode()
|
|
strongSelf.replyBackgroundNode = nil
|
|
}
|
|
|
|
if let (replyInfoSize, replyInfoApply) = replyInfoApply {
|
|
let replyInfoNode = replyInfoApply()
|
|
if strongSelf.replyInfoNode == nil {
|
|
strongSelf.replyInfoNode = replyInfoNode
|
|
strongSelf.addSubnode(replyInfoNode)
|
|
}
|
|
let replyInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (layoutConstants.bubble.edgeInset + 10.0) : (width - replyInfoSize.width - layoutConstants.bubble.edgeInset - 10.0)), y: imageSize.height - replyInfoSize.height - 8.0), size: replyInfoSize)
|
|
replyInfoNode.frame = replyInfoFrame
|
|
strongSelf.replyBackgroundNode?.frame = CGRect(origin: CGPoint(x: replyInfoFrame.minX - 4.0, y: replyInfoFrame.minY - 2.0), size: CGSize(width: replyInfoFrame.size.width + 8.0, height: replyInfoFrame.size.height + 5.0))
|
|
} else if let replyInfoNode = strongSelf.replyInfoNode {
|
|
replyInfoNode.removeFromSupernode()
|
|
strongSelf.replyInfoNode = nil
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
|
super.animateInsertion(currentTimestamp, duration: duration, short: short)
|
|
|
|
self.imageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
|
}
|
|
|
|
override func animateAdded(_ currentTimestamp: Double, duration: Double) {
|
|
super.animateAdded(currentTimestamp, duration: duration)
|
|
|
|
self.imageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
|
}
|
|
|
|
@objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
|
switch recognizer.state {
|
|
case .ended:
|
|
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
|
|
switch gesture {
|
|
case .tap:
|
|
if let avatarNode = self.accessoryItemNode as? ChatMessageAvatarAccessoryItemNode, avatarNode.frame.contains(location) {
|
|
if let item = self.item, let author = item.message.author {
|
|
self.controllerInteraction?.openPeer(author.id, .info, item.message.id)
|
|
}
|
|
return
|
|
}
|
|
/*if let nameNode = self.nameNode, nameNode.frame.contains(location) {
|
|
if let item = self.item {
|
|
for attribute in item.message.attributes {
|
|
if let attribute = attribute as? InlineBotMessageAttribute, let botPeer = item.message.peers[attribute.peerId], let addressName = botPeer.addressName {
|
|
self.controllerInteraction?.updateInputState { textInputState in
|
|
return ChatTextInputState(inputText: "@" + addressName + " ")
|
|
}
|
|
return
|
|
}
|
|
}
|
|
}
|
|
} else */
|
|
|
|
if let replyInfoNode = self.replyInfoNode, replyInfoNode.frame.contains(location) {
|
|
if let item = self.item {
|
|
for attribute in item.message.attributes {
|
|
if let attribute = attribute as? ReplyMessageAttribute {
|
|
self.controllerInteraction?.navigateToMessage(item.message.id, attribute.messageId)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*if let forwardInfoNode = self.forwardInfoNode, forwardInfoNode.frame.contains(location) {
|
|
if let item = self.item, let forwardInfo = item.message.forwardInfo {
|
|
if let sourceMessageId = forwardInfo.sourceMessageId {
|
|
self.controllerInteraction?.navigateToMessage(item.message.id, sourceMessageId)
|
|
} else {
|
|
self.controllerInteraction?.openPeer(forwardInfo.source?.id ?? forwardInfo.author.id, .chat(textInputState: nil))
|
|
}
|
|
return
|
|
}
|
|
}*/
|
|
|
|
if let item = self.item, self.imageNode.frame.contains(location) {
|
|
self.controllerInteraction?.openMessage(item.message.id)
|
|
return
|
|
}
|
|
|
|
self.controllerInteraction?.clickThroughMessage()
|
|
case .longTap, .doubleTap:
|
|
if let item = self.item, self.imageNode.frame.contains(location) {
|
|
self.controllerInteraction?.openMessageContextMenu(item.message.id, self, self.imageNode.frame)
|
|
}
|
|
case .hold:
|
|
break
|
|
}
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
|
|
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
|
return super.hitTest(point, with: event)
|
|
}
|
|
|
|
override func updateSelectionState(animated: Bool) {
|
|
guard let controllerInteraction = self.controllerInteraction else {
|
|
return
|
|
}
|
|
|
|
if let selectionState = controllerInteraction.selectionState {
|
|
var selected = false
|
|
var incoming = true
|
|
if let item = self.item {
|
|
selected = selectionState.selectedIds.contains(item.message.id)
|
|
incoming = item.message.effectivelyIncoming
|
|
}
|
|
let offset: CGFloat = incoming ? 42.0 : 0.0
|
|
|
|
if let selectionNode = self.selectionNode {
|
|
selectionNode.updateSelected(selected, animated: false)
|
|
selectionNode.frame = CGRect(origin: CGPoint(x: -offset, y: 0.0), size: CGSize(width: self.contentBounds.size.width, height: self.contentBounds.size.height))
|
|
self.subnodeTransform = CATransform3DMakeTranslation(offset, 0.0, 0.0);
|
|
} else {
|
|
let selectionNode = ChatMessageSelectionNode(toggle: { [weak self] in
|
|
if let strongSelf = self, let item = strongSelf.item {
|
|
strongSelf.controllerInteraction?.toggleMessageSelection(item.message.id)
|
|
}
|
|
})
|
|
|
|
selectionNode.frame = CGRect(origin: CGPoint(x: -offset, y: 0.0), size: CGSize(width: self.contentBounds.size.width, height: self.contentBounds.size.height))
|
|
self.addSubnode(selectionNode)
|
|
self.selectionNode = selectionNode
|
|
selectionNode.updateSelected(selected, animated: false)
|
|
let previousSubnodeTransform = self.subnodeTransform
|
|
self.subnodeTransform = CATransform3DMakeTranslation(offset, 0.0, 0.0);
|
|
if animated {
|
|
selectionNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
|
self.layer.animate(from: NSValue(caTransform3D: previousSubnodeTransform), to: NSValue(caTransform3D: self.subnodeTransform), keyPath: "sublayerTransform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.4)
|
|
|
|
if !incoming {
|
|
let position = selectionNode.layer.position
|
|
selectionNode.layer.animatePosition(from: CGPoint(x: position.x - 42.0, y: position.y), to: position, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if let selectionNode = self.selectionNode {
|
|
self.selectionNode = nil
|
|
let previousSubnodeTransform = self.subnodeTransform
|
|
self.subnodeTransform = CATransform3DIdentity
|
|
if animated {
|
|
self.layer.animate(from: NSValue(caTransform3D: previousSubnodeTransform), to: NSValue(caTransform3D: self.subnodeTransform), keyPath: "sublayerTransform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.4, completion: { [weak selectionNode]_ in
|
|
selectionNode?.removeFromSupernode()
|
|
})
|
|
selectionNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
|
if CGFloat(0.0).isLessThanOrEqualTo(selectionNode.frame.origin.x) {
|
|
let position = selectionNode.layer.position
|
|
selectionNode.layer.animatePosition(from: position, to: CGPoint(x: position.x - 42.0, y: position.y), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
|
}
|
|
} else {
|
|
selectionNode.removeFromSupernode()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
override func updateHighlightedState(animated: Bool) {
|
|
if let controllerInteraction = self.controllerInteraction, let item = self.item {
|
|
var highlighted = false
|
|
if let highlightedState = controllerInteraction.highlightedState {
|
|
if highlightedState.messageStableId == item.message.stableId {
|
|
highlighted = true
|
|
}
|
|
}
|
|
|
|
if self.highlightedState != highlighted {
|
|
self.highlightedState = highlighted
|
|
|
|
if highlighted {
|
|
self.imageNode.setOverlayColor(UIColor(white: 0.0, alpha: 0.2), animated: false)
|
|
} else {
|
|
self.imageNode.setOverlayColor(nil, animated: animated)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|