mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 22:25:57 +00:00
Temp: reaction improvements
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import SwiftSignalKit
|
||||
@@ -8,6 +9,426 @@ import AccountContext
|
||||
import TelegramPresentationData
|
||||
import UIKit
|
||||
import WebPBinding
|
||||
import AnimatedAvatarSetNode
|
||||
|
||||
private final class NullActionClass: NSObject, CAAction {
|
||||
@objc func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable : Any]?) {
|
||||
}
|
||||
}
|
||||
|
||||
private let nullAction = NullActionClass()
|
||||
|
||||
private final class SimpleLayer: CALayer {
|
||||
override func action(forKey event: String) -> CAAction? {
|
||||
return nullAction
|
||||
}
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
override init(layer: Any) {
|
||||
super.init(layer: layer)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate final class CounterLayer: CALayer {
|
||||
fileprivate final class Layout {
|
||||
struct Spec: Equatable {
|
||||
let clippingHeight: CGFloat
|
||||
var stringComponents: [String]
|
||||
var backgroundColor: UInt32
|
||||
var foregroundColor: UInt32
|
||||
}
|
||||
|
||||
let spec: Spec
|
||||
let size: CGSize
|
||||
|
||||
let image: UIImage
|
||||
|
||||
init(
|
||||
spec: Spec,
|
||||
size: CGSize,
|
||||
image: UIImage
|
||||
) {
|
||||
self.spec = spec
|
||||
self.size = size
|
||||
self.image = image
|
||||
}
|
||||
|
||||
static func calculate(spec: Spec, previousLayout: Layout?) -> Layout {
|
||||
let image: UIImage
|
||||
if let previousLayout = previousLayout, previousLayout.spec == spec {
|
||||
image = previousLayout.image
|
||||
} else {
|
||||
let textColor = UIColor(argb: spec.foregroundColor)
|
||||
let string = NSAttributedString(string: spec.stringComponents.joined(separator: ""), font: Font.medium(11.0), textColor: textColor)
|
||||
let boundingRect = string.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil)
|
||||
image = generateImage(CGSize(width: boundingRect.size.width, height: spec.clippingHeight), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
/*context.setFillColor(UIColor(argb: spec.backgroundColor).cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
if textColor.alpha < 1.0 {
|
||||
context.setBlendMode(.copy)
|
||||
}*/
|
||||
context.translateBy(x: 0.0, y: (size.height - boundingRect.size.height) / 2.0)
|
||||
UIGraphicsPushContext(context)
|
||||
string.draw(at: CGPoint())
|
||||
UIGraphicsPopContext()
|
||||
})!
|
||||
}
|
||||
|
||||
return Layout(
|
||||
spec: spec,
|
||||
size: image.size,
|
||||
image: image
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var layout: Layout?
|
||||
|
||||
override init(layer: Any) {
|
||||
super.init(layer: layer)
|
||||
}
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
self.masksToBounds = true
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func action(forKey event: String) -> CAAction? {
|
||||
return nullAction
|
||||
}
|
||||
|
||||
func apply(layout: Layout, animation: ListViewItemUpdateAnimation) {
|
||||
/*if animation.isAnimated, let previousContents = self.contents {
|
||||
self.animate(from: previousContents as! CGImage, to: layout.image.cgImage!, keyPath: "contents", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.2)
|
||||
} else {*/
|
||||
self.contents = layout.image.cgImage
|
||||
//}
|
||||
|
||||
self.layout = layout
|
||||
}
|
||||
}
|
||||
|
||||
public final class ReactionButtonAsyncView: UIButton {
|
||||
fileprivate final class Layout {
|
||||
struct Spec: Equatable {
|
||||
var component: ReactionButtonComponent
|
||||
}
|
||||
|
||||
let spec: Spec
|
||||
|
||||
let backgroundColor: UInt32
|
||||
let clippingHeight: CGFloat
|
||||
let sideInsets: CGFloat
|
||||
|
||||
let imageFrame: CGRect
|
||||
|
||||
let counter: CounterLayer.Layout?
|
||||
let counterFrame: CGRect?
|
||||
|
||||
let backgroundImage: UIImage
|
||||
|
||||
let size: CGSize
|
||||
|
||||
init(
|
||||
spec: Spec,
|
||||
backgroundColor: UInt32,
|
||||
clippingHeight: CGFloat,
|
||||
sideInsets: CGFloat,
|
||||
imageFrame: CGRect,
|
||||
counter: CounterLayer.Layout?,
|
||||
counterFrame: CGRect?,
|
||||
backgroundImage: UIImage,
|
||||
size: CGSize
|
||||
) {
|
||||
self.spec = spec
|
||||
self.backgroundColor = backgroundColor
|
||||
self.clippingHeight = clippingHeight
|
||||
self.sideInsets = sideInsets
|
||||
self.imageFrame = imageFrame
|
||||
self.counter = counter
|
||||
self.counterFrame = counterFrame
|
||||
self.backgroundImage = backgroundImage
|
||||
self.size = size
|
||||
}
|
||||
|
||||
static func calculate(spec: Spec, currentLayout: Layout?, currentCounter: CounterLayer.Layout?) -> Layout {
|
||||
let clippingHeight: CGFloat = 22.0
|
||||
let sideInsets: CGFloat = 8.0
|
||||
let height: CGFloat = 30.0
|
||||
let spacing: CGFloat = 4.0
|
||||
|
||||
let defaultImageSize = CGSize(width: 22.0, height: 22.0)
|
||||
let imageSize: CGSize
|
||||
if let file = spec.component.reaction.iconFile {
|
||||
imageSize = file.dimensions?.cgSize.aspectFitted(defaultImageSize) ?? defaultImageSize
|
||||
} else {
|
||||
imageSize = defaultImageSize
|
||||
}
|
||||
|
||||
var counterComponents: [String] = []
|
||||
for character in "\(spec.component.count)" {
|
||||
counterComponents.append(String(character))
|
||||
}
|
||||
|
||||
let backgroundColor = spec.component.isSelected ? spec.component.colors.selectedBackground : spec.component.colors.deselectedBackground
|
||||
|
||||
let imageFrame = CGRect(origin: CGPoint(x: sideInsets, y: floorToScreenPixels((height - imageSize.height) / 2.0)), size: imageSize)
|
||||
|
||||
var previousDisplayCounter: String?
|
||||
if let currentLayout = currentLayout {
|
||||
if currentLayout.spec.component.avatarPeers.isEmpty {
|
||||
previousDisplayCounter = "\(spec.component.count)"
|
||||
}
|
||||
}
|
||||
var currentDisplayCounter: String?
|
||||
if spec.component.avatarPeers.isEmpty {
|
||||
currentDisplayCounter = "\(spec.component.count)"
|
||||
}
|
||||
|
||||
let backgroundImage: UIImage
|
||||
if let currentLayout = currentLayout, currentLayout.spec.component.isSelected == spec.component.isSelected, currentLayout.spec.component.colors == spec.component.colors, previousDisplayCounter == currentDisplayCounter {
|
||||
backgroundImage = currentLayout.backgroundImage
|
||||
} else {
|
||||
backgroundImage = generateImage(CGSize(width: height + 18.0, height: height), rotatedContext: { size, context in
|
||||
UIGraphicsPushContext(context)
|
||||
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setBlendMode(.copy)
|
||||
|
||||
context.setFillColor(UIColor(argb: backgroundColor).cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: height, height: height)))
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - height, y: 0.0), size: CGSize(width: height, height: size.height)))
|
||||
context.fill(CGRect(origin: CGPoint(x: height / 2.0, y: 0.0), size: CGSize(width: size.width - height, height: size.height)))
|
||||
|
||||
context.setBlendMode(.normal)
|
||||
|
||||
if let currentDisplayCounter = currentDisplayCounter {
|
||||
let textColor = UIColor(argb: spec.component.isSelected ? spec.component.colors.selectedForeground : spec.component.colors.deselectedForeground)
|
||||
let string = NSAttributedString(string: currentDisplayCounter, font: Font.medium(11.0), textColor: textColor)
|
||||
let boundingRect = string.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil)
|
||||
if textColor.alpha < 1.0 {
|
||||
context.setBlendMode(.copy)
|
||||
}
|
||||
string.draw(at: CGPoint(x: size.width - sideInsets - boundingRect.width, y: (size.height - boundingRect.height) / 2.0))
|
||||
}
|
||||
|
||||
UIGraphicsPopContext()
|
||||
})!.stretchableImage(withLeftCapWidth: Int(height / 2.0), topCapHeight: Int(height / 2.0))
|
||||
}
|
||||
|
||||
var counter: CounterLayer.Layout?
|
||||
var counterFrame: CGRect?
|
||||
|
||||
var size = CGSize(width: imageSize.width + sideInsets * 2.0, height: height)
|
||||
if !spec.component.avatarPeers.isEmpty {
|
||||
size.width += 4.0 + 24.0
|
||||
if spec.component.avatarPeers.count > 1 {
|
||||
size.width += CGFloat(spec.component.avatarPeers.count - 1) * 12.0
|
||||
} else {
|
||||
size.width -= 2.0
|
||||
}
|
||||
} else {
|
||||
let counterSpec = CounterLayer.Layout.Spec(
|
||||
clippingHeight: clippingHeight,
|
||||
stringComponents: counterComponents,
|
||||
backgroundColor: backgroundColor,
|
||||
foregroundColor: spec.component.isSelected ? spec.component.colors.selectedForeground : spec.component.colors.deselectedForeground
|
||||
)
|
||||
let counterValue: CounterLayer.Layout
|
||||
if let currentCounter = currentCounter, currentCounter.spec == counterSpec {
|
||||
counterValue = currentCounter
|
||||
} else {
|
||||
counterValue = CounterLayer.Layout.calculate(
|
||||
spec: counterSpec,
|
||||
previousLayout: currentCounter
|
||||
)
|
||||
}
|
||||
counter = counterValue
|
||||
size.width += spacing + counterValue.size.width
|
||||
counterFrame = CGRect(origin: CGPoint(x: sideInsets + imageSize.width + spacing, y: floorToScreenPixels((height - counterValue.size.height) / 2.0)), size: counterValue.size)
|
||||
}
|
||||
|
||||
return Layout(
|
||||
spec: spec,
|
||||
backgroundColor: backgroundColor,
|
||||
clippingHeight: clippingHeight,
|
||||
sideInsets: sideInsets,
|
||||
imageFrame: imageFrame,
|
||||
counter: counter,
|
||||
counterFrame: counterFrame,
|
||||
backgroundImage: backgroundImage,
|
||||
size: size
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private var layout: Layout?
|
||||
|
||||
public let iconView: UIImageView
|
||||
private var counterLayer: CounterLayer?
|
||||
private var avatarsView: AnimatedAvatarSetView?
|
||||
|
||||
private let iconImageDisposable = MetaDisposable()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.iconView = UIImageView()
|
||||
self.iconView.isUserInteractionEnabled = false
|
||||
|
||||
super.init(frame: CGRect())
|
||||
|
||||
self.addSubview(self.iconView)
|
||||
|
||||
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.iconImageDisposable.dispose()
|
||||
}
|
||||
|
||||
@objc private func pressed() {
|
||||
guard let layout = self.layout else {
|
||||
return
|
||||
}
|
||||
layout.spec.component.action(layout.spec.component.reaction.value)
|
||||
}
|
||||
|
||||
fileprivate func apply(layout: Layout, animation: ListViewItemUpdateAnimation) {
|
||||
let backgroundCapInsets = layout.backgroundImage.capInsets
|
||||
if backgroundCapInsets.left.isZero && backgroundCapInsets.top.isZero {
|
||||
self.layer.contentsScale = layout.backgroundImage.scale
|
||||
self.layer.contents = layout.backgroundImage.cgImage
|
||||
} else {
|
||||
ASDisplayNodeSetResizableContents(self.layer, layout.backgroundImage)
|
||||
}
|
||||
|
||||
animation.animator.updateFrame(layer: self.iconView.layer, frame: layout.imageFrame, completion: nil)
|
||||
|
||||
if self.layout?.spec.component.reaction != layout.spec.component.reaction {
|
||||
if let file = layout.spec.component.reaction.iconFile {
|
||||
self.iconImageDisposable.set((layout.spec.component.context.account.postbox.mediaBox.resourceData(file.resource)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] data in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
if data.complete, let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) {
|
||||
if let image = WebP.convert(fromWebP: dataValue) {
|
||||
strongSelf.iconView.image = image
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
if let counter = layout.counter, let counterFrame = layout.counterFrame {
|
||||
let counterLayer: CounterLayer
|
||||
var counterAnimation = animation
|
||||
if let current = self.counterLayer {
|
||||
counterLayer = current
|
||||
} else {
|
||||
counterAnimation = .None
|
||||
counterLayer = CounterLayer()
|
||||
self.counterLayer = counterLayer
|
||||
//self.layer.addSublayer(counterLayer)
|
||||
if animation.isAnimated {
|
||||
counterLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
counterAnimation.animator.updateFrame(layer: counterLayer, frame: counterFrame, completion: nil)
|
||||
counterLayer.apply(layout: counter, animation: counterAnimation)
|
||||
} else if let counterLayer = self.counterLayer {
|
||||
self.counterLayer = nil
|
||||
if animation.isAnimated {
|
||||
animation.animator.updateAlpha(layer: counterLayer, alpha: 0.0, completion: { [weak counterLayer] _ in
|
||||
counterLayer?.removeFromSuperlayer()
|
||||
})
|
||||
} else {
|
||||
counterLayer.removeFromSuperlayer()
|
||||
}
|
||||
}
|
||||
|
||||
if !layout.spec.component.avatarPeers.isEmpty {
|
||||
let avatarsView: AnimatedAvatarSetView
|
||||
if let current = self.avatarsView {
|
||||
avatarsView = current
|
||||
} else {
|
||||
avatarsView = AnimatedAvatarSetView()
|
||||
avatarsView.isUserInteractionEnabled = false
|
||||
self.avatarsView = avatarsView
|
||||
self.addSubview(avatarsView)
|
||||
}
|
||||
let content = AnimatedAvatarSetContext().update(peers: layout.spec.component.avatarPeers, animated: false)
|
||||
let avatarsSize = avatarsView.update(
|
||||
context: layout.spec.component.context,
|
||||
content: content,
|
||||
itemSize: CGSize(width: 24.0, height: 24.0),
|
||||
customSpacing: 10.0,
|
||||
animation: animation,
|
||||
synchronousLoad: false
|
||||
)
|
||||
animation.animator.updateFrame(layer: avatarsView.layer, frame: CGRect(origin: CGPoint(x: layout.imageFrame.maxX + 4.0, y: floorToScreenPixels((layout.size.height - avatarsSize.height) / 2.0)), size: CGSize(width: avatarsSize.width, height: avatarsSize.height)), completion: nil)
|
||||
} else if let avatarsView = self.avatarsView {
|
||||
self.avatarsView = nil
|
||||
if animation.isAnimated {
|
||||
animation.animator.updateAlpha(layer: avatarsView.layer, alpha: 0.0, completion: { [weak avatarsView] _ in
|
||||
avatarsView?.removeFromSuperview()
|
||||
})
|
||||
animation.animator.updateScale(layer: avatarsView.layer, scale: 0.01, completion: nil)
|
||||
} else {
|
||||
avatarsView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
self.layout = layout
|
||||
}
|
||||
|
||||
public static func asyncLayout(_ view: ReactionButtonAsyncView?) -> (ReactionButtonComponent) -> (size: CGSize, apply: (_ animation: ListViewItemUpdateAnimation) -> ReactionButtonAsyncView) {
|
||||
let currentLayout = view?.layout
|
||||
|
||||
return { component in
|
||||
let spec = Layout.Spec(component: component)
|
||||
|
||||
let layout: Layout
|
||||
if let currentLayout = currentLayout, currentLayout.spec == spec {
|
||||
layout = currentLayout
|
||||
} else {
|
||||
layout = Layout.calculate(spec: spec, currentLayout: currentLayout, currentCounter: currentLayout?.counter)
|
||||
}
|
||||
|
||||
return (size: layout.size, apply: { animation in
|
||||
var animation = animation
|
||||
let updatedView: ReactionButtonAsyncView
|
||||
if let view = view {
|
||||
updatedView = view
|
||||
} else {
|
||||
updatedView = ReactionButtonAsyncView()
|
||||
animation = .None
|
||||
}
|
||||
|
||||
updatedView.apply(layout: layout, animation: animation)
|
||||
|
||||
return updatedView
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class ReactionButtonComponent: Component {
|
||||
public struct ViewTag: Equatable {
|
||||
@@ -60,6 +481,7 @@ public final class ReactionButtonComponent: Component {
|
||||
public let context: AccountContext
|
||||
public let colors: Colors
|
||||
public let reaction: Reaction
|
||||
public let avatarPeers: [EnginePeer]
|
||||
public let count: Int
|
||||
public let isSelected: Bool
|
||||
public let action: (String) -> Void
|
||||
@@ -68,6 +490,7 @@ public final class ReactionButtonComponent: Component {
|
||||
context: AccountContext,
|
||||
colors: Colors,
|
||||
reaction: Reaction,
|
||||
avatarPeers: [EnginePeer],
|
||||
count: Int,
|
||||
isSelected: Bool,
|
||||
action: @escaping (String) -> Void
|
||||
@@ -75,6 +498,7 @@ public final class ReactionButtonComponent: Component {
|
||||
self.context = context
|
||||
self.colors = colors
|
||||
self.reaction = reaction
|
||||
self.avatarPeers = avatarPeers
|
||||
self.count = count
|
||||
self.isSelected = isSelected
|
||||
self.action = action
|
||||
@@ -90,6 +514,9 @@ public final class ReactionButtonComponent: Component {
|
||||
if lhs.reaction != rhs.reaction {
|
||||
return false
|
||||
}
|
||||
if lhs.avatarPeers != rhs.avatarPeers {
|
||||
return false
|
||||
}
|
||||
if lhs.count != rhs.count {
|
||||
return false
|
||||
}
|
||||
@@ -247,19 +674,136 @@ public final class ReactionButtonComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
public final class ReactionButtonsAsyncLayoutContainer {
|
||||
public struct Result {
|
||||
public struct Item {
|
||||
public var size: CGSize
|
||||
}
|
||||
|
||||
public var items: [Item]
|
||||
public var apply: (ListViewItemUpdateAnimation) -> ApplyResult
|
||||
}
|
||||
|
||||
public struct ApplyResult {
|
||||
public struct Item {
|
||||
public var value: String
|
||||
public var view: ReactionButtonAsyncView
|
||||
public var size: CGSize
|
||||
}
|
||||
|
||||
public var items: [Item]
|
||||
public var removedViews: [ReactionButtonAsyncView]
|
||||
}
|
||||
|
||||
public private(set) var buttons: [String: ReactionButtonAsyncView] = [:]
|
||||
|
||||
public init() {
|
||||
}
|
||||
|
||||
public func update(
|
||||
context: AccountContext,
|
||||
action: @escaping (String) -> Void,
|
||||
reactions: [ReactionButtonsLayoutContainer.Reaction],
|
||||
colors: ReactionButtonComponent.Colors,
|
||||
constrainedWidth: CGFloat
|
||||
) -> Result {
|
||||
var items: [Result.Item] = []
|
||||
var applyItems: [(key: String, size: CGSize, apply: (_ animation: ListViewItemUpdateAnimation) -> ReactionButtonAsyncView)] = []
|
||||
|
||||
var validIds = Set<String>()
|
||||
for reaction in reactions.sorted(by: { lhs, rhs in
|
||||
var lhsCount = lhs.count
|
||||
if lhs.isSelected {
|
||||
lhsCount -= 1
|
||||
}
|
||||
var rhsCount = rhs.count
|
||||
if rhs.isSelected {
|
||||
rhsCount -= 1
|
||||
}
|
||||
if lhsCount != rhsCount {
|
||||
return lhsCount > rhsCount
|
||||
}
|
||||
return lhs.reaction.value < rhs.reaction.value
|
||||
}) {
|
||||
validIds.insert(reaction.reaction.value)
|
||||
|
||||
var avatarPeers = reaction.peers
|
||||
for i in 0 ..< avatarPeers.count {
|
||||
if avatarPeers[i].id == context.account.peerId {
|
||||
let peer = avatarPeers[i]
|
||||
avatarPeers.remove(at: i)
|
||||
avatarPeers.insert(peer, at: 0)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let viewLayout = ReactionButtonAsyncView.asyncLayout(self.buttons[reaction.reaction.value])
|
||||
let (size, apply) = viewLayout(ReactionButtonComponent(
|
||||
context: context,
|
||||
colors: colors,
|
||||
reaction: reaction.reaction,
|
||||
avatarPeers: avatarPeers,
|
||||
count: reaction.count,
|
||||
isSelected: reaction.isSelected,
|
||||
action: action
|
||||
))
|
||||
|
||||
items.append(Result.Item(
|
||||
size: size
|
||||
))
|
||||
applyItems.append((reaction.reaction.value, size, apply))
|
||||
}
|
||||
|
||||
var removeIds: [String] = []
|
||||
for (id, _) in self.buttons {
|
||||
if !validIds.contains(id) {
|
||||
removeIds.append(id)
|
||||
}
|
||||
}
|
||||
var removedViews: [ReactionButtonAsyncView] = []
|
||||
for id in removeIds {
|
||||
if let view = self.buttons.removeValue(forKey: id) {
|
||||
removedViews.append(view)
|
||||
}
|
||||
}
|
||||
|
||||
return Result(
|
||||
items: items,
|
||||
apply: { animation in
|
||||
var items: [ApplyResult.Item] = []
|
||||
for (key, size, apply) in applyItems {
|
||||
let view = apply(animation)
|
||||
items.append(ApplyResult.Item(value: key, view: view, size: size))
|
||||
|
||||
if let current = self.buttons[key] {
|
||||
assert(current === view)
|
||||
} else {
|
||||
self.buttons[key] = view
|
||||
}
|
||||
}
|
||||
|
||||
return ApplyResult(items: items, removedViews: removedViews)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public final class ReactionButtonsLayoutContainer {
|
||||
public struct Reaction {
|
||||
public var reaction: ReactionButtonComponent.Reaction
|
||||
public var count: Int
|
||||
public var peers: [EnginePeer]
|
||||
public var isSelected: Bool
|
||||
|
||||
public init(
|
||||
reaction: ReactionButtonComponent.Reaction,
|
||||
count: Int,
|
||||
peers: [EnginePeer],
|
||||
isSelected: Bool
|
||||
) {
|
||||
self.reaction = reaction
|
||||
self.count = count
|
||||
self.peers = peers
|
||||
self.isSelected = isSelected
|
||||
}
|
||||
}
|
||||
@@ -322,6 +866,7 @@ public final class ReactionButtonsLayoutContainer {
|
||||
context: context,
|
||||
colors: colors,
|
||||
reaction: reaction.reaction,
|
||||
avatarPeers: reaction.peers,
|
||||
count: reaction.count,
|
||||
isSelected: reaction.isSelected,
|
||||
action: action
|
||||
|
||||
Reference in New Issue
Block a user