From a07762e84ee8b27d88e2b6f50abb5e4cd8dc8ae1 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Fri, 28 Jun 2019 17:22:32 +0300 Subject: [PATCH] Update API Refactor animated sticker playback Move playback work to background Cache animated stickers' first frame previews Introduce cache lifetime classes --- .../NotificationViewController.swift | 2 +- NotificationService/Serialization.swift | 2 +- Share/ShareRootController.swift | 2 +- SiriIntents/IntentHandler.swift | 2 +- Widget/TodayViewController.swift | 2 +- submodules/BuildConfig/Sources/BuildConfig.h | 2 +- submodules/BuildConfig/Sources/BuildConfig.m | 20 +- submodules/Postbox/Postbox/MediaBox.swift | 40 ++- .../Postbox/Postbox/MediaResource.swift | 1 + submodules/Postbox/Postbox/Postbox.swift | 2 +- .../Postbox/Postbox/TimeBasedCleanup.swift | 58 ++-- .../project.pbxproj | 2 - .../PerformanceTests.swift | 3 +- .../SwiftSignalKitBasicTests.swift | 19 ++ submodules/TelegramApi/Sources/Api1.swift | 82 ++++++ submodules/TelegramApi/Sources/Api3.swift | 73 ++--- .../TelegramCore/TelegramCore/Account.swift | 2 +- .../ManageChannelDiscussionGroup.swift | 53 ++-- .../TelegramCore/TelegramCore/Network.swift | 72 ++++- .../RegisterNotificationToken.swift | 8 +- .../TelegramCore/Serialization.swift | 2 +- .../TelegramUI/AnimatedStickerNode.swift | 266 ++++++++++++++++- .../TelegramUI/AnimatedStickerPlayer.swift | 5 - .../AnimatedStickerPlayerManager.swift | 5 - .../TelegramUI/AnimatedStickerUtils.swift | 207 +++++-------- .../AnimatedStickerVideoCompositor.swift | 5 - .../TelegramUI/AnimationRenderer.swift | 3 +- .../TelegramUI/TelegramUI/AppDelegate.swift | 20 +- .../CachedResourceRepresentations.swift | 59 +++- .../TelegramUI/ChatAnimationGalleryItem.swift | 4 +- .../ChatMediaInputStickerGridItem.swift | 4 +- .../ChatMessageAnimatedStickerItemNode.swift | 214 +------------- .../ChatMessageInteractiveMediaNode.swift | 2 +- .../FetchCachedRepresentations.swift | 50 +++- .../TelegramUI/LegacyWebSearchGallery.swift | 9 +- .../TelegramUI/MetalAnimationRenderer.swift | 5 +- .../NotificationContentContext.swift | 6 +- .../TelegramUI/PhotoResources.swift | 278 ++++++++++-------- .../TelegramUI/ShareExtensionContext.swift | 2 +- .../TelegramUI/SharedAccountContext.swift | 26 +- .../SoftwareAnimationRenderer.swift | 20 +- .../StickerPackPreviewGridItem.swift | 4 +- .../StickerPreviewPeekContent.swift | 4 +- .../TelegramUI/StickerResources.swift | 174 ++++++++++- submodules/TelegramUI/TelegramUI/Tuple.swift | 43 +++ .../project.pbxproj | 24 +- 46 files changed, 1198 insertions(+), 690 deletions(-) delete mode 100644 submodules/TelegramUI/TelegramUI/AnimatedStickerPlayer.swift delete mode 100644 submodules/TelegramUI/TelegramUI/AnimatedStickerPlayerManager.swift delete mode 100644 submodules/TelegramUI/TelegramUI/AnimatedStickerVideoCompositor.swift create mode 100644 submodules/TelegramUI/TelegramUI/Tuple.swift diff --git a/NotificationContent/NotificationViewController.swift b/NotificationContent/NotificationViewController.swift index 36d3cc67d4..327854aae7 100644 --- a/NotificationContent/NotificationViewController.swift +++ b/NotificationContent/NotificationViewController.swift @@ -38,7 +38,7 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown" - self.impl = NotificationViewControllerImpl(initializationData: NotificationViewControllerInitializationData(appGroupPath: appGroupUrl.path, apiId: buildConfig.apiId, languagesCategory: languagesCategory, encryptionParameters: encryptionParameters, appVersion: appVersion, bundleData: buildConfig.bundleData), setPreferredContentSize: { [weak self] size in + self.impl = NotificationViewControllerImpl(initializationData: NotificationViewControllerInitializationData(appGroupPath: appGroupUrl.path, apiId: buildConfig.apiId, languagesCategory: languagesCategory, encryptionParameters: encryptionParameters, appVersion: appVersion, bundleData: buildConfig.bundleData(withAppToken: nil)), setPreferredContentSize: { [weak self] size in self?.preferredContentSize = size }) } diff --git a/NotificationService/Serialization.swift b/NotificationService/Serialization.swift index 5e93f7806c..425f455c35 100644 --- a/NotificationService/Serialization.swift +++ b/NotificationService/Serialization.swift @@ -14,7 +14,7 @@ public class BoxedMessage: NSObject { public class Serialization: NSObject, MTSerialization { public func currentLayer() -> UInt { - return 102 + return 103 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/Share/ShareRootController.swift b/Share/ShareRootController.swift index 76644e2efe..9e30ed6bde 100644 --- a/Share/ShareRootController.swift +++ b/Share/ShareRootController.swift @@ -36,7 +36,7 @@ class ShareRootController: UIViewController { let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown" - self.impl = ShareRootControllerImpl(initializationData: ShareRootControllerInitializationData(appGroupPath: appGroupUrl.path, apiId: buildConfig.apiId, languagesCategory: languagesCategory, encryptionParameters: encryptionParameters, appVersion: appVersion, bundleData: buildConfig.bundleData), getExtensionContext: { [weak self] in + self.impl = ShareRootControllerImpl(initializationData: ShareRootControllerInitializationData(appGroupPath: appGroupUrl.path, apiId: buildConfig.apiId, languagesCategory: languagesCategory, encryptionParameters: encryptionParameters, appVersion: appVersion, bundleData: buildConfig.bundleData(withAppToken: nil)), getExtensionContext: { [weak self] in return self?.extensionContext }) } diff --git a/SiriIntents/IntentHandler.swift b/SiriIntents/IntentHandler.swift index 8300741925..37161276a6 100644 --- a/SiriIntents/IntentHandler.swift +++ b/SiriIntents/IntentHandler.swift @@ -92,7 +92,7 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag let deviceSpecificEncryptionParameters = BuildConfig.deviceSpecificEncryptionParameters(rootPath, baseAppBundleId: baseAppBundleId) let encryptionParameters = ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: deviceSpecificEncryptionParameters.key)!, salt: ValueBoxEncryptionParameters.Salt(data: deviceSpecificEncryptionParameters.salt)!) - account = currentAccount(allocateIfNotExists: false, networkArguments: NetworkInitializationArguments(apiId: apiId, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0, appData: buildConfig.bundleData), supplementary: true, manager: accountManager, rootPath: rootPath, auxiliaryMethods: accountAuxiliaryMethods, encryptionParameters: encryptionParameters) + account = currentAccount(allocateIfNotExists: false, networkArguments: NetworkInitializationArguments(apiId: apiId, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0, appData: .single(buildConfig.bundleData(withAppToken: nil))), supplementary: true, manager: accountManager, rootPath: rootPath, auxiliaryMethods: accountAuxiliaryMethods, encryptionParameters: encryptionParameters) |> mapToSignal { account -> Signal in if let account = account { switch account { diff --git a/Widget/TodayViewController.swift b/Widget/TodayViewController.swift index a06923c2c7..e6cd84c2f4 100644 --- a/Widget/TodayViewController.swift +++ b/Widget/TodayViewController.swift @@ -89,7 +89,7 @@ class TodayViewController: UIViewController, NCWidgetProviding { let deviceSpecificEncryptionParameters = BuildConfig.deviceSpecificEncryptionParameters(rootPath, baseAppBundleId: baseAppBundleId) let encryptionParameters = ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: deviceSpecificEncryptionParameters.key)!, salt: ValueBoxEncryptionParameters.Salt(data: deviceSpecificEncryptionParameters.salt)!) - account = currentAccount(allocateIfNotExists: false, networkArguments: NetworkInitializationArguments(apiId: apiId, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0, appData: buildConfig.bundleData), supplementary: true, manager: accountManager, rootPath: rootPath, auxiliaryMethods: auxiliaryMethods, encryptionParameters: encryptionParameters) + account = currentAccount(allocateIfNotExists: false, networkArguments: NetworkInitializationArguments(apiId: apiId, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0, appData: .single(buildConfig.bundleData(withAppToken: nil))), supplementary: true, manager: accountManager, rootPath: rootPath, auxiliaryMethods: auxiliaryMethods, encryptionParameters: encryptionParameters) |> mapToSignal { account -> Signal in if let account = account { switch account { diff --git a/submodules/BuildConfig/Sources/BuildConfig.h b/submodules/BuildConfig/Sources/BuildConfig.h index 42e8cff123..32a001384d 100644 --- a/submodules/BuildConfig/Sources/BuildConfig.h +++ b/submodules/BuildConfig/Sources/BuildConfig.h @@ -11,7 +11,6 @@ - (instancetype _Nonnull)initWithBaseAppBundleId:(NSString * _Nonnull)baseAppBundleId; -@property (nonatomic, strong, readonly) NSData * _Nullable bundleData; @property (nonatomic, strong, readonly) NSString * _Nullable hockeyAppId; @property (nonatomic, readonly) int32_t apiId; @property (nonatomic, strong, readonly) NSString * _Nonnull apiHash; @@ -21,5 +20,6 @@ @property (nonatomic, strong, readonly) NSString * _Nonnull appSpecificUrlScheme; + (DeviceSpecificEncryptionParameters * _Nonnull)deviceSpecificEncryptionParameters:(NSString * _Nonnull)rootPath baseAppBundleId:(NSString * _Nonnull)baseAppBundleId; +- (NSData * _Nullable)bundleDataWithAppToken:(NSData * _Nullable)appToken; @end diff --git a/submodules/BuildConfig/Sources/BuildConfig.m b/submodules/BuildConfig/Sources/BuildConfig.m index 1c2d070f1d..599ba43722 100644 --- a/submodules/BuildConfig/Sources/BuildConfig.m +++ b/submodules/BuildConfig/Sources/BuildConfig.m @@ -293,6 +293,7 @@ API_AVAILABLE(ios(10)) int32_t _apiId; NSString * _Nonnull _apiHash; NSString * _Nullable _hockeyAppId; + NSMutableDictionary * _Nonnull _dataDict; } @end @@ -351,25 +352,28 @@ API_AVAILABLE(ios(10)) _hockeyAppId = @(APP_CONFIG_HOCKEYAPP_ID); MTPKCS *signature = checkSignature([[[NSBundle mainBundle] executablePath] UTF8String]); - NSMutableDictionary *dataDict = [[NSMutableDictionary alloc] init]; + _dataDict = [[NSMutableDictionary alloc] init]; if (baseAppBundleId != nil) { - dataDict[@"bundleId"] = baseAppBundleId; + _dataDict[@"bundleId"] = baseAppBundleId; } if (signature.name != nil) { - dataDict[@"name"] = signature.name; + _dataDict[@"name"] = signature.name; } if (signature.data != nil) { - dataDict[@"data"] = [MTSha1(signature.data) base64EncodedStringWithOptions:0]; + _dataDict[@"data"] = [MTSha1(signature.data) base64EncodedStringWithOptions:0]; } - - _bundleData = [NSJSONSerialization dataWithJSONObject:dataDict options:0 error:nil]; } return self; } -- (NSData * _Nullable)bundleData { - return _bundleData; +- (NSData * _Nullable)bundleDataWithAppToken:(NSData * _Nullable)appToken { + NSMutableDictionary *dataDict = [[NSMutableDictionary alloc] initWithDictionary:_dataDict]; + if (appToken != nil) { + dataDict[@"device_token"] = [appToken base64EncodedStringWithOptions:0]; + } + NSData *data = [NSJSONSerialization dataWithJSONObject:dataDict options:0 error:nil]; + return data; } - (int32_t)apiId { diff --git a/submodules/Postbox/Postbox/MediaBox.swift b/submodules/Postbox/Postbox/MediaBox.swift index 0ae392e760..5c1ae219fc 100644 --- a/submodules/Postbox/Postbox/MediaBox.swift +++ b/submodules/Postbox/Postbox/MediaBox.swift @@ -87,12 +87,14 @@ public enum MediaResourceDataFetchError { case generic } -public struct CachedMediaResourceRepresentationResult { - public let temporaryPath: String - - public init(temporaryPath: String) { - self.temporaryPath = temporaryPath - } +public enum CachedMediaResourceRepresentationResult { + case temporaryPath(String) + case tempFile(TempBoxFile) +} + +public enum CachedMediaRepresentationKeepDuration { + case general + case shortLived } private struct CachedMediaResourceRepresentationKey: Hashable { @@ -160,21 +162,24 @@ public final class MediaBox { lazy var ensureDirectoryCreated: Void = { try! FileManager.default.createDirectory(atPath: self.basePath, withIntermediateDirectories: true, attributes: nil) try! FileManager.default.createDirectory(atPath: self.basePath + "/cache", withIntermediateDirectories: true, attributes: nil) + try! FileManager.default.createDirectory(atPath: self.basePath + "/short-cache", withIntermediateDirectories: true, attributes: nil) }() public init(basePath: String) { self.basePath = basePath - self.timeBasedCleanup = TimeBasedCleanup(paths: [ + self.timeBasedCleanup = TimeBasedCleanup(generalPaths: [ self.basePath, self.basePath + "/cache" + ], shortLivedPaths: [ + self.basePath + "/short-cache" ]) let _ = self.ensureDirectoryCreated } - public func setMaxStoreTime(_ maxStoreTime: Int32) { - self.timeBasedCleanup.setMaxStoreTime(maxStoreTime) + public func setMaxStoreTimes(general: Int32, shortLived: Int32) { + self.timeBasedCleanup.setMaxStoreTimes(general: general, shortLived: shortLived) } private func fileNameForId(_ id: MediaResourceId) -> String { @@ -190,7 +195,14 @@ public final class MediaBox { } private func cachedRepresentationPathForId(_ id: MediaResourceId, representation: CachedMediaResourceRepresentation) -> String { - return "\(self.basePath)/cache/\(fileNameForId(id)):\(representation.uniqueId)" + let cacheString: String + switch representation.keepDuration { + case .general: + cacheString = "cache" + case .shortLived: + cacheString = "short-cache" + } + return "\(self.basePath)/\(cacheString)/\(fileNameForId(id)):\(representation.uniqueId)" } public func storeResourceData(_ id: MediaResourceId, data: Data) { @@ -734,7 +746,13 @@ public final class MediaBox { |> deliverOn(self.dataQueue) context.disposable.set(signal.start(next: { [weak self, weak context] next in if let next = next { - rename(next.temporaryPath, path) + switch next { + case let .temporaryPath(temporaryPath): + rename(temporaryPath, path) + case let .tempFile(tempFile): + rename(tempFile.path, path) + TempBox.shared.dispose(tempFile) + } if let strongSelf = self, let currentContext = strongSelf.cachedRepresentationContexts[key], currentContext === context { currentContext.disposable.dispose() diff --git a/submodules/Postbox/Postbox/MediaResource.swift b/submodules/Postbox/Postbox/MediaResource.swift index 9b11defe05..730c666386 100644 --- a/submodules/Postbox/Postbox/MediaResource.swift +++ b/submodules/Postbox/Postbox/MediaResource.swift @@ -51,6 +51,7 @@ public extension MediaResource { public protocol CachedMediaResourceRepresentation { var uniqueId: String { get } + var keepDuration: CachedMediaRepresentationKeepDuration { get } func isEqual(to: CachedMediaResourceRepresentation) -> Bool } diff --git a/submodules/Postbox/Postbox/Postbox.swift b/submodules/Postbox/Postbox/Postbox.swift index d08006c7e2..c629d2411b 100644 --- a/submodules/Postbox/Postbox/Postbox.swift +++ b/submodules/Postbox/Postbox/Postbox.swift @@ -942,7 +942,7 @@ public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration, #if DEBUG //debugSaveState(basePath: basePath, name: "previous1") - //debugRestoreState(basePath: basePath, name: "previous1") + debugRestoreState(basePath: basePath, name: "previous1") #endif let startTime = CFAbsoluteTimeGetCurrent() diff --git a/submodules/Postbox/Postbox/TimeBasedCleanup.swift b/submodules/Postbox/Postbox/TimeBasedCleanup.swift index 93f8aefa1e..2028c44674 100644 --- a/submodules/Postbox/Postbox/TimeBasedCleanup.swift +++ b/submodules/Postbox/Postbox/TimeBasedCleanup.swift @@ -37,17 +37,20 @@ private func scanFiles(at path: String, olderThan minTimestamp: Int32, _ f: (Str private final class TimeBasedCleanupImpl { private let queue: Queue - private let paths: [String] + private let generalPaths: [String] + private let shortLivedPaths: [String] private var scheduledTouches: [String] = [] private var scheduledTouchesTimer: SignalKitTimer? - private var maxStoreTime: Int32? + private var generalMaxStoreTime: Int32? + private var shortLivedMaxStoreTime: Int32? private let scheduledScanDisposable = MetaDisposable() - init(queue: Queue, paths: [String]) { + init(queue: Queue, generalPaths: [String], shortLivedPaths: [String]) { self.queue = queue - self.paths = paths + self.generalPaths = generalPaths + self.shortLivedPaths = shortLivedPaths } deinit { @@ -56,31 +59,38 @@ private final class TimeBasedCleanupImpl { self.scheduledScanDisposable.dispose() } - public func setMaxStoreTime(_ maxStoreTime: Int32) { - if self.maxStoreTime != maxStoreTime { - self.maxStoreTime = maxStoreTime - #if DEBUG - return; - #endif - self.resetScan(maxStoreTime: maxStoreTime) + func setMaxStoreTimes(general: Int32, shortLived: Int32) { + if self.generalMaxStoreTime != general || self.shortLivedMaxStoreTime != shortLived { + self.generalMaxStoreTime = general + self.shortLivedMaxStoreTime = shortLived + self.resetScan(general: general, shortLived: shortLived) } } - private func resetScan(maxStoreTime: Int32) { - let paths = self.paths + private func resetScan(general: Int32, shortLived: Int32) { + let generalPaths = self.generalPaths + let shortLivedPaths = self.shortLivedPaths let scanOnce = Signal { subscriber in DispatchQueue.global(qos: .utility).async { - var removedCount: Int = 0 + var removedShortLivedCount: Int = 0 + var removedGeneralCount: Int = 0 let timestamp = Int32(Date().timeIntervalSince1970) - let oldestTimestamp = timestamp - maxStoreTime - for path in paths { - scanFiles(at: path, olderThan: oldestTimestamp, { file in - removedCount += 1 + let oldestShortLivedTimestamp = timestamp - shortLived + let oldestGeneralTimestamp = timestamp - general + for path in shortLivedPaths { + scanFiles(at: path, olderThan: oldestShortLivedTimestamp, { file in + removedShortLivedCount += 1 unlink(file) }) } - if removedCount != 0 { - print("[TimeBasedCleanup] removed \(removedCount) files") + for path in generalPaths { + scanFiles(at: path, olderThan: oldestGeneralTimestamp, { file in + removedGeneralCount += 1 + unlink(file) + }) + } + if removedShortLivedCount != 0 || removedGeneralCount != 0 { + print("[TimeBasedCleanup] removed \(removedShortLivedCount) short-lived files, \(removedGeneralCount) general files") } subscriber.putCompletion() } @@ -133,10 +143,10 @@ final class TimeBasedCleanup { private let queue = Queue() private let impl: QueueLocalObject - init(paths: [String]) { + init(generalPaths: [String], shortLivedPaths: [String]) { let queue = self.queue self.impl = QueueLocalObject(queue: self.queue, generate: { - return TimeBasedCleanupImpl(queue: queue, paths: paths) + return TimeBasedCleanupImpl(queue: queue, generalPaths: generalPaths, shortLivedPaths: shortLivedPaths) }) } @@ -146,9 +156,9 @@ final class TimeBasedCleanup { } } - func setMaxStoreTime(_ maxStoreTime: Int32) { + func setMaxStoreTimes(general: Int32, shortLived: Int32) { self.impl.with { impl in - impl.setMaxStoreTime(maxStoreTime) + impl.setMaxStoreTimes(general: general, shortLived: shortLived) } } } diff --git a/submodules/SSignalKit/SSignalKit_Xcode.xcodeproj/project.pbxproj b/submodules/SSignalKit/SSignalKit_Xcode.xcodeproj/project.pbxproj index 187ad2873e..f0350878f7 100644 --- a/submodules/SSignalKit/SSignalKit_Xcode.xcodeproj/project.pbxproj +++ b/submodules/SSignalKit/SSignalKit_Xcode.xcodeproj/project.pbxproj @@ -242,7 +242,6 @@ D07A5CFC1BBDE6E400451791 /* Promise.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Promise.swift; sourceTree = ""; }; D09FD73C1BA9BAB900FF0A4F /* SVariable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SVariable.h; sourceTree = ""; }; D09FD73D1BA9BAB900FF0A4F /* SVariable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SVariable.m; sourceTree = ""; }; - D0B417E71D7DFA40004562A4 /* SwiftSignalKit copy-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "SwiftSignalKit copy-Info.plist"; path = "/Users/peter/Documents/Telegram-iOS/submodules/SSignalKit/SwiftSignalKit copy-Info.plist"; sourceTree = ""; }; D0B417ED1D7DFA63004562A4 /* SwiftSignalKitMac.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftSignalKitMac.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D0B417EF1D7DFA63004562A4 /* SwiftSignalKitMac.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftSignalKitMac.h; sourceTree = ""; }; D0B417F01D7DFA63004562A4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -364,7 +363,6 @@ D0085B301B282B9800EAF753 /* SwiftSignalKitTests */, D0B417EE1D7DFA63004562A4 /* SwiftSignalKitMac */, D0445DD91A7C2CA500267924 /* Products */, - D0B417E71D7DFA40004562A4 /* SwiftSignalKit copy-Info.plist */, ); sourceTree = ""; }; diff --git a/submodules/SSignalKit/SwiftSignalKitTests/PerformanceTests.swift b/submodules/SSignalKit/SwiftSignalKitTests/PerformanceTests.swift index 313d02aacc..824ffcca7c 100644 --- a/submodules/SSignalKit/SwiftSignalKitTests/PerformanceTests.swift +++ b/submodules/SSignalKit/SwiftSignalKitTests/PerformanceTests.swift @@ -3,7 +3,7 @@ import XCTest import SwiftSignalKit import Foundation -final class DisposableLock { +/*final class DisposableLock { private var action: (() -> Void)? private var lock = pthread_mutex_t() @@ -164,3 +164,4 @@ class PerformanceTests: XCTestCase { print("read2 = \(sum2) ops = \(reads.count)") } } +*/ diff --git a/submodules/SSignalKit/SwiftSignalKitTests/SwiftSignalKitBasicTests.swift b/submodules/SSignalKit/SwiftSignalKitTests/SwiftSignalKitBasicTests.swift index 210a969ff9..9ff9df469d 100644 --- a/submodules/SSignalKit/SwiftSignalKitTests/SwiftSignalKitBasicTests.swift +++ b/submodules/SSignalKit/SwiftSignalKitTests/SwiftSignalKitBasicTests.swift @@ -278,4 +278,23 @@ class SwiftSignalKitTests: XCTestCase { XCTAssert(flag == true) } + + func testSingleDeallocation() { + do { + let signal: Signal<(Bool, WrapData?, Int), NoError> = .single((true, WrapData(data: Data(count: 1000)), 123)) + let _ = signal.start() + } + } +} + +final class WrapData { + let data: Data? + + init(data: Data?) { + self.data = data + } + + deinit { + print("deinit") + } } diff --git a/submodules/TelegramApi/Sources/Api1.swift b/submodules/TelegramApi/Sources/Api1.swift index 4e43658dfa..47da8f6c43 100644 --- a/submodules/TelegramApi/Sources/Api1.swift +++ b/submodules/TelegramApi/Sources/Api1.swift @@ -3949,6 +3949,7 @@ public extension Api { case updateUserStatus(userId: Int32, status: Api.UserStatus) case updateUserName(userId: Int32, firstName: String, lastName: String, username: String) case updateUserPhoto(userId: Int32, date: Int32, photo: Api.UserProfilePhoto, previous: Api.Bool) + case updateContactLink(userId: Int32, myLink: Api.ContactLink, foreignLink: Api.ContactLink) case updateNewEncryptedMessage(message: Api.EncryptedMessage, qts: Int32) case updateEncryptedChatTyping(chatId: Int32) case updateEncryption(chat: Api.EncryptedChat, date: Int32) @@ -4087,6 +4088,14 @@ public extension Api { photo.serialize(buffer, true) previous.serialize(buffer, true) break + case .updateContactLink(let userId, let myLink, let foreignLink): + if boxed { + buffer.appendInt32(-1657903163) + } + serializeInt32(userId, buffer: buffer, boxed: false) + myLink.serialize(buffer, true) + foreignLink.serialize(buffer, true) + break case .updateNewEncryptedMessage(let message, let qts): if boxed { buffer.appendInt32(314359194) @@ -4635,6 +4644,8 @@ public extension Api { return ("updateUserName", [("userId", userId), ("firstName", firstName), ("lastName", lastName), ("username", username)]) case .updateUserPhoto(let userId, let date, let photo, let previous): return ("updateUserPhoto", [("userId", userId), ("date", date), ("photo", photo), ("previous", previous)]) + case .updateContactLink(let userId, let myLink, let foreignLink): + return ("updateContactLink", [("userId", userId), ("myLink", myLink), ("foreignLink", foreignLink)]) case .updateNewEncryptedMessage(let message, let qts): return ("updateNewEncryptedMessage", [("message", message), ("qts", qts)]) case .updateEncryptedChatTyping(let chatId): @@ -4922,6 +4933,27 @@ public extension Api { return nil } } + public static func parse_updateContactLink(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.ContactLink? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.ContactLink + } + var _3: Api.ContactLink? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.ContactLink + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateContactLink(userId: _1!, myLink: _2!, foreignLink: _3!) + } + else { + return nil + } + } public static func parse_updateNewEncryptedMessage(_ reader: BufferReader) -> Update? { var _1: Api.EncryptedMessage? if let signature = reader.readInt32() { @@ -17812,6 +17844,56 @@ public extension Api { } } + } + public enum ContactLink: TypeConstructorDescription { + case contactLinkUnknown + case contactLinkNone + case contactLinkContact + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .contactLinkUnknown: + if boxed { + buffer.appendInt32(1599050311) + } + + break + case .contactLinkNone: + if boxed { + buffer.appendInt32(-17968211) + } + + break + case .contactLinkContact: + if boxed { + buffer.appendInt32(-721239344) + } + + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .contactLinkUnknown: + return ("contactLinkUnknown", []) + case .contactLinkNone: + return ("contactLinkNone", []) + case .contactLinkContact: + return ("contactLinkContact", []) + } + } + + public static func parse_contactLinkUnknown(_ reader: BufferReader) -> ContactLink? { + return Api.ContactLink.contactLinkUnknown + } + public static func parse_contactLinkNone(_ reader: BufferReader) -> ContactLink? { + return Api.ContactLink.contactLinkNone + } + public static func parse_contactLinkContact(_ reader: BufferReader) -> ContactLink? { + return Api.ContactLink.contactLinkContact + } + } public enum WebDocument: TypeConstructorDescription { case webDocumentNoProxy(url: String, size: Int32, mimeType: String, attributes: [Api.DocumentAttribute]) diff --git a/submodules/TelegramApi/Sources/Api3.swift b/submodules/TelegramApi/Sources/Api3.swift index 3c6ea492d4..7c38bd5aea 100644 --- a/submodules/TelegramApi/Sources/Api3.swift +++ b/submodules/TelegramApi/Sources/Api3.swift @@ -2922,20 +2922,6 @@ public extension Api { }) } - public static func hidePeerSettingsBar(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1336717624) - peer.serialize(buffer, true) - return (FunctionDescription(name: "messages.hidePeerSettingsBar", parameters: [("peer", peer)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } - public static func searchGlobal(flags: Int32, folderId: Int32?, q: String, offsetRate: Int32, offsetPeer: Api.InputPeer, offsetId: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() buffer.appendInt32(-1083038300) @@ -2955,6 +2941,20 @@ public extension Api { return result }) } + + public static func hidePeerSettingsBar(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1336717624) + peer.serialize(buffer, true) + return (FunctionDescription(name: "messages.hidePeerSettingsBar", parameters: [("peer", peer)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } } public struct channels { public static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { @@ -5065,28 +5065,6 @@ public extension Api { }) } - public static func registerDevice(tokenType: Int32, token: String, appSandbox: Api.Bool, secret: Buffer, otherUids: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1555998096) - serializeInt32(tokenType, buffer: buffer, boxed: false) - serializeString(token, buffer: buffer, boxed: false) - appSandbox.serialize(buffer, true) - serializeBytes(secret, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(otherUids.count)) - for item in otherUids { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "account.registerDevice", parameters: [("tokenType", tokenType), ("token", token), ("appSandbox", appSandbox), ("secret", secret), ("otherUids", otherUids)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } - public static func getAllSecureValues() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.SecureValue]>) { let buffer = Buffer() buffer.appendInt32(-1299661699) @@ -5497,6 +5475,29 @@ public extension Api { return result }) } + + public static func registerDevice(flags: Int32, tokenType: Int32, token: String, appSandbox: Api.Bool, secret: Buffer, otherUids: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1754754159) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(tokenType, buffer: buffer, boxed: false) + serializeString(token, buffer: buffer, boxed: false) + appSandbox.serialize(buffer, true) + serializeBytes(secret, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(otherUids.count)) + for item in otherUids { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "account.registerDevice", parameters: [("flags", flags), ("tokenType", tokenType), ("token", token), ("appSandbox", appSandbox), ("secret", secret), ("otherUids", otherUids)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } } public struct langpack { public static func getLangPack(langPack: String, langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { diff --git a/submodules/TelegramCore/TelegramCore/Account.swift b/submodules/TelegramCore/TelegramCore/Account.swift index 9236492597..089b41ce56 100644 --- a/submodules/TelegramCore/TelegramCore/Account.swift +++ b/submodules/TelegramCore/TelegramCore/Account.swift @@ -1311,7 +1311,7 @@ public class Account { return } let settings: CacheStorageSettings = sharedData.entries[SharedDataKeys.cacheStorageSettings] as? CacheStorageSettings ?? CacheStorageSettings.defaultSettings - mediaBox.setMaxStoreTime(settings.defaultCacheStorageTimeout) + mediaBox.setMaxStoreTimes(general: settings.defaultCacheStorageTimeout, shortLived: 60 * 60) }) let _ = masterNotificationsKey(masterNotificationKeyValue: self.masterNotificationKey, postbox: self.postbox, ignoreDisabled: false).start(next: { key in diff --git a/submodules/TelegramCore/TelegramCore/ManageChannelDiscussionGroup.swift b/submodules/TelegramCore/TelegramCore/ManageChannelDiscussionGroup.swift index 495405972f..ce815e1f3a 100644 --- a/submodules/TelegramCore/TelegramCore/ManageChannelDiscussionGroup.swift +++ b/submodules/TelegramCore/TelegramCore/ManageChannelDiscussionGroup.swift @@ -43,21 +43,14 @@ public enum ChannelDiscussionGroupError { case hasNotPermissions } -public func updateGroupDiscussionForChannel(network: Network, postbox: Postbox, channelId: PeerId, groupId: PeerId?) -> Signal { +public func updateGroupDiscussionForChannel(network: Network, postbox: Postbox, channelId: PeerId?, groupId: PeerId?) -> Signal { return postbox.transaction { transaction -> (channel: Peer?, group: Peer?) in - return (channel: transaction.getPeer(channelId), group: groupId != nil ? transaction.getPeer(groupId!) : nil) + return (channel: channelId.flatMap(transaction.getPeer), group: groupId.flatMap(transaction.getPeer)) } |> mapError { _ in ChannelDiscussionGroupError.generic } - |> mapToSignal { peers -> Signal in - guard let channel = peers.channel else { - return .fail(.generic) - } - - let tempGroupApi = peers.group != nil ? apiInputChannel(peers.group!) : Api.InputChannel.inputChannelEmpty - - guard let apiChannel = apiInputChannel(channel), let apiGroup = tempGroupApi else { - return .fail(.generic) - } + |> mapToSignal { channel, group -> Signal in + let apiChannel = channel.flatMap(apiInputChannel) ?? Api.InputChannel.inputChannelEmpty + let apiGroup = group.flatMap(apiInputChannel) ?? Api.InputChannel.inputChannelEmpty return network.request(Api.functions.channels.setDiscussionGroup(broadcast: apiChannel, group: apiGroup)) |> map { result in @@ -82,17 +75,33 @@ public func updateGroupDiscussionForChannel(network: Network, postbox: Postbox, |> mapToSignal { result in if result { return postbox.transaction { transaction in - var previousGroupId: PeerId? - transaction.updatePeerCachedData(peerIds: Set([channelId]), update: { (_, current) -> CachedPeerData? in - let current: CachedChannelData = current as? CachedChannelData ?? CachedChannelData() - previousGroupId = current.linkedDiscussionPeerId - return current.withUpdatedLinkedDiscussionPeerId(groupId) - }) - if let associatedId = previousGroupId ?? groupId { - transaction.updatePeerCachedData(peerIds: Set([associatedId]), update: { (_, current) -> CachedPeerData? in - let cachedData = (current as? CachedChannelData ?? CachedChannelData()) - return cachedData.withUpdatedLinkedDiscussionPeerId(groupId == nil ? nil : channelId) + if let channelId = channelId { + var previousGroupId: PeerId? + transaction.updatePeerCachedData(peerIds: Set([channelId]), update: { (_, current) -> CachedPeerData? in + let current: CachedChannelData = current as? CachedChannelData ?? CachedChannelData() + previousGroupId = current.linkedDiscussionPeerId + return current.withUpdatedLinkedDiscussionPeerId(groupId) }) + if let previousGroupId = previousGroupId, previousGroupId != groupId { + transaction.updatePeerCachedData(peerIds: Set([previousGroupId]), update: { (_, current) -> CachedPeerData? in + let cachedData = (current as? CachedChannelData ?? CachedChannelData()) + return cachedData.withUpdatedLinkedDiscussionPeerId(nil) + }) + } + } + if let groupId = groupId { + var previousChannelId: PeerId? + transaction.updatePeerCachedData(peerIds: Set([groupId]), update: { (_, current) -> CachedPeerData? in + let current: CachedChannelData = current as? CachedChannelData ?? CachedChannelData() + previousChannelId = current.linkedDiscussionPeerId + return current.withUpdatedLinkedDiscussionPeerId(channelId) + }) + if let previousChannelId = previousChannelId, previousChannelId != channelId { + transaction.updatePeerCachedData(peerIds: Set([previousChannelId]), update: { (_, current) -> CachedPeerData? in + let cachedData = (current as? CachedChannelData ?? CachedChannelData()) + return cachedData.withUpdatedLinkedDiscussionPeerId(nil) + }) + } } } |> introduceError(ChannelDiscussionGroupError.self) diff --git a/submodules/TelegramCore/TelegramCore/Network.swift b/submodules/TelegramCore/TelegramCore/Network.swift index 8a4add6033..abf78cdf2b 100644 --- a/submodules/TelegramCore/TelegramCore/Network.swift +++ b/submodules/TelegramCore/TelegramCore/Network.swift @@ -406,9 +406,9 @@ public struct NetworkInitializationArguments { public let languagesCategory: String public let appVersion: String public let voipMaxLayer: Int32 - public let appData: Data? + public let appData: Signal - public init(apiId: Int32, languagesCategory: String, appVersion: String, voipMaxLayer: Int32, appData: Data?) { + public init(apiId: Int32, languagesCategory: String, appVersion: String, voipMaxLayer: Int32, appData: Signal) { self.apiId = apiId self.languagesCategory = languagesCategory self.appVersion = appVersion @@ -419,23 +419,14 @@ public struct NetworkInitializationArguments { func initializedNetwork(arguments: NetworkInitializationArguments, supplementary: Bool, datacenterId: Int, keychain: Keychain, basePath: String, testingEnvironment: Bool, languageCode: String?, proxySettings: ProxySettings?, networkSettings: NetworkSettings?, phoneNumber: String?) -> Signal { return Signal { subscriber in - Queue.concurrentDefaultQueue().async { + let queue = Queue() + queue.async { let _ = registeredLoggingFunctions let serialization = Serialization() var apiEnvironment = MTApiEnvironment() - if let appData = arguments.appData { - if let jsonData = JSON(data: appData) { - if let value = apiJson(jsonData) { - let buffer = Buffer() - value.serialize(buffer, true) - apiEnvironment = apiEnvironment.withUpdatedSystemCode(buffer.makeData()) - } - } - } - apiEnvironment.apiId = arguments.apiId apiEnvironment.langPack = arguments.languagesCategory apiEnvironment.layer = NSNumber(value: Int(serialization.currentLayer())) @@ -448,6 +439,23 @@ func initializedNetwork(arguments: NetworkInitializationArguments, supplementary apiEnvironment = apiEnvironment.withUpdatedNetworkSettings((networkSettings ?? NetworkSettings.defaultSettings).mtNetworkSettings) + var appDataUpdatedImpl: ((Data?) -> Void)? + let syncValue = Atomic(value: nil) + let appDataDisposable = (arguments.appData + |> deliverOn(queue)).start(next: { value in + let _ = syncValue.swap(value) + appDataUpdatedImpl?(value) + }) + if let currentAppData = syncValue.swap(Data()) { + if let jsonData = JSON(data: currentAppData) { + if let value = apiJson(jsonData) { + let buffer = Buffer() + value.serialize(buffer, true) + apiEnvironment = apiEnvironment.withUpdatedSystemCode(buffer.makeData()) + } + } + } + let context = MTContext(serialization: serialization, apiEnvironment: apiEnvironment, isTestingEnvironment: testingEnvironment, useTempAuthKeys: false)! let seedAddressList: [Int: [String]] @@ -508,7 +516,37 @@ func initializedNetwork(arguments: NetworkInitializationArguments, supplementary mtProto.delegate = connectionStatusDelegate mtProto.add(requestService) - subscriber.putNext(Network(queue: Queue(), datacenterId: datacenterId, context: context, mtProto: mtProto, requestService: requestService, connectionStatusDelegate: connectionStatusDelegate, _connectionStatus: connectionStatus, basePath: basePath)) + let network = Network(queue: queue, datacenterId: datacenterId, context: context, mtProto: mtProto, requestService: requestService, connectionStatusDelegate: connectionStatusDelegate, _connectionStatus: connectionStatus, basePath: basePath, appDataDisposable: appDataDisposable) + appDataUpdatedImpl = { [weak network] data in + guard let data = data else { + return + } + guard let jsonData = JSON(data: data) else { + return + } + guard let value = apiJson(jsonData) else { + return + } + let buffer = Buffer() + value.serialize(buffer, true) + let systemCode = buffer.makeData() + + network?.context.updateApiEnvironment { environment in + let current = environment?.systemCode + let updateNetwork: Bool + if let current = current { + updateNetwork = systemCode != current + } else { + updateNetwork = true + } + if updateNetwork { + return environment?.withUpdatedSystemCode(systemCode) + } else { + return nil + } + } + } + subscriber.putNext(network) subscriber.putCompletion() } @@ -602,6 +640,8 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate { let basePath: String private let connectionStatusDelegate: MTProtoConnectionStatusDelegate + private let appDataDisposable: Disposable + private var _multiplexedRequestManager: MultiplexedRequestManager? var multiplexedRequestManager: MultiplexedRequestManager { return self._multiplexedRequestManager! @@ -642,7 +682,7 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate { return "Network context: \(self.context)" } - fileprivate init(queue: Queue, datacenterId: Int, context: MTContext, mtProto: MTProto, requestService: MTRequestMessageService, connectionStatusDelegate: MTProtoConnectionStatusDelegate, _connectionStatus: Promise, basePath: String) { + fileprivate init(queue: Queue, datacenterId: Int, context: MTContext, mtProto: MTProto, requestService: MTRequestMessageService, connectionStatusDelegate: MTProtoConnectionStatusDelegate, _connectionStatus: Promise, basePath: String, appDataDisposable: Disposable) { self.queue = queue self.datacenterId = datacenterId self.context = context @@ -651,6 +691,7 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate { self.requestService = requestService self.connectionStatusDelegate = connectionStatusDelegate self._connectionStatus = _connectionStatus + self.appDataDisposable = appDataDisposable self.basePath = basePath super.init() @@ -736,6 +777,7 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate { deinit { self.shouldKeepConnectionDisposable.dispose() + self.appDataDisposable.dispose() } public var globalTime: TimeInterval { diff --git a/submodules/TelegramCore/TelegramCore/RegisterNotificationToken.swift b/submodules/TelegramCore/TelegramCore/RegisterNotificationToken.swift index a0e9e22569..1975810603 100644 --- a/submodules/TelegramCore/TelegramCore/RegisterNotificationToken.swift +++ b/submodules/TelegramCore/TelegramCore/RegisterNotificationToken.swift @@ -27,7 +27,7 @@ public func unregisterNotificationToken(account: Account, token: Data, type: Not |> ignoreValues } -public func registerNotificationToken(account: Account, token: Data, type: NotificationTokenType, sandbox: Bool, otherAccountUserIds: [Int32]) -> Signal { +public func registerNotificationToken(account: Account, token: Data, type: NotificationTokenType, sandbox: Bool, otherAccountUserIds: [Int32], excludeMutedChats: Bool) -> Signal { return masterNotificationsKey(account: account, ignoreDisabled: false) |> mapToSignal { masterKey -> Signal in let mappedType: Int32 @@ -42,7 +42,11 @@ public func registerNotificationToken(account: Account, token: Data, type: Notif mappedType = 9 keyData = masterKey.data } - return account.network.request(Api.functions.account.registerDevice(tokenType: mappedType, token: hexString(token), appSandbox: sandbox ? .boolTrue : .boolFalse, secret: Buffer(data: keyData), otherUids: otherAccountUserIds)) + var flags: Int32 = 0 + if excludeMutedChats { + flags |= 1 << 0 + } + return account.network.request(Api.functions.account.registerDevice(flags: flags, tokenType: mappedType, token: hexString(token), appSandbox: sandbox ? .boolTrue : .boolFalse, secret: Buffer(data: keyData), otherUids: otherAccountUserIds)) |> retryRequest |> ignoreValues } diff --git a/submodules/TelegramCore/TelegramCore/Serialization.swift b/submodules/TelegramCore/TelegramCore/Serialization.swift index 81e2594098..0cfa7a42fd 100644 --- a/submodules/TelegramCore/TelegramCore/Serialization.swift +++ b/submodules/TelegramCore/TelegramCore/Serialization.swift @@ -216,7 +216,7 @@ public class BoxedMessage: NSObject { public class Serialization: NSObject, MTSerialization { public func currentLayer() -> UInt { - return 102 + return 103 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/submodules/TelegramUI/TelegramUI/AnimatedStickerNode.swift b/submodules/TelegramUI/TelegramUI/AnimatedStickerNode.swift index 18e0a07faf..6c03326847 100644 --- a/submodules/TelegramUI/TelegramUI/AnimatedStickerNode.swift +++ b/submodules/TelegramUI/TelegramUI/AnimatedStickerNode.swift @@ -1,12 +1,268 @@ import Foundation -import AsyncDisplayKit -import Display import SwiftSignalKit import Postbox import TelegramCore -import AVFoundation -import CoreImage +import Compression +import Display +import AsyncDisplayKit + +private final class AnimationFrameCache { + private var cache: [Int: NSPurgeableData] = [:] + + func get(index: Int, _ f: (NSPurgeableData?) -> Void) { + guard let data = self.cache[index] else { + f(nil) + return + } + if data.beginContentAccess() { + f(data) + data.endContentAccess() + } else { + self.cache.removeValue(forKey: index) + f(nil) + } + } + + func set(index: Int, bytes: UnsafeRawPointer, length: Int) { + let data = NSPurgeableData(bytes: bytes, length: length) + data.endContentAccess() + self.cache[index] = data + } + + func removeAll() { + self.cache.removeAll() + } +} + +private let sharedQueue = Queue() + +private class AnimatedStickerNodeDisplayEvents: ASDisplayNode { + private var value: Bool = false + var updated: ((Bool) -> Void)? + + override init() { + super.init() + + self.isLayerBacked = true + } + + override func didEnterHierarchy() { + super.didEnterHierarchy() + + if !self.value { + self.value = true + self.updated?(true) + } + } + + override func didExitHierarchy() { + super.didExitHierarchy() + + if self.value { + self.value = false + self.updated?(false) + } + } +} final class AnimatedStickerNode: ASDisplayNode { - + private let queue: Queue + private var account: Account? + private var fileReference: FileMediaReference? + private let disposable = MetaDisposable() + private let fetchDisposable = MetaDisposable() + private let eventsNode: AnimatedStickerNodeDisplayEvents + + var started: () -> Void = {} + var reportedStarted = false + + private let timer = Atomic(value: nil) + + private var data: Data? + + private var renderer: (AnimationRenderer & ASDisplayNode)? + + private var isPlaying: Bool = false + + var visibility = false { + didSet { + if self.visibility != oldValue { + self.updateIsPlaying() + } + } + } + + private var isDisplaying = false { + didSet { + if self.isDisplaying != oldValue { + self.updateIsPlaying() + } + } + } + + override init() { + self.queue = sharedQueue + self.eventsNode = AnimatedStickerNodeDisplayEvents() + + super.init() + + self.eventsNode.updated = { [weak self] value in + guard let strongSelf = self else { + return + } + strongSelf.isDisplaying = value + } + self.addSubnode(self.eventsNode) + } + + deinit { + self.disposable.dispose() + self.fetchDisposable.dispose() + self.timer.swap(nil)?.invalidate() + } + + override func didLoad() { + super.didLoad() + + #if targetEnvironment(simulator) + self.renderer = SoftwareAnimationRenderer() + #else + self.renderer = SoftwareAnimationRenderer() + //self.renderer = MetalAnimationRenderer() + #endif + self.renderer?.frame = CGRect(origin: CGPoint(), size: self.bounds.size) + self.addSubnode(self.renderer!) + } + + func setup(account: Account, fileReference: FileMediaReference, width: Int, height: Int) { + self.disposable.set(chatMessageAnimationData(postbox: account.postbox, fileReference: fileReference, width: width, height: height, synchronousLoad: false).start(next: { [weak self] data in + if let strongSelf = self, data.complete { + strongSelf.data = try? Data(contentsOf: URL(fileURLWithPath: data.path), options: [.mappedRead]) + if strongSelf.isPlaying { + strongSelf.play() + } + } + })) + self.fetchDisposable.set(fetchedMediaResource(postbox: account.postbox, reference: fileReference.resourceReference(fileReference.media.resource)).start()) + } + + func reset() { + self.disposable.set(nil) + self.fetchDisposable.set(nil) + } + + private func updateIsPlaying() { + let isPlaying = self.visibility && self.isDisplaying + if self.isPlaying != isPlaying { + self.isPlaying = isPlaying + if isPlaying { + self.play() + } else{ + self.stop() + } + } + } + + func play() { + guard let data = self.data else { + return + } + let queue = self.queue + let timerHolder = self.timer + self.queue.async { [weak self] in + if #available(iOS 9.0, *) { + let dataCount = data.count + timerHolder.swap(nil)?.invalidate() + var scratchBuffer = Data(count: compression_decode_scratch_buffer_size(COMPRESSION_LZ4)) + + var offset = 0 + var width = 0 + var height = 0 + + var fps: Int32 = 0 + data.withUnsafeBytes { (bytes: UnsafePointer) -> Void in + memcpy(&fps, bytes.advanced(by: offset), 4) + offset += 4 + var widthValue: Int32 = 0 + var heightValue: Int32 = 0 + memcpy(&widthValue, bytes.advanced(by: offset), 4) + offset += 4 + memcpy(&heightValue, bytes.advanced(by: offset), 4) + offset += 4 + width = Int(widthValue) + height = Int(heightValue) + } + + let initialOffset = offset + + var decodeBuffer = Data(count: width * 4 * height) + var frameBuffer = Data(count: width * 4 * height) + let decodeBufferLength = decodeBuffer.count + frameBuffer.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer) -> Void in + memset(bytes, 0, decodeBufferLength) + } + + var frameIndex = 0 + let timer = SwiftSignalKit.Timer(timeout: 1.0 / Double(fps), repeat: true, completion: { + data.withUnsafeBytes { (bytes: UnsafePointer) -> Void in + var frameLength: Int32 = 0 + memcpy(&frameLength, bytes.advanced(by: offset), 4) + + scratchBuffer.withUnsafeMutableBytes { (scratchBytes: UnsafeMutablePointer) -> Void in + decodeBuffer.withUnsafeMutableBytes { (decodeBytes: UnsafeMutablePointer) -> Void in + frameBuffer.withUnsafeMutableBytes { (frameBytes: UnsafeMutablePointer) -> Void in + compression_decode_buffer(decodeBytes, decodeBufferLength, bytes.advanced(by: offset + 4), Int(frameLength), UnsafeMutableRawPointer(scratchBytes), COMPRESSION_LZ4) + + var lhs = UnsafeMutableRawPointer(frameBytes).assumingMemoryBound(to: UInt64.self) + var rhs = UnsafeRawPointer(decodeBytes).assumingMemoryBound(to: UInt64.self) + for _ in 0 ..< decodeBufferLength / 8 { + lhs.pointee = lhs.pointee ^ rhs.pointee + lhs = lhs.advanced(by: 1) + rhs = rhs.advanced(by: 1) + } + + let frameData = Data(bytes: frameBytes, count: decodeBufferLength) + + Queue.mainQueue().async { + guard let strongSelf = self else { + return + } + strongSelf.renderer?.render(queue: strongSelf.queue, width: width, height: height, data: frameData, completion: { + guard let strongSelf = self else { + return + } + if !strongSelf.reportedStarted { + strongSelf.started() + } + }) + } + } + } + } + + offset += 4 + Int(frameLength) + frameIndex += 1 + if offset == dataCount { + offset = initialOffset + frameIndex = 0 + frameBuffer.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer) -> Void in + memset(bytes, 0, decodeBufferLength) + } + } + } + }, queue: queue) + let _ = timerHolder.swap(timer) + timer.start() + } + } + } + + func stop() { + self.reportedStarted = false + self.timer.swap(nil)?.invalidate() + } + + func updateLayout(size: CGSize) { + self.renderer?.frame = CGRect(origin: CGPoint(), size: size) + } } diff --git a/submodules/TelegramUI/TelegramUI/AnimatedStickerPlayer.swift b/submodules/TelegramUI/TelegramUI/AnimatedStickerPlayer.swift deleted file mode 100644 index 742ef93d8b..0000000000 --- a/submodules/TelegramUI/TelegramUI/AnimatedStickerPlayer.swift +++ /dev/null @@ -1,5 +0,0 @@ -import UIKit - -class AnimatedStickerPlayer: NSObject { - -} diff --git a/submodules/TelegramUI/TelegramUI/AnimatedStickerPlayerManager.swift b/submodules/TelegramUI/TelegramUI/AnimatedStickerPlayerManager.swift deleted file mode 100644 index b1cf03b6ea..0000000000 --- a/submodules/TelegramUI/TelegramUI/AnimatedStickerPlayerManager.swift +++ /dev/null @@ -1,5 +0,0 @@ -import Foundation - -final class AnimatedStickerPlayerManager { - -} diff --git a/submodules/TelegramUI/TelegramUI/AnimatedStickerUtils.swift b/submodules/TelegramUI/TelegramUI/AnimatedStickerUtils.swift index 0ae8f57051..a4645ef6d8 100644 --- a/submodules/TelegramUI/TelegramUI/AnimatedStickerUtils.swift +++ b/submodules/TelegramUI/TelegramUI/AnimatedStickerUtils.swift @@ -9,6 +9,7 @@ import TelegramUIPrivateModule import Compression import GZip import RLottie +import MobileCoreServices private func validateAnimationItems(_ items: [Any]?, shapes: Bool = true) -> Bool { if let items = items { @@ -81,6 +82,79 @@ func validateAnimationComposition(json: [AnyHashable: Any]) -> Bool { return true } +func fetchCompressedLottieFirstFrameAJpeg(data: Data, size: CGSize, cacheKey: String) -> Signal { + return Signal({ subscriber in + let queue = Queue() + + queue.async { + let decompressedData = TGGUnzipData(data) + if let decompressedData = decompressedData, let player = LottieInstance(data: decompressedData, cacheKey: cacheKey) { + 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)) + + let yuvaLength = Int(size.width) * Int(size.height) * 2 + Int(size.width) * Int(size.height) / 2 + assert(yuvaLength % 8 == 0) + 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)) + decodeYUVAToRGBA(yuvaFrameData.assumingMemoryBound(to: UInt8.self), context.bytes.assumingMemoryBound(to: UInt8.self), Int32(size.width), Int32(size.height)) + + if let colorImage = context.generateImage() { + let colorData = NSMutableData() + let alphaData = NSMutableData() + + let alphaImage = generateImage(size, contextGenerator: { size, context in + context.setFillColor(UIColor.white.cgColor) + context.fill(CGRect(origin: CGPoint(), size: size)) + context.clip(to: CGRect(origin: CGPoint(), size: size), mask: colorImage.cgImage!) + context.setFillColor(UIColor.black.cgColor) + context.fill(CGRect(origin: CGPoint(), size: size)) + }, scale: 1.0) + + if let alphaImage = alphaImage, 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 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() + } + } + } + } + } + return EmptyDisposable + }) +} + @available(iOS 9.0, *) func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: CGSize, cacheKey: String) -> Signal { return Signal({ subscriber in @@ -190,136 +264,3 @@ func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: CGSize, return EmptyDisposable }) } - -func convertCompressedLottieToCombinedMp4(data: Data, size: CGSize) -> Signal { - return Signal({ subscriber in - let startTime = CACurrentMediaTime() - var drawingTime: Double = 0 - var appendingTime: Double = 0 - - let decompressedData = TGGUnzipData(data) - if let decompressedData = decompressedData, let json = (try? JSONSerialization.jsonObject(with: decompressedData, options: [])) as? [AnyHashable: Any] { - if validateAnimationComposition(json: json) { - let model = LOTComposition(json: json) - if let startFrame = model.startFrame?.int32Value, let endFrame = model.endFrame?.int32Value { - print("read at \(CACurrentMediaTime() - startTime)") - - var randomId: Int64 = 0 - arc4random_buf(&randomId, 8) - let path = NSTemporaryDirectory() + "\(randomId).mp4" - let url = URL(fileURLWithPath: path) - - let videoSize = CGSize(width: size.width, height: size.height * 2.0) - let scale = size.width / 512.0 - - if let assetWriter = try? AVAssetWriter(outputURL: url, fileType: AVFileType.mp4) { - let videoSettings: [String: AnyObject] = [AVVideoCodecKey : AVVideoCodecH264 as AnyObject, AVVideoWidthKey : videoSize.width as AnyObject, AVVideoHeightKey : videoSize.height as AnyObject] - - let assetWriterInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: videoSettings) - let sourceBufferAttributes = [(kCVPixelBufferPixelFormatTypeKey as String): Int(kCVPixelFormatType_32ARGB), - (kCVPixelBufferWidthKey as String): Float(videoSize.width), - (kCVPixelBufferHeightKey as String): Float(videoSize.height)] as [String : Any] - let pixelBufferAdaptor = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: assetWriterInput, sourcePixelBufferAttributes: sourceBufferAttributes) - - assetWriter.add(assetWriterInput) - - if assetWriter.startWriting() { - print("startedWriting at \(CACurrentMediaTime() - startTime)") - assetWriter.startSession(atSourceTime: kCMTimeZero) - - var currentFrame: Int32 = 0 - let writeQueue = DispatchQueue(label: "assetWriterQueue") - writeQueue.async { - let container = LOTAnimationLayerContainer(model: model, size: size) - - let singleContext = DrawingContext(size: size, scale: 1.0, clear: true) - let context = DrawingContext(size: videoSize, scale: 1.0, clear: false) - - let fps: Int32 = model.framerate?.int32Value ?? 30 - let frameDuration = CMTimeMake(1, fps) - - assetWriterInput.requestMediaDataWhenReady(on: writeQueue) { - while assetWriterInput.isReadyForMoreMediaData && startFrame + currentFrame < endFrame { - let lastFrameTime = CMTimeMake(Int64(currentFrame - startFrame), fps) - let presentationTime = currentFrame == 0 ? lastFrameTime : CMTimeAdd(lastFrameTime, frameDuration) - - let drawStartTime = CACurrentMediaTime() - singleContext.withContext { context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.saveGState() - context.scaleBy(x: scale, y: scale) - container?.renderFrame(startFrame + currentFrame, in: context) - context.restoreGState() - } - - if let image = singleContext.generateImage()?.cgImage { - - let maskDecode = [ - CGFloat(1.0), CGFloat(1.0), - CGFloat(1.0), CGFloat(1.0), - CGFloat(1.0), CGFloat(1.0), - CGFloat(1.0), CGFloat(1.0)] - - let maskImage = CGImage(width: image.width, height: image.height, bitsPerComponent: image.bitsPerComponent, bitsPerPixel: image.bitsPerPixel, bytesPerRow: image.bytesPerRow, space: image.colorSpace!, bitmapInfo: image.bitmapInfo, provider: image.dataProvider!, decode: maskDecode, shouldInterpolate: image.shouldInterpolate, intent: image.renderingIntent)! - - context.withFlippedContext { context in - context.setFillColor(UIColor.white.cgColor) - context.fill(CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: videoSize)) - context.draw(image, in: CGRect(origin: CGPoint(x: 0.0, y: size.height), size: size)) - context.draw(maskImage, in: CGRect(origin: CGPoint(), size: size)) - } - drawingTime += CACurrentMediaTime() - drawStartTime - - let appendStartTime = CACurrentMediaTime() - if let image = context.generateImage() { - if let pixelBufferPool = pixelBufferAdaptor.pixelBufferPool { - let pixelBufferPointer = UnsafeMutablePointer.allocate(capacity: 1) - let status = CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pixelBufferPool, pixelBufferPointer) - if let pixelBuffer = pixelBufferPointer.pointee, status == 0 { - fillPixelBufferFromImage(image, pixelBuffer: pixelBuffer) - - pixelBufferAdaptor.append(pixelBuffer, withPresentationTime: presentationTime) - pixelBufferPointer.deinitialize(count: 1) - } else { - break - } - - pixelBufferPointer.deallocate() - } else { - break - } - } - appendingTime += CACurrentMediaTime() - appendStartTime - } - currentFrame += 1 - } - - if startFrame + currentFrame >= endFrame { - assetWriterInput.markAsFinished() - assetWriter.finishWriting { - subscriber.putNext(path) - subscriber.putCompletion() - print("animation render time \(CACurrentMediaTime() - startTime)") - print("of which drawing time \(drawingTime)") - print("of which appending time \(appendingTime)") - } - } - } - } - } - } - } - } - } - return EmptyDisposable - }) -} - -private func fillPixelBufferFromImage(_ image: UIImage, pixelBuffer: CVPixelBuffer) { - CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0)) - let pixelData = CVPixelBufferGetBaseAddress(pixelBuffer) - let rgbColorSpace = CGColorSpaceCreateDeviceRGB() - let context = CGContext(data: pixelData, width: Int(image.size.width), height: Int(image.size.height), bitsPerComponent: 8, bytesPerRow: CVPixelBufferGetBytesPerRow(pixelBuffer), space: rgbColorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue) - context?.draw(image.cgImage!, in: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)) - CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0)) -} diff --git a/submodules/TelegramUI/TelegramUI/AnimatedStickerVideoCompositor.swift b/submodules/TelegramUI/TelegramUI/AnimatedStickerVideoCompositor.swift deleted file mode 100644 index cbadf2f9b2..0000000000 --- a/submodules/TelegramUI/TelegramUI/AnimatedStickerVideoCompositor.swift +++ /dev/null @@ -1,5 +0,0 @@ -import AVFoundation - -final class AnimatedStickerVideoCompositor: NSObject { - -} diff --git a/submodules/TelegramUI/TelegramUI/AnimationRenderer.swift b/submodules/TelegramUI/TelegramUI/AnimationRenderer.swift index 0af8fc5306..5589033a42 100644 --- a/submodules/TelegramUI/TelegramUI/AnimationRenderer.swift +++ b/submodules/TelegramUI/TelegramUI/AnimationRenderer.swift @@ -1,6 +1,7 @@ import Foundation +import SwiftSignalKit import AsyncDisplayKit protocol AnimationRenderer { - func render(width: Int, height: Int, bytes: UnsafeRawPointer, length: Int) + func render(queue: Queue, width: Int, height: Int, data: Data, completion: @escaping () -> Void) } diff --git a/submodules/TelegramUI/TelegramUI/AppDelegate.swift b/submodules/TelegramUI/TelegramUI/AppDelegate.swift index 5d48ccc686..8b009c44f7 100644 --- a/submodules/TelegramUI/TelegramUI/AppDelegate.swift +++ b/submodules/TelegramUI/TelegramUI/AppDelegate.swift @@ -14,6 +14,7 @@ import TelegramPresentationData import TelegramCallsUI import TelegramVoip import BuildConfig +import DeviceCheck private let handleVoipNotifications = false @@ -212,10 +213,25 @@ final class SharedApplicationContext { } } + private let deviceToken = Promise(nil) + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? = nil) -> Bool { precondition(!testIsLaunched) testIsLaunched = true + if #available(iOS 11.0, *) { + let curDevice = DCDevice.current + if curDevice.isSupported { + curDevice.generateToken(completionHandler: { (data, error) in + if let tokenData = data { + self.deviceToken.set(.single(data)) + } else { + print("Error: \(error!.localizedDescription)") + } + }) + } + } + let launchStartTime = CFAbsoluteTimeGetCurrent() let statusBarHost = ApplicationStatusBarHost() @@ -341,7 +357,9 @@ final class SharedApplicationContext { let apiId: Int32 = buildConfig.apiId let languagesCategory = "ios" - let networkArguments = NetworkInitializationArguments(apiId: apiId, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: PresentationCallManager.voipMaxLayer, appData: buildConfig.bundleData) + let networkArguments = NetworkInitializationArguments(apiId: apiId, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: PresentationCallManager.voipMaxLayer, appData: self.deviceToken.get() |> map { token in + return buildConfig.bundleData(withAppToken: token) + }) guard let appGroupUrl = maybeAppGroupUrl else { UIAlertView(title: nil, message: "Error 2", delegate: nil, cancelButtonTitle: "OK").show() diff --git a/submodules/TelegramUI/TelegramUI/CachedResourceRepresentations.swift b/submodules/TelegramUI/TelegramUI/CachedResourceRepresentations.swift index 20bd11248e..becdc85c14 100644 --- a/submodules/TelegramUI/TelegramUI/CachedResourceRepresentations.swift +++ b/submodules/TelegramUI/TelegramUI/CachedResourceRepresentations.swift @@ -5,6 +5,7 @@ import SwiftSignalKit final class CachedStickerAJpegRepresentation: CachedMediaResourceRepresentation { let size: CGSize? + let keepDuration: CachedMediaRepresentationKeepDuration = .general var uniqueId: String { if let size = self.size { @@ -33,6 +34,8 @@ enum CachedScaledImageRepresentationMode: Int32 { } final class CachedScaledImageRepresentation: CachedMediaResourceRepresentation { + let keepDuration: CachedMediaRepresentationKeepDuration = .general + let size: CGSize let mode: CachedScaledImageRepresentationMode @@ -55,6 +58,8 @@ final class CachedScaledImageRepresentation: CachedMediaResourceRepresentation { } final class CachedVideoFirstFrameRepresentation: CachedMediaResourceRepresentation { + let keepDuration: CachedMediaRepresentationKeepDuration = .general + var uniqueId: String { return "first-frame" } @@ -69,6 +74,8 @@ final class CachedVideoFirstFrameRepresentation: CachedMediaResourceRepresentati } final class CachedScaledVideoFirstFrameRepresentation: CachedMediaResourceRepresentation { + let keepDuration: CachedMediaRepresentationKeepDuration = .general + let size: CGSize var uniqueId: String { @@ -89,6 +96,8 @@ final class CachedScaledVideoFirstFrameRepresentation: CachedMediaResourceRepres } final class CachedBlurredWallpaperRepresentation: CachedMediaResourceRepresentation { + let keepDuration: CachedMediaRepresentationKeepDuration = .general + var uniqueId: String { return "blurred-wallpaper" } @@ -103,6 +112,8 @@ final class CachedBlurredWallpaperRepresentation: CachedMediaResourceRepresentat } final class CachedPatternWallpaperMaskRepresentation: CachedMediaResourceRepresentation { + let keepDuration: CachedMediaRepresentationKeepDuration = .general + let size: CGSize? var uniqueId: String { @@ -128,6 +139,8 @@ final class CachedPatternWallpaperMaskRepresentation: CachedMediaResourceReprese final class CachedPatternWallpaperRepresentation: CachedMediaResourceRepresentation { + let keepDuration: CachedMediaRepresentationKeepDuration = .general + let color: Int32 let intensity: Int32 @@ -150,6 +163,8 @@ final class CachedPatternWallpaperRepresentation: CachedMediaResourceRepresentat } final class CachedAlbumArtworkRepresentation: CachedMediaResourceRepresentation { + let keepDuration: CachedMediaRepresentationKeepDuration = .general + let size: CGSize? var uniqueId: String { @@ -174,6 +189,8 @@ final class CachedAlbumArtworkRepresentation: CachedMediaResourceRepresentation } final class CachedEmojiThumbnailRepresentation: CachedMediaResourceRepresentation { + let keepDuration: CachedMediaRepresentationKeepDuration = .general + let outline: Bool var uniqueId: String { @@ -194,6 +211,8 @@ final class CachedEmojiThumbnailRepresentation: CachedMediaResourceRepresentatio } final class CachedEmojiRepresentation: CachedMediaResourceRepresentation { + let keepDuration: CachedMediaRepresentationKeepDuration = .general + let tile: UInt8 let outline: Bool @@ -215,7 +234,39 @@ final class CachedEmojiRepresentation: CachedMediaResourceRepresentation { } } +final class CachedAnimatedStickerFirstFrameRepresentation: CachedMediaResourceRepresentation { + let keepDuration: CachedMediaRepresentationKeepDuration = .general + + let width: Int32 + let height: Int32 + + init(width: Int32, height: Int32) { + self.width = width + self.height = height + } + + var uniqueId: String { + return "animated-sticker-first-frame-\(self.width)x\(self.height)-v1" + } + + func isEqual(to: CachedMediaResourceRepresentation) -> Bool { + if let other = to as? CachedAnimatedStickerFirstFrameRepresentation { + if other.width != self.width { + return false + } + if other.height != self.height { + return false + } + return true + } else { + return false + } + } +} + final class CachedAnimatedStickerRepresentation: CachedMediaResourceRepresentation { + let keepDuration: CachedMediaRepresentationKeepDuration = .shortLived + let width: Int32 let height: Int32 @@ -229,7 +280,13 @@ final class CachedAnimatedStickerRepresentation: CachedMediaResourceRepresentati } func isEqual(to: CachedMediaResourceRepresentation) -> Bool { - if let _ = to as? CachedAnimatedStickerRepresentation { + if let other = to as? CachedAnimatedStickerRepresentation { + if other.width != self.width { + return false + } + if other.height != self.height { + return false + } return true } else { return false diff --git a/submodules/TelegramUI/TelegramUI/ChatAnimationGalleryItem.swift b/submodules/TelegramUI/TelegramUI/ChatAnimationGalleryItem.swift index ca0adedbec..578c2e6477 100644 --- a/submodules/TelegramUI/TelegramUI/ChatAnimationGalleryItem.swift +++ b/submodules/TelegramUI/TelegramUI/ChatAnimationGalleryItem.swift @@ -134,8 +134,8 @@ 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) - |> mapToSignal { data, completed -> Signal in - if completed, let data = data { + |> mapToSignal { value -> Signal in + if value._1, let data = value._0 { return .single(data) } else { return .complete() diff --git a/submodules/TelegramUI/TelegramUI/ChatMediaInputStickerGridItem.swift b/submodules/TelegramUI/TelegramUI/ChatMediaInputStickerGridItem.swift index 11f27eada4..d325a37b9b 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMediaInputStickerGridItem.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMediaInputStickerGridItem.swift @@ -166,7 +166,7 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode { private var currentState: (Account, StickerPackItem, CGSize)? private var currentSize: CGSize? private let imageNode: TransformImageNode - private var animationNode: StickerAnimationNode? + private var animationNode: AnimatedStickerNode? private let stickerFetchedDisposable = MetaDisposable() @@ -220,7 +220,7 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode { if let dimensions = item.stickerItem.file.dimensions { if item.stickerItem.file.isAnimatedSticker { if self.animationNode == nil { - let animationNode = StickerAnimationNode() + let animationNode = AnimatedStickerNode() animationNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.imageNodeTap(_:)))) self.animationNode = animationNode self.addSubnode(animationNode) diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageAnimatedStickerItemNode.swift index da213e8467..b6f89da5f4 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageAnimatedStickerItemNode.swift @@ -9,217 +9,9 @@ import CoreImage import TelegramPresentationData import Compression -private final class AnimationFrameCache { - private var cache: [Int: NSPurgeableData] = [:] - - func get(index: Int, _ f: (NSPurgeableData?) -> Void) { - guard let data = self.cache[index] else { - f(nil) - return - } - if data.beginContentAccess() { - f(data) - data.endContentAccess() - } else { - self.cache.removeValue(forKey: index) - f(nil) - } - } - - func set(index: Int, bytes: UnsafeRawPointer, length: Int) { - let data = NSPurgeableData(bytes: bytes, length: length) - data.endContentAccess() - self.cache[index] = data - } - - func removeAll() { - self.cache.removeAll() - } -} - -final class StickerAnimationNode: ASDisplayNode { - private var account: Account? - private var fileReference: FileMediaReference? - private let disposable = MetaDisposable() - private let fetchDisposable = MetaDisposable() - - var started: () -> Void = {} - private var reportedStarted = false - - private var timer: SwiftSignalKit.Timer? - - private var data: Data? - private var frameCache = AnimationFrameCache() - - private var renderer: (AnimationRenderer & ASDisplayNode)? - - var visibility = false { - didSet { - if self.visibility { - self.play() - } else{ - self.stop() - } - } - } - - override init() { - super.init() - } - - deinit { - self.disposable.dispose() - self.fetchDisposable.dispose() - self.timer?.invalidate() - } - - override func didLoad() { - super.didLoad() - - #if targetEnvironment(simulator) - self.renderer = SoftwareAnimationRenderer() - #else - self.renderer = SoftwareAnimationRenderer() - //self.renderer = MetalAnimationRenderer() - #endif - self.renderer?.frame = CGRect(origin: CGPoint(), size: self.bounds.size) - self.addSubnode(self.renderer!) - } - - func setup(account: Account, fileReference: FileMediaReference, width: Int, height: Int) { - self.disposable.set(chatMessageAnimationData(postbox: account.postbox, fileReference: fileReference, width: width, height: height, synchronousLoad: false).start(next: { [weak self] data in - if let strongSelf = self, data.complete { - strongSelf.data = try? Data(contentsOf: URL(fileURLWithPath: data.path), options: [.mappedRead]) - if strongSelf.visibility { - strongSelf.play() - } - } - })) - self.fetchDisposable.set(fetchedMediaResource(postbox: account.postbox, reference: fileReference.resourceReference(fileReference.media.resource)).start()) - } - - func reset() { - self.disposable.set(nil) - self.fetchDisposable.set(nil) - } - - func play() { - guard let data = self.data else { - return - } - if #available(iOS 9.0, *) { - let dataCount = data.count - self.timer?.invalidate() - var scratchBuffer = Data(count: compression_decode_scratch_buffer_size(COMPRESSION_LZ4)) - - var offset = 0 - var width = 0 - var height = 0 - - var fps: Int32 = 0 - data.withUnsafeBytes { (bytes: UnsafePointer) -> Void in - memcpy(&fps, bytes.advanced(by: offset), 4) - offset += 4 - var widthValue: Int32 = 0 - var heightValue: Int32 = 0 - memcpy(&widthValue, bytes.advanced(by: offset), 4) - offset += 4 - memcpy(&heightValue, bytes.advanced(by: offset), 4) - offset += 4 - width = Int(widthValue) - height = Int(heightValue) - } - - let initialOffset = offset - - var decodeBuffer = Data(count: width * 4 * height) - var frameBuffer = Data(count: width * 4 * height) - let decodeBufferLength = decodeBuffer.count - frameBuffer.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer) -> Void in - memset(bytes, 0, decodeBufferLength) - } - - var frameIndex = 0 - let timer = SwiftSignalKit.Timer(timeout: 1.0 / Double(fps), repeat: true, completion: { [weak self] in - guard let strongSelf = self else { - return - } - data.withUnsafeBytes { (bytes: UnsafePointer) -> Void in - var frameLength: Int32 = 0 - memcpy(&frameLength, bytes.advanced(by: offset), 4) - - var usedCache = false - strongSelf.frameCache.get(index: frameIndex, { data in - if let data = data { - usedCache = true - - strongSelf.renderer?.render(width: width, height: height, bytes: data.bytes, length: data.length) - - if !strongSelf.reportedStarted { - strongSelf.reportedStarted = true - strongSelf.started() - } - } - }) - - if !usedCache { - scratchBuffer.withUnsafeMutableBytes { (scratchBytes: UnsafeMutablePointer) -> Void in - decodeBuffer.withUnsafeMutableBytes { (decodeBytes: UnsafeMutablePointer) -> Void in - frameBuffer.withUnsafeMutableBytes { (frameBytes: UnsafeMutablePointer) -> Void in - compression_decode_buffer(decodeBytes, decodeBufferLength, bytes.advanced(by: offset + 4), Int(frameLength), UnsafeMutableRawPointer(scratchBytes), COMPRESSION_LZ4) - - var lhs = UnsafeMutableRawPointer(frameBytes).assumingMemoryBound(to: UInt64.self) - var rhs = UnsafeRawPointer(decodeBytes).assumingMemoryBound(to: UInt64.self) - for _ in 0 ..< decodeBufferLength / 8 { - lhs.pointee = lhs.pointee ^ rhs.pointee - lhs = lhs.advanced(by: 1) - rhs = rhs.advanced(by: 1) - } - - strongSelf.renderer?.render(width: width, height: height, bytes: frameBytes, length: decodeBufferLength) - - //strongSelf.frameCache.set(index: frameIndex, bytes: frameBytes, length: decodeBufferLength) - } - } - } - - if !strongSelf.reportedStarted { - strongSelf.reportedStarted = true - strongSelf.started() - } - } - - offset += 4 + Int(frameLength) - frameIndex += 1 - if offset == dataCount { - offset = initialOffset - frameIndex = 0 - frameBuffer.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer) -> Void in - memset(bytes, 0, decodeBufferLength) - } - } - } - }, queue: Queue.mainQueue()) - self.timer = timer - timer.start() - } - } - - func stop() { - self.timer?.invalidate() - self.timer = nil - self.reportedStarted = false - self.frameCache.removeAll() - } - - func updateLayout(size: CGSize) { - self.renderer?.frame = CGRect(origin: CGPoint(), size: size) - } -} - class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { let imageNode: TransformImageNode - private let animationNode: StickerAnimationNode + private let animationNode: AnimatedStickerNode private var swipeToReplyNode: ChatMessageSwipeToReplyNode? private var swipeToReplyFeedback: HapticFeedback? @@ -240,7 +32,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { required init() { self.imageNode = TransformImageNode() - self.animationNode = StickerAnimationNode() + self.animationNode = AnimatedStickerNode() self.dateAndStatusNode = ChatMessageDateAndStatusNode() super.init(layerBacked: false) @@ -315,7 +107,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { if let telegramFile = media as? TelegramMediaFile { if self.telegramFile?.id != telegramFile.id { self.telegramFile = telegramFile - self.imageNode.setSignal(chatMessageSticker(account: item.context.account, file: telegramFile, small: false, thumbnail: true)) + self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: item.context.account.postbox, file: telegramFile, small: false, size: CGSize(width: 360.0, height: 360.0), thumbnail: false)) self.animationNode.setup(account: item.context.account, fileReference: .message(message: MessageReference(item.message), media: telegramFile), width: 360, height: 360) } break diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageInteractiveMediaNode.swift index 69fc8c3285..482a2277b8 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageInteractiveMediaNode.swift @@ -236,7 +236,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { var unboundSize: CGSize if let image = media as? TelegramMediaImage, let dimensions = largestImageRepresentation(image.representations)?.dimensions { - unboundSize = CGSize(width: floor(dimensions.width * 0.5), height: floor(dimensions.height * 0.5)) + unboundSize = CGSize(width: max(10.0, floor(dimensions.width * 0.5)), height: max(10.0, floor(dimensions.height * 0.5))) } else if let file = media as? TelegramMediaFile, var dimensions = file.dimensions { if let thumbnail = file.previewRepresentations.first { let dimensionsVertical = dimensions.width < dimensions.height diff --git a/submodules/TelegramUI/TelegramUI/FetchCachedRepresentations.swift b/submodules/TelegramUI/TelegramUI/FetchCachedRepresentations.swift index 5162de2352..785671d0ae 100644 --- a/submodules/TelegramUI/TelegramUI/FetchCachedRepresentations.swift +++ b/submodules/TelegramUI/TelegramUI/FetchCachedRepresentations.swift @@ -119,6 +119,14 @@ public func fetchCachedResourceRepresentation(account: Account, resource: MediaR } return fetchAnimatedStickerRepresentation(account: account, resource: resource, resourceData: data, representation: representation) } + } else if let representation = representation as? CachedAnimatedStickerFirstFrameRepresentation { + return account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false)) + |> mapToSignal { data -> Signal in + if !data.complete { + return .complete() + } + return fetchAnimatedStickerFirstFrameRepresentation(account: account, resource: resource, resourceData: data, representation: representation) + } } return .never() } @@ -213,7 +221,7 @@ private func fetchCachedStickerAJpegRepresentation(account: Account, resource: M let _ = try? finalData.write(to: url, options: [.atomic]) - subscriber.putNext(CachedMediaResourceRepresentationResult(temporaryPath: path)) + subscriber.putNext(.temporaryPath(path)) subscriber.putCompletion() } } @@ -255,7 +263,7 @@ private func fetchCachedScaledImageRepresentation(resource: MediaResource, resou CGImageDestinationAddImage(colorDestination, colorImage.cgImage!, options as CFDictionary) if CGImageDestinationFinalize(colorDestination) { - subscriber.putNext(CachedMediaResourceRepresentationResult(temporaryPath: path)) + subscriber.putNext(.temporaryPath(path)) subscriber.putCompletion() } } @@ -317,7 +325,7 @@ private func fetchCachedVideoFirstFrameRepresentation(account: Account, resource CGImageDestinationAddImage(colorDestination, fullSizeImage, options as CFDictionary) if CGImageDestinationFinalize(colorDestination) { - subscriber.putNext(CachedMediaResourceRepresentationResult(temporaryPath: path)) + subscriber.putNext(.temporaryPath(path)) subscriber.putCompletion() } } @@ -360,7 +368,7 @@ private func fetchCachedScaledVideoFirstFrameRepresentation(account: Account, re CGImageDestinationAddImage(colorDestination, colorImage.cgImage!, options as CFDictionary) if CGImageDestinationFinalize(colorDestination) { - subscriber.putNext(CachedMediaResourceRepresentationResult(temporaryPath: path)) + subscriber.putNext(.temporaryPath(path)) subscriber.putCompletion() } } @@ -390,7 +398,7 @@ private func fetchCachedBlurredWallpaperRepresentation(resource: MediaResource, CGImageDestinationAddImage(colorDestination, colorImage.cgImage!, options as CFDictionary) if CGImageDestinationFinalize(colorDestination) { - subscriber.putNext(CachedMediaResourceRepresentationResult(temporaryPath: path)) + subscriber.putNext(.temporaryPath(path)) subscriber.putCompletion() } } @@ -429,7 +437,7 @@ private func fetchCachedPatternWallpaperMaskRepresentation(resource: MediaResour CGImageDestinationAddImage(alphaDestination, alphaImage.cgImage!, options as CFDictionary) if CGImageDestinationFinalize(alphaDestination) { - subscriber.putNext(CachedMediaResourceRepresentationResult(temporaryPath: path)) + subscriber.putNext(.temporaryPath(path)) subscriber.putCompletion() } } @@ -477,7 +485,7 @@ private func fetchCachedPatternWallpaperRepresentation(resource: MediaResource, CGImageDestinationAddImage(colorDestination, colorImage.cgImage!, options as CFDictionary) if CGImageDestinationFinalize(colorDestination) { - subscriber.putNext(CachedMediaResourceRepresentationResult(temporaryPath: path)) + subscriber.putNext(.temporaryPath(path)) subscriber.putCompletion() } } @@ -544,7 +552,7 @@ private func fetchCachedBlurredWallpaperRepresentation(account: Account, resourc CGImageDestinationAddImage(colorDestination, colorImage.cgImage!, options as CFDictionary) if CGImageDestinationFinalize(colorDestination) { - subscriber.putNext(CachedMediaResourceRepresentationResult(temporaryPath: path)) + subscriber.putNext(.temporaryPath(path)) subscriber.putCompletion() } } @@ -583,7 +591,7 @@ private func fetchCachedPatternWallpaperMaskRepresentation(account: Account, res CGImageDestinationAddImage(alphaDestination, alphaImage.cgImage!, options as CFDictionary) if CGImageDestinationFinalize(alphaDestination) { - subscriber.putNext(CachedMediaResourceRepresentationResult(temporaryPath: path)) + subscriber.putNext(.temporaryPath(path)) subscriber.putCompletion() } } @@ -631,7 +639,7 @@ private func fetchCachedPatternWallpaperRepresentation(account: Account, resourc CGImageDestinationAddImage(colorDestination, colorImage.cgImage!, options as CFDictionary) if CGImageDestinationFinalize(colorDestination) { - subscriber.putNext(CachedMediaResourceRepresentationResult(temporaryPath: path)) + subscriber.putNext(.temporaryPath(path)) subscriber.putCompletion() } } @@ -676,7 +684,7 @@ private func fetchCachedAlbumArtworkRepresentation(account: Account, resource: M CGImageDestinationAddImage(colorDestination, colorImage.cgImage!, options as CFDictionary) if CGImageDestinationFinalize(colorDestination) { - subscriber.putNext(CachedMediaResourceRepresentationResult(temporaryPath: path)) + subscriber.putNext(.temporaryPath(path)) } } } @@ -755,7 +763,7 @@ private func fetchEmojiThumbnailRepresentation(account: Account, resource: Media let options = NSMutableDictionary() CGImageDestinationAddImage(colorDestination, colorImage.cgImage!, options as CFDictionary) if CGImageDestinationFinalize(colorDestination) { - subscriber.putNext(CachedMediaResourceRepresentationResult(temporaryPath: path)) + subscriber.putNext(.temporaryPath(path)) } } subscriber.putCompletion() @@ -875,7 +883,7 @@ private func fetchEmojiRepresentation(account: Account, resource: MediaResource, let options = NSMutableDictionary() CGImageDestinationAddImage(colorDestination, colorImage.cgImage!, options as CFDictionary) if CGImageDestinationFinalize(colorDestination) { - subscriber.putNext(CachedMediaResourceRepresentationResult(temporaryPath: path)) + subscriber.putNext(.temporaryPath(path)) } } subscriber.putCompletion() @@ -885,12 +893,26 @@ private func fetchEmojiRepresentation(account: Account, resource: MediaResource, } } +private func fetchAnimatedStickerFirstFrameRepresentation(account: Account, resource: MediaResource, resourceData: MediaResourceData, representation: CachedAnimatedStickerFirstFrameRepresentation) -> Signal { + 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 + subscriber.putNext(.tempFile(file)) + subscriber.putCompletion() + }) + } else { + return EmptyDisposable + } + }) + |> runOn(Queue.concurrentDefaultQueue()) +} + private func fetchAnimatedStickerRepresentation(account: Account, resource: MediaResource, resourceData: MediaResourceData, representation: CachedAnimatedStickerRepresentation) -> Signal { 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 - subscriber.putNext(CachedMediaResourceRepresentationResult(temporaryPath: path)) + subscriber.putNext(.temporaryPath(path)) subscriber.putCompletion() }) } else { diff --git a/submodules/TelegramUI/TelegramUI/LegacyWebSearchGallery.swift b/submodules/TelegramUI/TelegramUI/LegacyWebSearchGallery.swift index d302c49ee4..feb361bc3b 100644 --- a/submodules/TelegramUI/TelegramUI/LegacyWebSearchGallery.swift +++ b/submodules/TelegramUI/TelegramUI/LegacyWebSearchGallery.swift @@ -261,7 +261,8 @@ func legacyWebSearchItem(account: Account, result: ChatContextResult) -> LegacyW representations.append(TelegramMediaImageRepresentation(dimensions: imageDimensions, resource: imageResource)) let tmpImage = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: representations, immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil) thumbnailSignal = chatMessagePhotoDatas(postbox: account.postbox, photoReference: .standalone(media: tmpImage), autoFetchFullSize: false) - |> mapToSignal { (thumbnailData, _, _) -> Signal in + |> mapToSignal { value -> Signal in + let thumbnailData = value._0 if let data = thumbnailData, let image = UIImage(data: data) { return .single(image) } else { @@ -269,7 +270,11 @@ func legacyWebSearchItem(account: Account, result: ChatContextResult) -> LegacyW } } originalSignal = chatMessagePhotoDatas(postbox: account.postbox, photoReference: .standalone(media: tmpImage), autoFetchFullSize: true) - |> mapToSignal { (thumbnailData, fullSizeData, fullSizeComplete) -> Signal in + |> mapToSignal { value -> Signal in + let thumbnailData = value._0 + let fullSizeData = value._1 + let fullSizeComplete = value._2 + if fullSizeComplete, let data = fullSizeData, let image = UIImage(data: data) { return .single(image) } else if let data = thumbnailData, let image = UIImage(data: data) { diff --git a/submodules/TelegramUI/TelegramUI/MetalAnimationRenderer.swift b/submodules/TelegramUI/TelegramUI/MetalAnimationRenderer.swift index c88c2c8d65..6bb38a85f7 100644 --- a/submodules/TelegramUI/TelegramUI/MetalAnimationRenderer.swift +++ b/submodules/TelegramUI/TelegramUI/MetalAnimationRenderer.swift @@ -1,9 +1,10 @@ import Foundation import UIKit import AsyncDisplayKit +import SwiftSignalKit import Metal -#if !targetEnvironment(simulator) +#if false//!targetEnvironment(simulator) final class MetalAnimationRenderer: ASDisplayNode, AnimationRenderer { private let device: MTLDevice @@ -140,7 +141,7 @@ fragment float4 basic_fragment( self.metalLayer.contentsScale = 2.0 } - func render(width: Int, height: Int, bytes: UnsafeRawPointer, length: Int) { + func render(queue: Queue, width: Int, height: Int, bytes: UnsafeRawPointer, length: Int, completion: @escaping () -> Void) { if self.metalLayer.bounds.width.isZero { return } diff --git a/submodules/TelegramUI/TelegramUI/NotificationContentContext.swift b/submodules/TelegramUI/TelegramUI/NotificationContentContext.swift index 86fa222473..5e7a3573eb 100644 --- a/submodules/TelegramUI/TelegramUI/NotificationContentContext.swift +++ b/submodules/TelegramUI/TelegramUI/NotificationContentContext.swift @@ -137,7 +137,7 @@ public final class NotificationViewControllerImpl { f(false) }) - sharedAccountContext = SharedAccountContext(mainWindow: nil, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, appData: self.initializationData.bundleData), rootPath: rootPath, legacyBasePath: nil, legacyCache: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in }) + sharedAccountContext = SharedAccountContext(mainWindow: nil, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, appData: .single(self.initializationData.bundleData)), rootPath: rootPath, legacyBasePath: nil, legacyCache: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in }) } } @@ -161,13 +161,13 @@ public final class NotificationViewControllerImpl { let mediaBoxPath = accountsPath + "/" + accountRecordIdPathName(AccountRecordId(rawValue: accountIdValue)) + "/postbox/media" if let data = try? Data(contentsOf: URL(fileURLWithPath: mediaBoxPath + "/\(largestRepresentation.resource.id.uniqueId)"), options: .mappedRead) { - self.imageNode.setSignal(chatMessagePhotoInternal(photoData: .single((nil, data, true))) + self.imageNode.setSignal(chatMessagePhotoInternal(photoData: .single(Tuple(nil, data, true))) |> map { $0.1 }) return } if let data = try? Data(contentsOf: URL(fileURLWithPath: mediaBoxPath + "/\(thumbnailRepresentation.resource.id.uniqueId)"), options: .mappedRead) { - self.imageNode.setSignal(chatMessagePhotoInternal(photoData: .single((data, nil, false))) + self.imageNode.setSignal(chatMessagePhotoInternal(photoData: .single(Tuple(data, nil, false))) |> map { $0.1 }) } diff --git a/submodules/TelegramUI/TelegramUI/PhotoResources.swift b/submodules/TelegramUI/TelegramUI/PhotoResources.swift index 245bfbcb15..d297052a78 100644 --- a/submodules/TelegramUI/TelegramUI/PhotoResources.swift +++ b/submodules/TelegramUI/TelegramUI/PhotoResources.swift @@ -23,16 +23,16 @@ public func largestRepresentationForPhoto(_ photo: TelegramMediaImage) -> Telegr return photo.representationForDisplayAtSize(CGSize(width: 1280.0, height: 1280.0)) } -func chatMessagePhotoDatas(postbox: Postbox, photoReference: ImageMediaReference, fullRepresentationSize: CGSize = CGSize(width: 1280.0, height: 1280.0), autoFetchFullSize: Bool = false, tryAdditionalRepresentations: Bool = false, synchronousLoad: Bool = false) -> Signal<(Data?, Data?, Bool), NoError> { +func chatMessagePhotoDatas(postbox: Postbox, photoReference: ImageMediaReference, fullRepresentationSize: CGSize = CGSize(width: 1280.0, height: 1280.0), autoFetchFullSize: Bool = false, tryAdditionalRepresentations: Bool = false, synchronousLoad: Bool = false) -> Signal, NoError> { if let smallestRepresentation = smallestImageRepresentation(photoReference.media.representations), let largestRepresentation = photoReference.media.representationForDisplayAtSize(fullRepresentationSize) { let maybeFullSize = postbox.mediaBox.resourceData(largestRepresentation.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad) let signal = maybeFullSize |> take(1) - |> mapToSignal { maybeData -> Signal<(Data?, Data?, Bool), NoError> in + |> mapToSignal { maybeData -> Signal, NoError> in if maybeData.complete { let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) - return .single((nil, loadedData, true)) + return .single(Tuple(nil, loadedData, true)) } else { let decodedThumbnailData = photoReference.media.immediateThumbnailData.flatMap(decodeTinyThumbnail) let fetchedThumbnail: Signal @@ -83,13 +83,13 @@ func chatMessagePhotoDatas(postbox: Postbox, photoReference: ImageMediaReference return mainThumbnail } - let fullSizeData: Signal<(Data?, Bool), NoError> + let fullSizeData: Signal, NoError> if autoFetchFullSize { - fullSizeData = Signal<(Data?, Bool), NoError> { subscriber in + fullSizeData = Signal, NoError> { subscriber in let fetchedFullSizeDisposable = fetchedFullSize.start() let fullSizeDisposable = postbox.mediaBox.resourceData(largestRepresentation.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad).start(next: { next in - subscriber.putNext((next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete)) + subscriber.putNext(Tuple(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete)) }, error: subscriber.putError, completed: subscriber.putCompletion) return ActionDisposable { @@ -99,8 +99,8 @@ func chatMessagePhotoDatas(postbox: Postbox, photoReference: ImageMediaReference } } else { fullSizeData = postbox.mediaBox.resourceData(largestRepresentation.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad) - |> map { next -> (Data?, Bool) in - return (next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete) + |> map { next -> Tuple2 in + return Tuple(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete) } } @@ -108,17 +108,17 @@ func chatMessagePhotoDatas(postbox: Postbox, photoReference: ImageMediaReference |> mapToSignal { thumbnailData in if let thumbnailData = thumbnailData { return fullSizeData - |> map { (fullSizeData, complete) in - return (thumbnailData, fullSizeData, complete) + |> map { value in + return Tuple(thumbnailData, value._0, value._1) } } else { - return .single((thumbnailData, nil, false)) + return .single(Tuple(thumbnailData, nil, false)) } } } } |> distinctUntilChanged(isEqual: { lhs, rhs in - if (lhs.0 == nil && lhs.1 == nil) && (rhs.0 == nil && rhs.1 == nil) { + if (lhs._0 == nil && lhs._1 == nil) && (rhs._0 == nil && rhs._1 == nil) { return true } else { return false @@ -131,7 +131,7 @@ func chatMessagePhotoDatas(postbox: Postbox, photoReference: ImageMediaReference } } -private func chatMessageFileDatas(account: Account, fileReference: FileMediaReference, pathExtension: String? = nil, progressive: Bool = false, fetched: Bool = false) -> Signal<(Data?, String?, Bool), NoError> { +private func chatMessageFileDatas(account: Account, fileReference: FileMediaReference, pathExtension: String? = nil, progressive: Bool = false, fetched: Bool = false) -> Signal, NoError> { let thumbnailResource = fetched ? nil : smallestImageRepresentation(fileReference.media.previewRepresentations)?.resource let fullSizeResource = fileReference.media.resource @@ -140,9 +140,9 @@ private func chatMessageFileDatas(account: Account, fileReference: FileMediaRefe let signal = maybeFullSize |> take(1) - |> mapToSignal { maybeData -> Signal<(Data?, String?, Bool), NoError> in + |> mapToSignal { maybeData -> Signal, NoError> in if maybeData.complete { - return .single((nil, maybeData.path, true)) + return .single(Tuple(nil, maybeData.path, true)) } else { let fetchedThumbnail: Signal if !fetched, let _ = decodedThumbnailData { @@ -176,17 +176,20 @@ private func chatMessageFileDatas(account: Account, fileReference: FileMediaRefe thumbnail = .single(nil) } - let fullSizeDataAndPath = account.postbox.mediaBox.resourceData(fullSizeResource, option: !progressive ? .complete(waitUntilFetchStatus: false) : .incremental(waitUntilFetchStatus: false)) |> map { next -> (String?, Bool) in - return (next.size == 0 ? nil : next.path, next.complete) + let fullSizeDataAndPath = account.postbox.mediaBox.resourceData(fullSizeResource, option: !progressive ? .complete(waitUntilFetchStatus: false) : .incremental(waitUntilFetchStatus: false)) |> map { next -> Tuple2 in + return Tuple(next.size == 0 ? nil : next.path, next.complete) } - return thumbnail |> mapToSignal { thumbnailData in - return fullSizeDataAndPath |> map { (dataPath, complete) in - return (thumbnailData, dataPath, complete) + return thumbnail + |> mapToSignal { thumbnailData in + return fullSizeDataAndPath + |> map { value -> Tuple3 in + return Tuple3(thumbnailData, value._0, value._1) } } } - } |> filter({ $0.0 != nil || $0.1 != nil }) + } + |> filter({ $0._0 != nil || $0._1 != nil }) return signal } @@ -199,7 +202,7 @@ private let thumbnailGenerationMimeTypes: Set = Set([ "image/heic" ]) -private func chatMessageImageFileThumbnailDatas(account: Account, fileReference: FileMediaReference, pathExtension: String? = nil, progressive: Bool = false, autoFetchFullSizeThumbnail: Bool = false) -> Signal<(Data?, String?, Bool), NoError> { +private func chatMessageImageFileThumbnailDatas(account: Account, fileReference: FileMediaReference, pathExtension: String? = nil, progressive: Bool = false, autoFetchFullSizeThumbnail: Bool = false) -> Signal, NoError> { let thumbnailRepresentation = smallestImageRepresentation(fileReference.media.previewRepresentations) let thumbnailResource = thumbnailRepresentation?.resource let decodedThumbnailData = fileReference.media.immediateThumbnailData.flatMap(decodeTinyThumbnail) @@ -211,7 +214,7 @@ private func chatMessageImageFileThumbnailDatas(account: Account, fileReference: let fetchedDisposable = fetchedMediaResource(postbox: account.postbox, reference: fileReference.resourceReference(thumbnailRepresentation.resource), statsCategory: .video).start() let thumbnailDisposable = account.postbox.mediaBox.resourceData(thumbnailRepresentation.resource, attemptSynchronously: false).start(next: { next in let data: Data? = next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []) - subscriber.putNext((data ?? decodedThumbnailData, nil, false)) + subscriber.putNext(Tuple(data ?? decodedThumbnailData, nil, false)) }, error: subscriber.putError, completed: subscriber.putCompletion) return ActionDisposable { @@ -220,7 +223,7 @@ private func chatMessageImageFileThumbnailDatas(account: Account, fileReference: } } } else { - return .single((decodedThumbnailData, nil, false)) + return .single(Tuple(decodedThumbnailData, nil, false)) } } else if let thumbnailResource = thumbnailResource { let fetchedThumbnail: Signal = fetchedMediaResource(postbox: account.postbox, reference: fileReference.resourceReference(thumbnailResource)) @@ -228,9 +231,9 @@ private func chatMessageImageFileThumbnailDatas(account: Account, fileReference: let fetchedDisposable = fetchedThumbnail.start() let thumbnailDisposable = account.postbox.mediaBox.resourceData(thumbnailResource, pathExtension: pathExtension).start(next: { next in if next.size != 0, let data = try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []) { - subscriber.putNext((data, nil, false)) + subscriber.putNext(Tuple(data, nil, false)) } else { - subscriber.putNext((nil, nil, false)) + subscriber.putNext(Tuple(nil, nil, false)) } }, error: subscriber.putError, completed: subscriber.putCompletion) @@ -240,7 +243,7 @@ private func chatMessageImageFileThumbnailDatas(account: Account, fileReference: } } } else { - return .single((nil, nil, false)) + return .single(Tuple(nil, nil, false)) } } @@ -251,9 +254,9 @@ private func chatMessageImageFileThumbnailDatas(account: Account, fileReference: let signal = maybeFullSize |> take(1) - |> mapToSignal { maybeData -> Signal<(Data?, String?, Bool), NoError> in + |> mapToSignal { maybeData -> Signal, NoError> in if maybeData.complete { - return .single((nil, maybeData.path, true)) + return .single(Tuple(nil, maybeData.path, true)) } else { let fetchedThumbnail: Signal if let _ = fileReference.media.immediateThumbnailData { @@ -288,24 +291,24 @@ private func chatMessageImageFileThumbnailDatas(account: Account, fileReference: } let fullSizeDataAndPath = fetchedFullSize - |> map { next -> (String?, Bool) in - return (next.size == 0 ? nil : next.path, next.complete) + |> map { next -> Tuple2in + return Tuple(next.size == 0 ? nil : next.path, next.complete) } return thumbnail |> mapToSignal { thumbnailData in return fullSizeDataAndPath - |> map { (dataPath, complete) in - return (thumbnailData, dataPath, complete) + |> map { value in + return Tuple(thumbnailData, value._0, value._1) } } } - } |> filter({ $0.0 != nil || $0.1 != nil }) + } |> filter({ $0._0 != nil || $0._1 != nil }) return signal } -private func chatMessageVideoDatas(postbox: Postbox, fileReference: FileMediaReference, thumbnailSize: Bool = false, onlyFullSize: Bool = false, synchronousLoad: Bool = false, autoFetchFullSizeThumbnail: Bool = false) -> Signal<(Data?, (Data, String)?, Bool), NoError> { +private func chatMessageVideoDatas(postbox: Postbox, fileReference: FileMediaReference, thumbnailSize: Bool = false, onlyFullSize: Bool = false, synchronousLoad: Bool = false, autoFetchFullSizeThumbnail: Bool = false) -> Signal?, Bool>, NoError> { let fullSizeResource = fileReference.media.resource let thumbnailRepresentation = smallestImageRepresentation(fileReference.media.previewRepresentations) @@ -316,10 +319,10 @@ private func chatMessageVideoDatas(postbox: Postbox, fileReference: FileMediaRef let signal = maybeFullSize |> take(1) - |> mapToSignal { maybeData -> Signal<(Data?, (Data, String)?, Bool), NoError> in + |> mapToSignal { maybeData -> Signal?, Bool>, NoError> in if maybeData.complete { let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) - return .single((nil, loadedData == nil ? nil : (loadedData!, maybeData.path), true)) + return .single(Tuple(nil, loadedData == nil ? nil : Tuple(loadedData!, maybeData.path), true)) } else { let thumbnail: Signal if onlyFullSize { @@ -369,22 +372,22 @@ private func chatMessageVideoDatas(postbox: Postbox, fileReference: FileMediaRef //fetchedDisposable.dispose() } } - |> map { next -> ((Data, String)?, Bool) in + |> map { next -> Tuple2?, Bool> in let data = next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: .mappedIfSafe) - return (data == nil ? nil : (data!, next.path), next.complete) + return Tuple(data == nil ? nil : Tuple(data!, next.path), next.complete) } return thumbnail |> mapToSignal { thumbnailData in return fullSizeDataAndPath - |> map { (dataAndPath, complete) in - return (thumbnailData, dataAndPath, complete) + |> map { value in + return Tuple(thumbnailData, value._0, value._1) } } } } |> filter({ if onlyFullSize { - return $0.1 != nil || $0.2 + return $0._1 != nil || $0._2 } else { return true//$0.0 != nil || $0.1 != nil || $0.2 } @@ -659,7 +662,10 @@ func addCorners(_ context: DrawingContext, arguments: TransformImageArguments) { func rawMessagePhoto(postbox: Postbox, photoReference: ImageMediaReference) -> Signal { return chatMessagePhotoDatas(postbox: postbox, photoReference: photoReference, autoFetchFullSize: true) - |> map { (thumbnailData, fullSizeData, fullSizeComplete) -> UIImage? in + |> map { value -> UIImage? in + let thumbnailData = value._0 + let fullSizeData = value._1 + let fullSizeComplete = value._2 if let fullSizeData = fullSizeData { if fullSizeComplete { return UIImage(data: fullSizeData)?.precomposed() @@ -679,9 +685,12 @@ public func chatMessagePhoto(postbox: Postbox, photoReference: ImageMediaReferen } } -public func chatMessagePhotoInternal(photoData: Signal<(Data?, Data?, Bool), NoError>, synchronousLoad: Bool = false) -> Signal<(() -> CGSize?, (TransformImageArguments) -> DrawingContext?), NoError> { +public func chatMessagePhotoInternal(photoData: Signal, NoError>, synchronousLoad: Bool = false) -> Signal<(() -> CGSize?, (TransformImageArguments) -> DrawingContext?), NoError> { return photoData - |> map { (thumbnailData, fullSizeData, fullSizeComplete) in + |> map { value in + let thumbnailData = value._0 + let fullSizeData = value._1 + let fullSizeComplete = value._2 return ({ return nil }, { arguments in @@ -870,7 +879,7 @@ public func chatMessagePhotoInternal(photoData: Signal<(Data?, Data?, Bool), NoE } } -private func chatMessagePhotoThumbnailDatas(account: Account, photoReference: ImageMediaReference, onlyFullSize: Bool = false) -> Signal<(Data?, Data?, Bool), NoError> { +private func chatMessagePhotoThumbnailDatas(account: Account, photoReference: ImageMediaReference, onlyFullSize: Bool = false) -> Signal, NoError> { let fullRepresentationSize: CGSize = CGSize(width: 1280.0, height: 1280.0) if let smallestRepresentation = smallestImageRepresentation(photoReference.media.representations), let largestRepresentation = photoReference.media.representationForDisplayAtSize(fullRepresentationSize) { @@ -879,10 +888,10 @@ private func chatMessagePhotoThumbnailDatas(account: Account, photoReference: Im let signal = maybeFullSize |> take(1) - |> mapToSignal { maybeData -> Signal<(Data?, Data?, Bool), NoError> in + |> mapToSignal { maybeData -> Signal, NoError> in if maybeData.complete { let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) - return .single((nil, loadedData, true)) + return .single(Tuple(nil, loadedData, true)) } else { let fetchedThumbnail = fetchedMediaResource(postbox: account.postbox, reference: photoReference.resourceReference(smallestRepresentation.resource), statsCategory: .image) @@ -898,21 +907,21 @@ private func chatMessagePhotoThumbnailDatas(account: Account, photoReference: Im } } - let fullSizeData: Signal<(Data?, Bool), NoError> = fetchedFullSize - |> map { next -> (Data?, Bool) in - return (next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete) + let fullSizeData: Signal, NoError> = fetchedFullSize + |> map { next -> Tuple2 in + return Tuple(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete) } return thumbnail |> mapToSignal { thumbnailData in return fullSizeData - |> map { (fullSizeData, complete) in - return (thumbnailData, fullSizeData, complete) + |> map { value in + return Tuple(thumbnailData, value._0, value._1) } } } } - |> filter({ $0.0 != nil || $0.1 != nil }) + |> filter({ $0._0 != nil || $0._1 != nil }) return signal } else { @@ -922,8 +931,11 @@ private func chatMessagePhotoThumbnailDatas(account: Account, photoReference: Im public func chatMessagePhotoThumbnail(account: Account, photoReference: ImageMediaReference, onlyFullSize: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { let signal = chatMessagePhotoThumbnailDatas(account: account, photoReference: photoReference, onlyFullSize: onlyFullSize) - - return signal |> map { (thumbnailData, fullSizeData, fullSizeComplete) in + return signal + |> map { value in + let thumbnailData = value._0 + let fullSizeData = value._1 + let fullSizeComplete = value._2 return { arguments in let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: true) @@ -1011,7 +1023,10 @@ public func chatMessageVideoThumbnail(account: Account, fileReference: FileMedia let signal = chatMessageVideoDatas(postbox: account.postbox, fileReference: fileReference, thumbnailSize: true, autoFetchFullSizeThumbnail: true) return signal - |> map { (thumbnailData, fullSizeData, fullSizeComplete) in + |> map { value in + let thumbnailData = value._0 + let fullSizeData = value._1 + let fullSizeComplete = value._2 return { arguments in let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: true) @@ -1033,7 +1048,7 @@ public func chatMessageVideoThumbnail(account: Account, fileReference: FileMedia var fullSizeImage: CGImage? var imageOrientation: UIImageOrientation = .up - if let fullSizeData = fullSizeData?.0 { + if let fullSizeData = fullSizeData?._0 { if fullSizeComplete { let options = NSMutableDictionary() options[kCGImageSourceShouldCache as NSString] = false as NSNumber @@ -1106,8 +1121,11 @@ public func chatMessageVideoThumbnail(account: Account, fileReference: FileMedia func chatSecretPhoto(account: Account, photoReference: ImageMediaReference) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { let signal = chatMessagePhotoDatas(postbox: account.postbox, photoReference: photoReference) - - return signal |> map { (thumbnailData, fullSizeData, fullSizeComplete) in + return signal + |> map { value in + let thumbnailData = value._0 + let fullSizeData = value._1 + let fullSizeComplete = value._2 return { arguments in let context = DrawingContext(size: arguments.drawingSize, clear: true) @@ -1206,16 +1224,16 @@ func chatSecretPhoto(account: Account, photoReference: ImageMediaReference) -> S } } -private func avatarGalleryThumbnailDatas(postbox: Postbox, representations: [ImageRepresentationWithReference], fullRepresentationSize: CGSize = CGSize(width: 1280.0, height: 1280.0), autoFetchFullSize: Bool = false) -> Signal<(Data?, Data?, Bool), NoError> { +private func avatarGalleryThumbnailDatas(postbox: Postbox, representations: [ImageRepresentationWithReference], fullRepresentationSize: CGSize = CGSize(width: 1280.0, height: 1280.0), autoFetchFullSize: Bool = false) -> Signal, NoError> { if let smallestRepresentation = smallestImageRepresentation(representations.map({ $0.representation })), let largestRepresentation = imageRepresentationLargerThan(representations.map({ $0.representation }), size: fullRepresentationSize), let smallestIndex = representations.index(where: { $0.representation == smallestRepresentation }), let largestIndex = representations.index(where: { $0.representation == largestRepresentation }) { let maybeFullSize = postbox.mediaBox.resourceData(largestRepresentation.resource) let signal = maybeFullSize |> take(1) - |> mapToSignal { maybeData -> Signal<(Data?, Data?, Bool), NoError> in + |> mapToSignal { maybeData -> Signal, NoError> in if maybeData.complete { let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) - return .single((nil, loadedData, true)) + return .single(Tuple(nil, loadedData, true)) } else { let fetchedThumbnail = fetchedMediaResource(postbox: postbox, reference: representations[smallestIndex].reference, statsCategory: .image) let fetchedFullSize = fetchedMediaResource(postbox: postbox, reference: representations[largestIndex].reference, statsCategory: .image) @@ -1232,13 +1250,13 @@ private func avatarGalleryThumbnailDatas(postbox: Postbox, representations: [Ima } } - let fullSizeData: Signal<(Data?, Bool), NoError> + let fullSizeData: Signal, NoError> if autoFetchFullSize { - fullSizeData = Signal<(Data?, Bool), NoError> { subscriber in + fullSizeData = Signal, NoError> { subscriber in let fetchedFullSizeDisposable = fetchedFullSize.start() let fullSizeDisposable = postbox.mediaBox.resourceData(largestRepresentation.resource).start(next: { next in - subscriber.putNext((next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete)) + subscriber.putNext(Tuple(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete)) }, error: subscriber.putError, completed: subscriber.putCompletion) return ActionDisposable { @@ -1248,20 +1266,22 @@ private func avatarGalleryThumbnailDatas(postbox: Postbox, representations: [Ima } } else { fullSizeData = postbox.mediaBox.resourceData(largestRepresentation.resource) - |> map { next -> (Data?, Bool) in - return (next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete) + |> map { next -> Tuple2 in + return Tuple(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete) } } - return thumbnail |> mapToSignal { thumbnailData in - return fullSizeData |> map { (fullSizeData, complete) in - return (thumbnailData, fullSizeData, complete) + return thumbnail + |> mapToSignal { thumbnailData in + return fullSizeData + |> map { value in + return Tuple(thumbnailData, value._0, value._1) } } } } |> distinctUntilChanged(isEqual: { lhs, rhs in - if (lhs.0 == nil && lhs.1 == nil) && (rhs.0 == nil && rhs.1 == nil) { + if (lhs._0 == nil && lhs._1 == nil) && (rhs._0 == nil && rhs._1 == nil) { return true } else { return false @@ -1276,8 +1296,12 @@ private func avatarGalleryThumbnailDatas(postbox: Postbox, representations: [Ima func avatarGalleryThumbnailPhoto(account: Account, representations: [ImageRepresentationWithReference]) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { let signal = avatarGalleryThumbnailDatas(postbox: account.postbox, representations: representations, fullRepresentationSize: CGSize(width: 127.0, height: 127.0), autoFetchFullSize: true) - - return signal |> map { (thumbnailData, fullSizeData, fullSizeComplete) in + return signal + |> map { value in + let thumbnailData = value._0 + let fullSizeData = value._1 + let fullSizeComplete = value._2 + return { arguments in assertNotOnMainThread() let context = DrawingContext(size: arguments.drawingSize, clear: true) @@ -1359,7 +1383,10 @@ func mediaGridMessagePhoto(account: Account, photoReference: ImageMediaReference let signal = chatMessagePhotoDatas(postbox: account.postbox, photoReference: photoReference, fullRepresentationSize: fullRepresentationSize, autoFetchFullSize: true, synchronousLoad: synchronousLoad) return signal - |> map { (thumbnailData, fullSizeData, fullSizeComplete) in + |> map { value in + let thumbnailData = value._0 + let fullSizeData = value._1 + let fullSizeComplete = value._2 return { arguments in let context = DrawingContext(size: arguments.drawingSize, clear: true) @@ -1514,24 +1541,30 @@ func mediaGridMessageVideo(postbox: Postbox, videoReference: FileMediaReference, } func internalMediaGridMessageVideo(postbox: Postbox, videoReference: FileMediaReference, imageReference: ImageMediaReference? = nil, onlyFullSize: Bool = false, synchronousLoad: Bool = false, autoFetchFullSizeThumbnail: Bool = false) -> Signal<(() -> CGSize?, (TransformImageArguments) -> DrawingContext?), NoError> { - let signal: Signal<(Data?, (Data, String)?, Bool), NoError> + let signal: Signal?, Bool>, NoError> if let imageReference = imageReference { signal = chatMessagePhotoDatas(postbox: postbox, photoReference: imageReference, tryAdditionalRepresentations: true, synchronousLoad: synchronousLoad) - |> map { (thumbnailData, fullSizeData, fullSizeComplete) -> (Data?, (Data, String)?, Bool) in - return (thumbnailData, fullSizeData.flatMap({ ($0, "") }), fullSizeComplete) + |> map { value -> Tuple3?, Bool> in + let thumbnailData = value._0 + let fullSizeData = value._1 + let fullSizeComplete = value._2 + return Tuple(thumbnailData, fullSizeData.flatMap({ Tuple($0, "") }), fullSizeComplete) } } else { signal = chatMessageVideoDatas(postbox: postbox, fileReference: videoReference, onlyFullSize: onlyFullSize, synchronousLoad: synchronousLoad, autoFetchFullSizeThumbnail: autoFetchFullSizeThumbnail) } return signal - |> map { (thumbnailData, fullSizeData, fullSizeComplete) in + |> map { value in + let thumbnailData = value._0 + let fullSizeData = value._1 + let fullSizeComplete = value._2 return ({ var fullSizeImage: CGImage? if let fullSizeData = fullSizeData { if fullSizeComplete { let options = NSMutableDictionary() - if let imageSource = CGImageSourceCreateWithData(fullSizeData.0 as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) { + if let imageSource = CGImageSourceCreateWithData(fullSizeData._0 as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) { fullSizeImage = image } } @@ -1563,13 +1596,13 @@ func internalMediaGridMessageVideo(postbox: Postbox, videoReference: FileMediaRe if let fullSizeData = fullSizeData { if fullSizeComplete { let options = NSMutableDictionary() - if let imageSource = CGImageSourceCreateWithData(fullSizeData.0 as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) { + if let imageSource = CGImageSourceCreateWithData(fullSizeData._0 as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) { imageOrientation = imageOrientationFromSource(imageSource) fullSizeImage = image } } else { let imageSource = CGImageSourceCreateIncremental(nil) - CGImageSourceUpdateData(imageSource, fullSizeData.0 as CFData, fullSizeComplete) + CGImageSourceUpdateData(imageSource, fullSizeData._0 as CFData, fullSizeComplete) let options = NSMutableDictionary() options[kCGImageSourceShouldCache as NSString] = false as NSNumber @@ -2087,7 +2120,7 @@ func drawImage(context: CGContext, image: CGImage, orientation: UIImageOrientati } func chatMessageImageFile(account: Account, fileReference: FileMediaReference, thumbnail: Bool, fetched: Bool = false, autoFetchFullSizeThumbnail: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { - let signal: Signal<(Data?, String?, Bool), NoError> + let signal: Signal, NoError> if thumbnail { signal = chatMessageImageFileThumbnailDatas(account: account, fileReference: fileReference, autoFetchFullSizeThumbnail: true) } else { @@ -2095,7 +2128,10 @@ func chatMessageImageFile(account: Account, fileReference: FileMediaReference, t } return signal - |> map { (thumbnailData, fullSizePath, fullSizeComplete) in + |> map { value in + let thumbnailData = value._0 + let fullSizePath = value._1 + let fullSizeComplete = value._2 return { arguments in assertNotOnMainThread() let context = DrawingContext(size: arguments.drawingSize, clear: true) @@ -2219,7 +2255,10 @@ func chatMessageImageFile(account: Account, fileReference: FileMediaReference, t func instantPageImageFile(account: Account, fileReference: FileMediaReference, fetched: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { return chatMessageFileDatas(account: account, fileReference: fileReference, progressive: false, fetched: fetched) - |> map { (thumbnailData, fullSizePath, fullSizeComplete) in + |> map { value in + let thumbnailData = value._0 + let fullSizePath = value._1 + let fullSizeComplete = value._2 return { arguments in assertNotOnMainThread() let context = DrawingContext(size: arguments.drawingSize, clear: true) @@ -2262,17 +2301,17 @@ func instantPageImageFile(account: Account, fileReference: FileMediaReference, f } } -private func avatarGalleryPhotoDatas(account: Account, fileReference: FileMediaReference? = nil, representations: [ImageRepresentationWithReference], autoFetchFullSize: Bool = false) -> Signal<(Data?, Data?, Bool), NoError> { +private func avatarGalleryPhotoDatas(account: Account, fileReference: FileMediaReference? = nil, representations: [ImageRepresentationWithReference], autoFetchFullSize: Bool = false) -> Signal, NoError> { if let smallestRepresentation = smallestImageRepresentation(representations.map({ $0.representation })), let largestRepresentation = largestImageRepresentation(representations.map({ $0.representation })), let smallestIndex = representations.index(where: { $0.representation == smallestRepresentation }), let largestIndex = representations.index(where: { $0.representation == largestRepresentation }) { let maybeFullSize = account.postbox.mediaBox.resourceData(largestRepresentation.resource) let signal = maybeFullSize |> take(1) - |> mapToSignal { maybeData -> Signal<(Data?, Data?, Bool), NoError> in + |> mapToSignal { maybeData -> Signal, NoError> in if maybeData.complete { let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) - return .single((nil, loadedData, true)) + return .single(Tuple(nil, loadedData, true)) } else { let fetchedThumbnail = fetchedMediaResource(postbox: account.postbox, reference: representations[smallestIndex].reference) let fetchedFullSize = fetchedMediaResource(postbox: account.postbox, reference: representations[largestIndex].reference) @@ -2289,13 +2328,13 @@ private func avatarGalleryPhotoDatas(account: Account, fileReference: FileMediaR } } - let fullSizeData: Signal<(Data?, Bool), NoError> + let fullSizeData: Signal, NoError> if autoFetchFullSize { - fullSizeData = Signal<(Data?, Bool), NoError> { subscriber in + fullSizeData = Signal, NoError> { subscriber in let fetchedFullSizeDisposable = fetchedFullSize.start() let fullSizeDisposable = account.postbox.mediaBox.resourceData(largestRepresentation.resource).start(next: { next in - subscriber.putNext((next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete)) + subscriber.putNext(Tuple(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete)) }, error: subscriber.putError, completed: subscriber.putCompletion) return ActionDisposable { @@ -2305,18 +2344,18 @@ private func avatarGalleryPhotoDatas(account: Account, fileReference: FileMediaR } } else { fullSizeData = account.postbox.mediaBox.resourceData(largestRepresentation.resource) - |> map { next -> (Data?, Bool) in - return (next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete) + |> map { next -> Tuple2 in + return Tuple(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete) } } return thumbnail |> mapToSignal { thumbnailData in - return fullSizeData |> map { (fullSizeData, complete) in - return (thumbnailData, fullSizeData, complete) + return fullSizeData |> map { value in + return Tuple(thumbnailData, value._0, value._1) } } } - } |> filter({ $0.0 != nil || $0.1 != nil }) + } |> filter({ $0._0 != nil || $0._1 != nil }) return signal } else { @@ -2328,7 +2367,10 @@ func chatAvatarGalleryPhoto(account: Account, representations: [ImageRepresentat let signal = avatarGalleryPhotoDatas(account: account, representations: representations, autoFetchFullSize: autoFetchFullSize) return signal - |> map { (thumbnailData, fullSizeData, fullSizeComplete) in + |> map { value in + let thumbnailData = value._0 + let fullSizeData = value._1 + let fullSizeComplete = value._2 return { arguments in let drawingRect = arguments.drawingRect var fittedSize = arguments.imageSize @@ -2591,10 +2633,10 @@ func chatWebFileImage(account: Account, file: TelegramMediaWebFile) -> Signal<(T private let precomposedSmallAlbumArt = Atomic(value: nil) -private func albumArtThumbnailData(postbox: Postbox, thumbnail: MediaResource) -> Signal<(Data?), NoError> { +private func albumArtThumbnailData(postbox: Postbox, thumbnail: MediaResource) -> Signal { let thumbnailResource = postbox.mediaBox.resourceData(thumbnail) - let signal = thumbnailResource |> take(1) |> mapToSignal { maybeData -> Signal<(Data?), NoError> in + let signal = thumbnailResource |> take(1) |> mapToSignal { maybeData -> Signal in if maybeData.complete { let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) return .single((loadedData)) @@ -2626,14 +2668,14 @@ private func albumArtThumbnailData(postbox: Postbox, thumbnail: MediaResource) - return signal } -private func albumArtFullSizeDatas(postbox: Postbox, thumbnail: MediaResource, fullSize: MediaResource, autoFetchFullSize: Bool = true) -> Signal<(Data?, Data?, Bool), NoError> { +private func albumArtFullSizeDatas(postbox: Postbox, thumbnail: MediaResource, fullSize: MediaResource, autoFetchFullSize: Bool = true) -> Signal, NoError> { let fullSizeResource = postbox.mediaBox.resourceData(fullSize) let thumbnailResource = postbox.mediaBox.resourceData(thumbnail) - let signal = fullSizeResource |> take(1) |> mapToSignal { maybeData -> Signal<(Data?, Data?, Bool), NoError> in + let signal = fullSizeResource |> take(1) |> mapToSignal { maybeData -> Signal, NoError> in if maybeData.complete { let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) - return .single((nil, loadedData, true)) + return .single(Tuple(nil, loadedData, true)) } else { let fetchedThumbnail = postbox.mediaBox.fetchedResource(thumbnail, parameters: nil) let fetchedFullSize = postbox.mediaBox.fetchedResource(fullSize, parameters: nil) @@ -2650,13 +2692,13 @@ private func albumArtFullSizeDatas(postbox: Postbox, thumbnail: MediaResource, f } } - let fullSizeData: Signal<(Data?, Bool), NoError> + let fullSizeData: Signal, NoError> if autoFetchFullSize { - fullSizeData = Signal<(Data?, Bool), NoError> { subscriber in + fullSizeData = Signal, NoError> { subscriber in let fetchedFullSizeDisposable = fetchedFullSize.start() let fullSizeDisposable = fullSizeResource.start(next: { next in - subscriber.putNext((next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete)) + subscriber.putNext(Tuple(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete)) }, error: subscriber.putError, completed: subscriber.putCompletion) return ActionDisposable { @@ -2666,20 +2708,20 @@ private func albumArtFullSizeDatas(postbox: Postbox, thumbnail: MediaResource, f } } else { fullSizeData = fullSizeResource - |> map { next -> (Data?, Bool) in - return (next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete) + |> map { next -> Tuple2 in + return Tuple(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete) } } return thumbnail |> mapToSignal { thumbnailData in - return fullSizeData |> map { (fullSizeData, complete) in - return (thumbnailData, fullSizeData, complete) + return fullSizeData |> map { value in + return Tuple(thumbnailData, value._0, value._1) } } } } |> distinctUntilChanged(isEqual: { lhs, rhs in - if (lhs.0 == nil && lhs.1 == nil) && (rhs.0 == nil && rhs.1 == nil) { + if (lhs._0 == nil && lhs._1 == nil) && (rhs._0 == nil && rhs._1 == nil) { return true } else { return false @@ -2745,7 +2787,7 @@ func playerAlbumArt(postbox: Postbox, fileReference: FileMediaReference?, albumA ) } - var immediateArtworkData: Signal<(Data?, Data?, Bool), NoError> = .single((nil, nil, false)) + var immediateArtworkData: Signal, NoError> = .single(Tuple(nil, nil, false)) if let fileReference = fileReference, let smallestRepresentation = smallestImageRepresentation(fileReference.media.previewRepresentations) { let thumbnailResource = smallestRepresentation.resource @@ -2765,13 +2807,13 @@ func playerAlbumArt(postbox: Postbox, fileReference: FileMediaReference?, albumA } immediateArtworkData = thumbnail |> map { thumbnailData in - return (thumbnailData, nil, false) + return Tuple(thumbnailData, nil, false) } } else if let albumArt = albumArt { if thumbnail { immediateArtworkData = albumArtThumbnailData(postbox: postbox, thumbnail: albumArt.thumbnailResource) |> map { thumbnailData in - return (thumbnailData, nil, false) + return Tuple(thumbnailData, nil, false) } } else { immediateArtworkData = albumArtFullSizeDatas(postbox: postbox, thumbnail: albumArt.thumbnailResource, fullSize: albumArt.fullSizeResource) @@ -2780,9 +2822,9 @@ func playerAlbumArt(postbox: Postbox, fileReference: FileMediaReference?, albumA return combineLatest(fileArtworkData, immediateArtworkData) |> map { fileArtworkData, remoteArtworkData in - let remoteThumbnailData = remoteArtworkData.0 - let remoteFullSizeData = remoteArtworkData.1 - let remoteFullSizeComplete = remoteArtworkData.2 + let remoteThumbnailData = remoteArtworkData._0 + let remoteFullSizeData = remoteArtworkData._1 + let remoteFullSizeComplete = remoteArtworkData._2 return { arguments in let context = DrawingContext(size: arguments.drawingSize, clear: true) @@ -2893,10 +2935,10 @@ func securePhotoInternal(account: Account, resource: TelegramMediaResource, acce } } -private func openInAppIconData(postbox: Postbox, appIcon: MediaResource) -> Signal<(Data?), NoError> { +private func openInAppIconData(postbox: Postbox, appIcon: MediaResource) -> Signal { let appIconResource = postbox.mediaBox.resourceData(appIcon) - let signal = appIconResource |> take(1) |> mapToSignal { maybeData -> Signal<(Data?), NoError> in + let signal = appIconResource |> take(1) |> mapToSignal { maybeData -> Signal in if maybeData.complete { let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) return .single((loadedData)) diff --git a/submodules/TelegramUI/TelegramUI/ShareExtensionContext.swift b/submodules/TelegramUI/TelegramUI/ShareExtensionContext.swift index 4f3a3d3c5e..3d57e67baa 100644 --- a/submodules/TelegramUI/TelegramUI/ShareExtensionContext.swift +++ b/submodules/TelegramUI/TelegramUI/ShareExtensionContext.swift @@ -166,7 +166,7 @@ public class ShareRootControllerImpl { }) semaphore.wait() - let sharedContext = SharedAccountContext(mainWindow: nil, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, appData: self.initializationData.bundleData), rootPath: rootPath, legacyBasePath: nil, legacyCache: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in }) + let sharedContext = SharedAccountContext(mainWindow: nil, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, appData: .single(self.initializationData.bundleData)), rootPath: rootPath, legacyBasePath: nil, legacyCache: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in }) internalContext = InternalContext(sharedContext: sharedContext) globalInternalContext = internalContext } diff --git a/submodules/TelegramUI/TelegramUI/SharedAccountContext.swift b/submodules/TelegramUI/TelegramUI/SharedAccountContext.swift index 6b91d7ac8d..f01eaa905d 100644 --- a/submodules/TelegramUI/TelegramUI/SharedAccountContext.swift +++ b/submodules/TelegramUI/TelegramUI/SharedAccountContext.swift @@ -671,15 +671,23 @@ public final class SharedAccountContext { sandbox = false #endif - let allAccounts = self.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.inAppNotificationSettings]) - |> map { sharedData -> Bool in + let settings = self.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.inAppNotificationSettings]) + |> map { sharedData -> (allAccounts: Bool, includeMuted: Bool) in let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.inAppNotificationSettings] as? InAppNotificationSettings ?? InAppNotificationSettings.defaultSettings - return settings.displayNotificationsFromAllAccounts + return (settings.displayNotificationsFromAllAccounts, settings.totalUnreadCountDisplayStyle == .raw) } - |> distinctUntilChanged + |> distinctUntilChanged(isEqual: { lhs, rhs in + if lhs.allAccounts != rhs.allAccounts { + return false + } + if lhs.includeMuted != rhs.includeMuted { + return false + } + return true + }) - self.registeredNotificationTokensDisposable.set((combineLatest(queue: .mainQueue(), allAccounts, self.activeAccounts) - |> mapToSignal { allAccounts, activeAccountsAndInfo -> Signal in + self.registeredNotificationTokensDisposable.set((combineLatest(queue: .mainQueue(), settings, self.activeAccounts) + |> mapToSignal { settings, activeAccountsAndInfo -> Signal in let (primary, activeAccounts, _) = activeAccountsAndInfo var applied: [Signal] = [] var activeProductionUserIds = activeAccounts.map({ $0.1 }).filter({ !$0.testingEnvironment }).map({ $0.peerId.id }) @@ -688,7 +696,7 @@ public final class SharedAccountContext { let allProductionUserIds = activeProductionUserIds let allTestingUserIds = activeTestingUserIds - if !allAccounts { + if !settings.allAccounts { if let primary = primary { if !primary.testingEnvironment { activeProductionUserIds = [primary.peerId.id] @@ -737,7 +745,7 @@ public final class SharedAccountContext { } else { encrypt = false } - return registerNotificationToken(account: account, token: token, type: .aps(encrypt: encrypt), sandbox: sandbox, otherAccountUserIds: (account.testingEnvironment ? activeTestingUserIds : activeProductionUserIds).filter({ $0 != account.peerId.id })) + return registerNotificationToken(account: account, token: token, type: .aps(encrypt: encrypt), sandbox: sandbox, otherAccountUserIds: (account.testingEnvironment ? activeTestingUserIds : activeProductionUserIds).filter({ $0 != account.peerId.id }), excludeMutedChats: !settings.includeMuted) } appliedVoip = self.voipNotificationToken |> distinctUntilChanged(isEqual: { $0 == $1 }) @@ -745,7 +753,7 @@ public final class SharedAccountContext { guard let token = token else { return .complete() } - return registerNotificationToken(account: account, token: token, type: .voip, sandbox: sandbox, otherAccountUserIds: (account.testingEnvironment ? activeTestingUserIds : activeProductionUserIds).filter({ $0 != account.peerId.id })) + return registerNotificationToken(account: account, token: token, type: .voip, sandbox: sandbox, otherAccountUserIds: (account.testingEnvironment ? activeTestingUserIds : activeProductionUserIds).filter({ $0 != account.peerId.id }), excludeMutedChats: !settings.includeMuted) } } diff --git a/submodules/TelegramUI/TelegramUI/SoftwareAnimationRenderer.swift b/submodules/TelegramUI/TelegramUI/SoftwareAnimationRenderer.swift index c0a8a295c7..aa93f8ccf1 100644 --- a/submodules/TelegramUI/TelegramUI/SoftwareAnimationRenderer.swift +++ b/submodules/TelegramUI/TelegramUI/SoftwareAnimationRenderer.swift @@ -2,14 +2,22 @@ import Foundation import UIKit import AsyncDisplayKit import Display +import SwiftSignalKit import TelegramUIPrivateModule final class SoftwareAnimationRenderer: ASDisplayNode, AnimationRenderer { - func render(width: Int, height: Int, bytes: UnsafeRawPointer, length: Int) { - let image = generateImagePixel(CGSize(width: CGFloat(width), height: CGFloat(height)), scale: 1.0, pixelGenerator: { _, pixelData in - decodeYUVAToRGBA(bytes.assumingMemoryBound(to: UInt8.self), pixelData, Int32(width), Int32(height)) - }) - - self.contents = image?.cgImage + func render(queue: Queue, width: Int, height: Int, data: Data, completion: @escaping () -> Void) { + queue.async { [weak self] in + let image = generateImagePixel(CGSize(width: CGFloat(width), height: CGFloat(height)), scale: 1.0, pixelGenerator: { _, pixelData in + data.withUnsafeBytes { (bytes: UnsafePointer) -> Void in + decodeYUVAToRGBA(bytes, pixelData, Int32(width), Int32(height)) + } + }) + + Queue.mainQueue().async { + self?.contents = image?.cgImage + completion() + } + } } } diff --git a/submodules/TelegramUI/TelegramUI/StickerPackPreviewGridItem.swift b/submodules/TelegramUI/TelegramUI/StickerPackPreviewGridItem.swift index 51feb375f6..3ea566b3d6 100644 --- a/submodules/TelegramUI/TelegramUI/StickerPackPreviewGridItem.swift +++ b/submodules/TelegramUI/TelegramUI/StickerPackPreviewGridItem.swift @@ -49,7 +49,7 @@ private let textFont = Font.regular(20.0) final class StickerPackPreviewGridItemNode: GridItemNode { private var currentState: (Account, StickerPackItem, CGSize)? private let imageNode: TransformImageNode - private var animationNode: StickerAnimationNode? + private var animationNode: AnimatedStickerNode? override var isVisibleInGrid: Bool { didSet { @@ -111,7 +111,7 @@ final class StickerPackPreviewGridItemNode: GridItemNode { if let dimensions = stickerItem.file.dimensions { if stickerItem.file.isAnimatedSticker { if self.animationNode == nil { - let animationNode = StickerAnimationNode() + let animationNode = AnimatedStickerNode() self.animationNode = animationNode self.addSubnode(animationNode) } diff --git a/submodules/TelegramUI/TelegramUI/StickerPreviewPeekContent.swift b/submodules/TelegramUI/TelegramUI/StickerPreviewPeekContent.swift index 8d2e6cd5f1..b5be42d5d6 100644 --- a/submodules/TelegramUI/TelegramUI/StickerPreviewPeekContent.swift +++ b/submodules/TelegramUI/TelegramUI/StickerPreviewPeekContent.swift @@ -66,7 +66,7 @@ private final class StickerPreviewPeekContentNode: ASDisplayNode, PeekController private var textNode: ASTextNode private var imageNode: TransformImageNode - private var animationNode: StickerAnimationNode? + private var animationNode: AnimatedStickerNode? private var containerLayout: (ContainerViewLayout, CGFloat)? @@ -84,7 +84,7 @@ private final class StickerPreviewPeekContentNode: ASDisplayNode, PeekController } if item.file.isAnimatedSticker { - let animationNode = StickerAnimationNode() + let animationNode = AnimatedStickerNode() self.animationNode = animationNode self.animationNode?.setup(account: account, fileReference: FileMediaReference.standalone(media: item.file), width: 320, height: 320) diff --git a/submodules/TelegramUI/TelegramUI/StickerResources.swift b/submodules/TelegramUI/TelegramUI/StickerResources.swift index 33c43fcd3c..aa9094cf68 100644 --- a/submodules/TelegramUI/TelegramUI/StickerResources.swift +++ b/submodules/TelegramUI/TelegramUI/StickerResources.swift @@ -45,7 +45,7 @@ func chatMessageStickerResource(file: TelegramMediaFile, small: Bool) -> MediaRe return resource } -private func chatMessageStickerDatas(postbox: Postbox, file: TelegramMediaFile, small: Bool, fetched: Bool, onlyFullSize: Bool, synchronousLoad: Bool) -> Signal<(Data?, Data?, Bool), NoError> { +private func chatMessageStickerDatas(postbox: Postbox, file: TelegramMediaFile, small: Bool, fetched: Bool, onlyFullSize: Bool, synchronousLoad: Bool) -> Signal, NoError> { let thumbnailResource = chatMessageStickerResource(file: file, small: true) let resource = chatMessageStickerResource(file: file, small: small) @@ -57,7 +57,7 @@ private func chatMessageStickerDatas(postbox: Postbox, file: TelegramMediaFile, if maybeData.complete { let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) - return .single((nil, loadedData, true)) + 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: CachedStickerAJpegRepresentation(size: small ? CGSize(width: 160.0, height: 160.0) : nil), complete: onlyFullSize) @@ -76,8 +76,8 @@ private func chatMessageStickerDatas(postbox: Postbox, file: TelegramMediaFile, fetchThumbnail = fetchedMediaResource(postbox: postbox, reference: stickerPackFileReference(file).resourceReference(thumbnailResource)).start() } let disposable = (combineLatest(thumbnailData, fullSizeData) - |> map { thumbnailData, fullSizeData -> (Data?, Data?, Bool) in - return (thumbnailData.complete ? try? Data(contentsOf: URL(fileURLWithPath: thumbnailData.path)) : nil, fullSizeData.0, fullSizeData.1) + |> map { thumbnailData, fullSizeData -> Tuple3 in + return Tuple(thumbnailData.complete ? try? Data(contentsOf: URL(fileURLWithPath: thumbnailData.path)) : nil, fullSizeData.0, fullSizeData.1) }).start(next: { next in subscriber.putNext(next) }, error: { error in @@ -96,6 +96,57 @@ private func chatMessageStickerDatas(postbox: Postbox, file: TelegramMediaFile, } } +private func chatMessageAnimatedStickerDatas(postbox: Postbox, file: TelegramMediaFile, small: Bool, size: CGSize, fetched: Bool, onlyFullSize: Bool, synchronousLoad: Bool) -> Signal, 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) + + return maybeFetched + |> take(1) + |> mapToSignal { maybeData in + if maybeData.complete { + let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) + + 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) + |> map { next in + return (next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: .mappedIfSafe), next.complete) + } + + return Signal { subscriber in + var fetch: Disposable? + if fetched { + fetch = fetchedMediaResource(postbox: postbox, reference: stickerPackFileReference(file).resourceReference(resource)).start() + } + + var fetchThumbnail: Disposable? + if !thumbnailResource.id.isEqual(to: resource.id) { + fetchThumbnail = fetchedMediaResource(postbox: postbox, reference: stickerPackFileReference(file).resourceReference(thumbnailResource)).start() + } + let disposable = (combineLatest(thumbnailData, fullSizeData) + |> map { thumbnailData, fullSizeData -> Tuple3 in + return Tuple(thumbnailData.complete ? try? Data(contentsOf: URL(fileURLWithPath: thumbnailData.path)) : nil, fullSizeData.0, fullSizeData.1) + }).start(next: { next in + subscriber.putNext(next) + }, error: { error in + subscriber.putError(error) + }, completed: { + subscriber.putCompletion() + }) + + return ActionDisposable { + fetch?.dispose() + fetchThumbnail?.dispose() + disposable.dispose() + } + } + } + } +} + private func chatMessageStickerThumbnailData(postbox: Postbox, file: TelegramMediaFile, synchronousLoad: Bool) -> Signal { let thumbnailResource = chatMessageStickerResource(file: file, small: true) @@ -182,7 +233,7 @@ func chatMessageAnimationData(postbox: Postbox, fileReference: FileMediaReferenc } } -func chatMessageAnimatedStrickerBackingData(postbox: Postbox, fileReference: FileMediaReference, synchronousLoad: Bool) -> Signal<(Data?, Bool), NoError> { +func chatMessageAnimatedStrickerBackingData(postbox: Postbox, fileReference: FileMediaReference, synchronousLoad: Bool) -> Signal, NoError> { let resource = fileReference.media.resource let maybeFetched = postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad) @@ -192,11 +243,11 @@ func chatMessageAnimatedStrickerBackingData(postbox: Postbox, fileReference: Fil |> mapToSignal { maybeData in if maybeData.complete { let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) - return .single((loadedData, true)) + return .single(Tuple(loadedData, true)) } else { let fullSizeData = postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad) - |> map { next -> (Data?, Bool) in - return (next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete) + |> map { next -> Tuple2 in + return Tuple(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete) } return fullSizeData } @@ -205,7 +256,10 @@ func chatMessageAnimatedStrickerBackingData(postbox: Postbox, fileReference: Fil func chatMessageLegacySticker(account: Account, file: TelegramMediaFile, small: Bool, fitSize: CGSize, fetched: Bool = false, onlyFullSize: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { let signal = chatMessageStickerDatas(postbox: account.postbox, file: file, small: small, fetched: fetched, onlyFullSize: onlyFullSize, synchronousLoad: false) - return signal |> map { (thumbnailData, fullSizeData, fullSizeComplete) in + return signal |> map { value in + let thumbnailData = value._0 + let fullSizeData = value._1 + let fullSizeComplete = value._2 return { preArguments in var fullSizeImage: (UIImage, UIImage)? if let fullSizeData = fullSizeData, fullSizeComplete { @@ -315,16 +369,110 @@ public func chatMessageStickerPackThumbnail(postbox: Postbox, representation: Te } public func chatMessageSticker(postbox: Postbox, file: TelegramMediaFile, small: Bool, fetched: Bool = false, onlyFullSize: Bool = false, thumbnail: Bool = false, synchronousLoad: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { - let signal: Signal<(Data?, Data?, Bool), NoError> + let signal: Signal, NoError> if thumbnail { signal = chatMessageStickerThumbnailData(postbox: postbox, file: file, synchronousLoad: synchronousLoad) - |> map { data -> (Data?, Data?, Bool) in - return (data, nil, false) + |> map { data -> Tuple3in + return Tuple3(data, nil, false) } } else { signal = chatMessageStickerDatas(postbox: postbox, file: file, small: small, fetched: fetched, onlyFullSize: onlyFullSize, synchronousLoad: synchronousLoad) } - return signal |> map { (thumbnailData, fullSizeData, fullSizeComplete) in + return signal |> map { value in + let thumbnailData = value._0 + let fullSizeData = value._1 + let fullSizeComplete = value._2 + return { arguments in + let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: arguments.emptyColor == nil) + + let drawingRect = arguments.drawingRect + let fittedSize = arguments.imageSize + let fittedRect = CGRect(origin: CGPoint(x: drawingRect.origin.x + (drawingRect.size.width - fittedSize.width) / 2.0, y: drawingRect.origin.y + (drawingRect.size.height - fittedSize.height) / 2.0), size: fittedSize) + //let fittedRect = arguments.drawingRect + + var fullSizeImage: (UIImage, UIImage)? + if let fullSizeData = fullSizeData, fullSizeComplete { + if let image = imageFromAJpeg(data: fullSizeData) { + fullSizeImage = image + } + } + + var thumbnailImage: (UIImage, UIImage)? + if fullSizeImage == nil, let thumbnailData = thumbnailData { + if let image = imageFromAJpeg(data: thumbnailData) { + thumbnailImage = image + } + } + + var blurredThumbnailImage: UIImage? + let thumbnailInset: CGFloat = 10.0 + if let thumbnailImage = thumbnailImage { + let thumbnailSize = thumbnailImage.0.size + var thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 150.0, height: 150.0)) + let thumbnailDrawingSize = thumbnailContextSize + thumbnailContextSize.width += thumbnailInset * 2.0 + thumbnailContextSize.height += thumbnailInset * 2.0 + let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0, clear: true) + thumbnailContext.withFlippedContext { c in + if let cgImage = thumbnailImage.0.cgImage, let cgImageAlpha = thumbnailImage.1.cgImage { + c.setBlendMode(.normal) + c.interpolationQuality = .medium + + let mask = CGImage(maskWidth: cgImageAlpha.width, height: cgImageAlpha.height, bitsPerComponent: cgImageAlpha.bitsPerComponent, bitsPerPixel: cgImageAlpha.bitsPerPixel, bytesPerRow: cgImageAlpha.bytesPerRow, provider: cgImageAlpha.dataProvider!, decode: nil, shouldInterpolate: true) + + c.draw(cgImage.masking(mask!)!, in: CGRect(origin: CGPoint(x: thumbnailInset, y: thumbnailInset), size: thumbnailDrawingSize)) + } + } + stickerThumbnailAlphaBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + + blurredThumbnailImage = thumbnailContext.generateImage() + } + + context.withFlippedContext { c in + if let color = arguments.emptyColor { + c.setBlendMode(.normal) + c.setFillColor(color.cgColor) + c.fill(drawingRect) + } else { + c.setBlendMode(.copy) + } + + if let blurredThumbnailImage = blurredThumbnailImage { + c.interpolationQuality = .low + let thumbnailScaledInset = thumbnailInset * (fittedRect.width / blurredThumbnailImage.size.width) + c.draw(blurredThumbnailImage.cgImage!, in: fittedRect.insetBy(dx: -thumbnailScaledInset, dy: -thumbnailScaledInset)) + } + + if let fullSizeImage = fullSizeImage, let cgImage = fullSizeImage.0.cgImage, let cgImageAlpha = fullSizeImage.1.cgImage { + c.setBlendMode(.normal) + c.interpolationQuality = .medium + + let mask = CGImage(maskWidth: cgImageAlpha.width, height: cgImageAlpha.height, bitsPerComponent: cgImageAlpha.bitsPerComponent, bitsPerPixel: cgImageAlpha.bitsPerPixel, bytesPerRow: cgImageAlpha.bytesPerRow, provider: cgImageAlpha.dataProvider!, decode: nil, shouldInterpolate: true) + + c.draw(cgImage.masking(mask!)!, in: fittedRect) + } + } + + return context + } + } +} + +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> { + let signal: Signal, NoError> + if thumbnail { + signal = chatMessageStickerThumbnailData(postbox: postbox, file: file, synchronousLoad: synchronousLoad) + |> map { data -> Tuple3 in + return Tuple(data, nil, false) + } + } else { + signal = chatMessageAnimatedStickerDatas(postbox: postbox, file: file, small: small, size: size, fetched: fetched, onlyFullSize: onlyFullSize, synchronousLoad: synchronousLoad) + } + return signal + |> map { value in + let thumbnailData = value._0 + let fullSizeData = value._1 + let fullSizeComplete = value._2 return { arguments in let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: arguments.emptyColor == nil) diff --git a/submodules/TelegramUI/TelegramUI/Tuple.swift b/submodules/TelegramUI/TelegramUI/Tuple.swift new file mode 100644 index 0000000000..4c2c48f40d --- /dev/null +++ b/submodules/TelegramUI/TelegramUI/Tuple.swift @@ -0,0 +1,43 @@ +import Foundation + +public final class Tuple1 { + public let _0: T0 + + public init(_ _0: T0) { + self._0 = _0 + } +} + +public final class Tuple2 { + public let _0: T0 + public let _1: T1 + + public init(_ _0: T0, _ _1: T1) { + self._0 = _0 + self._1 = _1 + } +} + +public final class Tuple3 { + public let _0: T0 + public let _1: T1 + public let _2: T2 + + public init(_ _0: T0, _ _1: T1, _ _2: T2) { + self._0 = _0 + self._1 = _1 + self._2 = _2 + } +} + +public func Tuple(_ _0: T0) -> Tuple1 { + return Tuple1(_0) +} + +public func Tuple(_ _0: T0, _ _1: T1) -> Tuple2 { + return Tuple2(_0, _1) +} + +public func Tuple(_ _0: T0, _ _1: T1, _ _2: T2) -> Tuple3 { + return Tuple3(_0, _1, _2) +} diff --git a/submodules/TelegramUI/TelegramUI_Xcode.xcodeproj/project.pbxproj b/submodules/TelegramUI/TelegramUI_Xcode.xcodeproj/project.pbxproj index a1c30d172d..c2a2917d3a 100644 --- a/submodules/TelegramUI/TelegramUI_Xcode.xcodeproj/project.pbxproj +++ b/submodules/TelegramUI/TelegramUI_Xcode.xcodeproj/project.pbxproj @@ -30,11 +30,7 @@ 0913469C21883C3700846E49 /* InstantPageDetailsItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0913469B21883C3700846E49 /* InstantPageDetailsItem.swift */; }; 091417F221EF4E5D00C8325A /* WallpaperGalleryController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 091417F121EF4E5D00C8325A /* WallpaperGalleryController.swift */; }; 091417F421EF4F5F00C8325A /* WallpaperGalleryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 091417F321EF4F5F00C8325A /* WallpaperGalleryItem.swift */; }; - 091954732294591B00E11046 /* AnimatedStickerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 091954722294591B00E11046 /* AnimatedStickerNode.swift */; }; - 09195475229474E900E11046 /* AnimatedStickerPlayerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09195474229474E900E11046 /* AnimatedStickerPlayerManager.swift */; }; - 091954772294752C00E11046 /* AnimatedStickerPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 091954762294752C00E11046 /* AnimatedStickerPlayer.swift */; }; 091954792294754E00E11046 /* AnimatedStickerUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 091954782294754E00E11046 /* AnimatedStickerUtils.swift */; }; - 0919547B2294788200E11046 /* AnimatedStickerVideoCompositor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0919547A2294788200E11046 /* AnimatedStickerVideoCompositor.swift */; }; 091BEAB3214552D9003AEA30 /* Vision.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D02DADBE2138D76F00116225 /* Vision.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 0921F60B228C8765001A13D7 /* ItemListPlaceholderItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0921F60A228C8765001A13D7 /* ItemListPlaceholderItem.swift */; }; 0921F60E228EE000001A13D7 /* ChatMessageActionUrlAuthController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0921F60D228EE000001A13D7 /* ChatMessageActionUrlAuthController.swift */; }; @@ -459,6 +455,7 @@ D079FCDF1F05C9280038FADE /* BotReceiptController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D079FCDE1F05C9280038FADE /* BotReceiptController.swift */; }; D079FCE11F05C9380038FADE /* BotReceiptControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D079FCE01F05C9380038FADE /* BotReceiptControllerNode.swift */; }; D079FCE91F06A76C0038FADE /* Notices.swift in Sources */ = {isa = PBXBuildFile; fileRef = D079FCE81F06A76C0038FADE /* Notices.swift */; }; + D07A33B722C578AC00F6D622 /* Tuple.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07A33B622C578AC00F6D622 /* Tuple.swift */; }; D07ABBA5202A14BC003671DE /* LegacyImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07ABBA4202A14BC003671DE /* LegacyImagePicker.swift */; }; D07ABBAB202A1BD1003671DE /* LegacyWallpaperEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07ABBAA202A1BD1003671DE /* LegacyWallpaperEditor.swift */; }; D07BCBFE1F2B792300ED97AA /* LegacyComponents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D07BCBFD1F2B792300ED97AA /* LegacyComponents.framework */; }; @@ -470,6 +467,7 @@ D081E108217F583F003CD921 /* LanguageLinkPreviewContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D081E107217F583F003CD921 /* LanguageLinkPreviewContentNode.swift */; }; D083491C209361DC008CFD52 /* AvatarGalleryItemFooterContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D083491B209361DC008CFD52 /* AvatarGalleryItemFooterContentNode.swift */; }; D084023420E295F000065674 /* GroupStickerPackSetupController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D084023320E295F000065674 /* GroupStickerPackSetupController.swift */; }; + D08557E722C5FEB90026D6D2 /* AnimatedStickerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08557E622C5FEB90026D6D2 /* AnimatedStickerNode.swift */; }; D087BFAD1F741B9D003FD209 /* ShareContentContainerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D087BFAC1F741B9D003FD209 /* ShareContentContainerNode.swift */; }; D087BFAF1F741BB7003FD209 /* ShareLoadingContainerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D087BFAE1F741BB7003FD209 /* ShareLoadingContainerNode.swift */; }; D087BFB11F745483003FD209 /* ShareSearchBarNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D087BFB01F745483003FD209 /* ShareSearchBarNode.swift */; }; @@ -1252,11 +1250,7 @@ 0913469B21883C3700846E49 /* InstantPageDetailsItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageDetailsItem.swift; sourceTree = ""; }; 091417F121EF4E5D00C8325A /* WallpaperGalleryController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallpaperGalleryController.swift; sourceTree = ""; }; 091417F321EF4F5F00C8325A /* WallpaperGalleryItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallpaperGalleryItem.swift; sourceTree = ""; }; - 091954722294591B00E11046 /* AnimatedStickerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatedStickerNode.swift; sourceTree = ""; }; - 09195474229474E900E11046 /* AnimatedStickerPlayerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatedStickerPlayerManager.swift; sourceTree = ""; }; - 091954762294752C00E11046 /* AnimatedStickerPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatedStickerPlayer.swift; sourceTree = ""; }; 091954782294754E00E11046 /* AnimatedStickerUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatedStickerUtils.swift; sourceTree = ""; }; - 0919547A2294788200E11046 /* AnimatedStickerVideoCompositor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatedStickerVideoCompositor.swift; sourceTree = ""; }; 0921F60A228C8765001A13D7 /* ItemListPlaceholderItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListPlaceholderItem.swift; sourceTree = ""; }; 0921F60D228EE000001A13D7 /* ChatMessageActionUrlAuthController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessageActionUrlAuthController.swift; sourceTree = ""; }; 092F368C2154AAE9001A9F49 /* SFCompactRounded-Semibold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SFCompactRounded-Semibold.otf"; sourceTree = ""; }; @@ -1857,6 +1851,7 @@ D079FCDE1F05C9280038FADE /* BotReceiptController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BotReceiptController.swift; sourceTree = ""; }; D079FCE01F05C9380038FADE /* BotReceiptControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BotReceiptControllerNode.swift; sourceTree = ""; }; D079FCE81F06A76C0038FADE /* Notices.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Notices.swift; sourceTree = ""; }; + D07A33B622C578AC00F6D622 /* Tuple.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tuple.swift; sourceTree = ""; }; D07A7DA21D957671005BCD27 /* ListMessageSnippetItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListMessageSnippetItemNode.swift; sourceTree = ""; }; D07A7DA41D95783C005BCD27 /* ListMessageNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListMessageNode.swift; sourceTree = ""; }; D07ABBA4202A14BC003671DE /* LegacyImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyImagePicker.swift; sourceTree = ""; }; @@ -1877,6 +1872,7 @@ D081E107217F583F003CD921 /* LanguageLinkPreviewContentNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguageLinkPreviewContentNode.swift; sourceTree = ""; }; D083491B209361DC008CFD52 /* AvatarGalleryItemFooterContentNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarGalleryItemFooterContentNode.swift; sourceTree = ""; }; D084023320E295F000065674 /* GroupStickerPackSetupController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupStickerPackSetupController.swift; sourceTree = ""; }; + D08557E622C5FEB90026D6D2 /* AnimatedStickerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatedStickerNode.swift; sourceTree = ""; }; D08774F71E3DE7BF00A97350 /* ItemListEditableDeleteControlNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemListEditableDeleteControlNode.swift; sourceTree = ""; }; D08774F91E3E2A5600A97350 /* ItemListCheckboxItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemListCheckboxItem.swift; sourceTree = ""; }; D08775081E3E59DE00A97350 /* PeerNotificationSoundStrings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerNotificationSoundStrings.swift; sourceTree = ""; }; @@ -2561,11 +2557,8 @@ 0919546D229458E900E11046 /* Animated Stickers */ = { isa = PBXGroup; children = ( - 091954722294591B00E11046 /* AnimatedStickerNode.swift */, - 091954762294752C00E11046 /* AnimatedStickerPlayer.swift */, - 09195474229474E900E11046 /* AnimatedStickerPlayerManager.swift */, - 0919547A2294788200E11046 /* AnimatedStickerVideoCompositor.swift */, 091954782294754E00E11046 /* AnimatedStickerUtils.swift */, + D08557E622C5FEB90026D6D2 /* AnimatedStickerNode.swift */, ); name = "Animated Stickers"; sourceTree = ""; @@ -4926,6 +4919,7 @@ 09E4A800223AE1B30038140F /* PeerType.swift */, 09E4A806223D4B860038140F /* AccountUtils.swift */, D099E21F229405BB00561B75 /* Weak.swift */, + D07A33B622C578AC00F6D622 /* Tuple.swift */, ); name = Utils; sourceTree = ""; @@ -5385,6 +5379,7 @@ D0104F281F47171F004E4881 /* InstantPageGalleryController.swift in Sources */, D0AF798F22C2E26500CECCB8 /* compressed.cc in Sources */, D0EC6CC81EB9F58800EBF1C3 /* ProgressiveImage.swift in Sources */, + D07A33B722C578AC00F6D622 /* Tuple.swift in Sources */, D081E108217F583F003CD921 /* LanguageLinkPreviewContentNode.swift in Sources */, D0EC6CC91EB9F58800EBF1C3 /* WebP.swift in Sources */, D0EC6CCA1EB9F58800EBF1C3 /* PeerPresenceStatusManager.swift in Sources */, @@ -5928,7 +5923,6 @@ D0EC6DC61EB9F58900EBF1C3 /* MultiplexedSoftwareVideoSourceManager.swift in Sources */, D0EC6DC71EB9F58900EBF1C3 /* SampleBufferPool.swift in Sources */, 0962E67721B673AF00245FD9 /* Permission.swift in Sources */, - 091954772294752C00E11046 /* AnimatedStickerPlayer.swift in Sources */, D0B21B13220D6E8C003F741D /* ActionSheetPeerItem.swift in Sources */, 0900678F21ED8E0E00530762 /* HexColor.swift in Sources */, D0EC6DC81EB9F58900EBF1C3 /* MultiplexedVideoNode.swift in Sources */, @@ -6061,7 +6055,6 @@ D0C26D571FDF2388004ABF18 /* OpenChatMessage.swift in Sources */, D00817CA22B47A14008A895F /* WatchRequestHandlers.swift in Sources */, D00817E222B47A14008A895F /* UIImage+ImageEffects.m in Sources */, - 0919547B2294788200E11046 /* AnimatedStickerVideoCompositor.swift in Sources */, D0FA08BE20481EA300DD23FC /* Locale.swift in Sources */, D0E412CE206A707400BEE4A2 /* FormControllerTextItem.swift in Sources */, D007019C2029E8F2006B9E34 /* LegacyICloudFileController.swift in Sources */, @@ -6186,6 +6179,7 @@ D0EC6E421EB9F58900EBF1C3 /* ItemListRevealOptionsNode.swift in Sources */, D0E8175920122FE100B82BBB /* ChatRecentActionsFilterController.swift in Sources */, D0EC6E431EB9F58900EBF1C3 /* ItemListEditableDeleteControlNode.swift in Sources */, + D08557E722C5FEB90026D6D2 /* AnimatedStickerNode.swift in Sources */, D0EC6E441EB9F58900EBF1C3 /* ItemListSingleLineInputItem.swift in Sources */, D01776B31F1D69A80044446D /* RadialStatusNode.swift in Sources */, D084023420E295F000065674 /* GroupStickerPackSetupController.swift in Sources */, @@ -6293,7 +6287,6 @@ D056CD741FF2996B00880D28 /* ExternalMusicAlbumArtResources.swift in Sources */, D0F0AAE41EC21AAA005EE2A5 /* CallControllerButtonsNode.swift in Sources */, D0EC6E7A1EB9F58900EBF1C3 /* DebugController.swift in Sources */, - 091954732294591B00E11046 /* AnimatedStickerNode.swift in Sources */, D07ABBAB202A1BD1003671DE /* LegacyWallpaperEditor.swift in Sources */, 09E2D9F1226F214000EA0AA4 /* EmojiResources.swift in Sources */, D0EC6E7B1EB9F58900EBF1C3 /* DebugAccountsController.swift in Sources */, @@ -6305,7 +6298,6 @@ D0EC6E7D1EB9F58900EBF1C3 /* ChangePhoneNumberIntroController.swift in Sources */, 09F215AB2264ABA600AEDF6D /* PasscodeBackground.swift in Sources */, D0EC6E7E1EB9F58900EBF1C3 /* ChangePhoneNumberController.swift in Sources */, - 09195475229474E900E11046 /* AnimatedStickerPlayerManager.swift in Sources */, D0B21B17220D85E7003F741D /* TabBarAccountSwitchControllerNode.swift in Sources */, D0EC6E7F1EB9F58900EBF1C3 /* ChangePhoneNumberControllerNode.swift in Sources */, D0EC6E801EB9F58900EBF1C3 /* ChangePhoneNumberCodeController.swift in Sources */,