mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 11:23:48 +00:00
Reply patterns
This commit is contained in:
parent
591e00161f
commit
53664ed268
@ -178,7 +178,13 @@ public final class TelegramChannel: Peer, Equatable {
|
||||
return .title(title: self.title, addressNames: addressNames)
|
||||
}
|
||||
|
||||
public var associatedMediaIds: [MediaId]? { return nil }
|
||||
public var associatedMediaIds: [MediaId]? {
|
||||
if let backgroundEmojiId = self.backgroundEmojiId {
|
||||
return [MediaId(namespace: Namespaces.Media.CloudFile, id: backgroundEmojiId)]
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public let associatedPeerId: PeerId? = nil
|
||||
public let notificationSettingsPeerId: PeerId? = nil
|
||||
|
@ -150,8 +150,19 @@ public final class TelegramUser: Peer, Equatable {
|
||||
}
|
||||
|
||||
public var associatedMediaIds: [MediaId]? {
|
||||
if let emojiStatus = self.emojiStatus {
|
||||
return [MediaId(namespace: Namespaces.Media.CloudFile, id: emojiStatus.fileId)]
|
||||
if let emojiStatus = self.emojiStatus, let backgroundEmojiId = self.backgroundEmojiId {
|
||||
return [
|
||||
MediaId(namespace: Namespaces.Media.CloudFile, id: emojiStatus.fileId),
|
||||
MediaId(namespace: Namespaces.Media.CloudFile, id: backgroundEmojiId)
|
||||
]
|
||||
} else if let emojiStatus = self.emojiStatus {
|
||||
return [
|
||||
MediaId(namespace: Namespaces.Media.CloudFile, id: emojiStatus.fileId)
|
||||
]
|
||||
} else if let backgroundEmojiId = self.backgroundEmojiId {
|
||||
return [
|
||||
MediaId(namespace: Namespaces.Media.CloudFile, id: backgroundEmojiId)
|
||||
]
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
@ -1350,6 +1350,4 @@ public struct PresentationResourcesChat {
|
||||
})?.resizableImage(withCapInsets: .zero, resizingMode: .tile).withRenderingMode(.alwaysTemplate)
|
||||
})
|
||||
}
|
||||
|
||||
//chatReplyLineDashTemplateImage
|
||||
}
|
||||
|
@ -1056,7 +1056,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
|
||||
var viaBotApply: (TextNodeLayout, () -> TextNode)?
|
||||
var threadInfoApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode)?
|
||||
var replyInfoApply: (CGSize, (CGSize, Bool) -> ChatMessageReplyInfoNode)?
|
||||
var replyInfoApply: (CGSize, (CGSize, Bool, ListViewItemUpdateAnimation) -> ChatMessageReplyInfoNode)?
|
||||
var needsReplyBackground = false
|
||||
var replyMarkup: ReplyMarkupMessageAttribute?
|
||||
|
||||
@ -1490,7 +1490,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
if let (replyInfoSize, replyInfoApply) = replyInfoApply {
|
||||
let replyInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: headersOffset + 8.0 + messageInfoSize.height), size: replyInfoSize)
|
||||
|
||||
let replyInfoNode = replyInfoApply(replyInfoFrame.size, synchronousLoads)
|
||||
let replyInfoNode = replyInfoApply(replyInfoFrame.size, synchronousLoads, animation)
|
||||
if strongSelf.replyInfoNode == nil {
|
||||
strongSelf.replyInfoNode = replyInfoNode
|
||||
strongSelf.contextSourceNode.contentNode.addSubnode(replyInfoNode)
|
||||
|
@ -1249,7 +1249,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
adminBadgeLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode),
|
||||
threadInfoLayout: (ChatMessageThreadInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageThreadInfoNode),
|
||||
forwardInfoLayout: (ChatPresentationData, PresentationStrings, ChatMessageForwardInfoType, Peer?, String?, String?, ChatMessageForwardInfoNode.StoryData?, CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode),
|
||||
replyInfoLayout: (ChatMessageReplyInfoNode.Arguments) -> (CGSize, (CGSize, Bool) -> ChatMessageReplyInfoNode),
|
||||
replyInfoLayout: (ChatMessageReplyInfoNode.Arguments) -> (CGSize, (CGSize, Bool, ListViewItemUpdateAnimation) -> ChatMessageReplyInfoNode),
|
||||
actionButtonsLayout: (AccountContext, ChatPresentationThemeData, PresentationChatBubbleCorners, PresentationStrings, WallpaperBackgroundNode?, ReplyMarkupMessageAttribute, Message, CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode)),
|
||||
reactionButtonsLayout: (ChatMessageReactionButtonsNode.Arguments) -> (minWidth: CGFloat, layout: (CGFloat) -> (size: CGSize, apply: (ListViewItemUpdateAnimation) -> ChatMessageReactionButtonsNode)),
|
||||
mosaicStatusLayout: (ChatMessageDateAndStatusNode.Arguments) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageDateAndStatusNode)),
|
||||
@ -2037,7 +2037,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
var threadInfoSizeApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode?) = (CGSize(), { _ in nil })
|
||||
|
||||
var replyInfoOriginY: CGFloat = 0.0
|
||||
var replyInfoSizeApply: (CGSize, (CGSize, Bool) -> ChatMessageReplyInfoNode?) = (CGSize(), { _, _ in nil })
|
||||
var replyInfoSizeApply: (CGSize, (CGSize, Bool, ListViewItemUpdateAnimation) -> ChatMessageReplyInfoNode?) = (CGSize(), { _, _, _ in nil })
|
||||
|
||||
var forwardInfoOriginY: CGFloat = 0.0
|
||||
var forwardInfoSizeApply: (CGSize, (CGFloat) -> ChatMessageForwardInfoNode?) = (CGSize(), { _ in nil })
|
||||
@ -2235,7 +2235,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer,
|
||||
associatedData: item.associatedData
|
||||
))
|
||||
replyInfoSizeApply = (sizeAndApply.0, { realSize, synchronousLoads in sizeAndApply.1(realSize, synchronousLoads) })
|
||||
replyInfoSizeApply = (sizeAndApply.0, { realSize, synchronousLoads, animation in sizeAndApply.1(realSize, synchronousLoads, animation) })
|
||||
|
||||
replyInfoOriginY = headerSize.height
|
||||
headerSize.width = max(headerSize.width, replyInfoSizeApply.0.width + bubbleWidthInsets)
|
||||
@ -2755,7 +2755,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
threadInfoOriginY: CGFloat,
|
||||
forwardInfoSizeApply: (CGSize, (CGFloat) -> ChatMessageForwardInfoNode?),
|
||||
forwardInfoOriginY: CGFloat,
|
||||
replyInfoSizeApply: (CGSize, (CGSize, Bool) -> ChatMessageReplyInfoNode?),
|
||||
replyInfoSizeApply: (CGSize, (CGSize, Bool, ListViewItemUpdateAnimation) -> ChatMessageReplyInfoNode?),
|
||||
replyInfoOriginY: CGFloat,
|
||||
removedContentNodeIndices: [Int]?,
|
||||
updatedContentNodeOrder: Bool,
|
||||
@ -3064,7 +3064,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
}
|
||||
|
||||
let replyInfoFrame = CGRect(origin: CGPoint(x: contentOrigin.x + layoutConstants.text.bubbleInsets.left, y: layoutConstants.bubble.contentInsets.top + replyInfoOriginY), size: CGSize(width: backgroundFrame.width - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right - 6.0, height: replyInfoSizeApply.0.height))
|
||||
if let replyInfoNode = replyInfoSizeApply.1(replyInfoFrame.size, synchronousLoads) {
|
||||
if let replyInfoNode = replyInfoSizeApply.1(replyInfoFrame.size, synchronousLoads, animation) {
|
||||
strongSelf.replyInfoNode = replyInfoNode
|
||||
var animateFrame = true
|
||||
if replyInfoNode.supernode == nil {
|
||||
|
@ -427,7 +427,7 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureReco
|
||||
let videoFrame = CGRect(origin: CGPoint(x: (incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + effectiveAvatarInset + layoutConstants.bubble.contentInsets.left) : (params.width - params.rightInset - videoLayout.contentSize.width - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - deliveryFailedInset)), y: 0.0), size: videoLayout.contentSize)
|
||||
|
||||
var viaBotApply: (TextNodeLayout, () -> TextNode)?
|
||||
var replyInfoApply: (CGSize, (CGSize, Bool) -> ChatMessageReplyInfoNode)?
|
||||
var replyInfoApply: (CGSize, (CGSize, Bool, ListViewItemUpdateAnimation) -> ChatMessageReplyInfoNode)?
|
||||
var updatedReplyBackgroundNode: NavigationBackgroundNode?
|
||||
var replyMarkup: ReplyMarkupMessageAttribute?
|
||||
|
||||
@ -763,7 +763,7 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureReco
|
||||
if let (replyInfoSize, replyInfoApply) = replyInfoApply {
|
||||
let replyInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: 8.0 + messageInfoSize.height), size: replyInfoSize)
|
||||
|
||||
let replyInfoNode = replyInfoApply(replyInfoFrame.size, synchronousLoads)
|
||||
let replyInfoNode = replyInfoApply(replyInfoFrame.size, synchronousLoads, animation)
|
||||
if strongSelf.replyInfoNode == nil {
|
||||
strongSelf.replyInfoNode = replyInfoNode
|
||||
strongSelf.contextSourceNode.contentNode.addSubnode(replyInfoNode)
|
||||
|
@ -253,7 +253,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
var viaBotApply: (TextNodeLayout, () -> TextNode)?
|
||||
var replyInfoApply: (CGSize, (CGSize, Bool) -> ChatMessageReplyInfoNode)?
|
||||
var replyInfoApply: (CGSize, (CGSize, Bool, ListViewItemUpdateAnimation) -> ChatMessageReplyInfoNode)?
|
||||
|
||||
var updatedInstantVideoBackgroundImage: UIImage?
|
||||
let instantVideoBackgroundImage: UIImage?
|
||||
@ -1002,7 +1002,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
||||
if let (replyInfoSize, replyInfoApply) = replyInfoApply {
|
||||
let replyInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (displayVideoFrame.maxX - width + 5.0) : (width - messageInfoSize.width - bubbleEdgeInset - 9.0 + 10.0)), y: 8.0 + messageInfoSize.height), size: replyInfoSize)
|
||||
|
||||
let replyInfoNode = replyInfoApply(replyInfoFrame.size, false)
|
||||
let replyInfoNode = replyInfoApply(replyInfoFrame.size, false, animation)
|
||||
if strongSelf.replyInfoNode == nil {
|
||||
strongSelf.replyInfoNode = replyInfoNode
|
||||
strongSelf.addSubnode(replyInfoNode)
|
||||
|
@ -26,6 +26,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/AnimationCache",
|
||||
"//submodules/TelegramUI/Components/MultiAnimationRenderer",
|
||||
"//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon",
|
||||
"//submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -16,6 +16,7 @@ import TextNodeWithEntities
|
||||
import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
import ChatMessageItemCommon
|
||||
import MessageInlineBlockBackgroundView
|
||||
|
||||
public enum ChatMessageReplyInfoType {
|
||||
case bubble(incoming: Bool)
|
||||
@ -119,8 +120,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
private let backgroundView: UIImageView
|
||||
private var lineDashView: UIImageView?
|
||||
private let backgroundView: MessageInlineBlockBackgroundView
|
||||
private var quoteIconView: UIImageView?
|
||||
private let contentNode: ASDisplayNode
|
||||
private var titleNode: TextNode?
|
||||
@ -131,7 +131,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
private var expiredStoryIconView: UIImageView?
|
||||
|
||||
override public init() {
|
||||
self.backgroundView = UIImageView()
|
||||
self.backgroundView = MessageInlineBlockBackgroundView(frame: CGRect())
|
||||
|
||||
self.contentNode = ASDisplayNode()
|
||||
self.contentNode.isUserInteractionEnabled = false
|
||||
@ -144,7 +144,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
self.addSubnode(self.contentNode)
|
||||
}
|
||||
|
||||
public static func asyncLayout(_ maybeNode: ChatMessageReplyInfoNode?) -> (_ arguments: Arguments) -> (CGSize, (CGSize, Bool) -> ChatMessageReplyInfoNode) {
|
||||
public static func asyncLayout(_ maybeNode: ChatMessageReplyInfoNode?) -> (_ arguments: Arguments) -> (CGSize, (CGSize, Bool, ListViewItemUpdateAnimation) -> ChatMessageReplyInfoNode) {
|
||||
let titleNodeLayout = TextNode.asyncLayout(maybeNode?.titleNode)
|
||||
let textNodeLayout = TextNodeWithEntities.asyncLayout(maybeNode?.textNode)
|
||||
let imageNodeLayout = TransformImageNode.asyncLayout(maybeNode?.imageNode)
|
||||
@ -187,6 +187,9 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
}
|
||||
} else {
|
||||
mainColor = arguments.presentationData.theme.theme.chat.message.outgoing.accentTextColor
|
||||
if dashSecondaryColor != nil {
|
||||
secondaryColor = .clear
|
||||
}
|
||||
}
|
||||
dustColor = incoming ? arguments.presentationData.theme.theme.chat.message.incoming.secondaryTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.secondaryTextColor
|
||||
case .standalone:
|
||||
@ -584,7 +587,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
size.width += 16.0
|
||||
}
|
||||
|
||||
return (size, { realSize, attemptSynchronous in
|
||||
return (size, { realSize, attemptSynchronous, animation in
|
||||
let node: ChatMessageReplyInfoNode
|
||||
if let maybeNode = maybeNode {
|
||||
node = maybeNode
|
||||
@ -696,26 +699,38 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
node.dustNode = nil
|
||||
}
|
||||
|
||||
if node.backgroundView.image == nil {
|
||||
if case .standalone = arguments.type {
|
||||
node.backgroundView.image = PresentationResourcesChat.chatReplyServiceBackgroundTemplateImage(arguments.presentationData.theme.theme)
|
||||
} else {
|
||||
node.backgroundView.image = PresentationResourcesChat.chatReplyBackgroundTemplateImage(arguments.presentationData.theme.theme, dashedOutgoing: !isIncoming && secondaryColor != nil)
|
||||
}
|
||||
if node.backgroundView.superview == nil {
|
||||
node.contentNode.view.insertSubview(node.backgroundView, at: 0)
|
||||
}
|
||||
}
|
||||
|
||||
var backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: realSize.width, height: realSize.height + 2.0))
|
||||
if case .standalone = arguments.type {
|
||||
backgroundFrame.size.height -= 1.0
|
||||
}
|
||||
|
||||
node.backgroundView.tintColor = mainColor
|
||||
node.backgroundView.frame = backgroundFrame
|
||||
|
||||
if let secondaryColor {
|
||||
var pattern: MessageInlineBlockBackgroundView.Pattern?
|
||||
if let backgroundEmojiId = author?.backgroundEmojiId {
|
||||
pattern = MessageInlineBlockBackgroundView.Pattern(
|
||||
context: arguments.context,
|
||||
fileId: backgroundEmojiId,
|
||||
file: arguments.parentMessage.associatedMedia[MediaId(
|
||||
namespace: Namespaces.Media.CloudFile,
|
||||
id: backgroundEmojiId
|
||||
)] as? TelegramMediaFile
|
||||
)
|
||||
}
|
||||
node.backgroundView.update(
|
||||
size: backgroundFrame.size,
|
||||
primaryColor: mainColor,
|
||||
secondaryColor: secondaryColor,
|
||||
pattern: pattern,
|
||||
animation: animation
|
||||
)
|
||||
|
||||
let _ = secondaryColor
|
||||
/*if let secondaryColor {
|
||||
let lineDashView: UIImageView
|
||||
if let current = node.lineDashView {
|
||||
lineDashView = current
|
||||
@ -736,7 +751,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
node.lineDashView = nil
|
||||
lineDashView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
if arguments.quote != nil || arguments.replyForward?.quote != nil {
|
||||
let quoteIconView: UIImageView
|
||||
|
@ -625,7 +625,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
|
||||
var viaBotApply: (TextNodeLayout, () -> TextNode)?
|
||||
var threadInfoApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode)?
|
||||
var replyInfoApply: (CGSize, (CGSize, Bool) -> ChatMessageReplyInfoNode)?
|
||||
var replyInfoApply: (CGSize, (CGSize, Bool, ListViewItemUpdateAnimation) -> ChatMessageReplyInfoNode)?
|
||||
var replyMarkup: ReplyMarkupMessageAttribute?
|
||||
|
||||
var availableWidth = max(60.0, params.width - params.leftInset - params.rightInset - max(imageSize.width, 160.0) - 20.0 - layoutConstants.bubble.edgeInset * 2.0 - avatarInset - layoutConstants.bubble.contentInsets.left)
|
||||
@ -1070,7 +1070,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
if let (replyInfoSize, replyInfoApply) = replyInfoApply {
|
||||
let replyInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: headersOffset + 8.0 + messageInfoSize.height), size: replyInfoSize)
|
||||
|
||||
let replyInfoNode = replyInfoApply(replyInfoFrame.size, synchronousLoads)
|
||||
let replyInfoNode = replyInfoApply(replyInfoFrame.size, synchronousLoads, animation)
|
||||
if strongSelf.replyInfoNode == nil {
|
||||
strongSelf.replyInfoNode = replyInfoNode
|
||||
strongSelf.contextSourceNode.contentNode.addSubnode(replyInfoNode)
|
||||
|
@ -0,0 +1,26 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "MessageInlineBlockBackgroundView",
|
||||
module_name = "MessageInlineBlockBackgroundView",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/Display",
|
||||
"//submodules/Components/HierarchyTrackingLayer",
|
||||
"//submodules/Postbox",
|
||||
"//submodules/TelegramCore",
|
||||
"//submodules/TelegramUI/Components/AnimationCache",
|
||||
"//submodules/TelegramUI/Components/MultiAnimationRenderer",
|
||||
"//submodules/TelegramUI/Components/EmojiTextAttachmentView",
|
||||
"//submodules/SSignalKit/SwiftSignalKit",
|
||||
"//submodules/AccountContext",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
@ -0,0 +1,431 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import HierarchyTrackingLayer
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
import SwiftSignalKit
|
||||
import AccountContext
|
||||
import EmojiTextAttachmentView
|
||||
|
||||
private let radius: CGFloat = 4.0
|
||||
private let lineWidth: CGFloat = 3.0
|
||||
|
||||
private func addRoundedRectPath(context: CGContext, rect: CGRect, radius: CGFloat) {
|
||||
context.saveGState()
|
||||
context.translateBy(x: rect.minX, y: rect.minY)
|
||||
context.scaleBy(x: radius, y: radius)
|
||||
let fw = rect.width / radius
|
||||
let fh = rect.height / radius
|
||||
context.move(to: CGPoint(x: fw, y: fh / 2.0))
|
||||
context.addArc(tangent1End: CGPoint(x: fw, y: fh), tangent2End: CGPoint(x: fw/2, y: fh), radius: 1.0)
|
||||
context.addArc(tangent1End: CGPoint(x: 0, y: fh), tangent2End: CGPoint(x: 0, y: fh/2), radius: 1)
|
||||
context.addArc(tangent1End: CGPoint(x: 0, y: 0), tangent2End: CGPoint(x: fw/2, y: 0), radius: 1)
|
||||
context.addArc(tangent1End: CGPoint(x: fw, y: 0), tangent2End: CGPoint(x: fw, y: fh/2), radius: 1)
|
||||
context.closePath()
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
private func generateTemplateImage(isMonochrome: Bool) -> UIImage {
|
||||
return generateImage(CGSize(width: radius * 2.0 + 4.0, height: radius * 2.0 + 8.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
//context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), cornerRadius: radius).cgPath)
|
||||
addRoundedRectPath(context: context, rect: CGRect(origin: CGPoint(), size: size), radius: radius)
|
||||
context.clip()
|
||||
|
||||
context.setFillColor(UIColor.white.withMultipliedAlpha(0.1).cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.setFillColor(UIColor.white.withAlphaComponent(isMonochrome ? 0.2 : 1.0).cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: CGSize(width: lineWidth, height: size.height)))
|
||||
})!.stretchableImage(withLeftCapWidth: Int(radius) + 2, topCapHeight: Int(radius) + 3).withRenderingMode(.alwaysTemplate)
|
||||
}
|
||||
|
||||
private let plainTemplateImage: UIImage = {
|
||||
return generateTemplateImage(isMonochrome: false)
|
||||
}()
|
||||
|
||||
private let monochromePatternTemplateImage: UIImage = {
|
||||
return generateTemplateImage(isMonochrome: true)
|
||||
}()
|
||||
|
||||
private func generateDashBackgroundTemplateImage() -> UIImage {
|
||||
return generateImage(CGSize(width: radius * 2.0 + 4.0, height: radius * 2.0 + 8.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), cornerRadius: radius).cgPath)
|
||||
context.clip()
|
||||
|
||||
context.setFillColor(UIColor.white.withMultipliedAlpha(0.1).cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.setFillColor(UIColor.white.withAlphaComponent(0.2).cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: CGSize(width: lineWidth, height: size.height)))
|
||||
})!.stretchableImage(withLeftCapWidth: Int(radius) + 2, topCapHeight: Int(radius) + 3).withRenderingMode(.alwaysTemplate)
|
||||
}
|
||||
|
||||
private let dashBackgroundTemplateImage: UIImage = {
|
||||
return generateDashBackgroundTemplateImage()
|
||||
}()
|
||||
|
||||
private func generateDashTemplateImage(isMonochrome: Bool) -> UIImage {
|
||||
return generateImage(CGSize(width: radius * 2.0, height: 18.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(UIColor.white.cgColor)
|
||||
|
||||
let dashOffset: CGFloat = isMonochrome ? -4.0 : 5.0
|
||||
|
||||
context.translateBy(x: 0.0, y: dashOffset)
|
||||
|
||||
for _ in 0 ..< 2 {
|
||||
context.move(to: CGPoint(x: 0.0, y: 3.0))
|
||||
context.addLine(to: CGPoint(x: lineWidth, y: 0.0))
|
||||
context.addLine(to: CGPoint(x: lineWidth, y: 9.0))
|
||||
context.addLine(to: CGPoint(x: 0.0, y: 9.0 + 3.0))
|
||||
context.closePath()
|
||||
context.fillPath()
|
||||
|
||||
context.translateBy(x: 0.0, y: 18.0)
|
||||
}
|
||||
|
||||
context.clear(CGRect(origin: CGPoint(x: lineWidth, y: 0.0), size: CGSize(width: size.width - lineWidth, height: size.height)))
|
||||
})!.resizableImage(withCapInsets: .zero, resizingMode: .tile).withRenderingMode(.alwaysTemplate)
|
||||
}
|
||||
|
||||
private let dashOpaqueTemplateImage: UIImage = {
|
||||
return generateDashTemplateImage(isMonochrome: false)
|
||||
}()
|
||||
|
||||
private let dashMonochromeTemplateImage: UIImage = {
|
||||
return generateDashTemplateImage(isMonochrome: true)
|
||||
}()
|
||||
|
||||
private final class PatternContentsTarget: MultiAnimationRenderTarget {
|
||||
private let imageUpdated: () -> Void
|
||||
|
||||
init(imageUpdated: @escaping () -> Void) {
|
||||
self.imageUpdated = imageUpdated
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
required init(coder: NSCoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
override func transitionToContents(_ contents: AnyObject, didLoop: Bool) {
|
||||
self.contents = contents
|
||||
self.imageUpdated()
|
||||
}
|
||||
}
|
||||
|
||||
public final class MessageInlineBlockBackgroundView: UIView {
|
||||
public final class Pattern: Equatable {
|
||||
public let context: AccountContext
|
||||
public let fileId: Int64
|
||||
public let file: TelegramMediaFile?
|
||||
|
||||
public init(context: AccountContext, fileId: Int64, file: TelegramMediaFile?) {
|
||||
self.context = context
|
||||
self.fileId = fileId
|
||||
self.file = file
|
||||
}
|
||||
|
||||
public static func ==(lhs: Pattern, rhs: Pattern) -> Bool {
|
||||
if lhs === rhs {
|
||||
return true
|
||||
}
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.fileId != rhs.fileId {
|
||||
return false
|
||||
}
|
||||
if lhs.file?.fileId != rhs.file?.fileId {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private struct Params: Equatable {
|
||||
var size: CGSize
|
||||
var primaryColor: UIColor
|
||||
var secondaryColor: UIColor?
|
||||
var pattern: Pattern?
|
||||
var displayProgress: Bool
|
||||
}
|
||||
|
||||
private var params: Params?
|
||||
|
||||
public var displayProgress: Bool = false {
|
||||
didSet {
|
||||
if self.displayProgress != oldValue {
|
||||
if let params = self.params {
|
||||
self.update(
|
||||
size: params.size,
|
||||
primaryColor: params.primaryColor,
|
||||
secondaryColor: params.secondaryColor,
|
||||
pattern: params.pattern,
|
||||
animation: .System(duration: 0.2, transition: ControlledTransition(
|
||||
duration: 0.2,
|
||||
curve: .easeInOut,
|
||||
interactive: false
|
||||
))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let backgroundView: UIImageView
|
||||
private var dashView: UIImageView?
|
||||
private var hierarchyTrackingLayer: HierarchyTrackingLayer?
|
||||
|
||||
private var patternContentsTarget: PatternContentsTarget?
|
||||
private var patternContentLayers: [SimpleLayer] = []
|
||||
private var patternFile: TelegramMediaFile?
|
||||
private var patternFileDisposable: Disposable?
|
||||
private var patternImage: UIImage?
|
||||
private var patternImageDisposable: Disposable?
|
||||
|
||||
override public init(frame: CGRect) {
|
||||
self.backgroundView = UIImageView()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.backgroundView)
|
||||
}
|
||||
|
||||
required public init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.patternFileDisposable?.dispose()
|
||||
self.patternImageDisposable?.dispose()
|
||||
}
|
||||
|
||||
private func updateAnimations() {
|
||||
}
|
||||
|
||||
private func loadPatternFromFile() {
|
||||
guard let pattern = self.params?.pattern else {
|
||||
return
|
||||
}
|
||||
guard let patternContentsTarget = self.patternContentsTarget else {
|
||||
return
|
||||
}
|
||||
guard let patternFile = self.patternFile else {
|
||||
return
|
||||
}
|
||||
self.patternImageDisposable = pattern.context.animationRenderer.loadFirstFrame(
|
||||
target: patternContentsTarget,
|
||||
cache: pattern.context.animationCache, itemId: "reply-pattern-\(patternFile.fileId)",
|
||||
size: CGSize(width: 64, height: 64),
|
||||
fetch: animationCacheFetchFile(
|
||||
postbox: pattern.context.account.postbox,
|
||||
userLocation: .other,
|
||||
userContentType: .sticker,
|
||||
resource: .media(media: .standalone(media: patternFile), resource: patternFile.resource),
|
||||
type: AnimationCacheAnimationType(file: patternFile),
|
||||
keyframeOnly: false,
|
||||
customColor: .white
|
||||
),
|
||||
completion: { [weak self] _, _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.updatePatternLayerImages()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private func updatePatternLayerImages() {
|
||||
let image = self.patternContentsTarget?.contents
|
||||
for patternContentLayer in self.patternContentLayers {
|
||||
patternContentLayer.contents = image
|
||||
}
|
||||
}
|
||||
|
||||
public func update(
|
||||
size: CGSize,
|
||||
primaryColor: UIColor,
|
||||
secondaryColor: UIColor?,
|
||||
pattern: Pattern?,
|
||||
animation: ListViewItemUpdateAnimation
|
||||
) {
|
||||
let params = Params(
|
||||
size: size,
|
||||
primaryColor: primaryColor,
|
||||
secondaryColor: secondaryColor,
|
||||
pattern: pattern,
|
||||
displayProgress: self.displayProgress
|
||||
)
|
||||
if self.params == params {
|
||||
return
|
||||
}
|
||||
let previousParams = self.params
|
||||
self.params = params
|
||||
|
||||
if previousParams?.primaryColor != params.primaryColor || previousParams?.secondaryColor != params.secondaryColor {
|
||||
for patternContentLayer in self.patternContentLayers {
|
||||
patternContentLayer.layerTintColor = primaryColor.cgColor
|
||||
}
|
||||
|
||||
if let secondaryColor = params.secondaryColor {
|
||||
self.backgroundView.tintColor = params.primaryColor
|
||||
|
||||
if self.dashView == nil {
|
||||
let dashView = UIImageView()
|
||||
dashView.layer.cornerRadius = radius
|
||||
if #available(iOS 13.0, *) {
|
||||
dashView.layer.cornerCurve = .circular
|
||||
}
|
||||
self.dashView = dashView
|
||||
self.addSubview(dashView)
|
||||
}
|
||||
|
||||
if secondaryColor.alpha == 0.0 {
|
||||
self.backgroundView.image = monochromePatternTemplateImage
|
||||
self.dashView?.image = dashMonochromeTemplateImage
|
||||
self.dashView?.tintColor = primaryColor
|
||||
} else {
|
||||
self.backgroundView.image = plainTemplateImage
|
||||
self.dashView?.image = dashOpaqueTemplateImage
|
||||
self.dashView?.tintColor = secondaryColor
|
||||
}
|
||||
} else {
|
||||
self.backgroundView.image = plainTemplateImage
|
||||
self.backgroundView.tintColor = params.primaryColor
|
||||
|
||||
if let dashView = self.dashView {
|
||||
self.dashView = dashView
|
||||
dashView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if previousParams?.pattern != params.pattern {
|
||||
if let pattern = params.pattern {
|
||||
self.layer.masksToBounds = true
|
||||
self.layer.cornerRadius = radius
|
||||
if #available(iOS 13.0, *) {
|
||||
self.layer.cornerCurve = .circular
|
||||
}
|
||||
|
||||
if self.patternContentsTarget == nil {
|
||||
self.patternContentsTarget = PatternContentsTarget(imageUpdated: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.updatePatternLayerImages()
|
||||
})
|
||||
}
|
||||
|
||||
if previousParams?.pattern?.fileId != pattern.fileId {
|
||||
self.patternFile = nil
|
||||
self.patternFileDisposable?.dispose()
|
||||
self.patternFileDisposable = nil
|
||||
self.patternImageDisposable?.dispose()
|
||||
|
||||
if let file = pattern.file {
|
||||
self.patternFile = file
|
||||
self.loadPatternFromFile()
|
||||
} else {
|
||||
let fileId = pattern.fileId
|
||||
self.patternFileDisposable = (pattern.context.engine.stickers.resolveInlineStickers(fileIds: [pattern.fileId])
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] files in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if let file = files[fileId] {
|
||||
self.patternFile = file
|
||||
self.loadPatternFromFile()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.layer.masksToBounds = false
|
||||
|
||||
self.patternContentsTarget = nil
|
||||
self.patternFileDisposable?.dispose()
|
||||
self.patternFileDisposable = nil
|
||||
self.patternFile = nil
|
||||
}
|
||||
}
|
||||
|
||||
self.dashView?.layer.masksToBounds = params.pattern == nil && params.secondaryColor != nil
|
||||
|
||||
animation.animator.updateFrame(layer: self.backgroundView.layer, frame: CGRect(origin: CGPoint(), size: size), completion: nil)
|
||||
if let dashView = self.dashView {
|
||||
animation.animator.updateFrame(layer: dashView.layer, frame: CGRect(origin: CGPoint(), size: CGSize(width: radius * 2.0, height: size.height)), completion: nil)
|
||||
}
|
||||
|
||||
if params.pattern != nil {
|
||||
|
||||
var maxIndex = 0
|
||||
|
||||
struct Placement {
|
||||
var position: CGPoint
|
||||
var size: CGFloat
|
||||
|
||||
init(_ position: CGPoint, _ size: CGFloat) {
|
||||
self.position = position
|
||||
self.size = size
|
||||
}
|
||||
}
|
||||
|
||||
let placements: [Placement] = [
|
||||
Placement(CGPoint(x: 176.0, y: 13.0), 38.0),
|
||||
Placement(CGPoint(x: 51.0, y: 45.0), 58.0),
|
||||
Placement(CGPoint(x: 349.0, y: 36.0), 58.0),
|
||||
Placement(CGPoint(x: 132.0, y: 64.0), 46.0),
|
||||
Placement(CGPoint(x: 241.0, y: 64.0), 54.0),
|
||||
Placement(CGPoint(x: 68.0, y: 121.0), 44.0),
|
||||
Placement(CGPoint(x: 178.0, y: 122.0), 47.0),
|
||||
Placement(CGPoint(x: 315.0, y: 122.0), 47.0),
|
||||
]
|
||||
|
||||
for placement in placements {
|
||||
let patternContentLayer: SimpleLayer
|
||||
if maxIndex < self.patternContentLayers.count {
|
||||
patternContentLayer = self.patternContentLayers[maxIndex]
|
||||
} else {
|
||||
patternContentLayer = SimpleLayer()
|
||||
patternContentLayer.layerTintColor = primaryColor.cgColor
|
||||
self.layer.addSublayer(patternContentLayer)
|
||||
self.patternContentLayers.append(patternContentLayer)
|
||||
}
|
||||
patternContentLayer.contents = self.patternContentsTarget?.contents
|
||||
|
||||
let itemSize = CGSize(width: placement.size / 3.0, height: placement.size / 3.0)
|
||||
patternContentLayer.frame = CGRect(origin: CGPoint(x: size.width - placement.position.x / 3.0 - itemSize.width * 0.5, y: placement.position.y / 3.0 - itemSize.height * 0.5), size: itemSize)
|
||||
var alphaFraction = abs(placement.position.x) / 400.0
|
||||
alphaFraction = min(1.0, max(0.0, alphaFraction))
|
||||
patternContentLayer.opacity = 0.5 * Float(1.0 - alphaFraction)
|
||||
|
||||
maxIndex += 1
|
||||
}
|
||||
|
||||
if maxIndex < self.patternContentLayers.count {
|
||||
for i in maxIndex ..< self.patternContentLayers.count {
|
||||
self.patternContentLayers[i].removeFromSuperlayer()
|
||||
}
|
||||
self.patternContentLayers.removeSubrange(maxIndex ..< self.patternContentLayers.count)
|
||||
}
|
||||
} else {
|
||||
for patternContentLayer in self.patternContentLayers {
|
||||
patternContentLayer.removeFromSuperlayer()
|
||||
}
|
||||
self.patternContentLayers.removeAll()
|
||||
}
|
||||
|
||||
self.updateAnimations()
|
||||
}
|
||||
}
|
@ -644,6 +644,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
private var adMessagesDisposable: Disposable?
|
||||
private var preloadAdPeerId: PeerId?
|
||||
private let preloadAdPeerDisposable = MetaDisposable()
|
||||
private var seenAdIds: [Data] = []
|
||||
private var pendingDynamicAdMessages: [Message] = []
|
||||
private var pendingDynamicAdMessageInterval: Int?
|
||||
private var remainingDynamicAdMessageInterval: Int?
|
||||
@ -2028,8 +2029,11 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !self.seenAdIds.contains(opaqueId) {
|
||||
self.seenAdIds.append(opaqueId)
|
||||
self.adMessagesContext?.markAsSeen(opaqueId: opaqueId)
|
||||
}
|
||||
}
|
||||
|
||||
private func processDisplayedItemRangeChanged(displayedRange: ListViewDisplayedItemRange, transactionState: ChatHistoryTransactionOpaqueState) {
|
||||
let historyView = transactionState.historyView
|
||||
|
Loading…
x
Reference in New Issue
Block a user