Emoji in chat folders

This commit is contained in:
Isaac
2024-12-25 00:17:19 +08:00
parent 1d28e11879
commit 4bed1703a2
59 changed files with 962 additions and 389 deletions

View File

@@ -20,11 +20,13 @@ private final class InlineStickerItem: Hashable {
let emoji: ChatTextInputTextCustomEmojiAttribute
let file: TelegramMediaFile?
let fontSize: CGFloat
let enableAnimation: Bool
init(emoji: ChatTextInputTextCustomEmojiAttribute, file: TelegramMediaFile?, fontSize: CGFloat) {
init(emoji: ChatTextInputTextCustomEmojiAttribute, file: TelegramMediaFile?, fontSize: CGFloat, enableAnimation: Bool) {
self.emoji = emoji
self.file = file
self.fontSize = fontSize
self.enableAnimation = enableAnimation
}
func hash(into hasher: inout Hasher) {
@@ -42,6 +44,9 @@ private final class InlineStickerItem: Hashable {
if lhs.fontSize != rhs.fontSize {
return false
}
if lhs.enableAnimation != rhs.enableAnimation {
return false
}
return true
}
}
@@ -65,19 +70,25 @@ public final class TextNodeWithEntities {
public let renderer: MultiAnimationRenderer
public let placeholderColor: UIColor
public let attemptSynchronous: Bool
public let emojiOffset: CGPoint
public let fontSizeNorm: CGFloat
public init(
context: AccountContext,
cache: AnimationCache,
renderer: MultiAnimationRenderer,
placeholderColor: UIColor,
attemptSynchronous: Bool
attemptSynchronous: Bool,
emojiOffset: CGPoint = CGPoint(),
fontSizeNorm: CGFloat = 17.0
) {
self.context = context
self.cache = cache
self.renderer = renderer
self.placeholderColor = placeholderColor
self.attemptSynchronous = attemptSynchronous
self.emojiOffset = emojiOffset
self.fontSizeNorm = fontSizeNorm
}
public func withUpdatedPlaceholderColor(_ color: UIColor) -> Arguments {
@@ -86,7 +97,8 @@ public final class TextNodeWithEntities {
cache: self.cache,
renderer: self.renderer,
placeholderColor: color,
attemptSynchronous: self.attemptSynchronous
attemptSynchronous: self.attemptSynchronous,
emojiOffset: self.emojiOffset
)
}
}
@@ -96,6 +108,8 @@ public final class TextNodeWithEntities {
private var enableLooping: Bool = true
public var resetEmojiToFirstFrameAutomatically: Bool = false
public var visibilityRect: CGRect? {
didSet {
if !self.inlineStickerItemLayers.isEmpty && oldValue != self.visibilityRect {
@@ -110,7 +124,13 @@ public final class TextNodeWithEntities {
} else {
isItemVisible = false
}
itemLayer.isVisibleForAnimations = self.enableLooping && isItemVisible
let isVisibleForAnimations = self.enableLooping && isItemVisible && itemLayer.enableAnimation
if itemLayer.isVisibleForAnimations != isVisibleForAnimations {
itemLayer.isVisibleForAnimations = isVisibleForAnimations
if !isVisibleForAnimations && self.resetEmojiToFirstFrameAutomatically {
itemLayer.reloadAnimation()
}
}
}
}
}
@@ -141,7 +161,7 @@ public final class TextNodeWithEntities {
let replacementRange = NSRange(location: 0, length: updatedSubstring.length)
updatedSubstring.addAttributes(string.attributes(at: range.location, effectiveRange: nil), range: replacementRange)
updatedSubstring.addAttribute(NSAttributedString.Key("Attribute__EmbeddedItem"), value: InlineStickerItem(emoji: value, file: value.file, fontSize: font.pointSize), range: replacementRange)
updatedSubstring.addAttribute(NSAttributedString.Key("Attribute__EmbeddedItem"), value: InlineStickerItem(emoji: value, file: value.file, fontSize: font.pointSize, enableAnimation: value.enableAnimation), range: replacementRange)
updatedSubstring.addAttribute(originalTextAttributeKey, value: OriginalTextAttribute(id: originalTextId, string: string.attributedSubstring(from: range).string), range: replacementRange)
originalTextId += 1
@@ -197,7 +217,7 @@ public final class TextNodeWithEntities {
if let maybeNode = maybeNode {
if let applyArguments = applyArguments {
maybeNode.updateInlineStickers(context: applyArguments.context, cache: applyArguments.cache, renderer: applyArguments.renderer, textLayout: layout, placeholderColor: applyArguments.placeholderColor, attemptSynchronousLoad: false)
maybeNode.updateInlineStickers(context: applyArguments.context, cache: applyArguments.cache, renderer: applyArguments.renderer, textLayout: layout, placeholderColor: applyArguments.placeholderColor, attemptSynchronousLoad: false, emojiOffset: applyArguments.emojiOffset, fontSizeNorm: applyArguments.fontSizeNorm)
}
return maybeNode
@@ -205,7 +225,7 @@ public final class TextNodeWithEntities {
let resultNode = TextNodeWithEntities(textNode: result)
if let applyArguments = applyArguments {
resultNode.updateInlineStickers(context: applyArguments.context, cache: applyArguments.cache, renderer: applyArguments.renderer, textLayout: layout, placeholderColor: applyArguments.placeholderColor, attemptSynchronousLoad: false)
resultNode.updateInlineStickers(context: applyArguments.context, cache: applyArguments.cache, renderer: applyArguments.renderer, textLayout: layout, placeholderColor: applyArguments.placeholderColor, attemptSynchronousLoad: false, emojiOffset: applyArguments.emojiOffset, fontSizeNorm: applyArguments.fontSizeNorm)
}
return resultNode
@@ -222,7 +242,7 @@ public final class TextNodeWithEntities {
}
}
private func updateInlineStickers(context: AccountContext, cache: AnimationCache, renderer: MultiAnimationRenderer, textLayout: TextNodeLayout?, placeholderColor: UIColor, attemptSynchronousLoad: Bool) {
private func updateInlineStickers(context: AccountContext, cache: AnimationCache, renderer: MultiAnimationRenderer, textLayout: TextNodeLayout?, placeholderColor: UIColor, attemptSynchronousLoad: Bool, emojiOffset: CGPoint, fontSizeNorm: CGFloat) {
self.enableLooping = context.sharedContext.energyUsageSettings.loopEmoji
var nextIndexById: [Int64: Int] = [:]
@@ -241,9 +261,9 @@ public final class TextNodeWithEntities {
let id = InlineStickerItemLayer.Key(id: stickerItem.emoji.fileId, index: index)
validIds.append(id)
let itemSize = floorToScreenPixels(stickerItem.fontSize * 24.0 / 17.0)
let itemSize = floorToScreenPixels(stickerItem.fontSize * 24.0 / fontSizeNorm)
var itemFrame = CGRect(origin: item.rect.offsetBy(dx: textLayout.insets.left, dy: textLayout.insets.top + 1.0).center, size: CGSize()).insetBy(dx: -itemSize / 2.0, dy: -itemSize / 2.0)
var itemFrame = CGRect(origin: item.rect.offsetBy(dx: textLayout.insets.left + emojiOffset.x, dy: textLayout.insets.top + 1.0 + emojiOffset.y).center, size: CGSize()).insetBy(dx: -itemSize / 2.0, dy: -itemSize / 2.0)
itemFrame.origin.x = floorToScreenPixels(itemFrame.origin.x)
itemFrame.origin.y = floorToScreenPixels(itemFrame.origin.y)
@@ -256,8 +276,13 @@ public final class TextNodeWithEntities {
itemLayer = InlineStickerItemLayer(context: context, userLocation: .other, attemptSynchronousLoad: attemptSynchronousLoad, emoji: stickerItem.emoji, file: stickerItem.file, cache: cache, renderer: renderer, placeholderColor: placeholderColor, pointSize: CGSize(width: pointSize, height: pointSize), dynamicColor: item.textColor)
self.inlineStickerItemLayers[id] = itemLayer
self.textNode.layer.addSublayer(itemLayer)
itemLayer.isVisibleForAnimations = self.enableLooping && self.isItemVisible(itemRect: itemFrame)
}
itemLayer.enableAnimation = stickerItem.enableAnimation
let isVisibleForAnimations = self.enableLooping && self.isItemVisible(itemRect: itemFrame) && itemLayer.enableAnimation
if itemLayer.isVisibleForAnimations != isVisibleForAnimations {
if !isVisibleForAnimations && self.resetEmojiToFirstFrameAutomatically {
itemLayer.reloadAnimation()
}
}
itemLayer.frame = itemFrame
@@ -301,12 +326,19 @@ public class ImmediateTextNodeWithEntities: TextNode {
private var inlineStickerItemLayers: [InlineStickerItemLayer.Key: InlineStickerItemLayer] = [:]
public private(set) var dustNode: InvisibleInkDustNode?
public var resetEmojiToFirstFrameAutomatically: Bool = false
public var visibility: Bool = false {
didSet {
if !self.inlineStickerItemLayers.isEmpty && oldValue != self.visibility {
for (_, itemLayer) in self.inlineStickerItemLayers {
let isItemVisible: Bool = self.visibility
itemLayer.isVisibleForAnimations = self.enableLooping && isItemVisible
let isVisibleForAnimations = self.enableLooping && self.visibility && itemLayer.enableAnimation
if itemLayer.isVisibleForAnimations != isVisibleForAnimations {
itemLayer.isVisibleForAnimations = isVisibleForAnimations
if !isVisibleForAnimations && self.resetEmojiToFirstFrameAutomatically {
itemLayer.reloadAnimation()
}
}
}
}
}
@@ -375,7 +407,7 @@ public class ImmediateTextNodeWithEntities: TextNode {
let replacementRange = NSRange(location: 0, length: updatedSubstring.length)
updatedSubstring.addAttributes(string.attributes(at: range.location, effectiveRange: nil), range: replacementRange)
updatedSubstring.addAttribute(NSAttributedString.Key("Attribute__EmbeddedItem"), value: InlineStickerItem(emoji: value, file: value.file, fontSize: font.pointSize), range: replacementRange)
updatedSubstring.addAttribute(NSAttributedString.Key("Attribute__EmbeddedItem"), value: InlineStickerItem(emoji: value, file: value.file, fontSize: font.pointSize, enableAnimation: value.enableAnimation), range: replacementRange)
updatedSubstring.addAttribute(originalTextAttributeKey, value: OriginalTextAttribute(id: originalTextId, string: string.attributedSubstring(from: range).string), range: replacementRange)
originalTextId += 1
@@ -437,7 +469,7 @@ public class ImmediateTextNodeWithEntities: TextNode {
var enableAnimations = true
if let arguments = self.arguments {
self.updateInlineStickers(context: arguments.context, cache: arguments.cache, renderer: arguments.renderer, textLayout: layout, placeholderColor: arguments.placeholderColor)
self.updateInlineStickers(context: arguments.context, cache: arguments.cache, renderer: arguments.renderer, textLayout: layout, placeholderColor: arguments.placeholderColor, fontSizeNorm: arguments.fontSizeNorm)
enableAnimations = arguments.context.sharedContext.energyUsageSettings.fullTranslucency
}
self.updateSpoilers(enableAnimations: enableAnimations, textLayout: layout)
@@ -450,7 +482,7 @@ public class ImmediateTextNodeWithEntities: TextNode {
return layout.size
}
private func updateInlineStickers(context: AccountContext, cache: AnimationCache, renderer: MultiAnimationRenderer, textLayout: TextNodeLayout?, placeholderColor: UIColor) {
private func updateInlineStickers(context: AccountContext, cache: AnimationCache, renderer: MultiAnimationRenderer, textLayout: TextNodeLayout?, placeholderColor: UIColor, fontSizeNorm: CGFloat) {
self.enableLooping = context.sharedContext.energyUsageSettings.loopEmoji
var nextIndexById: [Int64: Int] = [:]
@@ -469,7 +501,7 @@ public class ImmediateTextNodeWithEntities: TextNode {
let id = InlineStickerItemLayer.Key(id: stickerItem.emoji.fileId, index: index)
validIds.append(id)
let itemSide = floor(stickerItem.fontSize * 24.0 / 17.0)
let itemSide = floor(stickerItem.fontSize * 24.0 / fontSizeNorm)
var itemSize = CGSize(width: itemSide, height: itemSide)
if let file = stickerItem.file, let customItemLayout = self.customItemLayout {
itemSize = customItemLayout(itemSize, file)
@@ -486,8 +518,15 @@ public class ImmediateTextNodeWithEntities: TextNode {
itemLayer = InlineStickerItemLayer(context: context, userLocation: .other, attemptSynchronousLoad: false, emoji: stickerItem.emoji, file: stickerItem.file, cache: cache, renderer: renderer, placeholderColor: placeholderColor, pointSize: CGSize(width: pointSize, height: pointSize), dynamicColor: item.textColor)
self.inlineStickerItemLayers[id] = itemLayer
self.layer.addSublayer(itemLayer)
itemLayer.isVisibleForAnimations = self.enableLooping && self.visibility
}
itemLayer.enableAnimation = stickerItem.enableAnimation
let isVisibleForAnimations = self.enableLooping && self.visibility && itemLayer.enableAnimation
if itemLayer.isVisibleForAnimations != isVisibleForAnimations {
itemLayer.isVisibleForAnimations = isVisibleForAnimations
if !isVisibleForAnimations && self.resetEmojiToFirstFrameAutomatically {
itemLayer.reloadAnimation()
}
}
itemLayer.frame = itemFrame
@@ -535,7 +574,7 @@ public class ImmediateTextNodeWithEntities: TextNode {
let _ = apply()
if let arguments = self.arguments {
self.updateInlineStickers(context: arguments.context, cache: arguments.cache, renderer: arguments.renderer, textLayout: layout, placeholderColor: arguments.placeholderColor)
self.updateInlineStickers(context: arguments.context, cache: arguments.cache, renderer: arguments.renderer, textLayout: layout, placeholderColor: arguments.placeholderColor, fontSizeNorm: arguments.fontSizeNorm)
}
return ImmediateTextNodeLayoutInfo(size: layout.size, truncated: layout.truncated, numberOfLines: layout.numberOfLines)
@@ -550,7 +589,7 @@ public class ImmediateTextNodeWithEntities: TextNode {
let _ = apply()
if let arguments = self.arguments {
self.updateInlineStickers(context: arguments.context, cache: arguments.cache, renderer: arguments.renderer, textLayout: layout, placeholderColor: arguments.placeholderColor)
self.updateInlineStickers(context: arguments.context, cache: arguments.cache, renderer: arguments.renderer, textLayout: layout, placeholderColor: arguments.placeholderColor, fontSizeNorm: arguments.fontSizeNorm)
}
return layout