mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-01 04:08:07 +00:00
198 lines
6.8 KiB
Swift
198 lines
6.8 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import Display
|
|
import ComponentFlow
|
|
import TelegramCore
|
|
import AvatarNode
|
|
import AppBundle
|
|
import AccountContext
|
|
import HierarchyTrackingLayer
|
|
|
|
private func makePeerBadgeImage(engine: TelegramEngine, peer: EnginePeer, count: Int) async -> UIImage {
|
|
let avatarSize: CGFloat = 16.0
|
|
let avatarInset: CGFloat = 2.0
|
|
let avatarIconSpacing: CGFloat = 2.0
|
|
let iconTextSpacing: CGFloat = 2.0
|
|
let iconSize: CGFloat = 8.0
|
|
let rightInset: CGFloat = 4.0
|
|
|
|
let text = NSAttributedString(string: "\(count)", font: Font.semibold(10.0), textColor: .white)
|
|
var textSize = text.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil).size
|
|
textSize.width = ceil(textSize.width)
|
|
textSize.height = ceil(textSize.height)
|
|
|
|
let size = CGSize(width: avatarInset + avatarSize + avatarIconSpacing + iconSize + iconTextSpacing + textSize.height + rightInset, height: avatarSize + avatarInset * 2.0)
|
|
return generateImage(size, rotatedContext: { size, context in
|
|
UIGraphicsPushContext(context)
|
|
defer {
|
|
UIGraphicsPopContext()
|
|
}
|
|
|
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
|
context.setFillColor(UIColor(rgb: 0xFFB10D).cgColor)
|
|
context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), cornerRadius: size.height * 0.5).cgPath)
|
|
context.fillPath()
|
|
|
|
text.draw(at: CGPoint(x: avatarInset + avatarSize + avatarIconSpacing + iconSize + iconTextSpacing, y: floorToScreenPixels((size.height - textSize.height) * 0.5)))
|
|
})!
|
|
}
|
|
|
|
private actor LiveChatReactionItemTaskQueue {
|
|
private final class PeerTask {
|
|
let peer: EnginePeer
|
|
let count: Int
|
|
let completion: (UIImage) -> Void
|
|
|
|
init(peer: EnginePeer, count: Int, completion: @escaping (UIImage) -> Void) {
|
|
self.peer = peer
|
|
self.count = count
|
|
self.completion = completion
|
|
}
|
|
}
|
|
|
|
private let engine: TelegramEngine
|
|
private var tasks: [PeerTask] = []
|
|
|
|
init(engine: TelegramEngine) {
|
|
self.engine = engine
|
|
}
|
|
|
|
func add(peer: EnginePeer, count: Int, completion: @escaping (UIImage) -> Void) {
|
|
self.tasks.append(PeerTask(peer: peer, count: count, completion: completion))
|
|
if self.tasks.count == 1 {
|
|
Task {
|
|
await processTasks()
|
|
}
|
|
}
|
|
}
|
|
|
|
private func processTasks() async {
|
|
while !self.tasks.isEmpty {
|
|
let task = self.tasks.removeFirst()
|
|
let image = await makePeerBadgeImage(engine: self.engine, peer: task.peer, count: task.count)
|
|
task.completion(image)
|
|
}
|
|
}
|
|
}
|
|
|
|
final class LiveChatReactionStreamView: UIView {
|
|
private final class ItemLayer: SimpleLayer {
|
|
init(image: UIImage) {
|
|
super.init()
|
|
|
|
self.contents = image.cgImage
|
|
}
|
|
|
|
override init(layer: Any) {
|
|
super.init(layer: layer)
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
}
|
|
|
|
private var nextId: Int = 0
|
|
private var itemLayers: [Int: ItemLayer] = [:]
|
|
private let itemLayerContainer: SimpleLayer
|
|
private let hierarchyTracker: HierarchyTrackingLayer
|
|
private var previousTimestamp: Double = 0.0
|
|
private var displayLink: SharedDisplayLinkDriver.Link?
|
|
private var previousPhysicsTimestamp: Double = 0.0
|
|
|
|
private let taskQueue: LiveChatReactionItemTaskQueue
|
|
|
|
init(context: AccountContext) {
|
|
self.itemLayerContainer = SimpleLayer()
|
|
self.hierarchyTracker = HierarchyTrackingLayer()
|
|
self.taskQueue = LiveChatReactionItemTaskQueue(engine: context.engine)
|
|
|
|
super.init(frame: CGRect())
|
|
|
|
self.layer.addSublayer(self.itemLayerContainer)
|
|
|
|
self.layer.addSublayer(self.hierarchyTracker)
|
|
self.hierarchyTracker.isInHierarchyUpdated = { [weak self] inHierarchy in
|
|
guard let self else {
|
|
return
|
|
}
|
|
if inHierarchy {
|
|
if self.displayLink == nil {
|
|
self.displayLink = SharedDisplayLinkDriver.shared.add(framesPerSecond: .max, { [weak self] _ in
|
|
guard let self else {
|
|
return
|
|
}
|
|
self.updatePhysics()
|
|
})
|
|
}
|
|
} else {
|
|
self.displayLink = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
func add(peer: EnginePeer, count: Int) {
|
|
if !self.hierarchyTracker.isInHierarchy {
|
|
return
|
|
}
|
|
let timestamp = CFAbsoluteTimeGetCurrent()
|
|
if timestamp < self.previousTimestamp + 1.0 / 30.0 {
|
|
return
|
|
}
|
|
self.previousTimestamp = timestamp
|
|
Task {
|
|
await self.taskQueue.add(peer: peer, count: count, completion: { [weak self] image in
|
|
Task { @MainActor in
|
|
guard let self else {
|
|
return
|
|
}
|
|
self.addRenderedItem(image: image)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
private func addRenderedItem(image: UIImage) {
|
|
if "".isEmpty {
|
|
return
|
|
}
|
|
|
|
let id = self.nextId
|
|
self.nextId += 1
|
|
|
|
let itemLayer = ItemLayer(image: image)
|
|
itemLayer.frame = CGRect(origin: CGPoint(x: -image.size.width - 10.0, y: -image.size.height * 0.5), size: image.size)
|
|
self.itemLayers[id] = itemLayer
|
|
self.itemLayerContainer.addSublayer(itemLayer)
|
|
|
|
let transition = ComponentTransition(animation: .curve(duration: 2.0, curve: .linear))
|
|
transition.setPosition(layer: itemLayer, position: CGPoint(x: itemLayer.position.x, y: -300.0))
|
|
|
|
itemLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, delay: 2.0 - 0.18, removeOnCompletion: false, completion: { [weak self] _ in
|
|
guard let self else {
|
|
return
|
|
}
|
|
if let itemLayer = self.itemLayers[id] {
|
|
self.itemLayers.removeValue(forKey: id)
|
|
itemLayer.removeFromSuperlayer()
|
|
}
|
|
})
|
|
}
|
|
|
|
private func updatePhysics() {
|
|
let timestamp = CACurrentMediaTime()
|
|
let dt = max(1.0 / 120.0, min(1.0 / 30.0, timestamp - self.previousPhysicsTimestamp))
|
|
self.previousPhysicsTimestamp = timestamp
|
|
|
|
let _ = dt
|
|
}
|
|
|
|
func update(size: CGSize, sourcePoint: CGPoint, transition: ComponentTransition) {
|
|
transition.setFrame(layer: self.itemLayerContainer, frame: CGRect(origin: sourcePoint, size: CGSize()))
|
|
}
|
|
}
|