mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
376 lines
14 KiB
Swift
376 lines
14 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import Display
|
|
import ComponentFlow
|
|
import TelegramPresentationData
|
|
import TelegramCore
|
|
import Postbox
|
|
import SwiftSignalKit
|
|
import MultiAnimationRenderer
|
|
import AnimationCache
|
|
import AccountContext
|
|
import TelegramUIPreferences
|
|
import GenerateStickerPlaceholderImage
|
|
import EmojiTextAttachmentView
|
|
import LottieAnimationCache
|
|
|
|
public final class InlineFileIconLayer: MultiAnimationRenderTarget {
|
|
private final class Arguments {
|
|
let context: InlineFileIconLayer.Context
|
|
let userLocation: MediaResourceUserLocation
|
|
let file: TelegramMediaFile
|
|
let cache: AnimationCache
|
|
let renderer: MultiAnimationRenderer
|
|
let unique: Bool
|
|
let placeholderColor: UIColor
|
|
|
|
let pointSize: CGSize
|
|
let pixelSize: CGSize
|
|
|
|
init(context: InlineFileIconLayer.Context, userLocation: MediaResourceUserLocation, file: TelegramMediaFile, cache: AnimationCache, renderer: MultiAnimationRenderer, unique: Bool, placeholderColor: UIColor, pointSize: CGSize, pixelSize: CGSize) {
|
|
self.context = context
|
|
self.userLocation = userLocation
|
|
self.file = file
|
|
self.cache = cache
|
|
self.renderer = renderer
|
|
self.unique = unique
|
|
self.placeholderColor = placeholderColor
|
|
self.pointSize = pointSize
|
|
self.pixelSize = pixelSize
|
|
}
|
|
}
|
|
|
|
public enum Context: Equatable {
|
|
public final class Custom: Equatable {
|
|
public let postbox: Postbox
|
|
public let energyUsageSettings: () -> EnergyUsageSettings
|
|
public let resolveInlineStickers: ([Int64]) -> Signal<[Int64: TelegramMediaFile], NoError>
|
|
|
|
public init(postbox: Postbox, energyUsageSettings: @escaping () -> EnergyUsageSettings, resolveInlineStickers: @escaping ([Int64]) -> Signal<[Int64: TelegramMediaFile], NoError>) {
|
|
self.postbox = postbox
|
|
self.energyUsageSettings = energyUsageSettings
|
|
self.resolveInlineStickers = resolveInlineStickers
|
|
}
|
|
|
|
public static func ==(lhs: Custom, rhs: Custom) -> Bool {
|
|
if lhs.postbox !== rhs.postbox {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
|
|
case account(AccountContext)
|
|
case custom(Custom)
|
|
|
|
var postbox: Postbox {
|
|
switch self {
|
|
case let .account(account):
|
|
return account.account.postbox
|
|
case let .custom(custom):
|
|
return custom.postbox
|
|
}
|
|
}
|
|
|
|
var energyUsageSettings: EnergyUsageSettings {
|
|
switch self {
|
|
case let .account(account):
|
|
return account.sharedContext.energyUsageSettings
|
|
case let .custom(custom):
|
|
return custom.energyUsageSettings()
|
|
}
|
|
}
|
|
|
|
func resolveInlineStickers(fileIds: [Int64]) -> Signal<[Int64: TelegramMediaFile], NoError> {
|
|
switch self {
|
|
case let .account(account):
|
|
return account.engine.stickers.resolveInlineStickers(fileIds: fileIds)
|
|
case let .custom(custom):
|
|
return custom.resolveInlineStickers(fileIds)
|
|
}
|
|
}
|
|
|
|
public static func ==(lhs: Context, rhs: Context) -> Bool {
|
|
switch lhs {
|
|
case let .account(lhsContext):
|
|
if case let .account(rhsContext) = rhs, lhsContext === rhsContext {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
case let .custom(custom):
|
|
if case .custom(custom) = rhs {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public static let queue = Queue()
|
|
|
|
public struct Key: Hashable {
|
|
public var id: Int64
|
|
public var index: Int
|
|
|
|
public init(id: Int64, index: Int) {
|
|
self.id = id
|
|
self.index = index
|
|
}
|
|
}
|
|
|
|
private let arguments: Arguments?
|
|
|
|
private var isDisplayingPlaceholder: Bool = false
|
|
private var didProcessTintColor: Bool = false
|
|
|
|
public private(set) var file: TelegramMediaFile?
|
|
private var infoDisposable: Disposable?
|
|
private var disposable: Disposable?
|
|
private var fetchDisposable: Disposable?
|
|
private var loadDisposable: Disposable?
|
|
|
|
private var _contentTintColor: UIColor?
|
|
public var contentTintColor: UIColor? {
|
|
get {
|
|
return self._contentTintColor
|
|
}
|
|
set(value) {
|
|
if self._contentTintColor != value {
|
|
self._contentTintColor = value
|
|
}
|
|
}
|
|
}
|
|
|
|
private var _dynamicColor: UIColor?
|
|
public var dynamicColor: UIColor? {
|
|
get {
|
|
return self._dynamicColor
|
|
}
|
|
set(value) {
|
|
if self._dynamicColor != value {
|
|
self._dynamicColor = value
|
|
}
|
|
}
|
|
}
|
|
|
|
private var currentLoopCount: Int = 0
|
|
|
|
private var isInHierarchyValue: Bool = false
|
|
|
|
public convenience init(
|
|
context: AccountContext,
|
|
userLocation: MediaResourceUserLocation,
|
|
attemptSynchronousLoad: Bool,
|
|
file: TelegramMediaFile,
|
|
cache: AnimationCache,
|
|
renderer: MultiAnimationRenderer,
|
|
unique: Bool = false,
|
|
placeholderColor: UIColor,
|
|
pointSize: CGSize,
|
|
dynamicColor: UIColor? = nil
|
|
) {
|
|
self.init(
|
|
context: .account(context),
|
|
userLocation: userLocation,
|
|
attemptSynchronousLoad: attemptSynchronousLoad,
|
|
file: file,
|
|
cache: cache,
|
|
renderer: renderer,
|
|
unique: unique,
|
|
placeholderColor: placeholderColor,
|
|
pointSize: pointSize,
|
|
dynamicColor: dynamicColor
|
|
)
|
|
}
|
|
|
|
public init(
|
|
context: InlineFileIconLayer.Context,
|
|
userLocation: MediaResourceUserLocation,
|
|
attemptSynchronousLoad: Bool,
|
|
file: TelegramMediaFile,
|
|
cache: AnimationCache,
|
|
renderer: MultiAnimationRenderer,
|
|
unique: Bool = false,
|
|
placeholderColor: UIColor,
|
|
pointSize: CGSize,
|
|
dynamicColor: UIColor? = nil
|
|
) {
|
|
let scale = min(2.0, UIScreenScale)
|
|
|
|
self.arguments = Arguments(
|
|
context: context,
|
|
userLocation: userLocation,
|
|
file: file,
|
|
cache: cache,
|
|
renderer: renderer,
|
|
unique: unique,
|
|
placeholderColor: placeholderColor,
|
|
pointSize: pointSize,
|
|
pixelSize: CGSize(width: pointSize.width * scale, height: pointSize.height * scale)
|
|
)
|
|
|
|
self._dynamicColor = dynamicColor
|
|
|
|
super.init()
|
|
|
|
self.updateFile(file: file, attemptSynchronousLoad: attemptSynchronousLoad)
|
|
}
|
|
|
|
override public init(layer: Any) {
|
|
self.arguments = nil
|
|
|
|
super.init(layer: layer)
|
|
}
|
|
|
|
required public init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
deinit {
|
|
self.loadDisposable?.dispose()
|
|
self.infoDisposable?.dispose()
|
|
self.disposable?.dispose()
|
|
self.fetchDisposable?.dispose()
|
|
}
|
|
|
|
override public func action(forKey event: String) -> CAAction? {
|
|
if event == kCAOnOrderIn {
|
|
self.isInHierarchyValue = true
|
|
} else if event == kCAOnOrderOut {
|
|
self.isInHierarchyValue = false
|
|
}
|
|
return nullAction
|
|
}
|
|
|
|
private func updateFile(file: TelegramMediaFile, attemptSynchronousLoad: Bool) {
|
|
guard let arguments = self.arguments else {
|
|
return
|
|
}
|
|
|
|
if self.file?.fileId == file.fileId {
|
|
return
|
|
}
|
|
|
|
self.file = file
|
|
|
|
if attemptSynchronousLoad {
|
|
if !arguments.renderer.loadFirstFrameSynchronously(target: self, cache: arguments.cache, itemId: file.resource.id.stringRepresentation, size: arguments.pixelSize) {
|
|
if let image = generateStickerPlaceholderImage(data: file.immediateThumbnailData, size: arguments.pointSize, imageSize: file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0), backgroundColor: nil, foregroundColor: arguments.placeholderColor) {
|
|
self.contents = image.cgImage
|
|
self.isDisplayingPlaceholder = true
|
|
}
|
|
}
|
|
|
|
self.loadAnimation()
|
|
} else {
|
|
let isTemplate = file.isCustomTemplateEmoji
|
|
|
|
let pointSize = arguments.pointSize
|
|
let placeholderColor = arguments.placeholderColor
|
|
let isThumbnailCancelled = Atomic<Bool>(value: false)
|
|
self.loadDisposable = arguments.renderer.loadFirstFrame(
|
|
target: self,
|
|
cache: arguments.cache,
|
|
itemId: file.resource.id.stringRepresentation,
|
|
size: arguments.pixelSize,
|
|
fetch: animationCacheFetchFile(postbox: arguments.context.postbox, userLocation: arguments.userLocation, userContentType: .sticker, resource: .media(media: .standalone(media: file), resource: file.resource), type: AnimationCacheAnimationType(file: file), keyframeOnly: true, customColor: isTemplate ? .white : nil), completion: { [weak self] result, isFinal in
|
|
if !result {
|
|
MultiAnimationRendererImpl.firstFrameQueue.async {
|
|
let image = generateStickerPlaceholderImage(data: file.immediateThumbnailData, size: pointSize, scale: min(2.0, UIScreenScale), imageSize: file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0), backgroundColor: nil, foregroundColor: placeholderColor)
|
|
|
|
DispatchQueue.main.async {
|
|
guard let strongSelf = self, !isThumbnailCancelled.with({ $0 }) else {
|
|
return
|
|
}
|
|
if let image = image {
|
|
strongSelf.contents = image.cgImage
|
|
strongSelf.isDisplayingPlaceholder = true
|
|
}
|
|
|
|
if isFinal {
|
|
strongSelf.loadAnimation()
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
let _ = isThumbnailCancelled.swap(true)
|
|
strongSelf.loadAnimation()
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
private func loadAnimation() {
|
|
/*guard let arguments = self.arguments else {
|
|
return
|
|
}
|
|
|
|
guard let file = self.file else {
|
|
return
|
|
}
|
|
|
|
let isTemplate = file.isCustomTemplateEmoji
|
|
|
|
let context = arguments.context
|
|
if file.isAnimatedSticker || file.isVideoSticker || file.isVideoEmoji {
|
|
let keyframeOnly = arguments.pixelSize.width >= 120.0
|
|
|
|
self.disposable = arguments.renderer.add(target: self, cache: arguments.cache, itemId: file.resource.id.stringRepresentation, unique: arguments.unique, size: arguments.pixelSize, fetch: animationCacheFetchFile(postbox: arguments.context.postbox, userLocation: arguments.userLocation, userContentType: .sticker, resource: .media(media: .standalone(media: file), resource: file.resource), type: AnimationCacheAnimationType(file: file), keyframeOnly: keyframeOnly, customColor: isTemplate ? .white : nil))
|
|
} else {
|
|
self.disposable = arguments.renderer.add(target: self, cache: arguments.cache, itemId: file.resource.id.stringRepresentation, unique: arguments.unique, size: arguments.pixelSize, fetch: { options in
|
|
let dataDisposable = context.postbox.mediaBox.resourceData(file.resource).start(next: { result in
|
|
guard result.complete else {
|
|
return
|
|
}
|
|
|
|
cacheStillSticker(path: result.path, width: Int(options.size.width), height: Int(options.size.height), writer: options.writer, customColor: isTemplate ? .white : nil)
|
|
})
|
|
|
|
let fetchDisposable = freeMediaFileResourceInteractiveFetched(postbox: context.postbox, userLocation: arguments.userLocation, fileReference: .customEmoji(media: file), resource: file.resource).start()
|
|
|
|
return ActionDisposable {
|
|
dataDisposable.dispose()
|
|
fetchDisposable.dispose()
|
|
}
|
|
})
|
|
}*/
|
|
}
|
|
|
|
override public func updateDisplayPlaceholder(displayPlaceholder: Bool) {
|
|
if self.isDisplayingPlaceholder == displayPlaceholder {
|
|
return
|
|
}
|
|
self.isDisplayingPlaceholder = displayPlaceholder
|
|
}
|
|
|
|
override public func transitionToContents(_ contents: AnyObject, didLoop: Bool) {
|
|
if self.isDisplayingPlaceholder {
|
|
self.isDisplayingPlaceholder = false
|
|
|
|
if let current = self.contents {
|
|
let previousLayer = SimpleLayer()
|
|
previousLayer.contents = current
|
|
previousLayer.frame = self.frame
|
|
self.superlayer?.insertSublayer(previousLayer, below: self)
|
|
previousLayer.opacity = 0.0
|
|
previousLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak previousLayer] _ in
|
|
previousLayer?.removeFromSuperlayer()
|
|
})
|
|
|
|
self.contents = contents
|
|
self.animateAlpha(from: 0.0, to: 1.0, duration: 0.18)
|
|
} else {
|
|
self.contents = contents
|
|
self.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
|
}
|
|
} else {
|
|
self.contents = contents
|
|
}
|
|
}
|
|
}
|