mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-07-24 12:10:49 +00:00
Animated emoji skin coloring
This commit is contained in:
parent
d1bcfbc670
commit
40b1160bcd
@ -924,8 +924,11 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
|
||||
}
|
||||
updatedState.addMessages([message], location: .UpperHistoryBlock)
|
||||
}
|
||||
case let .updateServiceNotification(_, date, type, text, media, entities):
|
||||
if let date = date {
|
||||
case let .updateServiceNotification(flags, date, type, text, media, entities):
|
||||
let popup = (flags & (1 << 0)) != 0
|
||||
if popup {
|
||||
updatedState.addDisplayAlert(text, isDropAuth: type.hasPrefix("AUTH_KEY_DROP_"))
|
||||
} else if let date = date {
|
||||
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: 777000)
|
||||
|
||||
if updatedState.peers[peerId] == nil {
|
||||
@ -969,8 +972,6 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
|
||||
let message = StoreMessage(peerId: peerId, namespace: Namespaces.Message.Local, globallyUniqueId: nil, groupingKey: nil, timestamp: date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: peerId, text: messageText, attributes: attributes, media: medias)
|
||||
updatedState.addMessages([message], location: .UpperHistoryBlock)
|
||||
}
|
||||
} else {
|
||||
updatedState.addDisplayAlert(text, isDropAuth: type.hasPrefix("AUTH_KEY_DROP_"))
|
||||
}
|
||||
case let .updateReadChannelInbox(_, folderId, channelId, maxId, stillUnreadCount, pts):
|
||||
updatedState.resetIncomingReadState(groupId: PeerGroupId(rawValue: folderId ?? 0), peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId), namespace: Namespaces.Message.Cloud, maxIncomingReadId: maxId, count: stillUnreadCount, pts: pts)
|
||||
|
@ -13,6 +13,13 @@ public struct HistoryPreloadIndex: Comparable {
|
||||
public let isMuted: Bool
|
||||
public let isPriority: Bool
|
||||
|
||||
public init(index: ChatListIndex?, hasUnread: Bool, isMuted: Bool, isPriority: Bool) {
|
||||
self.index = index
|
||||
self.hasUnread = hasUnread
|
||||
self.isMuted = isMuted
|
||||
self.isPriority = isPriority
|
||||
}
|
||||
|
||||
public static func <(lhs: HistoryPreloadIndex, rhs: HistoryPreloadIndex) -> Bool {
|
||||
if lhs.isPriority != rhs.isPriority {
|
||||
if lhs.isPriority {
|
||||
|
@ -402,7 +402,7 @@ final class AnimatedStickerNode: ASDisplayNode {
|
||||
self.addSubnode(self.renderer!)
|
||||
}
|
||||
|
||||
func setup(account: Account, resource: MediaResource, width: Int, height: Int, playbackMode: AnimatedStickerPlaybackMode = .loop, mode: AnimatedStickerMode) {
|
||||
func setup(account: Account, resource: MediaResource, fitzModifier: EmojiFitzModifier? = nil, width: Int, height: Int, playbackMode: AnimatedStickerPlaybackMode = .loop, mode: AnimatedStickerMode) {
|
||||
if width < 2 || height < 2 {
|
||||
return
|
||||
}
|
||||
@ -410,27 +410,27 @@ final class AnimatedStickerNode: ASDisplayNode {
|
||||
switch mode {
|
||||
case .direct:
|
||||
self.disposable.set((account.postbox.mediaBox.resourceData(resource)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] data in
|
||||
guard let strongSelf = self, data.complete else {
|
||||
return
|
||||
}
|
||||
if let directData = try? Data(contentsOf: URL(fileURLWithPath: data.path), options: [.mappedRead]) {
|
||||
strongSelf.directData = Tuple(directData, data.path, width, height)
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] data in
|
||||
guard let strongSelf = self, data.complete else {
|
||||
return
|
||||
}
|
||||
if let directData = try? Data(contentsOf: URL(fileURLWithPath: data.path), options: [.mappedRead]) {
|
||||
strongSelf.directData = Tuple(directData, data.path, width, height)
|
||||
}
|
||||
if strongSelf.isPlaying {
|
||||
strongSelf.play()
|
||||
}
|
||||
}))
|
||||
case .cached:
|
||||
self.disposable.set((chatMessageAnimationData(postbox: account.postbox, resource: resource, fitzModifier: fitzModifier, width: width, height: height, synchronousLoad: false)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] data in
|
||||
if let strongSelf = self, data.complete {
|
||||
strongSelf.cachedData = try? Data(contentsOf: URL(fileURLWithPath: data.path), options: [.mappedRead])
|
||||
if strongSelf.isPlaying {
|
||||
strongSelf.play()
|
||||
}
|
||||
}))
|
||||
case .cached:
|
||||
self.disposable.set((chatMessageAnimationData(postbox: account.postbox, resource: resource, width: width, height: height, synchronousLoad: false)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] data in
|
||||
if let strongSelf = self, data.complete {
|
||||
strongSelf.cachedData = try? Data(contentsOf: URL(fileURLWithPath: data.path), options: [.mappedRead])
|
||||
if strongSelf.isPlaying {
|
||||
strongSelf.play()
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,7 +65,85 @@ public class LocalBundleResource: TelegramMediaResource {
|
||||
}
|
||||
}
|
||||
|
||||
func fetchCompressedLottieFirstFrameAJpeg(data: Data, size: CGSize, cacheKey: String) -> Signal<TempBoxFile, NoError> {
|
||||
let colorKeyRegex = try? NSRegularExpression(pattern: "\"k\":\\[[\\d\\.]+\\,[\\d\\.]+\\,[\\d\\.]+\\,[\\d\\.]+\\]")
|
||||
|
||||
private func transformedWithFitzModifier(data: Data, fitzModifier: EmojiFitzModifier?) -> Data {
|
||||
if let fitzModifier = fitzModifier, var string = String(data: data, encoding: .utf8) {
|
||||
let color1: UIColor
|
||||
let color2: UIColor
|
||||
let color3: UIColor
|
||||
let color4: UIColor
|
||||
|
||||
var colors: [UIColor] = [0xf77e41, 0xffb139, 0xffd140, 0xffdf79].map { UIColor(rgb: $0) }
|
||||
let replacementColors: [UIColor]
|
||||
switch fitzModifier {
|
||||
case .type12:
|
||||
replacementColors = [0xca907a, 0xedc5a5, 0xf7e3c3, 0xfbefd6].map { UIColor(rgb: $0) }
|
||||
case .type3:
|
||||
replacementColors = [0xaa7c60, 0xc8a987, 0xddc89f, 0xe6d6b2].map { UIColor(rgb: $0) }
|
||||
case .type4:
|
||||
replacementColors = [0x8c6148, 0xad8562, 0xc49e76, 0xd4b188].map { UIColor(rgb: $0) }
|
||||
case .type5:
|
||||
replacementColors = [0x6e3c2c, 0x925a34, 0xa16e46, 0xac7a52].map { UIColor(rgb: $0) }
|
||||
case .type6:
|
||||
replacementColors = [0x291c12, 0x472a22, 0x573b30, 0x68493c].map { UIColor(rgb: $0) }
|
||||
}
|
||||
|
||||
func colorToString(_ color: UIColor) -> String {
|
||||
var r: CGFloat = 0.0
|
||||
var g: CGFloat = 0.0
|
||||
var b: CGFloat = 0.0
|
||||
if color.getRed(&r, green: &g, blue: &b, alpha: nil) {
|
||||
return "\"k\":[\(r),\(g),\(b),1]"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func match(_ a: Double, _ b: Double, eps: Double) -> Bool {
|
||||
return abs(a - b) < eps
|
||||
}
|
||||
|
||||
var replacements: [(NSTextCheckingResult, String)] = []
|
||||
|
||||
if let colorKeyRegex = colorKeyRegex {
|
||||
let results = colorKeyRegex.matches(in: string, range: NSRange(string.startIndex..., in: string))
|
||||
for result in results.reversed() {
|
||||
if let range = Range(result.range, in: string) {
|
||||
let substring = String(string[range])
|
||||
let color = substring[substring.index(string.startIndex, offsetBy: "\"k\":[".count) ..< substring.index(before: substring.endIndex)]
|
||||
let components = color.split(separator: ",")
|
||||
if components.count == 4, let r = Double(components[0]), let g = Double(components[1]), let b = Double(components[2]), let a = Double(components[3]) {
|
||||
if match(a, 1.0, eps: 0.01) {
|
||||
for i in 0 ..< colors.count {
|
||||
let color = colors[i]
|
||||
var cr: CGFloat = 0.0
|
||||
var cg: CGFloat = 0.0
|
||||
var cb: CGFloat = 0.0
|
||||
if color.getRed(&cr, green: &cg, blue: &cb, alpha: nil) {
|
||||
if match(r, Double(cr), eps: 0.01) && match(g, Double(cg), eps: 0.01) && match(b, Double(cb), eps: 0.01) {
|
||||
replacements.append((result, colorToString(replacementColors[i])))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (result, text) in replacements {
|
||||
if let range = Range(result.range, in: string) {
|
||||
string = string.replacingCharacters(in: range, with: text)
|
||||
}
|
||||
}
|
||||
|
||||
return string.data(using: .utf8) ?? data
|
||||
} else {
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
func fetchCompressedLottieFirstFrameAJpeg(data: Data, size: CGSize, fitzModifier: EmojiFitzModifier? = nil, cacheKey: String) -> Signal<TempBoxFile, NoError> {
|
||||
return Signal({ subscriber in
|
||||
let queue = Queue()
|
||||
|
||||
@ -77,72 +155,75 @@ func fetchCompressedLottieFirstFrameAJpeg(data: Data, size: CGSize, cacheKey: St
|
||||
}
|
||||
|
||||
let decompressedData = TGGUnzipData(data, 8 * 1024 * 1024)
|
||||
if let decompressedData = decompressedData, let player = LottieInstance(data: decompressedData, cacheKey: cacheKey) {
|
||||
if cancelled.with({ $0 }) {
|
||||
return
|
||||
}
|
||||
|
||||
let context = DrawingContext(size: size, scale: 1.0, clear: true)
|
||||
player.renderFrame(with: 0, into: context.bytes.assumingMemoryBound(to: UInt8.self), width: Int32(size.width), height: Int32(size.height), bytesPerRow: Int32(context.bytesPerRow))
|
||||
|
||||
let yuvaPixelsPerAlphaRow = (Int(size.width) + 1) & (~1)
|
||||
assert(yuvaPixelsPerAlphaRow % 2 == 0)
|
||||
|
||||
let yuvaLength = Int(size.width) * Int(size.height) * 2 + yuvaPixelsPerAlphaRow * Int(size.height) / 2
|
||||
var yuvaFrameData = malloc(yuvaLength)!
|
||||
memset(yuvaFrameData, 0, yuvaLength)
|
||||
|
||||
defer {
|
||||
free(yuvaFrameData)
|
||||
}
|
||||
|
||||
encodeRGBAToYUVA(yuvaFrameData.assumingMemoryBound(to: UInt8.self), context.bytes.assumingMemoryBound(to: UInt8.self), Int32(size.width), Int32(size.height), Int32(context.bytesPerRow))
|
||||
decodeYUVAToRGBA(yuvaFrameData.assumingMemoryBound(to: UInt8.self), context.bytes.assumingMemoryBound(to: UInt8.self), Int32(size.width), Int32(size.height), Int32(context.bytesPerRow))
|
||||
|
||||
if let colorSourceImage = context.generateImage(), let alphaImage = generateGrayscaleAlphaMaskImage(image: colorSourceImage) {
|
||||
let colorContext = DrawingContext(size: size, scale: 1.0, clear: false)
|
||||
colorContext.withFlippedContext { c in
|
||||
c.setFillColor(UIColor.black.cgColor)
|
||||
c.fill(CGRect(origin: CGPoint(), size: size))
|
||||
c.draw(colorSourceImage.cgImage!, in: CGRect(origin: CGPoint(), size: size))
|
||||
}
|
||||
guard let colorImage = colorContext.generateImage() else {
|
||||
if let decompressedData = decompressedData {
|
||||
let decompressedData = transformedWithFitzModifier(data: decompressedData, fitzModifier: fitzModifier)
|
||||
if let player = LottieInstance(data: decompressedData, cacheKey: cacheKey) {
|
||||
if cancelled.with({ $0 }) {
|
||||
return
|
||||
}
|
||||
|
||||
let colorData = NSMutableData()
|
||||
let alphaData = NSMutableData()
|
||||
let context = DrawingContext(size: size, scale: 1.0, clear: true)
|
||||
player.renderFrame(with: 0, into: context.bytes.assumingMemoryBound(to: UInt8.self), width: Int32(size.width), height: Int32(size.height), bytesPerRow: Int32(context.bytesPerRow))
|
||||
|
||||
if let colorDestination = CGImageDestinationCreateWithData(colorData as CFMutableData, kUTTypeJPEG, 1, nil), let alphaDestination = CGImageDestinationCreateWithData(alphaData as CFMutableData, kUTTypeJPEG, 1, nil) {
|
||||
CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary)
|
||||
CGImageDestinationSetProperties(alphaDestination, [:] as CFDictionary)
|
||||
let yuvaPixelsPerAlphaRow = (Int(size.width) + 1) & (~1)
|
||||
assert(yuvaPixelsPerAlphaRow % 2 == 0)
|
||||
|
||||
let yuvaLength = Int(size.width) * Int(size.height) * 2 + yuvaPixelsPerAlphaRow * Int(size.height) / 2
|
||||
var yuvaFrameData = malloc(yuvaLength)!
|
||||
memset(yuvaFrameData, 0, yuvaLength)
|
||||
|
||||
defer {
|
||||
free(yuvaFrameData)
|
||||
}
|
||||
|
||||
encodeRGBAToYUVA(yuvaFrameData.assumingMemoryBound(to: UInt8.self), context.bytes.assumingMemoryBound(to: UInt8.self), Int32(size.width), Int32(size.height), Int32(context.bytesPerRow))
|
||||
decodeYUVAToRGBA(yuvaFrameData.assumingMemoryBound(to: UInt8.self), context.bytes.assumingMemoryBound(to: UInt8.self), Int32(size.width), Int32(size.height), Int32(context.bytesPerRow))
|
||||
|
||||
if let colorSourceImage = context.generateImage(), let alphaImage = generateGrayscaleAlphaMaskImage(image: colorSourceImage) {
|
||||
let colorContext = DrawingContext(size: size, scale: 1.0, clear: false)
|
||||
colorContext.withFlippedContext { c in
|
||||
c.setFillColor(UIColor.black.cgColor)
|
||||
c.fill(CGRect(origin: CGPoint(), size: size))
|
||||
c.draw(colorSourceImage.cgImage!, in: CGRect(origin: CGPoint(), size: size))
|
||||
}
|
||||
guard let colorImage = colorContext.generateImage() else {
|
||||
return
|
||||
}
|
||||
|
||||
let colorQuality: Float
|
||||
let alphaQuality: Float
|
||||
colorQuality = 0.5
|
||||
alphaQuality = 0.4
|
||||
let colorData = NSMutableData()
|
||||
let alphaData = NSMutableData()
|
||||
|
||||
let options = NSMutableDictionary()
|
||||
options.setObject(colorQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString)
|
||||
|
||||
let optionsAlpha = NSMutableDictionary()
|
||||
optionsAlpha.setObject(alphaQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString)
|
||||
|
||||
CGImageDestinationAddImage(colorDestination, colorImage.cgImage!, options as CFDictionary)
|
||||
CGImageDestinationAddImage(alphaDestination, alphaImage.cgImage!, optionsAlpha as CFDictionary)
|
||||
if CGImageDestinationFinalize(colorDestination) && CGImageDestinationFinalize(alphaDestination) {
|
||||
let finalData = NSMutableData()
|
||||
var colorSize: Int32 = Int32(colorData.length)
|
||||
finalData.append(&colorSize, length: 4)
|
||||
finalData.append(colorData as Data)
|
||||
var alphaSize: Int32 = Int32(alphaData.length)
|
||||
finalData.append(&alphaSize, length: 4)
|
||||
finalData.append(alphaData as Data)
|
||||
if let colorDestination = CGImageDestinationCreateWithData(colorData as CFMutableData, kUTTypeJPEG, 1, nil), let alphaDestination = CGImageDestinationCreateWithData(alphaData as CFMutableData, kUTTypeJPEG, 1, nil) {
|
||||
CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary)
|
||||
CGImageDestinationSetProperties(alphaDestination, [:] as CFDictionary)
|
||||
|
||||
let tempFile = TempBox.shared.tempFile(fileName: "image.ajpg")
|
||||
let _ = try? finalData.write(to: URL(fileURLWithPath: tempFile.path), options: [])
|
||||
subscriber.putNext(tempFile)
|
||||
subscriber.putCompletion()
|
||||
let colorQuality: Float
|
||||
let alphaQuality: Float
|
||||
colorQuality = 0.5
|
||||
alphaQuality = 0.4
|
||||
|
||||
let options = NSMutableDictionary()
|
||||
options.setObject(colorQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString)
|
||||
|
||||
let optionsAlpha = NSMutableDictionary()
|
||||
optionsAlpha.setObject(alphaQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString)
|
||||
|
||||
CGImageDestinationAddImage(colorDestination, colorImage.cgImage!, options as CFDictionary)
|
||||
CGImageDestinationAddImage(alphaDestination, alphaImage.cgImage!, optionsAlpha as CFDictionary)
|
||||
if CGImageDestinationFinalize(colorDestination) && CGImageDestinationFinalize(alphaDestination) {
|
||||
let finalData = NSMutableData()
|
||||
var colorSize: Int32 = Int32(colorData.length)
|
||||
finalData.append(&colorSize, length: 4)
|
||||
finalData.append(colorData as Data)
|
||||
var alphaSize: Int32 = Int32(alphaData.length)
|
||||
finalData.append(&alphaSize, length: 4)
|
||||
finalData.append(alphaData as Data)
|
||||
|
||||
let tempFile = TempBox.shared.tempFile(fileName: "image.ajpg")
|
||||
let _ = try? finalData.write(to: URL(fileURLWithPath: tempFile.path), options: [])
|
||||
subscriber.putNext(tempFile)
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -159,7 +240,7 @@ private let threadPool: ThreadPool = {
|
||||
}()
|
||||
|
||||
@available(iOS 9.0, *)
|
||||
func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: CGSize, cacheKey: String) -> Signal<String, NoError> {
|
||||
func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: CGSize, fitzModifier: EmojiFitzModifier? = nil, cacheKey: String) -> Signal<String, NoError> {
|
||||
return Signal({ subscriber in
|
||||
let cancelled = Atomic<Bool>(value: false)
|
||||
|
||||
@ -174,127 +255,130 @@ func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: CGSize,
|
||||
var appendingTime: Double = 0
|
||||
var deltaTime: Double = 0
|
||||
var compressionTime: Double = 0
|
||||
|
||||
|
||||
let decompressedData = TGGUnzipData(data, 8 * 1024 * 1024)
|
||||
if let decompressedData = decompressedData, let player = LottieInstance(data: decompressedData, cacheKey: cacheKey) {
|
||||
let endFrame = Int(player.frameCount)
|
||||
|
||||
if cancelled.with({ $0 }) {
|
||||
//print("cancelled 2")
|
||||
return
|
||||
}
|
||||
|
||||
var randomId: Int64 = 0
|
||||
arc4random_buf(&randomId, 8)
|
||||
let path = NSTemporaryDirectory() + "\(randomId).lz4v"
|
||||
guard let fileContext = ManagedFile(queue: nil, path: path, mode: .readwrite) else {
|
||||
return
|
||||
}
|
||||
|
||||
let bytesPerRow = (4 * Int(size.width) + 15) & (~15)
|
||||
|
||||
var currentFrame: Int32 = 0
|
||||
|
||||
var fps: Int32 = player.frameRate
|
||||
var frameCount: Int32 = player.frameCount
|
||||
let _ = fileContext.write(&fps, count: 4)
|
||||
let _ = fileContext.write(&frameCount, count: 4)
|
||||
var widthValue: Int32 = Int32(size.width)
|
||||
var heightValue: Int32 = Int32(size.height)
|
||||
var bytesPerRowValue: Int32 = Int32(bytesPerRow)
|
||||
let _ = fileContext.write(&widthValue, count: 4)
|
||||
let _ = fileContext.write(&heightValue, count: 4)
|
||||
let _ = fileContext.write(&bytesPerRowValue, count: 4)
|
||||
|
||||
let frameLength = bytesPerRow * Int(size.height)
|
||||
assert(frameLength % 16 == 0)
|
||||
|
||||
let currentFrameData = malloc(frameLength)!
|
||||
memset(currentFrameData, 0, frameLength)
|
||||
|
||||
let yuvaPixelsPerAlphaRow = (Int(size.width) + 1) & (~1)
|
||||
assert(yuvaPixelsPerAlphaRow % 2 == 0)
|
||||
|
||||
let yuvaLength = Int(size.width) * Int(size.height) * 2 + yuvaPixelsPerAlphaRow * Int(size.height) / 2
|
||||
var yuvaFrameData = malloc(yuvaLength)!
|
||||
memset(yuvaFrameData, 0, yuvaLength)
|
||||
|
||||
var previousYuvaFrameData = malloc(yuvaLength)!
|
||||
memset(previousYuvaFrameData, 0, yuvaLength)
|
||||
|
||||
defer {
|
||||
free(currentFrameData)
|
||||
free(previousYuvaFrameData)
|
||||
free(yuvaFrameData)
|
||||
}
|
||||
|
||||
var compressedFrameData = Data(count: frameLength)
|
||||
let compressedFrameDataLength = compressedFrameData.count
|
||||
|
||||
let scratchData = malloc(compression_encode_scratch_buffer_size(COMPRESSION_LZFSE))!
|
||||
defer {
|
||||
free(scratchData)
|
||||
}
|
||||
|
||||
while currentFrame < endFrame {
|
||||
if let decompressedData = decompressedData {
|
||||
let decompressedData = transformedWithFitzModifier(data: decompressedData, fitzModifier: fitzModifier)
|
||||
if let player = LottieInstance(data: decompressedData, cacheKey: cacheKey) {
|
||||
let endFrame = Int(player.frameCount)
|
||||
|
||||
if cancelled.with({ $0 }) {
|
||||
//print("cancelled 3")
|
||||
//print("cancelled 2")
|
||||
return
|
||||
}
|
||||
|
||||
let drawStartTime = CACurrentMediaTime()
|
||||
var randomId: Int64 = 0
|
||||
arc4random_buf(&randomId, 8)
|
||||
let path = NSTemporaryDirectory() + "\(randomId).lz4v"
|
||||
guard let fileContext = ManagedFile(queue: nil, path: path, mode: .readwrite) else {
|
||||
return
|
||||
}
|
||||
|
||||
let bytesPerRow = (4 * Int(size.width) + 15) & (~15)
|
||||
|
||||
var currentFrame: Int32 = 0
|
||||
|
||||
var fps: Int32 = player.frameRate
|
||||
var frameCount: Int32 = player.frameCount
|
||||
let _ = fileContext.write(&fps, count: 4)
|
||||
let _ = fileContext.write(&frameCount, count: 4)
|
||||
var widthValue: Int32 = Int32(size.width)
|
||||
var heightValue: Int32 = Int32(size.height)
|
||||
var bytesPerRowValue: Int32 = Int32(bytesPerRow)
|
||||
let _ = fileContext.write(&widthValue, count: 4)
|
||||
let _ = fileContext.write(&heightValue, count: 4)
|
||||
let _ = fileContext.write(&bytesPerRowValue, count: 4)
|
||||
|
||||
let frameLength = bytesPerRow * Int(size.height)
|
||||
assert(frameLength % 16 == 0)
|
||||
|
||||
let currentFrameData = malloc(frameLength)!
|
||||
memset(currentFrameData, 0, frameLength)
|
||||
player.renderFrame(with: Int32(currentFrame), into: currentFrameData.assumingMemoryBound(to: UInt8.self), width: Int32(size.width), height: Int32(size.height), bytesPerRow: Int32(bytesPerRow))
|
||||
drawingTime += CACurrentMediaTime() - drawStartTime
|
||||
|
||||
let appendStartTime = CACurrentMediaTime()
|
||||
let yuvaPixelsPerAlphaRow = (Int(size.width) + 1) & (~1)
|
||||
assert(yuvaPixelsPerAlphaRow % 2 == 0)
|
||||
|
||||
encodeRGBAToYUVA(yuvaFrameData.assumingMemoryBound(to: UInt8.self), currentFrameData.assumingMemoryBound(to: UInt8.self), Int32(size.width), Int32(size.height), Int32(bytesPerRow))
|
||||
let yuvaLength = Int(size.width) * Int(size.height) * 2 + yuvaPixelsPerAlphaRow * Int(size.height) / 2
|
||||
var yuvaFrameData = malloc(yuvaLength)!
|
||||
memset(yuvaFrameData, 0, yuvaLength)
|
||||
|
||||
appendingTime += CACurrentMediaTime() - appendStartTime
|
||||
var previousYuvaFrameData = malloc(yuvaLength)!
|
||||
memset(previousYuvaFrameData, 0, yuvaLength)
|
||||
|
||||
let deltaStartTime = CACurrentMediaTime()
|
||||
var lhs = previousYuvaFrameData.assumingMemoryBound(to: UInt64.self)
|
||||
var rhs = yuvaFrameData.assumingMemoryBound(to: UInt64.self)
|
||||
for _ in 0 ..< yuvaLength / 8 {
|
||||
lhs.pointee = rhs.pointee ^ lhs.pointee
|
||||
lhs = lhs.advanced(by: 1)
|
||||
rhs = rhs.advanced(by: 1)
|
||||
}
|
||||
var lhsRest = previousYuvaFrameData.assumingMemoryBound(to: UInt8.self).advanced(by: (yuvaLength / 8) * 8)
|
||||
var rhsRest = yuvaFrameData.assumingMemoryBound(to: UInt8.self).advanced(by: (yuvaLength / 8) * 8)
|
||||
for _ in (yuvaLength / 8) * 8 ..< yuvaLength {
|
||||
lhsRest.pointee = rhsRest.pointee ^ lhsRest.pointee
|
||||
lhsRest = lhsRest.advanced(by: 1)
|
||||
rhsRest = rhsRest.advanced(by: 1)
|
||||
}
|
||||
deltaTime += CACurrentMediaTime() - deltaStartTime
|
||||
|
||||
let compressionStartTime = CACurrentMediaTime()
|
||||
compressedFrameData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
|
||||
let length = compression_encode_buffer(bytes, compressedFrameDataLength, previousYuvaFrameData.assumingMemoryBound(to: UInt8.self), yuvaLength, scratchData, COMPRESSION_LZFSE)
|
||||
var frameLengthValue: Int32 = Int32(length)
|
||||
let _ = fileContext.write(&frameLengthValue, count: 4)
|
||||
let _ = fileContext.write(bytes, count: length)
|
||||
defer {
|
||||
free(currentFrameData)
|
||||
free(previousYuvaFrameData)
|
||||
free(yuvaFrameData)
|
||||
}
|
||||
|
||||
let tmp = previousYuvaFrameData
|
||||
previousYuvaFrameData = yuvaFrameData
|
||||
yuvaFrameData = tmp
|
||||
var compressedFrameData = Data(count: frameLength)
|
||||
let compressedFrameDataLength = compressedFrameData.count
|
||||
|
||||
compressionTime += CACurrentMediaTime() - compressionStartTime
|
||||
let scratchData = malloc(compression_encode_scratch_buffer_size(COMPRESSION_LZFSE))!
|
||||
defer {
|
||||
free(scratchData)
|
||||
}
|
||||
|
||||
currentFrame += 1
|
||||
while currentFrame < endFrame {
|
||||
if cancelled.with({ $0 }) {
|
||||
//print("cancelled 3")
|
||||
return
|
||||
}
|
||||
|
||||
let drawStartTime = CACurrentMediaTime()
|
||||
memset(currentFrameData, 0, frameLength)
|
||||
player.renderFrame(with: Int32(currentFrame), into: currentFrameData.assumingMemoryBound(to: UInt8.self), width: Int32(size.width), height: Int32(size.height), bytesPerRow: Int32(bytesPerRow))
|
||||
drawingTime += CACurrentMediaTime() - drawStartTime
|
||||
|
||||
let appendStartTime = CACurrentMediaTime()
|
||||
|
||||
encodeRGBAToYUVA(yuvaFrameData.assumingMemoryBound(to: UInt8.self), currentFrameData.assumingMemoryBound(to: UInt8.self), Int32(size.width), Int32(size.height), Int32(bytesPerRow))
|
||||
|
||||
appendingTime += CACurrentMediaTime() - appendStartTime
|
||||
|
||||
let deltaStartTime = CACurrentMediaTime()
|
||||
var lhs = previousYuvaFrameData.assumingMemoryBound(to: UInt64.self)
|
||||
var rhs = yuvaFrameData.assumingMemoryBound(to: UInt64.self)
|
||||
for _ in 0 ..< yuvaLength / 8 {
|
||||
lhs.pointee = rhs.pointee ^ lhs.pointee
|
||||
lhs = lhs.advanced(by: 1)
|
||||
rhs = rhs.advanced(by: 1)
|
||||
}
|
||||
var lhsRest = previousYuvaFrameData.assumingMemoryBound(to: UInt8.self).advanced(by: (yuvaLength / 8) * 8)
|
||||
var rhsRest = yuvaFrameData.assumingMemoryBound(to: UInt8.self).advanced(by: (yuvaLength / 8) * 8)
|
||||
for _ in (yuvaLength / 8) * 8 ..< yuvaLength {
|
||||
lhsRest.pointee = rhsRest.pointee ^ lhsRest.pointee
|
||||
lhsRest = lhsRest.advanced(by: 1)
|
||||
rhsRest = rhsRest.advanced(by: 1)
|
||||
}
|
||||
deltaTime += CACurrentMediaTime() - deltaStartTime
|
||||
|
||||
let compressionStartTime = CACurrentMediaTime()
|
||||
compressedFrameData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
|
||||
let length = compression_encode_buffer(bytes, compressedFrameDataLength, previousYuvaFrameData.assumingMemoryBound(to: UInt8.self), yuvaLength, scratchData, COMPRESSION_LZFSE)
|
||||
var frameLengthValue: Int32 = Int32(length)
|
||||
let _ = fileContext.write(&frameLengthValue, count: 4)
|
||||
let _ = fileContext.write(bytes, count: length)
|
||||
}
|
||||
|
||||
let tmp = previousYuvaFrameData
|
||||
previousYuvaFrameData = yuvaFrameData
|
||||
yuvaFrameData = tmp
|
||||
|
||||
compressionTime += CACurrentMediaTime() - compressionStartTime
|
||||
|
||||
currentFrame += 1
|
||||
}
|
||||
|
||||
subscriber.putNext(path)
|
||||
subscriber.putCompletion()
|
||||
print("animation render time \(CACurrentMediaTime() - startTime)")
|
||||
print("of which drawing time \(drawingTime)")
|
||||
print("of which appending time \(appendingTime)")
|
||||
print("of which delta time \(deltaTime)")
|
||||
|
||||
print("of which compression time \(compressionTime)")
|
||||
}
|
||||
|
||||
subscriber.putNext(path)
|
||||
subscriber.putCompletion()
|
||||
print("animation render time \(CACurrentMediaTime() - startTime)")
|
||||
print("of which drawing time \(drawingTime)")
|
||||
print("of which appending time \(appendingTime)")
|
||||
print("of which delta time \(deltaTime)")
|
||||
|
||||
print("of which compression time \(compressionTime)")
|
||||
}
|
||||
}))
|
||||
return ActionDisposable {
|
||||
|
@ -234,19 +234,51 @@ final class CachedEmojiRepresentation: CachedMediaResourceRepresentation {
|
||||
}
|
||||
}
|
||||
|
||||
public enum EmojiFitzModifier: Int32, Equatable {
|
||||
case type12
|
||||
case type3
|
||||
case type4
|
||||
case type5
|
||||
case type6
|
||||
|
||||
public init?(emoji: String) {
|
||||
switch emoji.unicodeScalars.first?.value {
|
||||
case 0x1f3fb:
|
||||
self = .type12
|
||||
case 0x1f3fc:
|
||||
self = .type3
|
||||
case 0x1f3fd:
|
||||
self = .type4
|
||||
case 0x1f3fe:
|
||||
self = .type5
|
||||
case 0x1f3ff:
|
||||
self = .type6
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class CachedAnimatedStickerFirstFrameRepresentation: CachedMediaResourceRepresentation {
|
||||
let keepDuration: CachedMediaRepresentationKeepDuration = .general
|
||||
|
||||
let width: Int32
|
||||
let height: Int32
|
||||
let fitzModifier: EmojiFitzModifier?
|
||||
|
||||
init(width: Int32, height: Int32) {
|
||||
init(width: Int32, height: Int32, fitzModifier: EmojiFitzModifier? = nil) {
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.fitzModifier = fitzModifier
|
||||
}
|
||||
|
||||
var uniqueId: String {
|
||||
return "animated-sticker-first-frame-\(self.width)x\(self.height)-v1"
|
||||
let version: Int = 1
|
||||
if let fitzModifier = self.fitzModifier {
|
||||
return "animated-sticker-first-frame-\(self.width)x\(self.height)-fitz\(fitzModifier.rawValue)-v\(version)"
|
||||
} else {
|
||||
return "animated-sticker-first-frame-\(self.width)x\(self.height)-v\(version)"
|
||||
}
|
||||
}
|
||||
|
||||
func isEqual(to: CachedMediaResourceRepresentation) -> Bool {
|
||||
@ -257,6 +289,9 @@ final class CachedAnimatedStickerFirstFrameRepresentation: CachedMediaResourceRe
|
||||
if other.height != self.height {
|
||||
return false
|
||||
}
|
||||
if other.fitzModifier != self.fitzModifier {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -269,14 +304,21 @@ final class CachedAnimatedStickerRepresentation: CachedMediaResourceRepresentati
|
||||
|
||||
let width: Int32
|
||||
let height: Int32
|
||||
let fitzModifier: EmojiFitzModifier?
|
||||
|
||||
var uniqueId: String {
|
||||
return "animated-sticker-\(self.width)x\(self.height)-v8"
|
||||
let version: Int = 8
|
||||
if let fitzModifier = self.fitzModifier {
|
||||
return "animated-sticker-\(self.width)x\(self.height)-fitz\(fitzModifier.rawValue)-v\(version)"
|
||||
} else {
|
||||
return "animated-sticker-\(self.width)x\(self.height)-v\(version)"
|
||||
}
|
||||
}
|
||||
|
||||
init(width: Int32, height: Int32) {
|
||||
init(width: Int32, height: Int32, fitzModifier: EmojiFitzModifier? = nil) {
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.fitzModifier = fitzModifier
|
||||
}
|
||||
|
||||
func isEqual(to: CachedMediaResourceRepresentation) -> Bool {
|
||||
@ -287,6 +329,9 @@ final class CachedAnimatedStickerRepresentation: CachedMediaResourceRepresentati
|
||||
if other.height != self.height {
|
||||
return false
|
||||
}
|
||||
if other.fitzModifier != self.fitzModifier {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
|
@ -133,7 +133,7 @@ final class ChatAnimationGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
|
||||
func setFile(context: AccountContext, fileReference: FileMediaReference) {
|
||||
if self.contextAndMedia == nil || !self.contextAndMedia!.1.media.isEqual(to: fileReference.media) {
|
||||
let signal = chatMessageAnimatedStrickerBackingData(postbox: context.account.postbox, fileReference: fileReference, synchronousLoad: false)
|
||||
let signal = chatMessageAnimatedStickerBackingData(postbox: context.account.postbox, fileReference: fileReference, synchronousLoad: false)
|
||||
|> mapToSignal { value -> Signal<Data, NoError> in
|
||||
if value._1, let data = value._0 {
|
||||
return .single(data)
|
||||
|
@ -39,7 +39,7 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView,
|
||||
|
||||
var contentTypeHint: ChatMessageEntryContentType = .generic
|
||||
if presentationData.largeEmoji {
|
||||
if let _ = associatedData.animatedEmojiStickers[entry.message.text.trimmedEmoji] {
|
||||
if let _ = associatedData.animatedEmojiStickers[entry.message.text.basicEmoji.0] {
|
||||
contentTypeHint = .animatedEmoji
|
||||
} else if messageIsElligibleForLargeEmoji(entry.message) {
|
||||
contentTypeHint = .largeEmoji
|
||||
|
@ -545,7 +545,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
var animatedEmojiStickers: [String: StickerPackItem] = [:]
|
||||
for case let item as StickerPackItem in items {
|
||||
if let emoji = item.getStringRepresentationsOfIndexKeys().first {
|
||||
animatedEmojiStickers[emoji.trimmedEmoji] = item
|
||||
animatedEmojiStickers[emoji.basicEmoji.0] = item
|
||||
}
|
||||
}
|
||||
return animatedEmojiStickers
|
||||
|
@ -176,7 +176,7 @@ func inputContextQueriesForChatPresentationIntefaceState(_ chatPresentationInter
|
||||
for (possibleQueryRange, possibleTypes, additionalStringRange) in textInputStateContextQueryRangeAndType(inputState) {
|
||||
let query = inputString.substring(with: possibleQueryRange)
|
||||
if possibleTypes == [.emoji] {
|
||||
result.append(.emoji(query.basicEmoji))
|
||||
result.append(.emoji(query.basicEmoji.0))
|
||||
} else if possibleTypes == [.hashtag] {
|
||||
result.append(.hashtag(query))
|
||||
} else if possibleTypes == [.mention] {
|
||||
|
@ -88,7 +88,7 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
|
||||
case .installed:
|
||||
scope = [.installed]
|
||||
}
|
||||
return searchStickers(account: context.account, query: query.trimmedEmoji, scope: scope)
|
||||
return searchStickers(account: context.account, query: query.basicEmoji.0, scope: scope)
|
||||
|> introduceError(ChatContextQueryError.self)
|
||||
}
|
||||
|> map { stickers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
|
||||
|
@ -231,12 +231,17 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
}
|
||||
}
|
||||
|
||||
if self.telegramFile == nil, let emojiFile = item.associatedData.animatedEmojiStickers[item.message.text.trimmedEmoji]?.file {
|
||||
let (emoji, fitz) = item.message.text.basicEmoji
|
||||
if self.telegramFile == nil, let emojiFile = item.associatedData.animatedEmojiStickers[emoji]?.file {
|
||||
if self.emojiFile?.id != emojiFile.id {
|
||||
self.emojiFile = emojiFile
|
||||
let dimensions = emojiFile.dimensions ?? CGSize(width: 512.0, height: 512.0)
|
||||
self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: item.context.account.postbox, file: emojiFile, small: false, size: dimensions.aspectFilled(CGSize(width: 384.0, height: 384.0)), thumbnail: false))
|
||||
self.disposable.set(freeMediaFileInteractiveFetched(account: item.context.account, fileReference: .message(message: MessageReference(item.message), media: emojiFile)).start())
|
||||
var fitzModifier: EmojiFitzModifier?
|
||||
if let fitz = fitz {
|
||||
fitzModifier = EmojiFitzModifier(emoji: fitz)
|
||||
}
|
||||
self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: item.context.account.postbox, file: emojiFile, small: false, size: dimensions.aspectFilled(CGSize(width: 384.0, height: 384.0)), fitzModifier: fitzModifier, thumbnail: false))
|
||||
self.disposable.set(freeMediaFileInteractiveFetched(account: item.context.account, fileReference: .standalone(media: emojiFile)).start())
|
||||
self.updateVisibility()
|
||||
}
|
||||
}
|
||||
@ -269,7 +274,8 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
var file: TelegramMediaFile?
|
||||
var playbackMode: AnimatedStickerPlaybackMode = .loop
|
||||
var isEmoji = false
|
||||
|
||||
var fitzModifier: EmojiFitzModifier?
|
||||
|
||||
if let telegramFile = self.telegramFile {
|
||||
file = telegramFile
|
||||
if !item.controllerInteraction.stickerSettings.loopAnimatedStickers {
|
||||
@ -279,12 +285,16 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
isEmoji = true
|
||||
file = emojiFile
|
||||
playbackMode = .once
|
||||
let (_, fitz) = item.message.text.basicEmoji
|
||||
if let fitz = fitz {
|
||||
fitzModifier = EmojiFitzModifier(emoji: fitz)
|
||||
}
|
||||
}
|
||||
|
||||
if let file = file {
|
||||
let dimensions = file.dimensions ?? CGSize(width: 512.0, height: 512.0)
|
||||
let fittedSize = isEmoji ? dimensions.aspectFilled(CGSize(width: 384.0, height: 384.0)) : dimensions.aspectFitted(CGSize(width: 384.0, height: 384.0))
|
||||
self.animationNode.setup(account: item.context.account, resource: file.resource, width: Int(fittedSize.width), height: Int(fittedSize.height), playbackMode: playbackMode, mode: .cached)
|
||||
self.animationNode.setup(account: item.context.account, resource: file.resource, fitzModifier: fitzModifier, width: Int(fittedSize.width), height: Int(fittedSize.height), playbackMode: playbackMode, mode: .cached)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -387,7 +387,7 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible {
|
||||
}
|
||||
|
||||
if viewClassName == ChatMessageBubbleItemNode.self && self.presentationData.largeEmoji && messageIsElligibleForLargeEmoji(self.message) {
|
||||
if let _ = self.associatedData.animatedEmojiStickers[self.message.text.trimmedEmoji] {
|
||||
if let _ = self.associatedData.animatedEmojiStickers[self.message.text.basicEmoji.0] {
|
||||
viewClassName = ChatMessageAnimatedStickerItemNode.self
|
||||
} else {
|
||||
viewClassName = ChatMessageStickerItemNode.self
|
||||
|
@ -129,7 +129,7 @@ extension String {
|
||||
return string
|
||||
}
|
||||
|
||||
var basicEmoji: String {
|
||||
var basicEmoji: (String, String?) {
|
||||
let fitzCodes: [UInt32] = [
|
||||
0x1f3fb,
|
||||
0x1f3fc,
|
||||
@ -139,13 +139,18 @@ extension String {
|
||||
]
|
||||
|
||||
var string = ""
|
||||
var fitzModifier: String?
|
||||
for scalar in self.unicodeScalars {
|
||||
if fitzCodes.contains(scalar.value) {
|
||||
fitzModifier = String(scalar)
|
||||
continue
|
||||
}
|
||||
string.unicodeScalars.append(scalar)
|
||||
if scalar.value == 0x2764, self.unicodeScalars.count > 1, self.emojis.count == 1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return string
|
||||
return (string, fitzModifier)
|
||||
}
|
||||
|
||||
var trimmedEmoji: String {
|
||||
|
@ -112,19 +112,7 @@ public func fetchCachedResourceRepresentation(account: Account, resource: MediaR
|
||||
} else if let representation = representation as? CachedEmojiRepresentation {
|
||||
return fetchEmojiRepresentation(account: account, resource: resource, representation: representation)
|
||||
} else if let representation = representation as? CachedAnimatedStickerRepresentation {
|
||||
let data: Signal<MediaResourceData, NoError>
|
||||
// if let resource = resource as? LocalBundleResource {
|
||||
// data = Signal { subscriber in
|
||||
// if let path = frameworkBundle.path(forResource: resource.name, ofType: resource.ext), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) {
|
||||
// subscriber.putNext(MediaResourceData(path: path, offset: 0, size: data.count, complete: true))
|
||||
// subscriber.putCompletion()
|
||||
// }
|
||||
// return EmptyDisposable
|
||||
// }
|
||||
// } else {
|
||||
data = account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false))
|
||||
// }
|
||||
return data
|
||||
return account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false))
|
||||
|> mapToSignal { data -> Signal<CachedMediaResourceRepresentationResult, NoError> in
|
||||
if !data.complete {
|
||||
return .complete()
|
||||
@ -908,7 +896,7 @@ private func fetchEmojiRepresentation(account: Account, resource: MediaResource,
|
||||
private func fetchAnimatedStickerFirstFrameRepresentation(account: Account, resource: MediaResource, resourceData: MediaResourceData, representation: CachedAnimatedStickerFirstFrameRepresentation) -> Signal<CachedMediaResourceRepresentationResult, NoError> {
|
||||
return Signal({ subscriber in
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) {
|
||||
return fetchCompressedLottieFirstFrameAJpeg(data: data, size: CGSize(width: CGFloat(representation.width), height: CGFloat(representation.height)), cacheKey: "\(resource.id.uniqueId)-\(representation.uniqueId)").start(next: { file in
|
||||
return fetchCompressedLottieFirstFrameAJpeg(data: data, size: CGSize(width: CGFloat(representation.width), height: CGFloat(representation.height)), fitzModifier: representation.fitzModifier, cacheKey: "\(resource.id.uniqueId)-\(representation.uniqueId)").start(next: { file in
|
||||
subscriber.putNext(.tempFile(file))
|
||||
subscriber.putCompletion()
|
||||
})
|
||||
@ -923,7 +911,7 @@ private func fetchAnimatedStickerRepresentation(account: Account, resource: Medi
|
||||
return Signal({ subscriber in
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) {
|
||||
if #available(iOS 9.0, *) {
|
||||
return experimentalConvertCompressedLottieToCombinedMp4(data: data, size: CGSize(width: CGFloat(representation.width), height: CGFloat(representation.height)), cacheKey: "\(resource.id.uniqueId)-\(representation.uniqueId)").start(next: { path in
|
||||
return experimentalConvertCompressedLottieToCombinedMp4(data: data, size: CGSize(width: CGFloat(representation.width), height: CGFloat(representation.height)), fitzModifier: representation.fitzModifier, cacheKey: "\(resource.id.uniqueId)-\(representation.uniqueId)").start(next: { path in
|
||||
subscriber.putNext(.temporaryPath(path))
|
||||
subscriber.putCompletion()
|
||||
})
|
||||
|
@ -9,6 +9,12 @@ public func freeMediaFileInteractiveFetched(account: Account, fileReference: Fil
|
||||
return fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: fileReference.resourceReference(fileReference.media.resource))
|
||||
}
|
||||
|
||||
func freeMediaFileInteractiveFetched(fetchManager: FetchManager, fileReference: FileMediaReference, priority: FetchManagerPriority) -> Signal<Void, NoError> {
|
||||
let file = fileReference.media
|
||||
let mediaReference = AnyMediaReference.standalone(media: fileReference.media)
|
||||
return fetchManager.interactivelyFetched(category: fetchCategoryForFile(file), location: .chat(PeerId(namespace: 0, id: 0)), locationKey: .free, mediaReference: mediaReference, resourceReference: mediaReference.resourceReference(file.resource), ranges: IndexSet(integersIn: 0 ..< Int(Int32.max) as Range<Int>), statsCategory: statsCategoryForFileWithAttributes(file.attributes), elevatedPriority: false, userInitiated: false, priority: priority)
|
||||
}
|
||||
|
||||
func freeMediaFileResourceInteractiveFetched(account: Account, fileReference: FileMediaReference, resource: MediaResource) -> Signal<FetchResourceSourceType, FetchResourceError> {
|
||||
return fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: fileReference.resourceReference(resource))
|
||||
}
|
||||
|
@ -11,6 +11,11 @@ private final class PrefetchMediaContext {
|
||||
}
|
||||
}
|
||||
|
||||
public enum PrefetchMediaItem {
|
||||
case chatHistory(ChatHistoryPreloadMediaItem)
|
||||
case animatedEmojiSticker(TelegramMediaFile)
|
||||
}
|
||||
|
||||
private final class PrefetchManagerImpl {
|
||||
private let queue: Queue
|
||||
private let account: Account
|
||||
@ -36,7 +41,37 @@ private final class PrefetchManagerImpl {
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|
||||
self.listDisposable = (combineLatest(account.viewTracker.orderedPreloadMedia, sharedContext.automaticMediaDownloadSettings, networkType)
|
||||
let orderedPreloadMedia = account.viewTracker.orderedPreloadMedia
|
||||
|> mapToSignal { orderedPreloadMedia in
|
||||
return loadedStickerPack(postbox: account.postbox, network: account.network, reference: .animatedEmoji, forceActualized: false)
|
||||
|> map { result -> [PrefetchMediaItem] in
|
||||
let chatHistoryMediaItems = orderedPreloadMedia.map { PrefetchMediaItem.chatHistory($0) }
|
||||
switch result {
|
||||
case let .result(_, items, _):
|
||||
var animatedEmojiStickers: [String: StickerPackItem] = [:]
|
||||
for case let item as StickerPackItem in items {
|
||||
if let emoji = item.getStringRepresentationsOfIndexKeys().first {
|
||||
animatedEmojiStickers[emoji.basicEmoji.0] = item
|
||||
}
|
||||
}
|
||||
var stickerItems: [PrefetchMediaItem] = []
|
||||
let popularEmoji = ["\u{2764}", "👍", "😳", "😒", "🥳"]
|
||||
for emoji in popularEmoji {
|
||||
if let sticker = animatedEmojiStickers[emoji] {
|
||||
if let _ = account.postbox.mediaBox.completedResourcePath(sticker.file.resource) {
|
||||
} else {
|
||||
stickerItems.append(.animatedEmojiSticker(sticker.file))
|
||||
}
|
||||
}
|
||||
}
|
||||
return stickerItems + chatHistoryMediaItems
|
||||
default:
|
||||
return chatHistoryMediaItems
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.listDisposable = (combineLatest(orderedPreloadMedia, sharedContext.automaticMediaDownloadSettings, networkType)
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] orderedPreloadMedia, automaticDownloadSettings, networkType in
|
||||
self?.updateOrderedPreloadMedia(orderedPreloadMedia, automaticDownloadSettings: automaticDownloadSettings, networkType: networkType)
|
||||
})
|
||||
@ -47,79 +82,119 @@ private final class PrefetchManagerImpl {
|
||||
self.listDisposable?.dispose()
|
||||
}
|
||||
|
||||
private func updateOrderedPreloadMedia(_ orderedPreloadMedia: [ChatHistoryPreloadMediaItem], automaticDownloadSettings: MediaAutoDownloadSettings, networkType: MediaAutoDownloadNetworkType) {
|
||||
private func updateOrderedPreloadMedia(_ items: [PrefetchMediaItem], automaticDownloadSettings: MediaAutoDownloadSettings, networkType: MediaAutoDownloadNetworkType) {
|
||||
var validIds = Set<MediaId>()
|
||||
for mediaItem in orderedPreloadMedia {
|
||||
guard let id = mediaItem.media.media.id else {
|
||||
continue
|
||||
}
|
||||
if validIds.contains(id) {
|
||||
continue
|
||||
}
|
||||
|
||||
var automaticDownload: InteractiveMediaNodeAutodownloadMode = .none
|
||||
let peerType: MediaAutoDownloadPeerType
|
||||
if mediaItem.media.authorIsContact {
|
||||
peerType = .contact
|
||||
} else if let channel = mediaItem.media.peer as? TelegramChannel {
|
||||
if case .group = channel.info {
|
||||
peerType = .group
|
||||
} else {
|
||||
peerType = .channel
|
||||
}
|
||||
} else if mediaItem.media.peer is TelegramGroup {
|
||||
peerType = .group
|
||||
} else {
|
||||
peerType = .otherPrivate
|
||||
}
|
||||
var mediaResource: MediaResource?
|
||||
|
||||
if let telegramImage = mediaItem.media.media as? TelegramMediaImage {
|
||||
mediaResource = largestRepresentationForPhoto(telegramImage)?.resource
|
||||
if shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, authorPeerId: nil, contactsPeerIds: [], media: telegramImage) {
|
||||
automaticDownload = .full
|
||||
}
|
||||
} else if let telegramFile = mediaItem.media.media as? TelegramMediaFile {
|
||||
mediaResource = telegramFile.resource
|
||||
if shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, authorPeerId: nil, contactsPeerIds: [], media: telegramFile) {
|
||||
automaticDownload = .full
|
||||
} else if shouldPredownloadMedia(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, media: telegramFile) {
|
||||
automaticDownload = .prefetch
|
||||
}
|
||||
}
|
||||
|
||||
if case .none = automaticDownload {
|
||||
continue
|
||||
}
|
||||
guard let resource = mediaResource else {
|
||||
continue
|
||||
}
|
||||
|
||||
validIds.insert(id)
|
||||
let context: PrefetchMediaContext
|
||||
if let current = self.contexts[id] {
|
||||
context = current
|
||||
} else {
|
||||
context = PrefetchMediaContext()
|
||||
self.contexts[id] = context
|
||||
|
||||
let media = mediaItem.media.media
|
||||
|
||||
let priority: FetchManagerPriority = .backgroundPrefetch(locationOrder: mediaItem.preloadIndex, localOrder: mediaItem.media.index)
|
||||
|
||||
if case .full = automaticDownload {
|
||||
if let image = media as? TelegramMediaImage {
|
||||
context.fetchDisposable.set(messageMediaImageInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), image: image, resource: resource, userInitiated: false, priority: priority, storeToDownloadsPeerType: nil).start())
|
||||
} else if let _ = media as? TelegramMediaWebFile {
|
||||
//strongSelf.fetchDisposable.set(chatMessageWebFileInteractiveFetched(account: context.account, image: image).start())
|
||||
} else if let file = media as? TelegramMediaFile {
|
||||
let fetchSignal = messageMediaFileInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), file: file, userInitiated: false, priority: priority)
|
||||
context.fetchDisposable.set(fetchSignal.start())
|
||||
var order: Int32 = 0
|
||||
for mediaItem in items {
|
||||
switch mediaItem {
|
||||
case let .chatHistory(mediaItem):
|
||||
guard let id = mediaItem.media.media.id else {
|
||||
continue
|
||||
}
|
||||
} else if case .prefetch = automaticDownload, mediaItem.media.peer.id.namespace != Namespaces.Peer.SecretChat {
|
||||
if let file = media as? TelegramMediaFile, let _ = file.size {
|
||||
context.fetchDisposable.set(preloadVideoResource(postbox: self.account.postbox, resourceReference: FileMediaReference.message(message: MessageReference(peer: mediaItem.media.peer, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), media: file).resourceReference(file.resource), duration: 4.0).start())
|
||||
if validIds.contains(id) {
|
||||
continue
|
||||
}
|
||||
|
||||
var automaticDownload: InteractiveMediaNodeAutodownloadMode = .none
|
||||
let peerType: MediaAutoDownloadPeerType
|
||||
if mediaItem.media.authorIsContact {
|
||||
peerType = .contact
|
||||
} else if let channel = mediaItem.media.peer as? TelegramChannel {
|
||||
if case .group = channel.info {
|
||||
peerType = .group
|
||||
} else {
|
||||
peerType = .channel
|
||||
}
|
||||
} else if mediaItem.media.peer is TelegramGroup {
|
||||
peerType = .group
|
||||
} else {
|
||||
peerType = .otherPrivate
|
||||
}
|
||||
var mediaResource: MediaResource?
|
||||
|
||||
if let telegramImage = mediaItem.media.media as? TelegramMediaImage {
|
||||
mediaResource = largestRepresentationForPhoto(telegramImage)?.resource
|
||||
if shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, authorPeerId: nil, contactsPeerIds: [], media: telegramImage) {
|
||||
automaticDownload = .full
|
||||
}
|
||||
} else if let telegramFile = mediaItem.media.media as? TelegramMediaFile {
|
||||
mediaResource = telegramFile.resource
|
||||
if shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, authorPeerId: nil, contactsPeerIds: [], media: telegramFile) {
|
||||
automaticDownload = .full
|
||||
} else if shouldPredownloadMedia(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, media: telegramFile) {
|
||||
automaticDownload = .prefetch
|
||||
}
|
||||
}
|
||||
|
||||
if case .none = automaticDownload {
|
||||
continue
|
||||
}
|
||||
guard let resource = mediaResource else {
|
||||
continue
|
||||
}
|
||||
|
||||
validIds.insert(id)
|
||||
let context: PrefetchMediaContext
|
||||
if let current = self.contexts[id] {
|
||||
context = current
|
||||
} else {
|
||||
context = PrefetchMediaContext()
|
||||
self.contexts[id] = context
|
||||
|
||||
let media = mediaItem.media.media
|
||||
|
||||
let priority: FetchManagerPriority = .backgroundPrefetch(locationOrder: mediaItem.preloadIndex, localOrder: mediaItem.media.index)
|
||||
|
||||
if case .full = automaticDownload {
|
||||
if let image = media as? TelegramMediaImage {
|
||||
context.fetchDisposable.set(messageMediaImageInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), image: image, resource: resource, userInitiated: false, priority: priority, storeToDownloadsPeerType: nil).start())
|
||||
} else if let _ = media as? TelegramMediaWebFile {
|
||||
//strongSelf.fetchDisposable.set(chatMessageWebFileInteractiveFetched(account: context.account, image: image).start())
|
||||
} else if let file = media as? TelegramMediaFile {
|
||||
let fetchSignal = messageMediaFileInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), file: file, userInitiated: false, priority: priority)
|
||||
context.fetchDisposable.set(fetchSignal.start())
|
||||
}
|
||||
} else if case .prefetch = automaticDownload, mediaItem.media.peer.id.namespace != Namespaces.Peer.SecretChat {
|
||||
if let file = media as? TelegramMediaFile, let _ = file.size {
|
||||
context.fetchDisposable.set(preloadVideoResource(postbox: self.account.postbox, resourceReference: FileMediaReference.message(message: MessageReference(peer: mediaItem.media.peer, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), media: file).resourceReference(file.resource), duration: 4.0).start())
|
||||
}
|
||||
}
|
||||
}
|
||||
case let .animatedEmojiSticker(media):
|
||||
guard let id = media.id else {
|
||||
continue
|
||||
}
|
||||
if validIds.contains(id) {
|
||||
continue
|
||||
}
|
||||
|
||||
var automaticDownload: InteractiveMediaNodeAutodownloadMode = .none
|
||||
let peerType = MediaAutoDownloadPeerType.contact
|
||||
|
||||
if shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, authorPeerId: nil, contactsPeerIds: [], media: media) {
|
||||
automaticDownload = .full
|
||||
}
|
||||
|
||||
if case .none = automaticDownload {
|
||||
continue
|
||||
}
|
||||
|
||||
validIds.insert(id)
|
||||
let context: PrefetchMediaContext
|
||||
if let current = self.contexts[id] {
|
||||
context = current
|
||||
} else {
|
||||
context = PrefetchMediaContext()
|
||||
self.contexts[id] = context
|
||||
|
||||
let priority: FetchManagerPriority = .backgroundPrefetch(locationOrder: HistoryPreloadIndex(index: nil, hasUnread: false, isMuted: false, isPriority: true), localOrder: MessageIndex(id: MessageId(peerId: PeerId(namespace: 0, id: 0), namespace: 0, id: order), timestamp: 0))
|
||||
|
||||
if case .full = automaticDownload {
|
||||
let fetchSignal = freeMediaFileInteractiveFetched(fetchManager: self.fetchManager, fileReference: .standalone(media: media), priority: priority)
|
||||
context.fetchDisposable.set(fetchSignal.start())
|
||||
}
|
||||
|
||||
order += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -134,6 +134,9 @@ public final class PrincipalThemeEssentialGraphics {
|
||||
let emptyImage = UIImage()
|
||||
if preview {
|
||||
self.chatMessageBackgroundIncomingImage = messageBubbleImage(incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .none)
|
||||
self.chatMessageBackgroundOutgoingImage = messageBubbleImage(incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .none)
|
||||
self.checkBubbleFullImage = generateCheckImage(partial: false, color: theme.message.outgoingCheckColor)!
|
||||
self.checkBubblePartialImage = generateCheckImage(partial: true, color: theme.message.outgoingCheckColor)!
|
||||
self.chatMessageBackgroundIncomingHighlightedImage = emptyImage
|
||||
self.chatMessageBackgroundIncomingMergedTopImage = emptyImage
|
||||
self.chatMessageBackgroundIncomingMergedTopHighlightedImage = emptyImage
|
||||
@ -145,7 +148,6 @@ public final class PrincipalThemeEssentialGraphics {
|
||||
self.chatMessageBackgroundIncomingMergedBothHighlightedImage = emptyImage
|
||||
self.chatMessageBackgroundIncomingMergedSideImage = emptyImage
|
||||
self.chatMessageBackgroundIncomingMergedSideHighlightedImage = emptyImage
|
||||
self.chatMessageBackgroundOutgoingImage = messageBubbleImage(incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .none)
|
||||
self.chatMessageBackgroundOutgoingHighlightedImage = emptyImage
|
||||
self.chatMessageBackgroundOutgoingMergedTopImage = emptyImage
|
||||
self.chatMessageBackgroundOutgoingMergedTopHighlightedImage = emptyImage
|
||||
@ -157,8 +159,6 @@ public final class PrincipalThemeEssentialGraphics {
|
||||
self.chatMessageBackgroundOutgoingMergedBothHighlightedImage = emptyImage
|
||||
self.chatMessageBackgroundOutgoingMergedSideImage = emptyImage
|
||||
self.chatMessageBackgroundOutgoingMergedSideHighlightedImage = emptyImage
|
||||
self.checkBubbleFullImage = emptyImage
|
||||
self.checkBubblePartialImage = emptyImage
|
||||
self.checkMediaFullImage = emptyImage
|
||||
self.checkMediaPartialImage = emptyImage
|
||||
self.checkFreeFullImage = emptyImage
|
||||
|
@ -269,8 +269,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
|
||||
|
||||
let query = text.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if query.isSingleEmoji {
|
||||
signals = .single([searchStickers(account: account, query: text.trimmedEmoji)
|
||||
//|> take(1)
|
||||
signals = .single([searchStickers(account: account, query: text.basicEmoji.0)
|
||||
|> map { (nil, $0) }])
|
||||
} else if query.count > 1, let languageCode = languageCode, !languageCode.isEmpty && languageCode != "emoji" {
|
||||
var signal = searchEmojiKeywords(postbox: account.postbox, inputLanguageCode: languageCode, query: query.lowercased(), completeMatch: query.count < 3)
|
||||
@ -292,7 +291,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
|
||||
var signals: [Signal<(String?, [FoundStickerItem]), NoError>] = []
|
||||
let emoticons = keywords.flatMap { $0.emoticons }
|
||||
for emoji in emoticons {
|
||||
signals.append(searchStickers(account: self.context.account, query: emoji.trimmedEmoji)
|
||||
signals.append(searchStickers(account: self.context.account, query: emoji.basicEmoji.0)
|
||||
|> take(1)
|
||||
|> map { (emoji, $0) })
|
||||
}
|
||||
|
@ -96,11 +96,12 @@ private func chatMessageStickerDatas(postbox: Postbox, file: TelegramMediaFile,
|
||||
}
|
||||
}
|
||||
|
||||
func chatMessageAnimatedStickerDatas(postbox: Postbox, file: TelegramMediaFile, small: Bool, size: CGSize, fetched: Bool, onlyFullSize: Bool, synchronousLoad: Bool) -> Signal<Tuple3<Data?, Data?, Bool>, NoError> {
|
||||
public func chatMessageAnimatedStickerDatas(postbox: Postbox, file: TelegramMediaFile, small: Bool, size: CGSize, fitzModifier: EmojiFitzModifier? = nil, fetched: Bool, onlyFullSize: Bool, synchronousLoad: Bool) -> Signal<Tuple3<Data?, Data?, Bool>, NoError> {
|
||||
let thumbnailResource = chatMessageStickerResource(file: file, small: true)
|
||||
let resource = chatMessageStickerResource(file: file, small: small)
|
||||
|
||||
let maybeFetched = postbox.mediaBox.cachedResourceRepresentation(resource, representation: CachedAnimatedStickerFirstFrameRepresentation(width: Int32(size.width), height: Int32(size.height)), complete: false, fetch: false, attemptSynchronously: synchronousLoad)
|
||||
let firstFrameRepresentation = CachedAnimatedStickerFirstFrameRepresentation(width: Int32(size.width), height: Int32(size.height), fitzModifier: fitzModifier)
|
||||
let maybeFetched = postbox.mediaBox.cachedResourceRepresentation(resource, representation: firstFrameRepresentation, complete: false, fetch: false, attemptSynchronously: synchronousLoad)
|
||||
|
||||
return maybeFetched
|
||||
|> take(1)
|
||||
@ -111,7 +112,7 @@ func chatMessageAnimatedStickerDatas(postbox: Postbox, file: TelegramMediaFile,
|
||||
return .single(Tuple(nil, loadedData, true))
|
||||
} else {
|
||||
let thumbnailData = postbox.mediaBox.cachedResourceRepresentation(thumbnailResource, representation: CachedStickerAJpegRepresentation(size: nil), complete: false)
|
||||
let fullSizeData = postbox.mediaBox.cachedResourceRepresentation(resource, representation: CachedAnimatedStickerFirstFrameRepresentation(width: Int32(size.width), height: Int32(size.height)), complete: onlyFullSize)
|
||||
let fullSizeData = postbox.mediaBox.cachedResourceRepresentation(resource, representation: firstFrameRepresentation, complete: onlyFullSize)
|
||||
|> map { next in
|
||||
return (next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: .mappedIfSafe), next.complete)
|
||||
}
|
||||
@ -162,7 +163,7 @@ private func chatMessageStickerThumbnailData(postbox: Postbox, file: TelegramMed
|
||||
let thumbnailData = postbox.mediaBox.cachedResourceRepresentation(thumbnailResource, representation: CachedStickerAJpegRepresentation(size: nil), complete: false)
|
||||
|
||||
return Signal { subscriber in
|
||||
var fetchThumbnail = fetchedMediaResource(mediaBox: postbox.mediaBox, reference: stickerPackFileReference(file).resourceReference(thumbnailResource)).start()
|
||||
let fetchThumbnail = fetchedMediaResource(mediaBox: postbox.mediaBox, reference: stickerPackFileReference(file).resourceReference(thumbnailResource)).start()
|
||||
|
||||
let disposable = (thumbnailData
|
||||
|> map { thumbnailData -> Data? in
|
||||
@ -225,8 +226,9 @@ private func chatMessageStickerPackThumbnailData(postbox: Postbox, resource: Med
|
||||
}
|
||||
}
|
||||
|
||||
func chatMessageAnimationData(postbox: Postbox, resource: MediaResource, width: Int, height: Int, synchronousLoad: Bool) -> Signal<MediaResourceData, NoError> {
|
||||
let maybeFetched = postbox.mediaBox.cachedResourceRepresentation(resource, representation: CachedAnimatedStickerRepresentation(width: Int32(width), height: Int32(height)), complete: false, fetch: false, attemptSynchronously: synchronousLoad)
|
||||
public func chatMessageAnimationData(postbox: Postbox, resource: MediaResource, fitzModifier: EmojiFitzModifier? = nil, width: Int, height: Int, synchronousLoad: Bool) -> Signal<MediaResourceData, NoError> {
|
||||
let representation = CachedAnimatedStickerRepresentation(width: Int32(width), height: Int32(height), fitzModifier: fitzModifier)
|
||||
let maybeFetched = postbox.mediaBox.cachedResourceRepresentation(resource, representation: representation, complete: false, fetch: false, attemptSynchronously: synchronousLoad)
|
||||
|
||||
return maybeFetched
|
||||
|> take(1)
|
||||
@ -234,12 +236,12 @@ func chatMessageAnimationData(postbox: Postbox, resource: MediaResource, width:
|
||||
if maybeData.complete {
|
||||
return .single(maybeData)
|
||||
} else {
|
||||
return postbox.mediaBox.cachedResourceRepresentation(resource, representation: CachedAnimatedStickerRepresentation(width: Int32(width), height: Int32(height)), complete: false)
|
||||
return postbox.mediaBox.cachedResourceRepresentation(resource, representation: representation, complete: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func chatMessageAnimatedStrickerBackingData(postbox: Postbox, fileReference: FileMediaReference, synchronousLoad: Bool) -> Signal<Tuple2<Data?, Bool>, NoError> {
|
||||
public func chatMessageAnimatedStickerBackingData(postbox: Postbox, fileReference: FileMediaReference, synchronousLoad: Bool) -> Signal<Tuple2<Data?, Bool>, NoError> {
|
||||
let resource = fileReference.media.resource
|
||||
|
||||
let maybeFetched = postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad)
|
||||
@ -464,7 +466,7 @@ public func chatMessageSticker(postbox: Postbox, file: TelegramMediaFile, small:
|
||||
}
|
||||
}
|
||||
|
||||
public func chatMessageAnimatedSticker(postbox: Postbox, file: TelegramMediaFile, small: Bool, size: CGSize, fetched: Bool = false, onlyFullSize: Bool = false, thumbnail: Bool = false, synchronousLoad: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
||||
public func chatMessageAnimatedSticker(postbox: Postbox, file: TelegramMediaFile, small: Bool, size: CGSize, fitzModifier: EmojiFitzModifier? = nil, fetched: Bool = false, onlyFullSize: Bool = false, thumbnail: Bool = false, synchronousLoad: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
||||
let signal: Signal<Tuple3<Data?, Data?, Bool>, NoError>
|
||||
if thumbnail {
|
||||
signal = chatMessageStickerThumbnailData(postbox: postbox, file: file, synchronousLoad: synchronousLoad)
|
||||
@ -472,7 +474,7 @@ public func chatMessageAnimatedSticker(postbox: Postbox, file: TelegramMediaFile
|
||||
return Tuple(data, nil, false)
|
||||
}
|
||||
} else {
|
||||
signal = chatMessageAnimatedStickerDatas(postbox: postbox, file: file, small: small, size: size, fetched: fetched, onlyFullSize: onlyFullSize, synchronousLoad: synchronousLoad)
|
||||
signal = chatMessageAnimatedStickerDatas(postbox: postbox, file: file, small: small, size: size, fitzModifier: fitzModifier, fetched: fetched, onlyFullSize: onlyFullSize, synchronousLoad: synchronousLoad)
|
||||
}
|
||||
return signal
|
||||
|> map { value in
|
||||
|
Loading…
x
Reference in New Issue
Block a user