Reaction improvements

This commit is contained in:
Ali
2022-08-19 22:51:12 +03:00
parent 8f2a7327d7
commit ffe00ac2e1
54 changed files with 1847 additions and 462 deletions

View File

@@ -11,24 +11,169 @@ import UIKit
import AnimatedAvatarSetNode
import ReactionImageComponent
import WebPBinding
import AnimationCache
import MultiAnimationRenderer
import EmojiTextAttachmentView
import TextFormat
public final class ReactionIconView: PortalSourceView {
public let imageView: UIImageView
private var animationLayer: InlineStickerItemLayer?
private var context: AccountContext?
private var fileId: Int64?
private var file: TelegramMediaFile?
private var animationCache: AnimationCache?
private var animationRenderer: MultiAnimationRenderer?
private var placeholderColor: UIColor?
private var size: CGSize?
private var animateIdle: Bool?
private var reaction: MessageReaction.Reaction?
private var isAnimationHidden: Bool = false
private var disposable: Disposable?
override public init(frame: CGRect) {
self.imageView = UIImageView()
super.init(frame: frame)
self.addSubview(self.imageView)
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public func update(size: CGSize, transition: ContainedViewLayoutTransition) {
transition.updateFrame(view: self.imageView, frame: CGRect(origin: CGPoint(), size: size))
deinit {
self.disposable?.dispose()
}
public func update(
size: CGSize,
context: AccountContext,
file: TelegramMediaFile?,
fileId: Int64,
animationCache: AnimationCache,
animationRenderer: MultiAnimationRenderer,
placeholderColor: UIColor,
animateIdle: Bool,
reaction: MessageReaction.Reaction,
transition: ContainedViewLayoutTransition
) {
self.context = context
self.animationCache = animationCache
self.animationRenderer = animationRenderer
self.placeholderColor = placeholderColor
self.size = size
self.animateIdle = animateIdle
self.reaction = reaction
if self.fileId != fileId {
self.fileId = fileId
self.file = file
self.animationLayer?.removeFromSuperlayer()
self.animationLayer = nil
if let _ = file {
self.disposable?.dispose()
self.disposable = nil
self.reloadFile()
} else {
self.disposable?.dispose()
self.disposable = (context.engine.stickers.resolveInlineStickers(fileIds: [fileId])
|> deliverOnMainQueue).start(next: { [weak self] files in
guard let strongSelf = self else {
return
}
strongSelf.file = files[fileId]
strongSelf.reloadFile()
})
}
}
if let animationLayer = self.animationLayer {
let iconSize: CGSize
switch reaction {
case .builtin:
iconSize = CGSize(width: floor(size.width * 2.0), height: floor(size.height * 2.0))
case .custom:
iconSize = size
}
transition.updateFrame(layer: animationLayer, frame: CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize))
}
}
public func updateIsAnimationHidden(isAnimationHidden: Bool, transition: ContainedViewLayoutTransition) {
if self.isAnimationHidden != isAnimationHidden {
self.isAnimationHidden = isAnimationHidden
if let animationLayer = self.animationLayer {
transition.updateAlpha(layer: animationLayer, alpha: isAnimationHidden ? 0.0 : 1.0)
}
}
}
private func reloadFile() {
guard let context = self.context, let file = self.file, let animationCache = self.animationCache, let animationRenderer = self.animationRenderer, let placeholderColor = self.placeholderColor, let size = self.size, let animateIdle = self.animateIdle, let reaction = self.reaction else {
return
}
self.animationLayer?.removeFromSuperlayer()
self.animationLayer = nil
let iconSize: CGSize
switch reaction {
case .builtin:
iconSize = CGSize(width: floor(size.width * 1.5), height: floor(size.height * 1.5))
case .custom:
iconSize = size
}
let animationLayer = InlineStickerItemLayer(
context: context,
attemptSynchronousLoad: false,
emoji: ChatTextInputTextCustomEmojiAttribute(
stickerPack: nil,
fileId: file.fileId.id,
file: file
),
file: file,
cache: animationCache,
renderer: animationRenderer,
placeholderColor: placeholderColor,
pointSize: CGSize(width: iconSize.width * 2.0, height: iconSize.height * 2.0)
)
self.animationLayer = animationLayer
self.layer.addSublayer(animationLayer)
animationLayer.frame = CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize)
animationLayer.isVisibleForAnimations = animateIdle
}
func reset() {
if let animationLayer = self.animationLayer {
self.animationLayer = nil
animationLayer.removeFromSuperlayer()
}
if let disposable = self.disposable {
self.disposable = nil
disposable.dispose()
}
self.context = nil
self.fileId = nil
self.file = nil
self.animationCache = nil
self.animationRenderer = nil
self.placeholderColor = nil
self.size = nil
self.animateIdle = nil
self.reaction = nil
self.isAnimationHidden = false
}
}
@@ -411,15 +556,13 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
let spacing: CGFloat = 2.0
let boundingImageSize = CGSize(width: 20.0, height: 20.0)
let imageSize: CGSize
if let file = spec.component.reaction.centerAnimation {
let imageSize: CGSize = boundingImageSize
/*if let file = spec.component.reaction.centerAnimation {
let defaultImageSize = CGSize(width: boundingImageSize.width + floor(boundingImageSize.width * 0.5 * 2.0), height: boundingImageSize.height + floor(boundingImageSize.height * 0.5 * 2.0))
imageSize = file.dimensions?.cgSize.aspectFitted(defaultImageSize) ?? defaultImageSize
} else if let file = spec.component.reaction.legacyIcon {
imageSize = file.dimensions?.cgSize.aspectFitted(boundingImageSize) ?? boundingImageSize
} else {
imageSize = boundingImageSize
}
}*/
var counterComponents: [String] = []
for character in countString(Int64(spec.component.count)) {
@@ -435,7 +578,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
}
#endif*/
let backgroundColor = spec.component.isSelected ? spec.component.colors.selectedBackground : spec.component.colors.deselectedBackground
let backgroundColor = spec.component.chosenOrder != nil ? spec.component.colors.selectedBackground : spec.component.colors.deselectedBackground
let imageFrame = CGRect(origin: CGPoint(x: sideInsets + floorToScreenPixels((boundingImageSize.width - imageSize.width) / 2.0), y: floorToScreenPixels((height - imageSize.height) / 2.0)), size: imageSize)
@@ -467,11 +610,11 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
}
let backgroundColors = ReactionButtonAsyncNode.ContainerButtonNode.Colors(
background: spec.component.isSelected ? spec.component.colors.selectedBackground : spec.component.colors.deselectedBackground,
foreground: spec.component.isSelected ? spec.component.colors.selectedForeground : spec.component.colors.deselectedForeground,
background: spec.component.chosenOrder != nil ? spec.component.colors.selectedBackground : spec.component.colors.deselectedBackground,
foreground: spec.component.chosenOrder != nil ? spec.component.colors.selectedForeground : spec.component.colors.deselectedForeground,
extractedBackground: spec.component.colors.extractedBackground,
extractedForeground: spec.component.colors.extractedForeground,
isSelected: spec.component.isSelected
isSelected: spec.component.chosenOrder != nil
)
var backgroundCounter: ReactionButtonAsyncNode.ContainerButtonNode.Counter?
if let counterLayout = counterLayout {
@@ -564,7 +707,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
}
func reset() {
self.iconView?.imageView.image = nil
self.iconView?.reset()
self.layout = nil
self.buttonNode.reset()
@@ -577,7 +720,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
layout.spec.component.action(layout.spec.component.reaction.value)
}
fileprivate func apply(layout: Layout, animation: ListViewItemUpdateAnimation) {
fileprivate func apply(layout: Layout, animation: ListViewItemUpdateAnimation, arguments: ReactionButtonsAsyncLayoutContainer.Arguments) {
self.containerView.frame = CGRect(origin: CGPoint(), size: layout.size)
self.containerView.contentView.frame = CGRect(origin: CGPoint(), size: layout.size)
self.containerView.contentRect = CGRect(origin: CGPoint(), size: layout.size)
@@ -587,10 +730,32 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
if let iconView = self.iconView {
animation.animator.updateFrame(layer: iconView.layer, frame: layout.imageFrame, completion: nil)
iconView.update(size: layout.imageFrame.size, transition: animation.transition)
if self.layout?.spec.component.reaction != layout.spec.component.reaction {
if let fileId = layout.spec.component.reaction.animationFileId ?? layout.spec.component.reaction.centerAnimation?.fileId.id {
let animateIdle: Bool
if case .custom = layout.spec.component.reaction.value {
animateIdle = true
} else {
animateIdle = false
}
iconView.update(
size: layout.imageFrame.size,
context: layout.spec.component.context,
file: layout.spec.component.reaction.centerAnimation,
fileId: fileId,
animationCache: arguments.animationCache,
animationRenderer: arguments.animationRenderer,
placeholderColor: .gray,
animateIdle: animateIdle,
reaction: layout.spec.component.reaction.value,
transition: animation.transition
)
}
/*if self.layout?.spec.component.reaction != layout.spec.component.reaction {
if let file = layout.spec.component.reaction.centerAnimation {
if let image = ReactionImageCache.shared.get(reaction: layout.spec.component.reaction.value) {
iconView.imageView.image = image
} else {
@@ -645,7 +810,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
strongSelf.iconView?.imageView.image = image
}))
}
}
}*/
}
if !layout.spec.component.avatarPeers.isEmpty {
@@ -683,7 +848,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
self.layout = layout
}
public static func asyncLayout(_ item: ReactionNodePool.Item?) -> (ReactionButtonComponent) -> (size: CGSize, apply: (_ animation: ListViewItemUpdateAnimation) -> ReactionNodePool.Item) {
public static func asyncLayout(_ item: ReactionNodePool.Item?) -> (ReactionButtonComponent) -> (size: CGSize, apply: (_ animation: ListViewItemUpdateAnimation, _ arguments: ReactionButtonsAsyncLayoutContainer.Arguments) -> ReactionNodePool.Item) {
let currentLayout = item?.view.layout
return { component in
@@ -696,7 +861,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
layout = Layout.calculate(spec: spec, currentLayout: currentLayout)
}
return (size: layout.size, apply: { animation in
return (size: layout.size, apply: { animation, arguments in
var animation = animation
let updatedItem: ReactionNodePool.Item
if let item = item {
@@ -706,7 +871,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
animation = .None
}
updatedItem.view.apply(layout: layout, animation: animation)
updatedItem.view.apply(layout: layout, animation: animation, arguments: arguments)
return updatedItem
})
@@ -718,12 +883,12 @@ public final class ReactionButtonComponent: Equatable {
public struct Reaction: Equatable {
public var value: MessageReaction.Reaction
public var centerAnimation: TelegramMediaFile?
public var legacyIcon: TelegramMediaFile?
public var animationFileId: Int64?
public init(value: MessageReaction.Reaction, centerAnimation: TelegramMediaFile?, legacyIcon: TelegramMediaFile?) {
public init(value: MessageReaction.Reaction, centerAnimation: TelegramMediaFile?, animationFileId: Int64?) {
self.value = value
self.centerAnimation = centerAnimation
self.legacyIcon = legacyIcon
self.animationFileId = animationFileId
}
public static func ==(lhs: Reaction, rhs: Reaction) -> Bool {
@@ -733,7 +898,7 @@ public final class ReactionButtonComponent: Equatable {
if lhs.centerAnimation?.fileId != rhs.centerAnimation?.fileId {
return false
}
if lhs.legacyIcon?.fileId != rhs.legacyIcon?.fileId {
if lhs.animationFileId != rhs.animationFileId {
return false
}
return true
@@ -770,7 +935,7 @@ public final class ReactionButtonComponent: Equatable {
public let reaction: Reaction
public let avatarPeers: [EnginePeer]
public let count: Int
public let isSelected: Bool
public let chosenOrder: Int?
public let action: (MessageReaction.Reaction) -> Void
public init(
@@ -779,7 +944,7 @@ public final class ReactionButtonComponent: Equatable {
reaction: Reaction,
avatarPeers: [EnginePeer],
count: Int,
isSelected: Bool,
chosenOrder: Int?,
action: @escaping (MessageReaction.Reaction) -> Void
) {
self.context = context
@@ -787,7 +952,7 @@ public final class ReactionButtonComponent: Equatable {
self.reaction = reaction
self.avatarPeers = avatarPeers
self.count = count
self.isSelected = isSelected
self.chosenOrder = chosenOrder
self.action = action
}
@@ -807,7 +972,7 @@ public final class ReactionButtonComponent: Equatable {
if lhs.count != rhs.count {
return false
}
if lhs.isSelected != rhs.isSelected {
if lhs.chosenOrder != rhs.chosenOrder {
return false
}
return true
@@ -858,22 +1023,35 @@ public final class ReactionNodePool {
}
public final class ReactionButtonsAsyncLayoutContainer {
public final class Arguments {
public let animationCache: AnimationCache
public let animationRenderer: MultiAnimationRenderer
public init(
animationCache: AnimationCache,
animationRenderer: MultiAnimationRenderer
) {
self.animationCache = animationCache
self.animationRenderer = animationRenderer
}
}
public struct Reaction {
public var reaction: ReactionButtonComponent.Reaction
public var count: Int
public var peers: [EnginePeer]
public var isSelected: Bool
public var chosenOrder: Int?
public init(
reaction: ReactionButtonComponent.Reaction,
count: Int,
peers: [EnginePeer],
isSelected: Bool
chosenOrder: Int?
) {
self.reaction = reaction
self.count = count
self.peers = peers
self.isSelected = isSelected
self.chosenOrder = chosenOrder
}
}
@@ -883,7 +1061,7 @@ public final class ReactionButtonsAsyncLayoutContainer {
}
public var items: [Item]
public var apply: (ListViewItemUpdateAnimation) -> ApplyResult
public var apply: (ListViewItemUpdateAnimation, Arguments) -> ApplyResult
}
public struct ApplyResult {
@@ -916,23 +1094,34 @@ public final class ReactionButtonsAsyncLayoutContainer {
constrainedWidth: CGFloat
) -> Result {
var items: [Result.Item] = []
var applyItems: [(key: MessageReaction.Reaction, size: CGSize, apply: (_ animation: ListViewItemUpdateAnimation) -> ReactionNodePool.Item)] = []
var applyItems: [(key: MessageReaction.Reaction, size: CGSize, apply: (_ animation: ListViewItemUpdateAnimation, _ arguments: Arguments) -> ReactionNodePool.Item)] = []
var validIds = Set<MessageReaction.Reaction>()
for reaction in reactions.sorted(by: { lhs, rhs in
var lhsCount = lhs.count
if lhs.isSelected {
if lhs.chosenOrder != nil {
lhsCount -= 1
}
var rhsCount = rhs.count
if rhs.isSelected {
if rhs.chosenOrder != nil {
rhsCount -= 1
}
if lhsCount != rhsCount {
return lhsCount > rhsCount
}
if (lhs.chosenOrder != nil) != (rhs.chosenOrder != nil) {
if lhs.chosenOrder != nil {
return true
} else {
return false
}
} else if let lhsIndex = lhs.chosenOrder, let rhsIndex = rhs.chosenOrder {
return lhsIndex < rhsIndex
}
return false
}) {
}).prefix(10) {
validIds.insert(reaction.reaction.value)
var avatarPeers = reaction.peers
@@ -952,7 +1141,7 @@ public final class ReactionButtonsAsyncLayoutContainer {
reaction: reaction.reaction,
avatarPeers: avatarPeers,
count: reaction.count,
isSelected: reaction.isSelected,
chosenOrder: reaction.chosenOrder,
action: action
))
@@ -977,10 +1166,10 @@ public final class ReactionButtonsAsyncLayoutContainer {
return Result(
items: items,
apply: { animation in
apply: { animation, arguments in
var items: [ApplyResult.Item] = []
for (key, size, apply) in applyItems {
let nodeItem = apply(animation)
let nodeItem = apply(animation, arguments)
items.append(ApplyResult.Item(value: key, node: nodeItem, size: size))
if let current = self.buttons[key] {