diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index da34cbb409..3a68ee7ab2 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -372,6 +372,15 @@ swift_library( "//submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode", "//submodules/TelegramUI/Components/Chat/ChatMessageItem", "//submodules/TelegramUI/Components/Chat/ChatMessageItemView", + "//submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageSwipeToReplyNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageSelectionNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageDeliveryFailedNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageShareButton", + "//submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode", + "//submodules/TelegramUI/Components/Chat/ChatSwipeToReplyRecognizer", ] + select({ "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, "//build-system:ios_sim_arm64": [], diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/BUILD new file mode 100644 index 0000000000..43f3ea0a24 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/BUILD @@ -0,0 +1,24 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageActionButtonsNode", + module_name = "ChatMessageActionButtonsNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/TelegramCore", + "//submodules/Postbox", + "//submodules/Display", + "//submodules/TelegramPresentationData", + "//submodules/AccountContext", + "//submodules/WallpaperBackgroundNode", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageActionButtonsNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift similarity index 96% rename from submodules/TelegramUI/Sources/ChatMessageActionButtonsNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift index 0b99c2e7fa..08017ba773 100644 --- a/submodules/TelegramUI/Sources/ChatMessageActionButtonsNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift @@ -343,7 +343,7 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { let titleFrame = CGRect(origin: CGPoint(x: floor((width - titleSize.size.width) / 2.0), y: floor((42.0 - titleSize.size.height) / 2.0) + 1.0), size: titleSize.size) titleNode.layer.bounds = CGRect(origin: CGPoint(), size: titleFrame.size) - animation.animator.updatePosition(layer: titleNode.layer, position: titleFrame.center, completion: nil) + animation.animator.updatePosition(layer: titleNode.layer, position: CGPoint(x: titleFrame.midX, y: titleFrame.midY), completion: nil) if let buttonView = node.buttonView { buttonView.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: 42.0)) @@ -366,17 +366,17 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { } } -final class ChatMessageActionButtonsNode: ASDisplayNode { +public final class ChatMessageActionButtonsNode: ASDisplayNode { private var buttonNodes: [ChatMessageActionButtonNode] = [] private var buttonPressedWrapper: ((ReplyMarkupButton) -> Void)? private var buttonLongTappedWrapper: ((ReplyMarkupButton) -> Void)? - var buttonPressed: ((ReplyMarkupButton) -> Void)? - var buttonLongTapped: ((ReplyMarkupButton) -> Void)? + public var buttonPressed: ((ReplyMarkupButton) -> Void)? + public var buttonLongTapped: ((ReplyMarkupButton) -> Void)? private var absolutePosition: (CGRect, CGSize)? - override init() { + override public init() { super.init() self.buttonPressedWrapper = { [weak self] button in @@ -392,7 +392,7 @@ final class ChatMessageActionButtonsNode: ASDisplayNode { } } - func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { self.absolutePosition = (rect, containerSize) for button in buttonNodes { @@ -403,7 +403,7 @@ final class ChatMessageActionButtonsNode: ASDisplayNode { } } - class func asyncLayout(_ maybeNode: ChatMessageActionButtonsNode?) -> (_ context: AccountContext, _ theme: ChatPresentationThemeData, _ chatBubbleCorners: PresentationChatBubbleCorners, _ strings: PresentationStrings, _ backgroundNode: WallpaperBackgroundNode?, _ replyMarkup: ReplyMarkupMessageAttribute, _ message: Message, _ constrainedWidth: CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode)) { + public class func asyncLayout(_ maybeNode: ChatMessageActionButtonsNode?) -> (_ context: AccountContext, _ theme: ChatPresentationThemeData, _ chatBubbleCorners: PresentationChatBubbleCorners, _ strings: PresentationStrings, _ backgroundNode: WallpaperBackgroundNode?, _ replyMarkup: ReplyMarkupMessageAttribute, _ message: Message, _ constrainedWidth: CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode)) { let currentButtonLayouts = maybeNode?.buttonNodes.map { ChatMessageActionButtonNode.asyncLayout($0) } ?? [] return { context, theme, chatBubbleCorners, strings, backgroundNode, replyMarkup, message, constrainedWidth in diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageDeliveryFailedNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageDeliveryFailedNode/BUILD new file mode 100644 index 0000000000..526ff0cdbc --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageDeliveryFailedNode/BUILD @@ -0,0 +1,20 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageDeliveryFailedNode", + module_name = "ChatMessageDeliveryFailedNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/TelegramPresentationData", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageDeliveryFailedNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageDeliveryFailedNode/Sources/ChatMessageDeliveryFailedNode.swift similarity index 81% rename from submodules/TelegramUI/Sources/ChatMessageDeliveryFailedNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageDeliveryFailedNode/Sources/ChatMessageDeliveryFailedNode.swift index 9a44437b6a..caec43e2cd 100644 --- a/submodules/TelegramUI/Sources/ChatMessageDeliveryFailedNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageDeliveryFailedNode/Sources/ChatMessageDeliveryFailedNode.swift @@ -4,11 +4,11 @@ import AsyncDisplayKit import Display import TelegramPresentationData -final class ChatMessageDeliveryFailedNode: ASImageNode { +public final class ChatMessageDeliveryFailedNode: ASImageNode { private let tapped: () -> Void private var theme: PresentationTheme? - init(tapped: @escaping () -> Void) { + public init(tapped: @escaping () -> Void) { self.tapped = tapped super.init() @@ -18,7 +18,7 @@ final class ChatMessageDeliveryFailedNode: ASImageNode { self.isUserInteractionEnabled = true } - override func didLoad() { + override public func didLoad() { super.didLoad() self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) } @@ -29,7 +29,7 @@ final class ChatMessageDeliveryFailedNode: ASImageNode { } } - func updateLayout(theme: PresentationTheme) -> CGSize { + public func updateLayout(theme: PresentationTheme) -> CGSize { if self.theme !== theme { self.theme = theme self.image = PresentationResourcesChat.chatBubbleDeliveryFailedIcon(theme) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItem/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageItem/BUILD index 067280a8d2..8c82069452 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItem/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItem/BUILD @@ -17,6 +17,7 @@ swift_library( "//submodules/TelegramCore", "//submodules/AccountContext", "//submodules/TelegramUI/Components/Chat/ChatHistoryEntry", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", "//submodules/TelegramUI/Components/ChatControllerInteraction", "//submodules/TelegramPresentationData", ], diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItem/Sources/ChatMessageItem.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItem/Sources/ChatMessageItem.swift index ac38ef18a7..e1356d27ce 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItem/Sources/ChatMessageItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItem/Sources/ChatMessageItem.swift @@ -9,6 +9,7 @@ import AccountContext import ChatHistoryEntry import ChatControllerInteraction import TelegramPresentationData +import ChatMessageItemCommon public enum ChatMessageItemContent: Sequence { case message(message: Message, read: Bool, selection: ChatHistoryMessageSelection, attributes: ChatMessageEntryAttributes, location: MessageHistoryEntryLocation?) @@ -124,3 +125,47 @@ public protocol ChatMessageItem: ListViewItem { func mergedWithItems(top: ListViewItem?, bottom: ListViewItem?) -> (top: ChatMessageMerge, bottom: ChatMessageMerge, dateAtBottom: Bool) } + +public func hasCommentButton(item: ChatMessageItem) -> Bool { + let firstMessage = item.content.firstMessage + + var hasDiscussion = false + if let channel = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case let .broadcast(info) = channel.info, info.flags.contains(.hasDiscussionGroup) { + hasDiscussion = true + } + if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.effectiveTopId == firstMessage.id { + hasDiscussion = false + } + + if firstMessage.adAttribute != nil { + hasDiscussion = false + } + + if hasDiscussion { + var canComment = false + if case .pinnedMessages = item.associatedData.subject { + canComment = false + } else if firstMessage.id.namespace == Namespaces.Message.Local { + canComment = true + } else { + for attribute in firstMessage.attributes { + if let attribute = attribute as? ReplyThreadMessageAttribute, let commentsPeerId = attribute.commentsPeerId { + switch item.associatedData.channelDiscussionGroup { + case .unknown: + canComment = true + case let .known(groupId): + canComment = groupId == commentsPeerId + } + break + } + } + } + + if canComment { + return true + } + } else if firstMessage.id.peerId.isReplies { + return true + } + return false +} diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/BUILD index 527d1deaaf..0d4b817002 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/BUILD @@ -13,6 +13,7 @@ swift_library( "//submodules/Display", "//submodules/Postbox", "//submodules/TelegramCore", + "//submodules/Emoji", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift index 65e338b5ad..3f7e6f0f67 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift @@ -3,6 +3,7 @@ import UIKit import Display import Postbox import TelegramCore +import Emoji public struct ChatMessageItemWidthFill { public var compactInset: CGFloat @@ -230,3 +231,68 @@ public func isPollEffectivelyClosed(message: Message, poll: TelegramMediaPoll) - return false } } + +public extension ChatReplyThreadMessage { + var effectiveTopId: MessageId { + return self.channelMessageId ?? self.messageId + } +} + +public func messageIsElligibleForLargeEmoji(_ message: Message) -> Bool { + if !message.text.isEmpty && message.text.containsOnlyEmoji { + if !(message.textEntitiesAttribute?.entities.isEmpty ?? true) { + return false + } + return true + } else { + return false + } +} + +public func messageIsElligibleForLargeCustomEmoji(_ message: Message) -> Bool { + let text = message.text.replacingOccurrences(of: "\n", with: "").replacingOccurrences(of: " ", with: "") + guard !text.isEmpty && text.containsOnlyEmoji else { + return false + } + let entities = message.textEntitiesAttribute?.entities ?? [] + guard entities.count > 0 else { + return false + } + for entity in entities { + if case let .CustomEmoji(_, fileId) = entity.type { + if let _ = message.associatedMedia[MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile { + + } else { + return false + } + } else { + return false + } + } + return true +} + +public func canAddMessageReactions(message: Message) -> Bool { + if message.id.namespace != Namespaces.Message.Cloud { + return false + } + if let peer = message.peers[message.id.peerId] { + if let _ = peer as? TelegramSecretChat { + return false + } + } else { + return false + } + for media in message.media { + if let _ = media as? TelegramMediaAction { + return false + } else if let story = media as? TelegramMediaStory { + if story.isMention { + return false + } + } else if let _ = media as? TelegramMediaExpiredContent { + return false + } + } + return true +} diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift index dfc51036b8..d16216c1df 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift @@ -614,6 +614,12 @@ public final class ChatMessageAccessibilityData { } } +public enum InternalBubbleTapAction { + case action(() -> Void) + case optionalAction(() -> Void) + case openContextMenu(tapMessage: Message, selectAll: Bool, subFrame: CGRect) +} + open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol { public let layoutConstants = (ChatMessageItemLayoutConstants.compact, ChatMessageItemLayoutConstants.regular) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode/BUILD new file mode 100644 index 0000000000..5b4f65efeb --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode/BUILD @@ -0,0 +1,32 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageReactionsFooterContentNode", + module_name = "ChatMessageReactionsFooterContentNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Postbox", + "//submodules/Display", + "//submodules/AsyncDisplayKit", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramCore", + "//submodules/TelegramPresentationData", + "//submodules/RadialStatusNode", + "//submodules/AnimatedCountLabelNode", + "//submodules/AnimatedAvatarSetNode", + "//submodules/Components/ReactionButtonListComponent", + "//submodules/AccountContext", + "//submodules/WallpaperBackgroundNode", + "//submodules/TelegramUI/Components/ChatControllerInteraction", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageReactionsFooterContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode/Sources/ChatMessageReactionsFooterContentNode.swift similarity index 87% rename from submodules/TelegramUI/Sources/ChatMessageReactionsFooterContentNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode/Sources/ChatMessageReactionsFooterContentNode.swift index 94ee3c1bdd..b27dcf2704 100644 --- a/submodules/TelegramUI/Sources/ChatMessageReactionsFooterContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode/Sources/ChatMessageReactionsFooterContentNode.swift @@ -16,14 +16,14 @@ import ChatControllerInteraction import ChatMessageBubbleContentNode import ChatMessageItemCommon -final class MessageReactionButtonsNode: ASDisplayNode { - enum DisplayType { +public final class MessageReactionButtonsNode: ASDisplayNode { + public enum DisplayType { case incoming case outgoing case freeform } - enum DisplayAlignment { + public enum DisplayAlignment { case left case right } @@ -33,10 +33,10 @@ final class MessageReactionButtonsNode: ASDisplayNode { private var backgroundMaskView: UIView? private var backgroundMaskButtons: [MessageReaction.Reaction: UIView] = [:] - var reactionSelected: ((MessageReaction.Reaction) -> Void)? - var openReactionPreview: ((ContextGesture?, ContextExtractedContentContainingView, MessageReaction.Reaction) -> Void)? + public var reactionSelected: ((MessageReaction.Reaction) -> Void)? + public var openReactionPreview: ((ContextGesture?, ContextExtractedContentContainingView, MessageReaction.Reaction) -> Void)? - override init() { + override public init() { self.container = ReactionButtonsAsyncLayoutContainer() super.init() @@ -46,10 +46,10 @@ final class MessageReactionButtonsNode: ASDisplayNode { } - func update() { + public func update() { } - func prepareUpdate( + public func prepareUpdate( context: AccountContext, presentationData: ChatPresentationData, presentationContext: ChatPresentationContext, @@ -387,7 +387,7 @@ final class MessageReactionButtonsNode: ASDisplayNode { private var absoluteRect: (CGRect, CGSize)? - func update(rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition) { + public func update(rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition) { self.absoluteRect = (rect, containerSize) if let bubbleBackgroundNode = self.bubbleBackgroundNode { @@ -395,7 +395,7 @@ final class MessageReactionButtonsNode: ASDisplayNode { } } - func update(rect: CGRect, within containerSize: CGSize, transition: CombinedTransition) { + public func update(rect: CGRect, within containerSize: CGSize, transition: CombinedTransition) { self.absoluteRect = (rect, containerSize) if let bubbleBackgroundNode = self.bubbleBackgroundNode { @@ -403,19 +403,19 @@ final class MessageReactionButtonsNode: ASDisplayNode { } } - func offset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) { + public func offset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) { if let bubbleBackgroundNode = self.bubbleBackgroundNode { bubbleBackgroundNode.offset(value: value, animationCurve: animationCurve, duration: duration) } } - func offsetSpring(value: CGFloat, duration: Double, damping: CGFloat) { + public func offsetSpring(value: CGFloat, duration: Double, damping: CGFloat) { if let bubbleBackgroundNode = self.bubbleBackgroundNode { bubbleBackgroundNode.offsetSpring(value: value, duration: duration, damping: damping) } } - func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { + public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { for (key, button) in self.container.buttons { if key == value { return button.view.iconView @@ -424,19 +424,19 @@ final class MessageReactionButtonsNode: ASDisplayNode { return nil } - func animateIn(animation: ListViewItemUpdateAnimation) { + public func animateIn(animation: ListViewItemUpdateAnimation) { for (_, button) in self.container.buttons { animation.animator.animateScale(layer: button.view.layer, from: 0.01, to: 1.0, completion: nil) } } - func animateOut(animation: ListViewItemUpdateAnimation) { + public func animateOut(animation: ListViewItemUpdateAnimation) { for (_, button) in self.container.buttons { animation.animator.updateScale(layer: button.view.layer, scale: 0.01, completion: nil) } } - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { for (_, button) in self.container.buttons { if button.view.frame.contains(point) { if let result = button.view.hitTest(self.view.convert(point, to: button.view), with: event) { @@ -449,10 +449,10 @@ final class MessageReactionButtonsNode: ASDisplayNode { } } -final class ChatMessageReactionsFooterContentNode: ChatMessageBubbleContentNode { +public final class ChatMessageReactionsFooterContentNode: ChatMessageBubbleContentNode { private let buttonsNode: MessageReactionButtonsNode - required init() { + required public init() { self.buttonsNode = MessageReactionButtonsNode() super.init() @@ -476,11 +476,11 @@ final class ChatMessageReactionsFooterContentNode: ChatMessageBubbleContentNode } } - required init?(coder aDecoder: NSCoder) { + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - override 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))) { + 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 buttonsNode = self.buttonsNode return { item, layoutConstants, preparePosition, _, constrainedSize, _ in @@ -529,25 +529,25 @@ final class ChatMessageReactionsFooterContentNode: ChatMessageBubbleContentNode } } - override func animateInsertion(_ currentTimestamp: Double, duration: Double) { + override public func animateInsertion(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } - override func animateAdded(_ currentTimestamp: Double, duration: Double) { + override public func animateAdded(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) self.buttonsNode.animateOut(animation: ListViewItemUpdateAnimation.System(duration: 0.25, transition: ControlledTransition(duration: 0.25, curve: .spring, interactive: false))) } - override func animateInsertionIntoBubble(_ duration: Double) { + override public func animateInsertionIntoBubble(_ duration: Double) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) self.layer.animatePosition(from: CGPoint(x: 0.0, y: -self.bounds.height / 2.0), to: CGPoint(), duration: duration, removeOnCompletion: true, additive: true) } - override func animateRemovalFromBubble(_ duration: Double, completion: @escaping () -> Void) { + override public func animateRemovalFromBubble(_ duration: Double, completion: @escaping () -> Void) { self.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -self.bounds.height / 2.0), duration: duration, removeOnCompletion: false, additive: true) self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in completion() @@ -555,38 +555,38 @@ final class ChatMessageReactionsFooterContentNode: ChatMessageBubbleContentNode self.buttonsNode.animateOut(animation: ListViewItemUpdateAnimation.System(duration: 0.25, transition: ControlledTransition(duration: 0.25, curve: .spring, interactive: false))) } - override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { + override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { if let result = self.buttonsNode.hitTest(self.view.convert(point, to: self.buttonsNode.view), with: nil), result !== self.buttonsNode.view { return .ignore } return .none } - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if let result = self.buttonsNode.hitTest(self.view.convert(point, to: self.buttonsNode.view), with: event), result !== self.buttonsNode.view { return result } return nil } - override func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { + override public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { return self.buttonsNode.reactionTargetView(value: value) } } -final class ChatMessageReactionButtonsNode: ASDisplayNode { - final class Arguments { - let context: AccountContext - let presentationData: ChatPresentationData - let presentationContext: ChatPresentationContext - let availableReactions: AvailableReactions? - let reactions: ReactionsMessageAttribute - let message: Message - let accountPeer: EnginePeer? - let isIncoming: Bool - let constrainedWidth: CGFloat +public final class ChatMessageReactionButtonsNode: ASDisplayNode { + public final class Arguments { + public let context: AccountContext + public let presentationData: ChatPresentationData + public let presentationContext: ChatPresentationContext + public let availableReactions: AvailableReactions? + public let reactions: ReactionsMessageAttribute + public let message: Message + public let accountPeer: EnginePeer? + public let isIncoming: Bool + public let constrainedWidth: CGFloat - init( + public init( context: AccountContext, presentationData: ChatPresentationData, presentationContext: ChatPresentationContext, @@ -611,10 +611,10 @@ final class ChatMessageReactionButtonsNode: ASDisplayNode { private let buttonsNode: MessageReactionButtonsNode - var reactionSelected: ((MessageReaction.Reaction) -> Void)? - var openReactionPreview: ((ContextGesture?, ContextExtractedContentContainingView, MessageReaction.Reaction) -> Void)? + public var reactionSelected: ((MessageReaction.Reaction) -> Void)? + public var openReactionPreview: ((ContextGesture?, ContextExtractedContentContainingView, MessageReaction.Reaction) -> Void)? - override init() { + override public init() { self.buttonsNode = MessageReactionButtonsNode() super.init() @@ -630,7 +630,7 @@ final class ChatMessageReactionButtonsNode: ASDisplayNode { } } - class func asyncLayout(_ maybeNode: ChatMessageReactionButtonsNode?) -> (_ arguments: ChatMessageReactionButtonsNode.Arguments) -> (minWidth: CGFloat, layout: (CGFloat) -> (size: CGSize, apply: (_ animation: ListViewItemUpdateAnimation) -> ChatMessageReactionButtonsNode)) { + public class func asyncLayout(_ maybeNode: ChatMessageReactionButtonsNode?) -> (_ arguments: ChatMessageReactionButtonsNode.Arguments) -> (minWidth: CGFloat, layout: (CGFloat) -> (size: CGSize, apply: (_ animation: ListViewItemUpdateAnimation) -> ChatMessageReactionButtonsNode)) { return { arguments in let node = maybeNode ?? ChatMessageReactionButtonsNode() @@ -660,12 +660,12 @@ final class ChatMessageReactionButtonsNode: ASDisplayNode { } } - func animateIn(animation: ListViewItemUpdateAnimation) { + public func animateIn(animation: ListViewItemUpdateAnimation) { self.buttonsNode.animateIn(animation: animation) self.buttonsNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - func animateOut(animation: ListViewItemUpdateAnimation, completion: @escaping () -> Void) { + public func animateOut(animation: ListViewItemUpdateAnimation, completion: @escaping () -> Void) { self.buttonsNode.animateOut(animation: animation) animation.animator.updateAlpha(layer: self.buttonsNode.layer, alpha: 0.0, completion: { _ in completion() @@ -673,30 +673,30 @@ final class ChatMessageReactionButtonsNode: ASDisplayNode { animation.animator.updateFrame(layer: self.buttonsNode.layer, frame: self.buttonsNode.layer.frame.offsetBy(dx: 0.0, dy: -self.buttonsNode.layer.bounds.height / 2.0), completion: nil) } - func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { + public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { return self.buttonsNode.reactionTargetView(value: value) } - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if let result = self.buttonsNode.hitTest(self.view.convert(point, to: self.buttonsNode.view), with: event), result !== self.buttonsNode.view { return result } return nil } - func update(rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition) { + public func update(rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition) { self.buttonsNode.update(rect: rect, within: containerSize, transition: transition) } - func update(rect: CGRect, within containerSize: CGSize, transition: CombinedTransition) { + public func update(rect: CGRect, within containerSize: CGSize, transition: CombinedTransition) { self.buttonsNode.update(rect: rect, within: containerSize, transition: transition) } - func offset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) { + public func offset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) { self.buttonsNode.offset(value: value, animationCurve: animationCurve, duration: duration) } - func offsetSpring(value: CGFloat, duration: Double, damping: CGFloat) { + public func offsetSpring(value: CGFloat, duration: Double, damping: CGFloat) { self.buttonsNode.offsetSpring(value: value, duration: duration, damping: damping) } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageSelectionNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageSelectionNode/BUILD new file mode 100644 index 0000000000..3e8e689e0d --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageSelectionNode/BUILD @@ -0,0 +1,21 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageSelectionNode", + module_name = "ChatMessageSelectionNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/TelegramPresentationData", + "//submodules/CheckNode", + "//submodules/TelegramCore", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageSelectionNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageSelectionNode/Sources/ChatMessageSelectionNode.swift similarity index 74% rename from submodules/TelegramUI/Sources/ChatMessageSelectionNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageSelectionNode/Sources/ChatMessageSelectionNode.swift index 3921dad579..2f9d7a0b10 100644 --- a/submodules/TelegramUI/Sources/ChatMessageSelectionNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageSelectionNode/Sources/ChatMessageSelectionNode.swift @@ -5,13 +5,13 @@ import TelegramPresentationData import CheckNode import TelegramCore -final class ChatMessageSelectionNode: ASDisplayNode { +public final class ChatMessageSelectionNode: ASDisplayNode { private let toggle: (Bool) -> Void - private(set) var selected = false + public private(set) var selected = false private let checkNode: CheckNode - init(wallpaper: TelegramWallpaper, theme: PresentationTheme, toggle: @escaping (Bool) -> Void) { + public init(wallpaper: TelegramWallpaper, theme: PresentationTheme, toggle: @escaping (Bool) -> Void) { self.toggle = toggle let style: CheckNodeTheme.Style @@ -29,26 +29,26 @@ final class ChatMessageSelectionNode: ASDisplayNode { self.addSubnode(self.checkNode) } - override func didLoad() { + override public func didLoad() { super.didLoad() self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) } - func updateSelected(_ selected: Bool, animated: Bool) { + public func updateSelected(_ selected: Bool, animated: Bool) { if self.selected != selected { self.selected = selected self.checkNode.setSelected(selected, animated: animated) } } - @objc func tapGesture(_ recognizer: UITapGestureRecognizer) { + @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state { self.toggle(!self.selected) } } - func updateLayout(size: CGSize, leftInset: CGFloat) { + public func updateLayout(size: CGSize, leftInset: CGFloat) { let checkSize = CGSize(width: 28.0, height: 28.0) self.checkNode.frame = CGRect(origin: CGPoint(x: 6.0 + leftInset, y: floor((size.height - checkSize.height) / 2.0)), size: checkSize) } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/BUILD new file mode 100644 index 0000000000..cc323f2e04 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/BUILD @@ -0,0 +1,26 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageShareButton", + module_name = "ChatMessageShareButton", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/TelegramPresentationData", + "//submodules/TelegramUI/Components/ChatControllerInteraction", + "//submodules/AccountContext", + "//submodules/TelegramCore", + "//submodules/Postbox", + "//submodules/WallpaperBackgroundNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/Sources/ChatMessageShareButton.swift b/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/Sources/ChatMessageShareButton.swift new file mode 100644 index 0000000000..f1f33becaa --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/Sources/ChatMessageShareButton.swift @@ -0,0 +1,176 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import TelegramPresentationData +import ChatControllerInteraction +import AccountContext +import TelegramCore +import Postbox +import WallpaperBackgroundNode +import ChatMessageItemCommon + +public class ChatMessageShareButton: HighlightableButtonNode { + private var backgroundContent: WallpaperBubbleBackgroundNode? + private var backgroundBlurView: PortalView? + + private let iconNode: ASImageNode + private var iconOffset = CGPoint() + + private var theme: PresentationTheme? + private var isReplies: Bool = false + + private var textNode: ImmediateTextNode? + + private var absolutePosition: (CGRect, CGSize)? + + public init() { + self.iconNode = ASImageNode() + + super.init(pointerStyle: nil) + + self.allowsGroupOpacity = true + + self.addSubnode(self.iconNode) + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public func update(presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction, chatLocation: ChatLocation, subject: ChatControllerSubject?, message: Message, account: Account, disableComments: Bool = false) -> CGSize { + var isReplies = false + var replyCount = 0 + if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info { + for attribute in message.attributes { + if let attribute = attribute as? ReplyThreadMessageAttribute { + replyCount = Int(attribute.count) + isReplies = true + break + } + } + } + if case let .replyThread(replyThreadMessage) = chatLocation, replyThreadMessage.effectiveTopId == message.id { + replyCount = 0 + isReplies = false + } + if disableComments { + replyCount = 0 + isReplies = false + } + + if self.theme !== presentationData.theme.theme || self.isReplies != isReplies { + self.theme = presentationData.theme.theme + self.isReplies = isReplies + + var updatedIconImage: UIImage? + var updatedIconOffset = CGPoint() + if case .pinnedMessages = subject { + updatedIconImage = PresentationResourcesChat.chatFreeNavigateButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) + updatedIconOffset = CGPoint(x: UIScreenPixel, y: 1.0) + } else if isReplies { + updatedIconImage = PresentationResourcesChat.chatFreeCommentButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) + } else if message.id.peerId.isRepliesOrSavedMessages(accountPeerId: account.peerId) { + updatedIconImage = PresentationResourcesChat.chatFreeNavigateButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) + updatedIconOffset = CGPoint(x: UIScreenPixel, y: 1.0) + } else { + updatedIconImage = PresentationResourcesChat.chatFreeShareButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) + } + //self.backgroundNode.updateColor(color: selectDateFillStaticColor(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper), enableBlur: controllerInteraction.enableFullTranslucency && dateFillNeedsBlur(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper), transition: .immediate) + self.iconNode.image = updatedIconImage + self.iconOffset = updatedIconOffset + } + var size = CGSize(width: 30.0, height: 30.0) + var offsetIcon = false + if isReplies, replyCount > 0 { + offsetIcon = true + + let textNode: ImmediateTextNode + if let current = self.textNode { + textNode = current + } else { + textNode = ImmediateTextNode() + self.textNode = textNode + self.addSubnode(textNode) + } + + let textColor = bubbleVariableColor(variableColor: presentationData.theme.theme.chat.message.shareButtonForegroundColor, wallpaper: presentationData.theme.wallpaper) + + let countString: String + if replyCount >= 1000 * 1000 { + countString = "\(replyCount / 1000_000)M" + } else if replyCount >= 1000 { + countString = "\(replyCount / 1000)K" + } else { + countString = "\(replyCount)" + } + + textNode.attributedText = NSAttributedString(string: countString, font: Font.regular(11.0), textColor: textColor) + let textSize = textNode.updateLayout(CGSize(width: 100.0, height: 100.0)) + size.height += textSize.height - 1.0 + textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: size.height - textSize.height - 4.0), size: textSize) + } else if let textNode = self.textNode { + self.textNode = nil + textNode.removeFromSupernode() + } + + if self.backgroundBlurView == nil { + if let backgroundBlurView = controllerInteraction.presentationContext.backgroundNode?.makeFreeBackground() { + self.backgroundBlurView = backgroundBlurView + self.view.insertSubview(backgroundBlurView.view, at: 0) + + backgroundBlurView.view.clipsToBounds = true + } + } + if let backgroundBlurView = self.backgroundBlurView { + backgroundBlurView.view.frame = CGRect(origin: CGPoint(), size: size) + backgroundBlurView.view.layer.cornerRadius = min(size.width, size.height) / 2.0 + } + + //self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size) + //self.backgroundNode.update(size: self.backgroundNode.bounds.size, cornerRadius: min(self.backgroundNode.bounds.width, self.backgroundNode.bounds.height) / 2.0, transition: .immediate) + if let image = self.iconNode.image { + self.iconNode.frame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0) + self.iconOffset.x, y: floor((size.width - image.size.width) / 2.0) - (offsetIcon ? 1.0 : 0.0) + self.iconOffset.y), size: image.size) + } + + + if controllerInteraction.presentationContext.backgroundNode?.hasExtraBubbleBackground() == true { + if self.backgroundContent == nil, let backgroundContent = controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) { + backgroundContent.clipsToBounds = true + self.backgroundContent = backgroundContent + self.insertSubnode(backgroundContent, at: 0) + } + } else { + self.backgroundContent?.removeFromSupernode() + self.backgroundContent = nil + } + + if let backgroundContent = self.backgroundContent { + //self.backgroundNode.isHidden = true + self.backgroundBlurView?.view.isHidden = true + backgroundContent.cornerRadius = min(size.width, size.height) / 2.0 + backgroundContent.frame = CGRect(origin: CGPoint(), size: size) + if let (rect, containerSize) = self.absolutePosition { + var backgroundFrame = backgroundContent.frame + backgroundFrame.origin.x += rect.minX + backgroundFrame.origin.y += rect.minY + backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) + } + } else { + //self.backgroundNode.isHidden = false + self.backgroundBlurView?.view.isHidden = false + } + + return size + } + + public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + self.absolutePosition = (rect, containerSize) + if let backgroundContent = self.backgroundContent { + var backgroundFrame = backgroundContent.frame + backgroundFrame.origin.x += rect.minX + backgroundFrame.origin.y += rect.minY + backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) + } + } +} diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/BUILD new file mode 100644 index 0000000000..54233cea21 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/BUILD @@ -0,0 +1,45 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageStickerItemNode", + module_name = "ChatMessageStickerItemNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/TelegramPresentationData", + "//submodules/TextFormat", + "//submodules/AccountContext", + "//submodules/StickerResources", + "//submodules/ContextUI", + "//submodules/Markdown", + "//submodules/ShimmerEffect", + "//submodules/WallpaperBackgroundNode", + "//submodules/TelegramUI/Components/ChatControllerInteraction", + "//submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + "//submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItem", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemView", + "//submodules/TelegramUI/Components/Chat/ChatMessageSwipeToReplyNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageSelectionNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageDeliveryFailedNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageShareButton", + "//submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode", + "//submodules/TelegramUI/Components/Chat/ChatSwipeToReplyRecognizer", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift similarity index 94% rename from submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift index 6fb867c683..353a727f9d 100644 --- a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift @@ -20,18 +20,26 @@ import ChatMessageItemCommon import ChatMessageReplyInfoNode import ChatMessageItem import ChatMessageItemView +import ChatMessageSwipeToReplyNode +import ChatMessageSelectionNode +import ChatMessageDeliveryFailedNode +import ChatMessageShareButton +import ChatMessageThreadInfoNode +import ChatMessageActionButtonsNode +import ChatMessageReactionsFooterContentNode +import ChatSwipeToReplyRecognizer private let nameFont = Font.medium(14.0) private let inlineBotPrefixFont = Font.regular(14.0) private let inlineBotNameFont = nameFont -class ChatMessageStickerItemNode: ChatMessageItemView { - let contextSourceNode: ContextExtractedContentContainingNode +public class ChatMessageStickerItemNode: ChatMessageItemView { + public let contextSourceNode: ContextExtractedContentContainingNode private let containerNode: ContextControllerSourceNode - let imageNode: TransformImageNode + public let imageNode: TransformImageNode private var backgroundNode: WallpaperBubbleBackgroundNode? private var placeholderNode: StickerShimmerEffectNode - var textNode: TextNode? + public var textNode: TextNode? private var swipeToReplyNode: ChatMessageSwipeToReplyNode? private var swipeToReplyFeedback: HapticFeedback? @@ -40,7 +48,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { private var deliveryFailedNode: ChatMessageDeliveryFailedNode? private var shareButtonNode: ChatMessageShareButton? - var telegramFile: TelegramMediaFile? + public var telegramFile: TelegramMediaFile? private let fetchDisposable = MetaDisposable() private var viaBotNode: TextNode? @@ -67,7 +75,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { private var enableSynchronousImageApply: Bool = false - override var visibility: ListViewItemNodeVisibility { + override public var visibility: ListViewItemNodeVisibility { didSet { let wasVisible = oldValue != .none let isVisible = self.visibility != .none @@ -87,7 +95,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } } - required init() { + required public init() { self.contextSourceNode = ContextExtractedContentContainingNode() self.containerNode = ContextControllerSourceNode() self.imageNode = TransformImageNode() @@ -171,7 +179,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } } - required init?(coder aDecoder: NSCoder) { + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -179,7 +187,6 @@ class ChatMessageStickerItemNode: ChatMessageItemView { self.fetchDisposable.dispose() } - private func removePlaceholder(animated: Bool) { if !animated { self.placeholderNode.removeFromSupernode() @@ -191,7 +198,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } } - override func didLoad() { + override public func didLoad() { super.didLoad() let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:))) @@ -268,7 +275,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { self.view.addGestureRecognizer(replyRecognizer) } - override func setupItem(_ item: ChatMessageItem, synchronousLoad: Bool) { + override public func setupItem(_ item: ChatMessageItem, synchronousLoad: Bool) { super.setupItem(item, synchronousLoad: synchronousLoad) self.replyRecognizer?.allowBothDirections = !item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply @@ -291,7 +298,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } private var absoluteRect: (CGRect, CGSize)? - override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + override public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { self.absoluteRect = (rect, containerSize) if !self.contextSourceNode.isExtractedToContextPreview { var rect = rect @@ -345,7 +352,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } } - override func applyAbsoluteOffset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) { + override public func applyAbsoluteOffset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) { if let backgroundNode = self.backgroundNode { backgroundNode.offset(value: value, animationCurve: animationCurve, duration: duration) } @@ -355,7 +362,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } } - override func updateAccessibilityData(_ accessibilityData: ChatMessageAccessibilityData) { + override public func updateAccessibilityData(_ accessibilityData: ChatMessageAccessibilityData) { super.updateAccessibilityData(accessibilityData) self.messageAccessibilityArea.accessibilityLabel = accessibilityData.label @@ -386,7 +393,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } } - override func asyncLayout() -> (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) { + override public func asyncLayout() -> (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) { let displaySize = CGSize(width: 184.0, height: 184.0) let telegramFile = self.telegramFile let layoutConstants = self.layoutConstants @@ -1261,7 +1268,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } } - @objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { + @objc private func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { switch recognizer.state { case .ended: if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation { @@ -1383,7 +1390,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { return nil } - @objc func shareButtonPressed() { + @objc private func shareButtonPressed() { if let item = self.item { if case .pinnedMessages = item.associatedData.subject { item.controllerInteraction.navigateToMessageStandalone(item.content.firstMessage.id) @@ -1415,7 +1422,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } private var playedSwipeToReplyHaptic = false - @objc func swipeToReplyGesture(_ recognizer: ChatSwipeToReplyRecognizer) { + @objc private func swipeToReplyGesture(_ recognizer: ChatSwipeToReplyRecognizer) { var offset: CGFloat = 0.0 var leftOffset: CGFloat = 0.0 var swipeOffset: CGFloat = 45.0 @@ -1537,7 +1544,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } } - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if let shareButtonNode = self.shareButtonNode, shareButtonNode.frame.contains(point) { return shareButtonNode.view } @@ -1552,7 +1559,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { return super.hitTest(point, with: event) } - override func updateSelectionState(animated: Bool) { + override public func updateSelectionState(animated: Bool) { guard let item = self.item else { return } @@ -1660,7 +1667,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } } - override func updateHighlightedState(animated: Bool) { + override public func updateHighlightedState(animated: Bool) { super.updateHighlightedState(animated: animated) if let item = self.item { @@ -1683,37 +1690,51 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } } - override func cancelInsertionAnimations() { + override public func cancelInsertionAnimations() { self.layer.removeAllAnimations() } - override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { + override public func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { super.animateInsertion(currentTimestamp, duration: duration, short: short) self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { super.animateRemoved(currentTimestamp, duration: duration) self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) } - override func animateAdded(_ currentTimestamp: Double, duration: Double) { + override public func animateAdded(_ currentTimestamp: Double, duration: Double) { super.animateAdded(currentTimestamp, duration: duration) self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - override func getMessageContextSourceNode(stableId: UInt32?) -> ContextExtractedContentContainingNode? { + override public func getMessageContextSourceNode(stableId: UInt32?) -> ContextExtractedContentContainingNode? { return self.contextSourceNode } - override func addAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) { + override public func addAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) { self.contextSourceNode.contentNode.addSubnode(accessoryItemNode) } + + public final class AnimationTransitionTextInput { + public let backgroundView: UIView + public let contentView: UIView + public let sourceRect: CGRect + public let scrollOffset: CGFloat - func animateContentFromTextInputField(textInput: ChatMessageTransitionNode.Source.TextInput, transition: CombinedTransition) { + public init(backgroundView: UIView, contentView: UIView, sourceRect: CGRect, scrollOffset: CGFloat) { + self.backgroundView = backgroundView + self.contentView = contentView + self.sourceRect = sourceRect + self.scrollOffset = scrollOffset + } + } + + public func animateContentFromTextInputField(textInput: AnimationTransitionTextInput, transition: CombinedTransition) { guard let _ = self.item else { return } @@ -1765,8 +1786,56 @@ class ChatMessageStickerItemNode: ChatMessageItemView { self.dateAndStatusNode.layer.animateAlpha(from: 0.0, to: self.dateAndStatusNode.alpha, duration: 0.15, delay: 0.16) } + + public final class AnimationTransitionSticker { + public let imageNode: TransformImageNode? + public let animationNode: ASDisplayNode? + public let placeholderNode: ASDisplayNode? + public let imageLayer: CALayer? + public let relativeSourceRect: CGRect + + var sourceFrame: CGRect { + if let imageNode = self.imageNode { + return imageNode.frame + } else if let imageLayer = self.imageLayer { + return imageLayer.bounds + } else { + return CGRect(origin: CGPoint(), size: relativeSourceRect.size) + } + } + + var sourceLayer: CALayer? { + if let imageNode = self.imageNode { + return imageNode.layer + } else if let imageLayer = self.imageLayer { + return imageLayer + } else { + return nil + } + } + + func snapshotContentTree() -> UIView? { + if let animationNode = self.animationNode { + return animationNode.view.snapshotContentTree() + } else if let imageNode = self.imageNode { + return imageNode.view.snapshotContentTree() + } else if let sourceLayer = self.imageLayer { + return sourceLayer.snapshotContentTreeAsView() + } else { + return nil + } + } + + public init(imageNode: TransformImageNode?, animationNode: ASDisplayNode?, placeholderNode: ASDisplayNode?, imageLayer: CALayer?, relativeSourceRect: CGRect) { + self.imageNode = imageNode + self.animationNode = animationNode + self.placeholderNode = placeholderNode + self.imageLayer = imageLayer + self.relativeSourceRect = relativeSourceRect + } + } - func animateContentFromStickerGridItem(stickerSource: ChatMessageTransitionNode.Sticker, transition: CombinedTransition) { + public func animateContentFromStickerGridItem(stickerSource: AnimationTransitionSticker, transition: CombinedTransition) { guard let _ = self.item else { return } @@ -1850,8 +1919,26 @@ class ChatMessageStickerItemNode: ChatMessageItemView { placeholderNode.layer.animateAlpha(from: 0.0, to: placeholderNode.alpha, duration: 0.4) } } + + public final class AnimationTransitionReplyPanel { + public let titleNode: ASDisplayNode + public let textNode: ASDisplayNode + public let lineNode: ASDisplayNode + public let imageNode: ASDisplayNode + public let relativeSourceRect: CGRect + public let relativeTargetRect: CGRect - func animateReplyPanel(sourceReplyPanel: ChatMessageTransitionNode.ReplyPanel, transition: CombinedTransition) { + public init(titleNode: ASDisplayNode, textNode: ASDisplayNode, lineNode: ASDisplayNode, imageNode: ASDisplayNode, relativeSourceRect: CGRect, relativeTargetRect: CGRect) { + self.titleNode = titleNode + self.textNode = textNode + self.lineNode = lineNode + self.imageNode = imageNode + self.relativeSourceRect = relativeSourceRect + self.relativeTargetRect = relativeTargetRect + } + } + + public func animateReplyPanel(sourceReplyPanel: AnimationTransitionReplyPanel, transition: CombinedTransition) { if let replyInfoNode = self.replyInfoNode { let localRect = self.contextSourceNode.contentNode.view.convert(sourceReplyPanel.relativeSourceRect, to: replyInfoNode.view) let mappedPanel = ChatMessageReplyInfoNode.TransitionReplyPanel( @@ -1871,7 +1958,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } } - func animateFromLoadingPlaceholder(messageContainer: ChatLoadingPlaceholderMessageContainer, delay: Double, transition: ContainedViewLayoutTransition) { + public func animateFromLoadingPlaceholder(delay: Double, transition: ContainedViewLayoutTransition) { guard let item = self.item else { return } @@ -1881,14 +1968,14 @@ class ChatMessageStickerItemNode: ChatMessageItemView { transition.animateTransformScale(node: self, from: CGPoint(x: 0.85, y: 0.85), delay: delay) } - override func openMessageContextMenu() { + override public func openMessageContextMenu() { guard let item = self.item else { return } item.controllerInteraction.openMessageContextMenu(item.message, false, self, self.imageNode.frame, nil, nil) } - override func targetForStoryTransition(id: StoryId) -> UIView? { + override public func targetForStoryTransition(id: StoryId) -> UIView? { guard let item = self.item else { return nil } @@ -1904,7 +1991,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { return nil } - override func targetReactionView(value: MessageReaction.Reaction) -> UIView? { + override public func targetReactionView(value: MessageReaction.Reaction) -> UIView? { if let result = self.reactionButtonsNode?.reactionTargetView(value: value) { return result } @@ -1914,7 +2001,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { return nil } - override func contentFrame() -> CGRect { + override public func contentFrame() -> CGRect { return self.imageNode.frame } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageSwipeToReplyNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageSwipeToReplyNode/BUILD new file mode 100644 index 0000000000..150eeba1ad --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageSwipeToReplyNode/BUILD @@ -0,0 +1,22 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageSwipeToReplyNode", + module_name = "ChatMessageSwipeToReplyNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display", + "//submodules/AsyncDisplayKit", + "//submodules/AppBundle", + "//submodules/WallpaperBackgroundNode", + "//submodules/TelegramUI/Components/ChatControllerInteraction", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageSwipeToReplyNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageSwipeToReplyNode/Sources/ChatMessageSwipeToReplyNode.swift similarity index 95% rename from submodules/TelegramUI/Sources/ChatMessageSwipeToReplyNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageSwipeToReplyNode/Sources/ChatMessageSwipeToReplyNode.swift index 97aac1c46e..8a26af4a4a 100644 --- a/submodules/TelegramUI/Sources/ChatMessageSwipeToReplyNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageSwipeToReplyNode/Sources/ChatMessageSwipeToReplyNode.swift @@ -8,8 +8,8 @@ import ChatControllerInteraction private let size = CGSize(width: 33.0, height: 33.0) -final class ChatMessageSwipeToReplyNode: ASDisplayNode { - enum Action { +public final class ChatMessageSwipeToReplyNode: ASDisplayNode { + public enum Action { case reply case like case unlike @@ -27,7 +27,7 @@ final class ChatMessageSwipeToReplyNode: ASDisplayNode { private var absolutePosition: (CGRect, CGSize)? - init(fillColor: UIColor, enableBlur: Bool, foregroundColor: UIColor, backgroundNode: WallpaperBackgroundNode?, action: ChatMessageSwipeToReplyNode.Action) { + public init(fillColor: UIColor, enableBlur: Bool, foregroundColor: UIColor, backgroundNode: WallpaperBackgroundNode?, action: ChatMessageSwipeToReplyNode.Action) { self.backgroundNode = NavigationBackgroundNode(color: fillColor, enableBlur: enableBlur) self.backgroundNode.isUserInteractionEnabled = false @@ -138,7 +138,7 @@ final class ChatMessageSwipeToReplyNode: ASDisplayNode { } } - override func didLoad() { + override public func didLoad() { super.didLoad() if let backgroundContent = self.backgroundContent { @@ -149,7 +149,7 @@ final class ChatMessageSwipeToReplyNode: ASDisplayNode { } private var animatedWave = false - func updateProgress(_ progress: CGFloat) { + public func updateProgress(_ progress: CGFloat) { let progress = max(0.0, min(1.0, progress)) var foregroundProgress = min(1.0, progress * 1.2) var scaleProgress = 0.65 + foregroundProgress * 0.35 @@ -175,7 +175,7 @@ final class ChatMessageSwipeToReplyNode: ASDisplayNode { } } - func playSuccessAnimation() { + public func playSuccessAnimation() { guard !self.animatedWave else { return } @@ -214,7 +214,7 @@ final class ChatMessageSwipeToReplyNode: ASDisplayNode { self.fillLayer.animate(from: path, to: targetPath, keyPath: "path", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.3) } - func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { self.absolutePosition = (rect, containerSize) if let backgroundContent = self.backgroundContent { var backgroundFrame = backgroundContent.frame @@ -225,7 +225,7 @@ final class ChatMessageSwipeToReplyNode: ASDisplayNode { } } -extension ChatMessageSwipeToReplyNode.Action { +public extension ChatMessageSwipeToReplyNode.Action { init(_ action: ChatControllerInteractionSwipeAction?) { if let action = action { switch action { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode/BUILD new file mode 100644 index 0000000000..a870c33413 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode/BUILD @@ -0,0 +1,36 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageThreadInfoNode", + module_name = "ChatMessageThreadInfoNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Postbox", + "//submodules/Display", + "//submodules/TelegramCore", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramPresentationData", + "//submodules/AccountContext", + "//submodules/LocalizedPeerData", + "//submodules/PhotoResources", + "//submodules/TelegramStringFormatting", + "//submodules/TextFormat", + "//submodules/InvisibleInkDustNode", + "//submodules/TelegramUI/Components/TextNodeWithEntities", + "//submodules/TelegramUI/Components/AnimationCache", + "//submodules/TelegramUI/Components/MultiAnimationRenderer", + "//submodules/ComponentFlow", + "//submodules/TelegramUI/Components/EmojiStatusComponent", + "//submodules/WallpaperBackgroundNode", + "//submodules/TelegramUI/Components/ChatControllerInteraction", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMessageThreadInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode/Sources/ChatMessageThreadInfoNode.swift similarity index 96% rename from submodules/TelegramUI/Sources/ChatMessageThreadInfoNode.swift rename to submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode/Sources/ChatMessageThreadInfoNode.swift index 4f610e79a9..1fab74cb24 100644 --- a/submodules/TelegramUI/Sources/ChatMessageThreadInfoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode/Sources/ChatMessageThreadInfoNode.swift @@ -172,25 +172,25 @@ private func generateRectsImage(color: UIColor, rects: [CGRect], inset: CGFloat, })) } -enum ChatMessageThreadInfoType { +public enum ChatMessageThreadInfoType { case bubble(incoming: Bool) case standalone } -class ChatMessageThreadInfoNode: ASDisplayNode { - class Arguments { - let presentationData: ChatPresentationData - let strings: PresentationStrings - let context: AccountContext - let controllerInteraction: ChatControllerInteraction - let type: ChatMessageThreadInfoType - let threadId: Int64 - let parentMessage: Message - let constrainedSize: CGSize - let animationCache: AnimationCache? - let animationRenderer: MultiAnimationRenderer? +public class ChatMessageThreadInfoNode: ASDisplayNode { + public class Arguments { + public let presentationData: ChatPresentationData + public let strings: PresentationStrings + public let context: AccountContext + public let controllerInteraction: ChatControllerInteraction + public let type: ChatMessageThreadInfoType + public let threadId: Int64 + public let parentMessage: Message + public let constrainedSize: CGSize + public let animationCache: AnimationCache? + public let animationRenderer: MultiAnimationRenderer? - init( + public init( presentationData: ChatPresentationData, strings: PresentationStrings, context: AccountContext, @@ -215,7 +215,7 @@ class ChatMessageThreadInfoNode: ASDisplayNode { } } - var visibility: Bool = false { + public var visibility: Bool = false { didSet { if self.visibility != oldValue { self.textNode?.visibilityRect = self.visibility ? CGRect.infinite : nil @@ -249,7 +249,7 @@ class ChatMessageThreadInfoNode: ASDisplayNode { private var absolutePosition: (CGRect, CGSize)? - override init() { + override public init() { self.contentNode = HighlightTrackingButtonNode() self.contentBackgroundNode = ASImageNode() @@ -298,7 +298,7 @@ class ChatMessageThreadInfoNode: ASDisplayNode { self.pressed() } - func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { self.absolutePosition = (rect, containerSize) if let backgroundContent = self.backgroundContent { var backgroundFrame = backgroundContent.frame @@ -308,7 +308,7 @@ class ChatMessageThreadInfoNode: ASDisplayNode { } } - class func asyncLayout(_ maybeNode: ChatMessageThreadInfoNode?) -> (_ arguments: Arguments) -> (CGSize, (Bool) -> ChatMessageThreadInfoNode) { + public class func asyncLayout(_ maybeNode: ChatMessageThreadInfoNode?) -> (_ arguments: Arguments) -> (CGSize, (Bool) -> ChatMessageThreadInfoNode) { let textNodeLayout = TextNodeWithEntities.asyncLayout(maybeNode?.textNode) return { arguments in diff --git a/submodules/TelegramUI/Components/Chat/ChatSwipeToReplyRecognizer/BUILD b/submodules/TelegramUI/Components/Chat/ChatSwipeToReplyRecognizer/BUILD new file mode 100644 index 0000000000..0627218e50 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatSwipeToReplyRecognizer/BUILD @@ -0,0 +1,17 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatSwipeToReplyRecognizer", + module_name = "ChatSwipeToReplyRecognizer", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatSwipeToReplyRecognizer.swift b/submodules/TelegramUI/Components/Chat/ChatSwipeToReplyRecognizer/Sources/ChatSwipeToReplyRecognizer.swift similarity index 72% rename from submodules/TelegramUI/Sources/ChatSwipeToReplyRecognizer.swift rename to submodules/TelegramUI/Components/Chat/ChatSwipeToReplyRecognizer/Sources/ChatSwipeToReplyRecognizer.swift index 9cc8bae71b..a3592134b2 100644 --- a/submodules/TelegramUI/Sources/ChatSwipeToReplyRecognizer.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSwipeToReplyRecognizer/Sources/ChatSwipeToReplyRecognizer.swift @@ -1,26 +1,26 @@ import Foundation import UIKit -class ChatSwipeToReplyRecognizer: UIPanGestureRecognizer { - var validatedGesture = false - var firstLocation: CGPoint = CGPoint() - var allowBothDirections: Bool = true +public class ChatSwipeToReplyRecognizer: UIPanGestureRecognizer { + public var validatedGesture = false + public var firstLocation: CGPoint = CGPoint() + public var allowBothDirections: Bool = true - var shouldBegin: (() -> Bool)? + public var shouldBegin: (() -> Bool)? - override init(target: Any?, action: Selector?) { + override public init(target: Any?, action: Selector?) { super.init(target: target, action: action) self.maximumNumberOfTouches = 1 } - override func reset() { + override public func reset() { super.reset() self.validatedGesture = false } - override func touchesBegan(_ touches: Set, with event: UIEvent) { + override public func touchesBegan(_ touches: Set, with event: UIEvent) { super.touchesBegan(touches, with: event) if let shouldBegin = self.shouldBegin, !shouldBegin() { @@ -31,7 +31,7 @@ class ChatSwipeToReplyRecognizer: UIPanGestureRecognizer { } } - override func touchesMoved(_ touches: Set, with event: UIEvent) { + override public func touchesMoved(_ touches: Set, with event: UIEvent) { let location = touches.first!.location(in: self.view) let translation = CGPoint(x: location.x - firstLocation.x, y: location.y - firstLocation.y) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index abfbc28314..5425b99ef4 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -109,6 +109,7 @@ import TextSelectionNode import ChatMessagePollBubbleContentNode import ChatMessageItem import ChatMessageItemView +import ChatMessageItemCommon public enum ChatControllerPeekActions { case standard @@ -19523,31 +19524,6 @@ extension Peer { } } -func canAddMessageReactions(message: Message) -> Bool { - if message.id.namespace != Namespaces.Message.Cloud { - return false - } - if let peer = message.peers[message.id.peerId] { - if let _ = peer as? TelegramSecretChat { - return false - } - } else { - return false - } - for media in message.media { - if let _ = media as? TelegramMediaAction { - return false - } else if let story = media as? TelegramMediaStory { - if story.isMention { - return false - } - } else if let _ = media as? TelegramMediaExpiredContent { - return false - } - } - return true -} - enum AllowedReactions { case set(Set) case all diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 9062053cfb..9c9e1ce48b 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -33,6 +33,7 @@ import ChatInputContextPanelNode import TextSelectionNode import ReplyAccessoryPanelNode import ChatMessageItemView +import ChatMessageSelectionNode final class VideoNavigationControllerDropContentItem: NavigationControllerDropContentItem { let itemNode: OverlayMediaItemNode diff --git a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift index fbb3b17d8a..2078f00d80 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift @@ -6,6 +6,7 @@ import Emoji import AccountContext import TelegramPresentationData import ChatHistoryEntry +import ChatMessageItemCommon func chatHistoryEntriesForView( location: ChatLocation, diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index aef74ea964..1fb4f83786 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -29,12 +29,6 @@ import ChatBotInfoItem import ChatMessageItem import ChatMessageItemView -extension ChatReplyThreadMessage { - var effectiveTopId: MessageId { - return self.channelMessageId ?? self.messageId - } -} - struct ChatTopVisibleMessageRange: Equatable { var lowerBound: MessageIndex var upperBound: MessageIndex diff --git a/submodules/TelegramUI/Sources/ChatLoadingNode.swift b/submodules/TelegramUI/Sources/ChatLoadingNode.swift index fd56b2abff..075dfbc237 100644 --- a/submodules/TelegramUI/Sources/ChatLoadingNode.swift +++ b/submodules/TelegramUI/Sources/ChatLoadingNode.swift @@ -12,6 +12,7 @@ import ChatPresentationInterfaceState import AccountContext import ChatMessageItem import ChatMessageItemView +import ChatMessageStickerItemNode final class ChatLoadingNode: ASDisplayNode { private let backgroundNode: NavigationBackgroundNode @@ -95,7 +96,7 @@ final class ChatLoadingPlaceholderMessageContainer { if let bubbleItemNode = listItemNode as? ChatMessageBubbleItemNode { bubbleItemNode.animateFromLoadingPlaceholder(messageContainer: self, delay: delay, transition: transition) } else if let stickerItemNode = listItemNode as? ChatMessageStickerItemNode { - stickerItemNode.animateFromLoadingPlaceholder(messageContainer: self, delay: delay, transition: transition) + stickerItemNode.animateFromLoadingPlaceholder(delay: delay, transition: transition) } else if let stickerItemNode = listItemNode as? ChatMessageAnimatedStickerItemNode { stickerItemNode.animateFromLoadingPlaceholder(messageContainer: self, delay: delay, transition: transition) } else if let videoItemNode = listItemNode as? ChatMessageInstantVideoItemNode { diff --git a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift index 71b2e97e36..db7c451820 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -35,6 +35,14 @@ import ChatMessageBubbleContentNode import ChatMessageReplyInfoNode import ChatMessageItem import ChatMessageItemView +import ChatMessageSwipeToReplyNode +import ChatMessageSelectionNode +import ChatMessageDeliveryFailedNode +import ChatMessageShareButton +import ChatMessageThreadInfoNode +import ChatMessageActionButtonsNode +import ChatSwipeToReplyRecognizer +import ChatMessageReactionsFooterContentNode private let nameFont = Font.medium(14.0) private let inlineBotPrefixFont = Font.regular(14.0) @@ -63,174 +71,6 @@ extension SlotMachineAnimationNode: GenericAnimatedStickerNode { } } -class ChatMessageShareButton: HighlightableButtonNode { - private var backgroundContent: WallpaperBubbleBackgroundNode? - //private let backgroundNode: NavigationBackgroundNode - private var backgroundBlurView: PortalView? - - private let iconNode: ASImageNode - private var iconOffset = CGPoint() - - private var theme: PresentationTheme? - private var isReplies: Bool = false - - private var textNode: ImmediateTextNode? - - private var absolutePosition: (CGRect, CGSize)? - - init() { - //self.backgroundNode = NavigationBackgroundNode(color: .clear) - self.iconNode = ASImageNode() - - super.init(pointerStyle: nil) - - self.allowsGroupOpacity = true - - //self.addSubnode(self.backgroundNode) - self.addSubnode(self.iconNode) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func update(presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction, chatLocation: ChatLocation, subject: ChatControllerSubject?, message: Message, account: Account, disableComments: Bool = false) -> CGSize { - var isReplies = false - var replyCount = 0 - if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info { - for attribute in message.attributes { - if let attribute = attribute as? ReplyThreadMessageAttribute { - replyCount = Int(attribute.count) - isReplies = true - break - } - } - } - if case let .replyThread(replyThreadMessage) = chatLocation, replyThreadMessage.effectiveTopId == message.id { - replyCount = 0 - isReplies = false - } - if disableComments { - replyCount = 0 - isReplies = false - } - - if self.theme !== presentationData.theme.theme || self.isReplies != isReplies { - self.theme = presentationData.theme.theme - self.isReplies = isReplies - - var updatedIconImage: UIImage? - var updatedIconOffset = CGPoint() - if case .pinnedMessages = subject { - updatedIconImage = PresentationResourcesChat.chatFreeNavigateButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) - updatedIconOffset = CGPoint(x: UIScreenPixel, y: 1.0) - } else if isReplies { - updatedIconImage = PresentationResourcesChat.chatFreeCommentButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) - } else if message.id.peerId.isRepliesOrSavedMessages(accountPeerId: account.peerId) { - updatedIconImage = PresentationResourcesChat.chatFreeNavigateButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) - updatedIconOffset = CGPoint(x: UIScreenPixel, y: 1.0) - } else { - updatedIconImage = PresentationResourcesChat.chatFreeShareButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) - } - //self.backgroundNode.updateColor(color: selectDateFillStaticColor(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper), enableBlur: controllerInteraction.enableFullTranslucency && dateFillNeedsBlur(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper), transition: .immediate) - self.iconNode.image = updatedIconImage - self.iconOffset = updatedIconOffset - } - var size = CGSize(width: 30.0, height: 30.0) - var offsetIcon = false - if isReplies, replyCount > 0 { - offsetIcon = true - - let textNode: ImmediateTextNode - if let current = self.textNode { - textNode = current - } else { - textNode = ImmediateTextNode() - self.textNode = textNode - self.addSubnode(textNode) - } - - let textColor = bubbleVariableColor(variableColor: presentationData.theme.theme.chat.message.shareButtonForegroundColor, wallpaper: presentationData.theme.wallpaper) - - let countString: String - if replyCount >= 1000 * 1000 { - countString = "\(replyCount / 1000_000)M" - } else if replyCount >= 1000 { - countString = "\(replyCount / 1000)K" - } else { - countString = "\(replyCount)" - } - - textNode.attributedText = NSAttributedString(string: countString, font: Font.regular(11.0), textColor: textColor) - let textSize = textNode.updateLayout(CGSize(width: 100.0, height: 100.0)) - size.height += textSize.height - 1.0 - textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: size.height - textSize.height - 4.0), size: textSize) - } else if let textNode = self.textNode { - self.textNode = nil - textNode.removeFromSupernode() - } - - if self.backgroundBlurView == nil { - if let backgroundBlurView = controllerInteraction.presentationContext.backgroundNode?.makeFreeBackground() { - self.backgroundBlurView = backgroundBlurView - self.view.insertSubview(backgroundBlurView.view, at: 0) - - backgroundBlurView.view.clipsToBounds = true - } - } - if let backgroundBlurView = self.backgroundBlurView { - backgroundBlurView.view.frame = CGRect(origin: CGPoint(), size: size) - backgroundBlurView.view.layer.cornerRadius = min(size.width, size.height) / 2.0 - } - - //self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size) - //self.backgroundNode.update(size: self.backgroundNode.bounds.size, cornerRadius: min(self.backgroundNode.bounds.width, self.backgroundNode.bounds.height) / 2.0, transition: .immediate) - if let image = self.iconNode.image { - self.iconNode.frame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0) + self.iconOffset.x, y: floor((size.width - image.size.width) / 2.0) - (offsetIcon ? 1.0 : 0.0) + self.iconOffset.y), size: image.size) - } - - - if controllerInteraction.presentationContext.backgroundNode?.hasExtraBubbleBackground() == true { - if self.backgroundContent == nil, let backgroundContent = controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) { - backgroundContent.clipsToBounds = true - self.backgroundContent = backgroundContent - self.insertSubnode(backgroundContent, at: 0) - } - } else { - self.backgroundContent?.removeFromSupernode() - self.backgroundContent = nil - } - - if let backgroundContent = self.backgroundContent { - //self.backgroundNode.isHidden = true - self.backgroundBlurView?.view.isHidden = true - backgroundContent.cornerRadius = min(size.width, size.height) / 2.0 - backgroundContent.frame = CGRect(origin: CGPoint(), size: size) - if let (rect, containerSize) = self.absolutePosition { - var backgroundFrame = backgroundContent.frame - backgroundFrame.origin.x += rect.minX - backgroundFrame.origin.y += rect.minY - backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) - } - } else { - //self.backgroundNode.isHidden = false - self.backgroundBlurView?.view.isHidden = false - } - - return size - } - - func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { - self.absolutePosition = (rect, containerSize) - if let backgroundContent = self.backgroundContent { - var backgroundFrame = backgroundContent.frame - backgroundFrame.origin.x += rect.minX - backgroundFrame.origin.y += rect.minY - backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) - } - } -} - class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { let contextSourceNode: ContextExtractedContentContainingNode private let containerNode: ContextControllerSourceNode diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index c8197a2b80..ba17e4618d 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -43,12 +43,14 @@ import ChatMessageWebpageBubbleContentNode import ChatMessagePollBubbleContentNode import ChatMessageItem import ChatMessageItemView - -enum InternalBubbleTapAction { - case action(() -> Void) - case optionalAction(() -> Void) - case openContextMenu(tapMessage: Message, selectAll: Bool, subFrame: CGRect) -} +import ChatMessageSwipeToReplyNode +import ChatMessageSelectionNode +import ChatMessageDeliveryFailedNode +import ChatMessageShareButton +import ChatMessageThreadInfoNode +import ChatMessageActionButtonsNode +import ChatSwipeToReplyRecognizer +import ChatMessageReactionsFooterContentNode private struct BubbleItemAttributes { var isAttachment: Bool @@ -67,50 +69,6 @@ private final class ChatMessageBubbleClippingNode: ASDisplayNode { } } -func hasCommentButton(item: ChatMessageItem) -> Bool { - let firstMessage = item.content.firstMessage - - var hasDiscussion = false - if let channel = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case let .broadcast(info) = channel.info, info.flags.contains(.hasDiscussionGroup) { - hasDiscussion = true - } - if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.effectiveTopId == firstMessage.id { - hasDiscussion = false - } - - if firstMessage.adAttribute != nil { - hasDiscussion = false - } - - if hasDiscussion { - var canComment = false - if case .pinnedMessages = item.associatedData.subject { - canComment = false - } else if firstMessage.id.namespace == Namespaces.Message.Local { - canComment = true - } else { - for attribute in firstMessage.attributes { - if let attribute = attribute as? ReplyThreadMessageAttribute, let commentsPeerId = attribute.commentsPeerId { - switch item.associatedData.channelDiscussionGroup { - case .unknown: - canComment = true - case let .known(groupId): - canComment = groupId == commentsPeerId - } - break - } - } - } - - if canComment { - return true - } - } else if firstMessage.id.peerId.isReplies { - return true - } - return false -} - private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([(Message, AnyClass, ChatMessageEntryAttributes, BubbleItemAttributes)], Bool, Bool) { var result: [(Message, AnyClass, ChatMessageEntryAttributes, BubbleItemAttributes)] = [] var skipText = false diff --git a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift index e7a3aa5990..473285e4a8 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift @@ -21,6 +21,13 @@ import ChatMessageReplyInfoNode import ChatMessageInteractiveInstantVideoNode import ChatMessageItem import ChatMessageItemView +import ChatMessageSwipeToReplyNode +import ChatMessageSelectionNode +import ChatMessageDeliveryFailedNode +import ChatMessageShareButton +import ChatMessageActionButtonsNode +import ChatSwipeToReplyRecognizer +import ChatMessageReactionsFooterContentNode private let nameFont = Font.medium(14.0) diff --git a/submodules/TelegramUI/Sources/ChatMessageItemImpl.swift b/submodules/TelegramUI/Sources/ChatMessageItemImpl.swift index 8c9d792683..0ee5496862 100644 --- a/submodules/TelegramUI/Sources/ChatMessageItemImpl.swift +++ b/submodules/TelegramUI/Sources/ChatMessageItemImpl.swift @@ -14,6 +14,7 @@ import ChatControllerInteraction import ChatHistoryEntry import ChatMessageItem import ChatMessageItemView +import ChatMessageStickerItemNode private func mediaMergeableStyle(_ media: Media) -> ChatMessageMerge { if let story = media as? TelegramMediaStory, story.isMention { diff --git a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift index fbdb412c3e..5ba20f9eee 100644 --- a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift @@ -14,6 +14,7 @@ import FeaturedStickersScreen import ChatTextInputMediaRecordingButton import ReplyAccessoryPanelNode import ChatMessageItemView +import ChatMessageStickerItemNode private func convertAnimatingSourceRect(_ rect: CGRect, fromView: UIView, toView: UIView?) -> CGRect { if let presentationLayer = fromView.layer.presentation() { @@ -418,9 +419,27 @@ public final class ChatMessageTransitionNode: ASDisplayNode, ChatMessageTransiti itemNode.animateReplyPanel(sourceReplyPanel: sourceReplyPanel, transition: combinedTransition) } } else if let itemNode = self.itemNode as? ChatMessageStickerItemNode { - itemNode.animateContentFromTextInputField(textInput: textInput, transition: combinedTransition) + itemNode.animateContentFromTextInputField( + textInput: ChatMessageStickerItemNode.AnimationTransitionTextInput( + backgroundView: textInput.backgroundView, + contentView: textInput.contentView, + sourceRect: textInput.sourceRect, + scrollOffset: textInput.scrollOffset + ), + transition: combinedTransition + ) if let sourceReplyPanel = sourceReplyPanel { - itemNode.animateReplyPanel(sourceReplyPanel: sourceReplyPanel, transition: combinedTransition) + itemNode.animateReplyPanel( + sourceReplyPanel: ChatMessageStickerItemNode.AnimationTransitionReplyPanel( + titleNode: sourceReplyPanel.titleNode, + textNode: sourceReplyPanel.textNode, + lineNode: sourceReplyPanel.lineNode, + imageNode: sourceReplyPanel.imageNode, + relativeSourceRect: sourceReplyPanel.relativeSourceRect, + relativeTargetRect: sourceReplyPanel.relativeTargetRect + ), + transition: combinedTransition + ) } } case let .stickerMediaInput(stickerMediaInput, replyPanel): @@ -470,9 +489,28 @@ public final class ChatMessageTransitionNode: ASDisplayNode, ChatMessageTransiti itemNode.animateReplyPanel(sourceReplyPanel: sourceReplyPanel, transition: combinedTransition) } } else if let itemNode = self.itemNode as? ChatMessageStickerItemNode { - itemNode.animateContentFromStickerGridItem(stickerSource: stickerSource, transition: combinedTransition) + itemNode.animateContentFromStickerGridItem( + stickerSource: ChatMessageStickerItemNode.AnimationTransitionSticker( + imageNode: stickerSource.imageNode, + animationNode: stickerSource.animationNode, + placeholderNode: stickerSource.placeholderNode, + imageLayer: stickerSource.imageLayer, + relativeSourceRect: stickerSource.relativeSourceRect + ), + transition: combinedTransition + ) if let sourceReplyPanel = sourceReplyPanel { - itemNode.animateReplyPanel(sourceReplyPanel: sourceReplyPanel, transition: combinedTransition) + itemNode.animateReplyPanel( + sourceReplyPanel: ChatMessageStickerItemNode.AnimationTransitionReplyPanel( + titleNode: sourceReplyPanel.titleNode, + textNode: sourceReplyPanel.textNode, + lineNode: sourceReplyPanel.lineNode, + imageNode: sourceReplyPanel.imageNode, + relativeSourceRect: sourceReplyPanel.relativeSourceRect, + relativeTargetRect: sourceReplyPanel.relativeTargetRect + ), + transition: combinedTransition + ) } } diff --git a/submodules/TelegramUI/Sources/EmojiResources.swift b/submodules/TelegramUI/Sources/EmojiResources.swift deleted file mode 100644 index 228f5ea1c9..0000000000 --- a/submodules/TelegramUI/Sources/EmojiResources.swift +++ /dev/null @@ -1,39 +0,0 @@ -import Foundation -import UIKit -import Postbox -import TelegramCore -import Emoji - -func messageIsElligibleForLargeEmoji(_ message: Message) -> Bool { - if !message.text.isEmpty && message.text.containsOnlyEmoji { - if !(message.textEntitiesAttribute?.entities.isEmpty ?? true) { - return false - } - return true - } else { - return false - } -} - -func messageIsElligibleForLargeCustomEmoji(_ message: Message) -> Bool { - let text = message.text.replacingOccurrences(of: "\n", with: "").replacingOccurrences(of: " ", with: "") - guard !text.isEmpty && text.containsOnlyEmoji else { - return false - } - let entities = message.textEntitiesAttribute?.entities ?? [] - guard entities.count > 0 else { - return false - } - for entity in entities { - if case let .CustomEmoji(_, fileId) = entity.type { - if let _ = message.associatedMedia[MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile { - - } else { - return false - } - } else { - return false - } - } - return true -}