mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
151 lines
5.2 KiB
Swift
151 lines
5.2 KiB
Swift
import Foundation
|
|
import AsyncDisplayKit
|
|
import Postbox
|
|
import TelegramCore
|
|
import Display
|
|
import SwiftSignalKit
|
|
|
|
private let textFont = Font.regular(15.0)
|
|
|
|
private func generateDotsImage(color: UIColor) -> UIImage? {
|
|
var images: [UIImage] = []
|
|
let size = CGSize(width: 20.0, height: 10.0)
|
|
for i in 0 ..< 4 {
|
|
if let image = generateImage(size, rotatedContext: { size, context in
|
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
|
context.setFillColor(color.cgColor)
|
|
var string = ""
|
|
if i >= 1 {
|
|
for _ in 1 ... i {
|
|
string.append(".")
|
|
}
|
|
}
|
|
let attributedString = NSAttributedString(string: string, attributes: [.font: textFont, .foregroundColor: color])
|
|
UIGraphicsPushContext(context)
|
|
attributedString.draw(at: CGPoint(x: 1.0, y: -9.0))
|
|
UIGraphicsPopContext()
|
|
}) {
|
|
images.append(image)
|
|
}
|
|
}
|
|
return UIImage.animatedImage(with: images, duration: 0.6)
|
|
}
|
|
|
|
private final class ChatListInputActivitiesDotsNode: ASDisplayNode {
|
|
var image: UIImage? {
|
|
didSet {
|
|
if self.image !== oldValue {
|
|
if self.image != nil && self.isInHierarchy {
|
|
self.beginAnimation()
|
|
} else {
|
|
self.layer.removeAnimation(forKey: "image")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
override init() {
|
|
super.init()
|
|
|
|
self.isLayerBacked = true
|
|
}
|
|
|
|
private func beginAnimation() {
|
|
guard let images = self.image?.images else {
|
|
return
|
|
}
|
|
let animation = CAKeyframeAnimation(keyPath: "contents")
|
|
animation.values = images.map { $0.cgImage! }
|
|
animation.duration = 0.54
|
|
animation.repeatCount = Float.infinity
|
|
animation.calculationMode = kCAAnimationDiscrete
|
|
self.layer.add(animation, forKey: "image")
|
|
}
|
|
|
|
override func willEnterHierarchy() {
|
|
super.willEnterHierarchy()
|
|
|
|
self.beginAnimation()
|
|
}
|
|
|
|
override func didExitHierarchy() {
|
|
super.didExitHierarchy()
|
|
|
|
self.layer.removeAnimation(forKey: "image")
|
|
}
|
|
}
|
|
|
|
private let cachedImages = Atomic<[UInt32: UIImage]>(value: [:])
|
|
private func getDotsImage(color: UIColor) -> UIImage? {
|
|
let key = color.argb
|
|
let cached = cachedImages.with { dict -> UIImage? in
|
|
return dict[key]
|
|
}
|
|
if let cached = cached {
|
|
return cached
|
|
} else if let image = generateDotsImage(color: color) {
|
|
let _ = cachedImages.modify { dict in
|
|
var dict = dict
|
|
dict[key] = image
|
|
return dict
|
|
}
|
|
return image
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
final class ChatListInputActivitiesNode: ASDisplayNode {
|
|
private let textNode: TextNode
|
|
private let dotsNode: ChatListInputActivitiesDotsNode
|
|
|
|
override init() {
|
|
self.textNode = TextNode()
|
|
self.textNode.isLayerBacked = true
|
|
|
|
self.dotsNode = ChatListInputActivitiesDotsNode()
|
|
|
|
super.init()
|
|
|
|
self.addSubnode(self.textNode)
|
|
self.addSubnode(self.dotsNode)
|
|
}
|
|
|
|
func asyncLayout() -> (CGSize, PresentationStrings, UIColor, PeerId, [(Peer, PeerInputActivity)]) -> (CGSize, () -> Void) {
|
|
let makeTextLayout = TextNode.asyncLayout(self.textNode)
|
|
return { [weak self] boundingSize, strings, color, peerId, activities in
|
|
let string: NSAttributedString?
|
|
if !activities.isEmpty {
|
|
if activities.count == 1 {
|
|
if activities[0].0.id == peerId {
|
|
string = NSAttributedString(string: strings.DialogList_Typing, font: textFont, textColor: color)
|
|
} else {
|
|
string = NSAttributedString(string: strings.DialogList_SingleTypingSuffix(activities[0].0.compactDisplayTitle).0, font: textFont, textColor: color)
|
|
}
|
|
} else {
|
|
string = NSAttributedString(string: strings.DialogList_MultipleTypingSuffix(activities.count).0, font: textFont, textColor: color)
|
|
}
|
|
} else {
|
|
string = nil
|
|
}
|
|
let (textLayout, textApply) = makeTextLayout(string, nil, 1, .end, CGSize(width: boundingSize.width - 12.0, height: boundingSize.height), .left, nil, UIEdgeInsets(top: 1.0, left: 0.0, bottom: 1.0, right: 0.0))
|
|
|
|
let dots = getDotsImage(color: color)
|
|
|
|
return (boundingSize, {
|
|
if let strongSelf = self {
|
|
let _ = textApply()
|
|
strongSelf.textNode.frame = CGRect(origin: CGPoint(), size: textLayout.size)
|
|
|
|
if let dots = dots {
|
|
strongSelf.dotsNode.image = dots
|
|
let dotsSize = CGSize(width: 20.0, height: 10.0)
|
|
let dotsFrame = CGRect(origin: CGPoint(x: textLayout.size.width - 1.0, y: textLayout.size.height - dotsSize.height - 2.0), size: dotsSize)
|
|
strongSelf.dotsNode.frame = dotsFrame
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|