diff --git a/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift b/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift index 92df089561..f21f09f4a8 100644 --- a/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift +++ b/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift @@ -71,13 +71,24 @@ private protocol AnimatedStickerFrameSource: class { var frameRate: Int { get } var frameCount: Int { get } - func takeFrame() -> AnimatedStickerFrame + func takeFrame() -> AnimatedStickerFrame? +} + +private final class AnimatedStickerFrameSourceWrapper { + let value: AnimatedStickerFrameSource + + init(_ value: AnimatedStickerFrameSource) { + self.value = value + } } @available(iOS 9.0, *) private final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource { private let queue: Queue - private let data: Data + private var data: Data + private var dataComplete: Bool + private let notifyUpdated: () -> Void + private var scratchBuffer: Data let width: Int let bytesPerRow: Int @@ -90,9 +101,11 @@ private final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource var decodeBuffer: Data var frameBuffer: Data - init?(queue: Queue, data: Data) { + init?(queue: Queue, data: Data, complete: Bool, notifyUpdated: @escaping () -> Void) { self.queue = queue self.data = data + self.dataComplete = complete + self.notifyUpdated = notifyUpdated self.scratchBuffer = Data(count: compression_decode_scratch_buffer_size(COMPRESSION_LZFSE)) var offset = 0 @@ -152,7 +165,7 @@ private final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource assert(self.queue.isCurrent()) } - func takeFrame() -> AnimatedStickerFrame { + func takeFrame() -> AnimatedStickerFrame? { var frameData: Data? var isLastFrame = false @@ -163,8 +176,24 @@ private final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource let frameIndex = self.frameIndex self.data.withUnsafeBytes { (bytes: UnsafePointer) -> Void in + if self.offset + 4 > dataLength { + if self.dataComplete { + self.frameIndex = 0 + self.offset = self.initialOffset + self.frameBuffer.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer) -> Void in + memset(bytes, 0, frameBufferLength) + } + } + return + } + var frameLength: Int32 = 0 memcpy(&frameLength, bytes.advanced(by: self.offset), 4) + + if self.offset + 4 + Int(frameLength) > dataLength { + return + } + self.offset += 4 self.scratchBuffer.withUnsafeMutableBytes { (scratchBytes: UnsafeMutablePointer) -> Void in @@ -194,7 +223,7 @@ private final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource self.frameIndex += 1 self.offset += Int(frameLength) - if self.offset == dataLength { + if self.offset == dataLength && self.dataComplete { isLastFrame = true self.frameIndex = 0 self.offset = self.initialOffset @@ -204,7 +233,16 @@ private final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource } } - return AnimatedStickerFrame(data: frameData!, type: .yuva, width: self.width, height: self.height, bytesPerRow: self.bytesPerRow, index: frameIndex, isLastFrame: isLastFrame) + if let frameData = frameData { + return AnimatedStickerFrame(data: frameData, type: .yuva, width: self.width, height: self.height, bytesPerRow: self.bytesPerRow, index: frameIndex, isLastFrame: isLastFrame) + } else { + return nil + } + } + + func updateData(data: Data, complete: Bool) { + self.data = data + self.dataComplete = complete } } @@ -241,7 +279,7 @@ private final class AnimatedStickerDirectFrameSource: AnimatedStickerFrameSource assert(self.queue.isCurrent()) } - func takeFrame() -> AnimatedStickerFrame { + func takeFrame() -> AnimatedStickerFrame? { let frameIndex = self.currentFrame % self.frameCount self.currentFrame += 1 var frameData = Data(count: self.bytesPerRow * self.height) @@ -271,15 +309,23 @@ private final class AnimatedStickerFrameQueue { func take() -> AnimatedStickerFrame? { if self.frames.isEmpty { - self.frames.append(self.source.takeFrame()) + if let frame = self.source.takeFrame() { + self.frames.append(frame) + } + } + if !self.frames.isEmpty { + let frame = self.frames.removeFirst() + return frame + } else { + return nil } - let frame = self.frames.removeFirst() - return frame } func generateFramesIfNeeded() { if self.frames.isEmpty { - self.frames.append(self.source.takeFrame()) + if let frame = self.source.takeFrame() { + self.frames.append(frame) + } } } } @@ -297,7 +343,7 @@ public struct AnimatedStickerStatus: Equatable { } public protocol AnimatedStickerNodeSource { - func cachedDataPath(width: Int, height: Int) -> Signal + func cachedDataPath(width: Int, height: Int) -> Signal<(String, Bool), NoError> func directDataPath() -> Signal } @@ -312,7 +358,7 @@ public final class AnimatedStickerNodeLocalFileSource: AnimatedStickerNodeSource return .single(self.path) } - public func cachedDataPath(width: Int, height: Int) -> Signal { + public func cachedDataPath(width: Int, height: Int) -> Signal<(String, Bool), NoError> { return .never() } } @@ -330,9 +376,10 @@ public final class AnimatedStickerNode: ASDisplayNode { private var reportedStarted = false private let timer = Atomic(value: nil) + private let frameSource = Atomic?>(value: nil) private var directData: (Data, String, Int, Int)? - private var cachedData: Data? + private var cachedData: (Data, Bool)? private var renderer: (AnimationRenderer & ASDisplayNode)? @@ -421,15 +468,30 @@ public final class AnimatedStickerNode: ASDisplayNode { })) case .cached: self.disposable.set((source.cachedDataPath(width: width, height: height) - |> deliverOnMainQueue).start(next: { [weak self] path in + |> deliverOnMainQueue).start(next: { [weak self] path, complete in guard let strongSelf = self else { return } - strongSelf.cachedData = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) - if strongSelf.isPlaying { - strongSelf.play() - } else if strongSelf.canDisplayFirstFrame { - strongSelf.play(firstFrame: true) + if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) { + if let (_, currentComplete) = strongSelf.cachedData { + if !currentComplete { + strongSelf.cachedData = (data, complete) + strongSelf.frameSource.with { frameSource in + frameSource?.with { frameSource in + if let frameSource = frameSource.value as? AnimatedStickerCachedFrameSource { + frameSource.updateData(data: data, complete: complete) + } + } + } + } + } else { + strongSelf.cachedData = (data, complete) + if strongSelf.isPlaying { + strongSelf.play() + } else if strongSelf.canDisplayFirstFrame { + strongSelf.play(firstFrame: true) + } + } } })) } @@ -464,15 +526,24 @@ public final class AnimatedStickerNode: ASDisplayNode { let cachedData = self.cachedData let queue = self.queue let timerHolder = self.timer + let frameSourceHolder = self.frameSource self.queue.async { [weak self] in var maybeFrameSource: AnimatedStickerFrameSource? + var notifyUpdated: (() -> Void)? if let directData = directData { maybeFrameSource = AnimatedStickerDirectFrameSource(queue: queue, data: directData.0, width: directData.2, height: directData.3) - } else if let cachedData = cachedData { + } else if let (cachedData, cachedDataComplete) = cachedData { if #available(iOS 9.0, *) { - maybeFrameSource = AnimatedStickerCachedFrameSource(queue: queue, data: cachedData) + maybeFrameSource = AnimatedStickerCachedFrameSource(queue: queue, data: cachedData, complete: cachedDataComplete, notifyUpdated: { + notifyUpdated?() + }) } } + let _ = frameSourceHolder.swap(maybeFrameSource.flatMap { maybeFrameSource in + return QueueLocalObject(queue: queue, generate: { + return AnimatedStickerFrameSourceWrapper(maybeFrameSource) + }) + }) guard let frameSource = maybeFrameSource else { return } @@ -541,9 +612,9 @@ public final class AnimatedStickerNode: ASDisplayNode { var maybeFrameSource: AnimatedStickerFrameSource? if let directData = directData { maybeFrameSource = AnimatedStickerDirectFrameSource(queue: queue, data: directData.0, width: directData.2, height: directData.3) - } else if let cachedData = cachedData { + } else if let (cachedData, cachedDataComplete) = cachedData { if #available(iOS 9.0, *) { - maybeFrameSource = AnimatedStickerCachedFrameSource(queue: queue, data: cachedData) + maybeFrameSource = AnimatedStickerCachedFrameSource(queue: queue, data: cachedData, complete: cachedDataComplete, notifyUpdated: {}) } } guard let frameSource = maybeFrameSource else { diff --git a/submodules/Postbox/Sources/Coding.swift b/submodules/Postbox/Sources/Coding.swift index d69995fb19..d1232ad1de 100644 --- a/submodules/Postbox/Sources/Coding.swift +++ b/submodules/Postbox/Sources/Coding.swift @@ -148,7 +148,7 @@ public final class WriteBuffer: MemoryBuffer { self.offset = 0 } - public func write(_ data: UnsafeRawPointer, offset: Int, length: Int) { + public func write(_ data: UnsafeRawPointer, offset: Int = 0, length: Int) { if self.offset + length > self.capacity { self.capacity = self.offset + length + 256 if self.length == 0 { diff --git a/submodules/Postbox/Sources/MediaBox.swift b/submodules/Postbox/Sources/MediaBox.swift index 45a01133f4..874d137503 100644 --- a/submodules/Postbox/Sources/MediaBox.swift +++ b/submodules/Postbox/Sources/MediaBox.swift @@ -85,6 +85,9 @@ public enum MediaResourceDataFetchError { } public enum CachedMediaResourceRepresentationResult { + case reset + case data(Data) + case done case temporaryPath(String) case tempFile(TempBoxFile) } @@ -107,9 +110,19 @@ private struct CachedMediaResourceRepresentationKey: Hashable { } } +private final class CachedMediaResourceRepresentationSubscriber { + let update: (MediaResourceData) -> Void + let onlyComplete: Bool + + init(update: @escaping (MediaResourceData) -> Void, onlyComplete: Bool) { + self.update = update + self.onlyComplete = onlyComplete + } +} + private final class CachedMediaResourceRepresentationContext { var currentData: MediaResourceData? - let dataSubscribers = Bag<(MediaResourceData) -> Void>() + let dataSubscribers = Bag() let disposable = MetaDisposable() var initialized = false } @@ -191,7 +204,7 @@ public final class MediaBox { return ResourceStorePaths(partial: "\(self.basePath)/\(fileNameForId(id))_partial", complete: "\(self.basePath)/\(fileNameForId(id))") } - private func cachedRepresentationPathForId(_ id: MediaResourceId, representation: CachedMediaResourceRepresentation) -> String { + private func cachedRepresentationPathsForId(_ id: MediaResourceId, representation: CachedMediaResourceRepresentation) -> ResourceStorePaths { let cacheString: String switch representation.keepDuration { case .general: @@ -199,7 +212,7 @@ public final class MediaBox { case .shortLived: cacheString = "short-cache" } - return "\(self.basePath)/\(cacheString)/\(fileNameForId(id)):\(representation.uniqueId)" + return ResourceStorePaths(partial: "\(self.basePath)/\(cacheString)/\(fileNameForId(id))_partial:\(representation.uniqueId)", complete: "\(self.basePath)/\(cacheString)/\(fileNameForId(id)):\(representation.uniqueId)") } public func storeResourceData(_ id: MediaResourceId, data: Data, synchronous: Bool = false) { @@ -662,7 +675,7 @@ public final class MediaBox { public func storeCachedResourceRepresentation(_ resource: MediaResource, representation: CachedMediaResourceRepresentation, data: Data) { self.dataQueue.async { - let path = self.cachedRepresentationPathForId(resource.id, representation: representation) + let path = self.cachedRepresentationPathsForId(resource.id, representation: representation).complete let _ = try? data.write(to: URL(fileURLWithPath: path)) } } @@ -672,26 +685,26 @@ public final class MediaBox { let disposable = MetaDisposable() let begin: () -> Void = { - let path = self.cachedRepresentationPathForId(resource.id, representation: representation) - if let size = fileSize(path) { + let paths = self.cachedRepresentationPathsForId(resource.id, representation: representation) + if let size = fileSize(paths.complete) { self.timeBasedCleanup.touch(paths: [ - path + paths.complete ]) if let pathExtension = pathExtension { - let symlinkPath = path + ".\(pathExtension)" + let symlinkPath = paths.complete + ".\(pathExtension)" if fileSize(symlinkPath) == nil { - let _ = try? FileManager.default.linkItem(atPath: path, toPath: symlinkPath) + let _ = try? FileManager.default.linkItem(atPath: paths.complete, toPath: symlinkPath) } subscriber.putNext(MediaResourceData(path: symlinkPath, offset: 0, size: size, complete: true)) subscriber.putCompletion() } else { - subscriber.putNext(MediaResourceData(path: path, offset: 0, size: size, complete: true)) + subscriber.putNext(MediaResourceData(path: paths.complete, offset: 0, size: size, complete: true)) subscriber.putCompletion() } } else if fetch { if attemptSynchronously && complete { - subscriber.putNext(MediaResourceData(path: path, offset: 0, size: 0, complete: false)) + subscriber.putNext(MediaResourceData(path: paths.partial, offset: 0, size: 0, complete: false)) } self.dataQueue.async { let key = CachedMediaResourceRepresentationKey(resourceId: resource.id, representation: representation) @@ -703,7 +716,7 @@ public final class MediaBox { self.cachedRepresentationContexts[key] = context } - let index = context.dataSubscribers.add(({ data in + let index = context.dataSubscribers.add(CachedMediaResourceRepresentationSubscriber(update: { data in if !complete || data.complete { if let pathExtension = pathExtension, data.complete { let symlinkPath = data.path + ".\(pathExtension)" @@ -718,7 +731,7 @@ public final class MediaBox { if data.complete { subscriber.putCompletion() } - })) + }, onlyComplete: complete)) if let currentData = context.currentData { if !complete || currentData.complete { subscriber.putNext(currentData) @@ -727,7 +740,7 @@ public final class MediaBox { subscriber.putCompletion() } } else if !complete { - subscriber.putNext(MediaResourceData(path: path, offset: 0, size: 0, complete: false)) + subscriber.putNext(MediaResourceData(path: paths.partial, offset: 0, size: 0, complete: false)) } disposable.set(ActionDisposable { [weak context] in @@ -752,32 +765,65 @@ public final class MediaBox { } |> deliverOn(self.dataQueue) context.disposable.set(signal.start(next: { [weak self, weak context] next in + guard let strongSelf = self else { + return + } if let next = next { + var isDone = false switch next { - case let .temporaryPath(temporaryPath): - rename(temporaryPath, path) - case let .tempFile(tempFile): - rename(tempFile.path, path) - TempBox.shared.dispose(tempFile) + case let .temporaryPath(temporaryPath): + rename(temporaryPath, paths.complete) + isDone = true + case let .tempFile(tempFile): + rename(tempFile.path, paths.complete) + TempBox.shared.dispose(tempFile) + isDone = true + case .reset: + let file = ManagedFile(queue: strongSelf.dataQueue, path: paths.partial, mode: .readwrite) + file?.truncate(count: 0) + unlink(paths.complete) + case let .data(dataPart): + let file = ManagedFile(queue: strongSelf.dataQueue, path: paths.partial, mode: .append) + let dataCount = dataPart.count + dataPart.withUnsafeBytes { (bytes: UnsafePointer) -> Void in + file?.write(bytes, count: dataCount) + } + case .done: + link(paths.partial, paths.complete) + isDone = true } if let strongSelf = self, let currentContext = strongSelf.cachedRepresentationContexts[key], currentContext === context { - currentContext.disposable.dispose() - strongSelf.cachedRepresentationContexts.removeValue(forKey: key) - if let size = fileSize(path) { - let data = MediaResourceData(path: path, offset: 0, size: size, complete: true) + if isDone { + currentContext.disposable.dispose() + strongSelf.cachedRepresentationContexts.removeValue(forKey: key) + } + if let size = fileSize(paths.complete) { + let data = MediaResourceData(path: paths.complete, offset: 0, size: size, complete: isDone) currentContext.currentData = data for subscriber in currentContext.dataSubscribers.copyItems() { - subscriber(data) + if !subscriber.onlyComplete || isDone { + subscriber.update(data) + } + } + } else if let size = fileSize(paths.partial) { + let data = MediaResourceData(path: paths.partial, offset: 0, size: size, complete: isDone) + currentContext.currentData = data + for subscriber in currentContext.dataSubscribers.copyItems() { + if !subscriber.onlyComplete || isDone { + subscriber.update(data) + } } } } } else { if let strongSelf = self, let context = strongSelf.cachedRepresentationContexts[key] { - let data = MediaResourceData(path: path, offset: 0, size: 0, complete: false) + let data = MediaResourceData(path: paths.partial, offset: 0, size: 0, complete: false) context.currentData = data for subscriber in context.dataSubscribers.copyItems() { - subscriber(data) + if !subscriber.onlyComplete { + subscriber.update(data) + } } } } @@ -785,7 +831,7 @@ public final class MediaBox { } } } else { - subscriber.putNext(MediaResourceData(path: path, offset: 0, size: 0, complete: false)) + subscriber.putNext(MediaResourceData(path: paths.partial, offset: 0, size: 0, complete: false)) subscriber.putCompletion() } } diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewController.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewController.swift index 77b517f282..e0ab9e6f48 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewController.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewController.swift @@ -166,8 +166,28 @@ public final class StickerPackPreviewController: ViewController, StandalonePrese } switch next { - case let .result(_, items, _): + case let .result(info, items, _): var preloadSignals: [Signal] = [] + + if let thumbnail = info.thumbnail { + let signal = Signal { subscriber in + let fetched = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: .stickerPackThumbnail(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), resource: thumbnail.resource)).start() + let data = account.postbox.mediaBox.resourceData(thumbnail.resource, option: .incremental(waitUntilFetchStatus: false)).start(next: { data in + if data.complete { + subscriber.putNext(true) + subscriber.putCompletion() + } else { + subscriber.putNext(false) + } + }) + return ActionDisposable { + fetched.dispose() + data.dispose() + } + } + preloadSignals.append(signal) + } + let topItems = items.prefix(16) for item in topItems { if let item = item as? StickerPackItem, item.file.isAnimatedSticker { diff --git a/submodules/TelegramAnimatedStickerNode/Sources/AnimatedStickerUtils.swift b/submodules/TelegramAnimatedStickerNode/Sources/AnimatedStickerUtils.swift index 01b15180e1..3615d9008f 100644 --- a/submodules/TelegramAnimatedStickerNode/Sources/AnimatedStickerUtils.swift +++ b/submodules/TelegramAnimatedStickerNode/Sources/AnimatedStickerUtils.swift @@ -183,13 +183,12 @@ private let threadPool: ThreadPool = { }() @available(iOS 9.0, *) -public func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: CGSize, fitzModifier: EmojiFitzModifier? = nil, cacheKey: String) -> Signal { +public func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: CGSize, fitzModifier: EmojiFitzModifier? = nil, cacheKey: String) -> Signal { return Signal({ subscriber in let cancelled = Atomic(value: false) threadPool.addTask(ThreadPoolTask({ _ in if cancelled.with({ $0 }) { - //print("cancelled 1") return } @@ -206,12 +205,6 @@ public func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: C let endFrame = Int(player.frameCount) if cancelled.with({ $0 }) { - //print("cancelled 2") - return - } - - let path = NSTemporaryDirectory() + "\(arc4random64()).lz4v" - guard let fileContext = ManagedFile(queue: nil, path: path, mode: .readwrite) else { return } @@ -219,16 +212,19 @@ public func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: C var currentFrame: Int32 = 0 + let writeBuffer = WriteBuffer() + var numberOfFramesCommitted = 0 + var fps: Int32 = player.frameRate var frameCount: Int32 = player.frameCount - let _ = fileContext.write(&fps, count: 4) - let _ = fileContext.write(&frameCount, count: 4) + writeBuffer.write(&fps, length: 4) + writeBuffer.write(&frameCount, length: 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) + writeBuffer.write(&widthValue, length: 4) + writeBuffer.write(&heightValue, length: 4) + writeBuffer.write(&bytesPerRowValue, length: 4) let frameLength = bytesPerRow * Int(size.height) assert(frameLength % 16 == 0) @@ -262,7 +258,6 @@ public func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: C while currentFrame < endFrame { if cancelled.with({ $0 }) { - //print("cancelled 3") return } @@ -298,8 +293,8 @@ public func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: C compressedFrameData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer) -> 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) + writeBuffer.write(&frameLengthValue, length: 4) + writeBuffer.write(bytes, length: length) } let tmp = previousYuvaFrameData @@ -309,16 +304,33 @@ public func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: C compressionTime += CACurrentMediaTime() - compressionStartTime currentFrame += 1 + + numberOfFramesCommitted += 1 + + if numberOfFramesCommitted >= 5 { + numberOfFramesCommitted = 0 + + subscriber.putNext(writeBuffer.makeData()) + writeBuffer.reset() + + /*#if DEBUG + usleep(500000) + #endif*/ + } + + } + + if writeBuffer.length != 0 { + subscriber.putNext(writeBuffer.makeData()) } - subscriber.putNext(path) subscriber.putCompletion() - print("animation render time \(CACurrentMediaTime() - startTime)") + /*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)") + print("of which compression time \(compressionTime)")*/ } } })) diff --git a/submodules/TelegramAnimatedStickerNode/Sources/TelegramAnimatedStickerNode.swift b/submodules/TelegramAnimatedStickerNode/Sources/TelegramAnimatedStickerNode.swift index 7ac6fcf1f4..6947908e03 100644 --- a/submodules/TelegramAnimatedStickerNode/Sources/TelegramAnimatedStickerNode.swift +++ b/submodules/TelegramAnimatedStickerNode/Sources/TelegramAnimatedStickerNode.swift @@ -18,13 +18,13 @@ public final class AnimatedStickerResourceSource: AnimatedStickerNodeSource { self.fitzModifier = fitzModifier } - public func cachedDataPath(width: Int, height: Int) -> Signal { + public func cachedDataPath(width: Int, height: Int) -> Signal<(String, Bool), NoError> { return chatMessageAnimationData(postbox: self.account.postbox, resource: self.resource, fitzModifier: self.fitzModifier, width: width, height: height, synchronousLoad: false) |> filter { data in - return data.complete + return data.size != 0 } - |> map { data -> String in - return data.path + |> map { data -> (String, Bool) in + return (data.path, data.complete) } } diff --git a/submodules/TelegramUI/TelegramUI/FetchCachedRepresentations.swift b/submodules/TelegramUI/TelegramUI/FetchCachedRepresentations.swift index 3baf603f0b..6d9ac34c7f 100644 --- a/submodules/TelegramUI/TelegramUI/FetchCachedRepresentations.swift +++ b/submodules/TelegramUI/TelegramUI/FetchCachedRepresentations.swift @@ -855,8 +855,11 @@ 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)), fitzModifier: representation.fitzModifier, cacheKey: "\(resource.id.uniqueId)-\(representation.uniqueId)").start(next: { path in - subscriber.putNext(.temporaryPath(path)) + subscriber.putNext(.reset) + 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: { data in + subscriber.putNext(.data(data)) + }, completed: { + subscriber.putNext(.done) subscriber.putCompletion() }) } else { diff --git a/submodules/TelegramUI/TelegramUI/StickerPanePeerSpecificSetupGridItem.swift b/submodules/TelegramUI/TelegramUI/StickerPanePeerSpecificSetupGridItem.swift index 664cd70303..885253d798 100644 --- a/submodules/TelegramUI/TelegramUI/StickerPanePeerSpecificSetupGridItem.swift +++ b/submodules/TelegramUI/TelegramUI/StickerPanePeerSpecificSetupGridItem.swift @@ -148,7 +148,7 @@ class StickerPanePeerSpecificSetupGridItemNode: GridItemNode { let textSpacing: CGFloat = 3.0 let buttonSpacing: CGFloat = 6.0 - let (installLayout, installApply) = makeInstallLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.Stickers_GroupChooseStickerPack, font: buttonFont, textColor: item.theme.list.itemAccentColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (installLayout, installApply) = makeInstallLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.Stickers_GroupChooseStickerPack, font: buttonFont, textColor: item.theme.list.itemCheckColors.foregroundColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.Stickers_GroupStickers.uppercased(), font: titleFont, textColor: item.theme.chat.inputMediaPanel.stickersSectionTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - leftInset - rightInset - 20.0 - installLayout.size.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) diff --git a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift index c18fd9780c..ff48060730 100644 --- a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift +++ b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift @@ -530,6 +530,8 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { }) } + self.animatedStickerNode?.visibility = true + self.checkTimer() }