Entity input: improved animation cache and rendering

This commit is contained in:
Ali
2022-06-24 02:06:02 +01:00
parent 0f1b382265
commit c112bc5146
37 changed files with 2557 additions and 383 deletions

View File

@@ -11,25 +11,39 @@ import TelegramCore
import Postbox
import AnimationCache
import LottieAnimationCache
import VideoAnimationCache
import MultiAnimationRenderer
import ShimmerEffect
import TextFormat
public final class InlineStickerItemLayer: MultiAnimationRenderTarget {
public static let queue = Queue()
public struct Key: Hashable {
public var id: MediaId
public var id: Int64
public var index: Int
public init(id: MediaId, index: Int) {
public init(id: Int64, index: Int) {
self.id = id
self.index = index
}
}
private let file: TelegramMediaFile
private let context: AccountContext
private let groupId: String
private let emoji: ChatTextInputTextCustomEmojiAttribute
private let cache: AnimationCache
private let renderer: MultiAnimationRenderer
private let placeholderColor: UIColor
private let pointSize: CGSize
private let pixelSize: CGSize
private var file: TelegramMediaFile?
private var infoDisposable: Disposable?
private var disposable: Disposable?
private var fetchDisposable: Disposable?
private var loadDisposable: Disposable?
private var isInHierarchyValue: Bool = false
public var isVisibleForAnimations: Bool = false {
@@ -39,45 +53,36 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget {
}
}
}
private var displayLink: ConstantDisplayLinkAnimator?
public init(context: AccountContext, groupId: String, attemptSynchronousLoad: Bool, file: TelegramMediaFile, cache: AnimationCache, renderer: MultiAnimationRenderer, placeholderColor: UIColor) {
self.file = file
public init(context: AccountContext, groupId: String, attemptSynchronousLoad: Bool, emoji: ChatTextInputTextCustomEmojiAttribute, cache: AnimationCache, renderer: MultiAnimationRenderer, placeholderColor: UIColor) {
self.context = context
self.groupId = groupId
self.emoji = emoji
self.cache = cache
self.renderer = renderer
self.placeholderColor = placeholderColor
let scale = min(2.0, UIScreenScale)
self.pointSize = CGSize(width: 24, height: 24)
self.pixelSize = CGSize(width: self.pointSize.width * scale, height: self.pointSize.height * scale)
super.init()
let scale = min(2.0, UIScreenScale)
let pixelSize = CGSize(width: 24 * scale, height: 24 * scale)
if attemptSynchronousLoad {
if !renderer.loadFirstFrameSynchronously(groupId: groupId, target: self, cache: cache, itemId: file.resource.id.stringRepresentation, size: pixelSize) {
let size = CGSize(width: pixelSize.width / scale, height: pixelSize.height / scale)
if let image = generateStickerPlaceholderImage(data: file.immediateThumbnailData, size: size, imageSize: file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0), backgroundColor: nil, foregroundColor: placeholderColor) {
self.contents = image.cgImage
}
self.infoDisposable = (context.engine.stickers.loadedStickerPack(reference: emoji.stickerPack, forceActualized: false)
|> deliverOnMainQueue).start(next: { [weak self] result in
guard let strongSelf = self else {
return
}
}
self.disposable = renderer.add(groupId: groupId, target: self, cache: cache, itemId: file.resource.id.stringRepresentation, size: pixelSize, fetch: { size, writer in
let source = AnimatedStickerResourceSource(account: context.account, resource: file.resource, fitzModifier: nil, isVideo: false)
let dataDisposable = source.directDataPath(attemptSynchronously: false).start(next: { result in
guard let result = result else {
return
switch result {
case let .result(_, items, _):
for item in items {
if item.file.fileId.id == emoji.fileId {
strongSelf.updateFile(file: item.file, attemptSynchronousLoad: false)
break
}
}
guard let data = try? Data(contentsOf: URL(fileURLWithPath: result)) else {
writer.finish()
return
}
cacheLottieAnimation(data: data, width: Int(size.width), height: Int(size.height), writer: writer)
})
let fetchDisposable = freeMediaFileInteractiveFetched(account: context.account, fileReference: .standalone(media: file)).start()
return ActionDisposable {
dataDisposable.dispose()
fetchDisposable.dispose()
default:
break
}
})
}
@@ -91,6 +96,8 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget {
}
deinit {
self.loadDisposable?.dispose()
self.infoDisposable?.dispose()
self.disposable?.dispose()
self.fetchDisposable?.dispose()
}
@@ -110,13 +117,70 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget {
self.shouldBeAnimating = shouldBePlaying
}
private func updateFile(file: TelegramMediaFile, attemptSynchronousLoad: Bool) {
if self.file?.fileId == file.fileId {
return
}
self.file = file
if attemptSynchronousLoad {
if !self.renderer.loadFirstFrameSynchronously(groupId: self.groupId, target: self, cache: self.cache, itemId: file.resource.id.stringRepresentation, size: self.pixelSize) {
if let image = generateStickerPlaceholderImage(data: file.immediateThumbnailData, size: self.pointSize, imageSize: file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0), backgroundColor: nil, foregroundColor: self.placeholderColor) {
self.contents = image.cgImage
}
}
self.loadAnimation()
} else {
self.loadDisposable = self.renderer.loadFirstFrame(groupId: self.groupId, target: self, cache: self.cache, itemId: file.resource.id.stringRepresentation, size: self.pixelSize, completion: { [weak self] _ in
self?.loadAnimation()
})
self.loadAnimation()
}
}
private func loadAnimation() {
guard let file = self.file else {
return
}
let context = self.context
self.disposable = renderer.add(groupId: self.groupId, target: self, cache: self.cache, itemId: file.resource.id.stringRepresentation, size: self.pixelSize, fetch: { size, writer in
let source = AnimatedStickerResourceSource(account: context.account, resource: file.resource, fitzModifier: nil, isVideo: false)
let dataDisposable = source.directDataPath(attemptSynchronously: false).start(next: { result in
guard let result = result else {
return
}
if file.isVideoSticker {
cacheVideoAnimation(path: result, width: Int(size.width), height: Int(size.height), writer: writer)
} else {
guard let data = try? Data(contentsOf: URL(fileURLWithPath: result)) else {
writer.finish()
return
}
cacheLottieAnimation(data: data, width: Int(size.width), height: Int(size.height), writer: writer)
}
})
let fetchDisposable = freeMediaFileResourceInteractiveFetched(account: context.account, fileReference: stickerPackFileReference(file), resource: file.resource).start()
return ActionDisposable {
dataDisposable.dispose()
fetchDisposable.dispose()
}
})
}
}
public final class EmojiTextAttachmentView: UIView {
private let contentLayer: InlineStickerItemLayer
public init(context: AccountContext, file: TelegramMediaFile, cache: AnimationCache, renderer: MultiAnimationRenderer, placeholderColor: UIColor) {
self.contentLayer = InlineStickerItemLayer(context: context, groupId: "textInputView", attemptSynchronousLoad: true, file: file, cache: cache, renderer: renderer, placeholderColor: placeholderColor)
public init(context: AccountContext, emoji: ChatTextInputTextCustomEmojiAttribute, cache: AnimationCache, renderer: MultiAnimationRenderer, placeholderColor: UIColor) {
self.contentLayer = InlineStickerItemLayer(context: context, groupId: "textInputView", attemptSynchronousLoad: true, emoji: emoji, cache: cache, renderer: renderer, placeholderColor: placeholderColor)
super.init(frame: CGRect())
@@ -131,6 +195,6 @@ public final class EmojiTextAttachmentView: UIView {
override public func layoutSubviews() {
super.layoutSubviews()
self.contentLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: -2.0), size: CGSize(width: self.bounds.width - 0.0, height: self.bounds.height + 9.0))
self.contentLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: self.bounds.width, height: self.bounds.height))
}
}