mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 14:20:20 +00:00
Refactoring
This commit is contained in:
@@ -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": [],
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
@@ -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
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
@@ -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)
|
||||
@@ -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",
|
||||
],
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ swift_library(
|
||||
"//submodules/Display",
|
||||
"//submodules/Postbox",
|
||||
"//submodules/TelegramCore",
|
||||
"//submodules/Emoji",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
func animateContentFromTextInputField(textInput: ChatMessageTransitionNode.Source.TextInput, transition: CombinedTransition) {
|
||||
public final class AnimationTransitionTextInput {
|
||||
public let backgroundView: UIView
|
||||
public let contentView: UIView
|
||||
public let sourceRect: CGRect
|
||||
public let scrollOffset: CGFloat
|
||||
|
||||
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
|
||||
}
|
||||
@@ -1766,7 +1787,55 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
self.dateAndStatusNode.layer.animateAlpha(from: 0.0, to: self.dateAndStatusNode.alpha, duration: 0.15, delay: 0.16)
|
||||
}
|
||||
|
||||
func animateContentFromStickerGridItem(stickerSource: ChatMessageTransitionNode.Sticker, transition: CombinedTransition) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
public func animateContentFromStickerGridItem(stickerSource: AnimationTransitionSticker, transition: CombinedTransition) {
|
||||
guard let _ = self.item else {
|
||||
return
|
||||
}
|
||||
@@ -1851,7 +1920,25 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
}
|
||||
}
|
||||
|
||||
func animateReplyPanel(sourceReplyPanel: ChatMessageTransitionNode.ReplyPanel, transition: CombinedTransition) {
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
@@ -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 {
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
@@ -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
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
@@ -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<UITouch>, with event: UIEvent) {
|
||||
override public func touchesBegan(_ touches: Set<UITouch>, 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<UITouch>, with event: UIEvent) {
|
||||
override public func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
let location = touches.first!.location(in: self.view)
|
||||
let translation = CGPoint(x: location.x - firstLocation.x, y: location.y - firstLocation.y)
|
||||
|
||||
@@ -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<MessageReaction.Reaction>)
|
||||
case all
|
||||
|
||||
@@ -33,6 +33,7 @@ import ChatInputContextPanelNode
|
||||
import TextSelectionNode
|
||||
import ReplyAccessoryPanelNode
|
||||
import ChatMessageItemView
|
||||
import ChatMessageSelectionNode
|
||||
|
||||
final class VideoNavigationControllerDropContentItem: NavigationControllerDropContentItem {
|
||||
let itemNode: OverlayMediaItemNode
|
||||
|
||||
@@ -6,6 +6,7 @@ import Emoji
|
||||
import AccountContext
|
||||
import TelegramPresentationData
|
||||
import ChatHistoryEntry
|
||||
import ChatMessageItemCommon
|
||||
|
||||
func chatHistoryEntriesForView(
|
||||
location: ChatLocation,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user