Swiftgram/TelegramUI/ChatListTypingNode.swift
Peter Iakovlev 9bd9327502 Reduce layerBacked usage
Improved shuffling
2018-11-16 15:57:13 +04:00

205 lines
8.5 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()
}
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
animation.beginTime = 1.0
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.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 {
var commonKey: Int32? = activities[0].1.key
for i in 1 ..< activities.count {
if activities[i].1.key != commonKey {
commonKey = nil
break
}
}
if activities.count == 1 {
if activities[0].0.id == peerId {
let text: String
switch activities[0].1 {
case .uploadingVideo:
text = strings.Activity_UploadingVideo
case .uploadingInstantVideo:
text = strings.Activity_UploadingVideoMessage
case .uploadingPhoto:
text = strings.Activity_UploadingPhoto
case .uploadingFile:
text = strings.Activity_UploadingDocument
case .recordingVoice:
text = strings.Activity_RecordingAudio
case .recordingInstantVideo:
text = strings.Activity_RecordingVideoMessage
case .playingGame:
text = strings.Activity_PlayingGame
case .typingText:
text = strings.DialogList_Typing
}
string = NSAttributedString(string: text, font: textFont, textColor: color)
} else {
let text: String
if let _ = commonKey {
let peerTitle = activities[0].0.compactDisplayTitle
switch activities[0].1 {
case .uploadingVideo:
text = strings.DialogList_SingleUploadingVideoSuffix(peerTitle).0
case .uploadingInstantVideo:
text = strings.DialogList_SingleUploadingVideoSuffix(peerTitle).0
case .uploadingPhoto:
text = strings.DialogList_SingleUploadingPhotoSuffix(peerTitle).0
case .uploadingFile:
text = strings.DialogList_SingleUploadingFileSuffix(peerTitle).0
case .recordingVoice:
text = strings.DialogList_SingleRecordingAudioSuffix(peerTitle).0
case .recordingInstantVideo:
text = strings.DialogList_SingleRecordingVideoMessageSuffix(peerTitle).0
case .playingGame:
text = strings.DialogList_SinglePlayingGameSuffix(peerTitle).0
case .typingText:
text = strings.DialogList_SingleTypingSuffix(peerTitle).0
}
} else {
text = activities[0].0.compactDisplayTitle
}
string = NSAttributedString(string: text, font: textFont, textColor: color)
}
} else {
if activities.count > 1 {
let peerTitle = activities[0].0.compactDisplayTitle
string = NSAttributedString(string: strings.DialogList_MultipleTyping(peerTitle, strings.DialogList_MultipleTypingSuffix(activities.count - 1).0).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(TextNodeLayoutArguments(attributedString: string, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: boundingSize.width - 12.0, height: boundingSize.height), alignment: .left, cutout: nil, insets: 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
}
}
})
}
}
}