Swiftgram/TelegramUI/ChatListTypingNode.swift
Ilya Laktyushin 5167b609bc Updated activity indicator animations
Disabled video pre-download on iOS < 10.3
2019-02-23 01:33:55 +04:00

218 lines
9.3 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 activityNode: ChatTitleActivityNode
override init() {
self.activityNode = ChatTitleActivityNode()
super.init()
self.addSubnode(self.activityNode)
}
func asyncLayout() -> (CGSize, PresentationStrings, UIColor, PeerId, [(Peer, PeerInputActivity)]) -> (CGSize, () -> Void) {
return { [weak self] boundingSize, strings, color, peerId, activities in
var state = ChatTitleActivityNodeState.none
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
}
let string = NSAttributedString(string: text, font: textFont, textColor: color)
switch activities[0].1 {
case .typingText:
state = .typingText(string, color)
case .recordingVoice:
state = .recordingVoice(string, color)
case .recordingInstantVideo:
state = .recordingVideo(string, color)
case .uploadingFile, .uploadingInstantVideo, .uploadingPhoto, .uploadingVideo:
state = .uploading(string, color)
case .playingGame:
state = .playingGame(string, 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
}
let string = NSAttributedString(string: text, font: textFont, textColor: color)
switch activities[0].1 {
case .typingText:
state = .typingText(string, color)
case .recordingVoice:
state = .recordingVoice(string, color)
case .recordingInstantVideo:
state = .recordingVideo(string, color)
case .uploadingFile, .uploadingInstantVideo, .uploadingPhoto, .uploadingVideo:
state = .uploading(string, color)
case .playingGame:
state = .playingGame(string, color)
}
}
} else {
let string: NSAttributedString
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)
}
state = .typingText(string, color)
}
}
return (boundingSize, {
if let strongSelf = self {
let _ = strongSelf.activityNode.transitionToState(state, animation: .none)
let size = strongSelf.activityNode.updateLayout(CGSize(width: boundingSize.width - 12.0, height: boundingSize.height), alignment: .left)
strongSelf.activityNode.frame = CGRect(origin: CGPoint(x: -3.0, y: 1.0), size: size)
}
})
}
}
}