mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 22:25:57 +00:00
Reaction improvements
This commit is contained in:
@@ -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] {
|
||||
|
||||
Reference in New Issue
Block a user