From d1bcfbc670080b90e187aa42075670febd43685e Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 7 Aug 2019 12:15:59 +0300 Subject: [PATCH 1/8] Disabled CarPlay and backported some bugfixes --- SiriIntents/IntentHandler.swift | 530 ++++++++++-------- .../Display/Display/AlertControllerNode.swift | 9 +- .../TelegramUI/AnimatedStickerNode.swift | 106 ++-- .../TelegramUI/AnimatedStickerUtils.swift | 2 + .../TelegramUI/TelegramUI/AppDelegate.swift | 10 +- ...ceAwaitingAccountResetControllerNode.swift | 2 +- .../Bridge Audio/TGBridgeAudioEncoder.m | 258 ++++----- .../CachedResourceRepresentations.swift | 2 +- .../TelegramUI/ChatListController.swift | 8 + .../TelegramUI/ChatListControllerNode.swift | 4 +- .../ChatMessageAnimatedStickerItemNode.swift | 354 +++++++----- .../TelegramUI/TelegramUI/EmojiUtils.swift | 2 + .../TelegramUI/ManagedAudioRecorder.swift | 162 +++--- .../TelegramUI/ShareExtensionContext.swift | 4 +- 14 files changed, 824 insertions(+), 629 deletions(-) diff --git a/SiriIntents/IntentHandler.swift b/SiriIntents/IntentHandler.swift index 1bc5d9ab2a..27479e02d0 100644 --- a/SiriIntents/IntentHandler.swift +++ b/SiriIntents/IntentHandler.swift @@ -98,30 +98,30 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag 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: .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 { + |> mapToSignal { account -> Signal in + if let account = account { + switch account { case .upgrading: return .complete() case let .authorized(account): return applicationSettings(accountManager: accountManager) - |> deliverOnMainQueue - |> map { settings -> Account in - accountCache = account - Logger.shared.logToFile = settings.logging.logToFile - Logger.shared.logToConsole = settings.logging.logToConsole - - Logger.shared.redactSensitiveData = settings.logging.redactSensitiveData - return account + |> deliverOnMainQueue + |> map { settings -> Account in + accountCache = account + Logger.shared.logToFile = settings.logging.logToFile + Logger.shared.logToConsole = settings.logging.logToConsole + + Logger.shared.redactSensitiveData = settings.logging.redactSensitiveData + return account } case .unauthorized: return .complete() + } + } else { + return .single(nil) } - } else { - return .single(nil) } - } - |> take(1) + |> take(1) } self.accountPromise.set(account) } @@ -135,14 +135,48 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag return self } - private func resolve(persons: [INPerson]?, with completion: @escaping ([INPersonResolutionResult]) -> Void) { - guard CNContactStore.authorizationStatus(for: .contacts) == .authorized else { - completion([INPersonResolutionResult.notRequired()]) - return + enum ResolveResult { + case success(INPerson) + case disambiguation([INPerson]) + case needsValue + case noResult + case skip + + @available(iOSApplicationExtension 11.0, *) + var sendMessageRecipientResulutionResult: INSendMessageRecipientResolutionResult { + switch self { + case let .success(person): + return .success(with: person) + case let .disambiguation(persons): + return .disambiguation(with: persons) + case .needsValue: + return .needsValue() + case .noResult: + return .unsupported() + case .skip: + return .notRequired() + } } + var personResolutionResult: INPersonResolutionResult { + switch self { + case let .success(person): + return .success(with: person) + case let .disambiguation(persons): + return .disambiguation(with: persons) + case .needsValue: + return .needsValue() + case .noResult: + return .unsupported() + case .skip: + return .notRequired() + } + } + } + + private func resolve(persons: [INPerson]?, with completion: @escaping ([ResolveResult]) -> Void) { guard let initialPersons = persons, !initialPersons.isEmpty else { - completion([INPersonResolutionResult.needsValue()]) + completion([.needsValue]) return } @@ -164,12 +198,12 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag } if filteredPersons.isEmpty { - completion([INPersonResolutionResult.needsValue()]) + completion([.noResult]) return } if filteredPersons.count > 1 { - completion([INPersonResolutionResult.disambiguation(with: filteredPersons)]) + completion([.disambiguation(filteredPersons)]) return } @@ -182,7 +216,7 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag } if allPersonsAlreadyMatched { - completion([INPersonResolutionResult.success(with: filteredPersons[0])]) + completion([.success(filteredPersons[0])]) return } @@ -205,74 +239,84 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag let account = self.accountPromise.get() let signal = matchingDeviceContacts(stableIds: stableIds) - |> take(1) - |> mapToSignal { matchedContacts in - return account - |> introduceError(IntentContactsError.self) - |> mapToSignal { account -> Signal<[(String, TelegramUser)], IntentContactsError> in - if let account = account { - return matchingCloudContacts(postbox: account.postbox, contacts: matchedContacts) + |> take(1) + |> mapToSignal { matchedContacts in + return account |> introduceError(IntentContactsError.self) - } else { - return .fail(.generic) + |> mapToSignal { account -> Signal<[(String, TelegramUser)], IntentContactsError> in + if let account = account { + return matchingCloudContacts(postbox: account.postbox, contacts: matchedContacts) + |> introduceError(IntentContactsError.self) + } else { + return .fail(.generic) + } } - } } self.resolvePersonsDisposable.set((signal - |> deliverOnMainQueue).start(next: { peers in - if peers.isEmpty { - completion([INPersonResolutionResult.needsValue()]) - } else { - completion(peers.map { stableId, user in - let person = personWithUser(stableId: stableId, user: user) - return INPersonResolutionResult.success(with: person) - }) - } - }, error: { error in - completion([INPersonResolutionResult.unsupported()]) - })) + |> deliverOnMainQueue).start(next: { peers in + if peers.isEmpty { + completion([.needsValue]) + } else { + completion(peers.map { .success(personWithUser(stableId: $0, user: $1)) }) + } + }, error: { error in + completion([.skip]) + })) } // MARK: - INSendMessageIntentHandling func resolveRecipients(for intent: INSendMessageIntent, with completion: @escaping ([INPersonResolutionResult]) -> Void) { - if #available(iOSApplicationExtension 11.0, *) { - if let peerId = intent.conversationIdentifier.flatMap(Int64.init) { - let account = self.accountPromise.get() - - let signal = account + guard CNContactStore.authorizationStatus(for: .contacts) == .authorized else { + completion([INPersonResolutionResult.notRequired()]) + return + } + self.resolve(persons: intent.recipients, with: { result in + completion(result.map { $0.personResolutionResult }) + }) + } + + @available(iOSApplicationExtension 11.0, *) + func resolveRecipients(for intent: INSendMessageIntent, with completion: @escaping ([INSendMessageRecipientResolutionResult]) -> Void) { + if let peerId = intent.conversationIdentifier.flatMap(Int64.init) { + let account = self.accountPromise.get() + + let signal = account |> introduceError(IntentHandlingError.self) |> mapToSignal { account -> Signal in if let account = account { return matchingCloudContact(postbox: account.postbox, peerId: PeerId(peerId)) - |> introduceError(IntentHandlingError.self) - |> map { user -> INPerson? in - if let user = user { - return personWithUser(stableId: "tg\(peerId)", user: user) - } else { - return nil - } + |> introduceError(IntentHandlingError.self) + |> map { user -> INPerson? in + if let user = user { + return personWithUser(stableId: "tg\(peerId)", user: user) + } else { + return nil + } } } else { return .fail(.generic) } - } - - self.resolvePersonsDisposable.set((signal + } + + self.resolvePersonsDisposable.set((signal |> deliverOnMainQueue).start(next: { person in if let person = person { - completion([INPersonResolutionResult.success(with: person)]) + completion([INSendMessageRecipientResolutionResult.success(with: person)]) } else { - completion([INPersonResolutionResult.needsValue()]) + completion([INSendMessageRecipientResolutionResult.needsValue()]) } }, error: { error in - completion([INPersonResolutionResult.notRequired()]) + completion([INSendMessageRecipientResolutionResult.unsupported(forReason: .noAccount)]) })) - } else { - self.resolve(persons: intent.recipients, with: completion) - } } else { - self.resolve(persons: intent.recipients, with: completion) + guard CNContactStore.authorizationStatus(for: .contacts) == .authorized else { + completion([INSendMessageRecipientResolutionResult.notRequired()]) + return + } + self.resolve(persons: intent.recipients, with: { result in + completion(result.map { $0.sendMessageRecipientResulutionResult }) + }) } } @@ -301,48 +345,48 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag func handle(intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Void) { self.actionDisposable.set((self.accountPromise.get() - |> take(1) - |> mapError { _ -> IntentHandlingError in - return .generic - } - |> mapToSignal { account -> Signal in - guard let account = account else { - return .fail(.generic) - } - guard let recipient = intent.recipients?.first, let customIdentifier = recipient.customIdentifier, customIdentifier.hasPrefix("tg") else { - return .fail(.generic) - } - - guard let peerIdValue = Int64(String(customIdentifier[customIdentifier.index(customIdentifier.startIndex, offsetBy: 2)...])) else { - return .fail(.generic) - } - - let peerId = PeerId(peerIdValue) - if peerId.namespace != Namespaces.Peer.CloudUser { - return .fail(.generic) - } - - account.shouldBeServiceTaskMaster.set(.single(.now)) - return standaloneSendMessage(account: account, peerId: peerId, text: intent.content ?? "", attributes: [], media: nil, replyToMessageId: nil) + |> take(1) |> mapError { _ -> IntentHandlingError in return .generic } - |> mapToSignal { _ -> Signal in - return .complete() + |> mapToSignal { account -> Signal in + guard let account = account else { + return .fail(.generic) + } + guard let recipient = intent.recipients?.first, let customIdentifier = recipient.customIdentifier, customIdentifier.hasPrefix("tg") else { + return .fail(.generic) + } + + guard let peerIdValue = Int64(String(customIdentifier[customIdentifier.index(customIdentifier.startIndex, offsetBy: 2)...])) else { + return .fail(.generic) + } + + let peerId = PeerId(peerIdValue) + if peerId.namespace != Namespaces.Peer.CloudUser { + return .fail(.generic) + } + + account.shouldBeServiceTaskMaster.set(.single(.now)) + return standaloneSendMessage(account: account, peerId: peerId, text: intent.content ?? "", attributes: [], media: nil, replyToMessageId: nil) + |> mapError { _ -> IntentHandlingError in + return .generic + } + |> mapToSignal { _ -> Signal in + return .complete() + } + |> afterDisposed { + account.shouldBeServiceTaskMaster.set(.single(.never)) + } } - |> afterDisposed { - account.shouldBeServiceTaskMaster.set(.single(.never)) - } - } - |> deliverOnMainQueue).start(error: { _ in - let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self)) - let response = INSendMessageIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity) - completion(response) - }, completed: { - let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self)) - let response = INSendMessageIntentResponse(code: .success, userActivity: userActivity) - completion(response) - })) + |> deliverOnMainQueue).start(error: { _ in + let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self)) + let response = INSendMessageIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity) + completion(response) + }, completed: { + let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self)) + let response = INSendMessageIntentResponse(code: .success, userActivity: userActivity) + completion(response) + })) } // MARK: - INSearchForMessagesIntentHandling @@ -353,42 +397,42 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag func handle(intent: INSearchForMessagesIntent, completion: @escaping (INSearchForMessagesIntentResponse) -> Void) { self.actionDisposable.set((self.accountPromise.get() - |> take(1) - |> introduceError(IntentHandlingError.self) - |> mapToSignal { account -> Signal<[INMessage], IntentHandlingError> in - guard let account = account else { - return .fail(.generic) - } - - account.shouldBeServiceTaskMaster.set(.single(.now)) - account.resetStateManagement() - - let completion: Signal = account.stateManager.pollStateUpdateCompletion() - |> map { _ in - return Void() - } - - return (completion |> timeout(4.0, queue: Queue.mainQueue(), alternate: .single(Void()))) - |> introduceError(IntentHandlingError.self) |> take(1) - |> mapToSignal { _ -> Signal<[INMessage], IntentHandlingError> in - return unreadMessages(account: account) - |> introduceError(IntentHandlingError.self) - |> afterDisposed { - account.shouldBeServiceTaskMaster.set(.single(.never)) + |> introduceError(IntentHandlingError.self) + |> mapToSignal { account -> Signal<[INMessage], IntentHandlingError> in + guard let account = account else { + return .fail(.generic) + } + + account.shouldBeServiceTaskMaster.set(.single(.now)) + account.resetStateManagement() + + let completion: Signal = account.stateManager.pollStateUpdateCompletion() + |> map { _ in + return Void() + } + + return (completion |> timeout(4.0, queue: Queue.mainQueue(), alternate: .single(Void()))) + |> introduceError(IntentHandlingError.self) + |> take(1) + |> mapToSignal { _ -> Signal<[INMessage], IntentHandlingError> in + return unreadMessages(account: account) + |> introduceError(IntentHandlingError.self) + |> afterDisposed { + account.shouldBeServiceTaskMaster.set(.single(.never)) + } } } - } - |> deliverOnMainQueue).start(next: { messages in - let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchForMessagesIntent.self)) - let response = INSearchForMessagesIntentResponse(code: .success, userActivity: userActivity) - response.messages = messages - completion(response) - }, error: { _ in - let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchForMessagesIntent.self)) - let response = INSearchForMessagesIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity) - completion(response) - })) + |> deliverOnMainQueue).start(next: { messages in + let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchForMessagesIntent.self)) + let response = INSearchForMessagesIntentResponse(code: .success, userActivity: userActivity) + response.messages = messages + completion(response) + }, error: { _ in + let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchForMessagesIntent.self)) + let response = INSearchForMessagesIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity) + completion(response) + })) } // MARK: - INSetMessageAttributeIntentHandling @@ -408,101 +452,107 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag func handle(intent: INSetMessageAttributeIntent, completion: @escaping (INSetMessageAttributeIntentResponse) -> Void) { self.actionDisposable.set((self.accountPromise.get() - |> take(1) - |> mapError { _ -> IntentHandlingError in - return .generic - } - |> mapToSignal { account -> Signal in - guard let account = account else { - return .fail(.generic) + |> take(1) + |> mapError { _ -> IntentHandlingError in + return .generic } - - var signals: [Signal] = [] - var maxMessageIdsToApply: [PeerId: MessageId] = [:] - if let identifiers = intent.identifiers { - for identifier in identifiers { - let components = identifier.components(separatedBy: "_") - if let first = components.first, let peerId = Int64(first), let namespace = Int32(components[1]), let id = Int32(components[2]) { - let peerId = PeerId(peerId) - let messageId = MessageId(peerId: peerId, namespace: namespace, id: id) - if let currentMessageId = maxMessageIdsToApply[peerId] { - if currentMessageId < messageId { + |> mapToSignal { account -> Signal in + guard let account = account else { + return .fail(.generic) + } + + var signals: [Signal] = [] + var maxMessageIdsToApply: [PeerId: MessageId] = [:] + if let identifiers = intent.identifiers { + for identifier in identifiers { + let components = identifier.components(separatedBy: "_") + if let first = components.first, let peerId = Int64(first), let namespace = Int32(components[1]), let id = Int32(components[2]) { + let peerId = PeerId(peerId) + let messageId = MessageId(peerId: peerId, namespace: namespace, id: id) + if let currentMessageId = maxMessageIdsToApply[peerId] { + if currentMessageId < messageId { + maxMessageIdsToApply[peerId] = messageId + } + } else { maxMessageIdsToApply[peerId] = messageId } - } else { - maxMessageIdsToApply[peerId] = messageId } } } - } - - for (_, messageId) in maxMessageIdsToApply { - signals.append(applyMaxReadIndexInteractively(postbox: account.postbox, stateManager: account.stateManager, index: MessageIndex(id: messageId, timestamp: 0)) - |> introduceError(IntentHandlingError.self)) - } - - if signals.isEmpty { - return .complete() - } else { - account.shouldBeServiceTaskMaster.set(.single(.now)) - return combineLatest(signals) - |> mapToSignal { _ -> Signal in + + for (_, messageId) in maxMessageIdsToApply { + signals.append(applyMaxReadIndexInteractively(postbox: account.postbox, stateManager: account.stateManager, index: MessageIndex(id: messageId, timestamp: 0)) + |> introduceError(IntentHandlingError.self)) + } + + if signals.isEmpty { return .complete() - } - |> afterDisposed { - account.shouldBeServiceTaskMaster.set(.single(.never)) + } else { + account.shouldBeServiceTaskMaster.set(.single(.now)) + return combineLatest(signals) + |> mapToSignal { _ -> Signal in + return .complete() + } + |> afterDisposed { + account.shouldBeServiceTaskMaster.set(.single(.never)) + } } } - } - |> deliverOnMainQueue).start(error: { _ in - let userActivity = NSUserActivity(activityType: NSStringFromClass(INSetMessageAttributeIntent.self)) - let response = INSetMessageAttributeIntentResponse(code: .failure, userActivity: userActivity) - completion(response) - }, completed: { - let userActivity = NSUserActivity(activityType: NSStringFromClass(INSetMessageAttributeIntent.self)) - let response = INSetMessageAttributeIntentResponse(code: .success, userActivity: userActivity) - completion(response) - })) + |> deliverOnMainQueue).start(error: { _ in + let userActivity = NSUserActivity(activityType: NSStringFromClass(INSetMessageAttributeIntent.self)) + let response = INSetMessageAttributeIntentResponse(code: .failure, userActivity: userActivity) + completion(response) + }, completed: { + let userActivity = NSUserActivity(activityType: NSStringFromClass(INSetMessageAttributeIntent.self)) + let response = INSetMessageAttributeIntentResponse(code: .success, userActivity: userActivity) + completion(response) + })) } // MARK: - INStartAudioCallIntentHandling func resolveContacts(for intent: INStartAudioCallIntent, with completion: @escaping ([INPersonResolutionResult]) -> Void) { - self.resolve(persons: intent.contacts, with: completion) + guard CNContactStore.authorizationStatus(for: .contacts) == .authorized else { + completion([INPersonResolutionResult.notRequired()]) + return + } + self.resolve(persons: intent.contacts, with: { result in + completion(result.map { $0.personResolutionResult }) + }) } func handle(intent: INStartAudioCallIntent, completion: @escaping (INStartAudioCallIntentResponse) -> Void) { self.actionDisposable.set((self.accountPromise.get() - |> take(1) - |> mapError { _ -> IntentHandlingError in - return .generic - } - |> mapToSignal { account -> Signal in - guard let contact = intent.contacts?.first, let customIdentifier = contact.customIdentifier, customIdentifier.hasPrefix("tg") else { - return .fail(.generic) + |> take(1) + |> mapError { _ -> IntentHandlingError in + return .generic } - - guard let peerIdValue = Int64(String(customIdentifier[customIdentifier.index(customIdentifier.startIndex, offsetBy: 2)...])) else { - return .fail(.generic) + |> mapToSignal { account -> Signal in + guard let contact = intent.contacts?.first, let customIdentifier = contact.customIdentifier, customIdentifier.hasPrefix("tg") else { + return .fail(.generic) + } + + guard let peerIdValue = Int64(String(customIdentifier[customIdentifier.index(customIdentifier.startIndex, offsetBy: 2)...])) else { + return .fail(.generic) + } + + let peerId = PeerId(peerIdValue) + if peerId.namespace != Namespaces.Peer.CloudUser { + return .fail(.generic) + } + + return .single(peerId) } - - let peerId = PeerId(peerIdValue) - if peerId.namespace != Namespaces.Peer.CloudUser { - return .fail(.generic) - } - - return .single(peerId) - } - |> deliverOnMainQueue).start(next: { peerId in - let userActivity = NSUserActivity(activityType: NSStringFromClass(INStartAudioCallIntent.self)) - userActivity.userInfo = ["handle": "TGCA\(peerId.toInt64())"] - let response = INStartAudioCallIntentResponse(code: .continueInApp, userActivity: userActivity) - completion(response) - }, error: { _ in - let userActivity = NSUserActivity(activityType: NSStringFromClass(INStartAudioCallIntent.self)) - let response = INStartAudioCallIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity) - completion(response) - })) + |> deliverOnMainQueue).start(next: { peerId in + let userActivity = NSUserActivity(activityType: NSStringFromClass(INStartAudioCallIntent.self)) + userActivity.userInfo = ["handle": "TGCA\(peerId.toInt64())"] + let response = INStartAudioCallIntentResponse(code: .continueInApp, userActivity: userActivity) + completion(response) + }, error: { _ in + let userActivity = NSUserActivity(activityType: NSStringFromClass(INStartAudioCallIntent.self)) + let response = INStartAudioCallIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity) + completion(response) + })) } // MARK: - INSearchCallHistoryIntentHandling @@ -518,34 +568,34 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag func handle(intent: INSearchCallHistoryIntent, completion: @escaping (INSearchCallHistoryIntentResponse) -> Void) { self.actionDisposable.set((self.accountPromise.get() - |> take(1) - |> introduceError(IntentHandlingError.self) - |> mapToSignal { account -> Signal<[CallRecord], IntentHandlingError> in - guard let account = account else { - return .fail(.generic) - } - - account.shouldBeServiceTaskMaster.set(.single(.now)) - return missedCalls(account: account) + |> take(1) |> introduceError(IntentHandlingError.self) - |> afterDisposed { - account.shouldBeServiceTaskMaster.set(.single(.never)) + |> mapToSignal { account -> Signal<[CallRecord], IntentHandlingError> in + guard let account = account else { + return .fail(.generic) + } + + account.shouldBeServiceTaskMaster.set(.single(.now)) + return missedCalls(account: account) + |> introduceError(IntentHandlingError.self) + |> afterDisposed { + account.shouldBeServiceTaskMaster.set(.single(.never)) + } } - } - |> deliverOnMainQueue).start(next: { calls in - let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchCallHistoryIntent.self)) - let response: INSearchCallHistoryIntentResponse - if #available(iOSApplicationExtension 11.0, *) { - response = INSearchCallHistoryIntentResponse(code: .success, userActivity: userActivity) - response.callRecords = calls.map { $0.intentCall } - } else { - response = INSearchCallHistoryIntentResponse(code: .continueInApp, userActivity: userActivity) - } - completion(response) - }, error: { _ in - let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchCallHistoryIntent.self)) - let response = INSearchCallHistoryIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity) - completion(response) - })) + |> deliverOnMainQueue).start(next: { calls in + let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchCallHistoryIntent.self)) + let response: INSearchCallHistoryIntentResponse + if #available(iOSApplicationExtension 11.0, *) { + response = INSearchCallHistoryIntentResponse(code: .success, userActivity: userActivity) + response.callRecords = calls.map { $0.intentCall } + } else { + response = INSearchCallHistoryIntentResponse(code: .continueInApp, userActivity: userActivity) + } + completion(response) + }, error: { _ in + let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchCallHistoryIntent.self)) + let response = INSearchCallHistoryIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity) + completion(response) + })) } } diff --git a/submodules/Display/Display/AlertControllerNode.swift b/submodules/Display/Display/AlertControllerNode.swift index 82d0b9e84e..e532753a68 100644 --- a/submodules/Display/Display/AlertControllerNode.swift +++ b/submodules/Display/Display/AlertControllerNode.swift @@ -95,14 +95,17 @@ final class AlertControllerNode: ASDisplayNode { self.bottomDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) self.leftDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) self.rightDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) - self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion: { [weak self] _ in - self?.centerDimView.backgroundColor = nil - self?.centerDimView.image = generateStretchableFilledCircleImage(radius: 16.0, color: nil, backgroundColor: UIColor(white: 0.0, alpha: 0.5)) + self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion: { [weak self] finished in + if finished { + self?.centerDimView.backgroundColor = nil + self?.centerDimView.image = generateStretchableFilledCircleImage(radius: 16.0, color: nil, backgroundColor: UIColor(white: 0.0, alpha: 0.5)) + } }) self.containerNode.layer.animateSpring(from: 0.8 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5, initialVelocity: 0.0, removeOnCompletion: true, additive: false, completion: nil) } func animateOut(completion: @escaping () -> Void) { + self.containerNode.layer.removeAllAnimations() self.centerDimView.backgroundColor = UIColor(white: 0.0, alpha: 0.5) self.centerDimView.image = nil diff --git a/submodules/TelegramUI/TelegramUI/AnimatedStickerNode.swift b/submodules/TelegramUI/TelegramUI/AnimatedStickerNode.swift index db46c3acbc..6028a4679d 100644 --- a/submodules/TelegramUI/TelegramUI/AnimatedStickerNode.swift +++ b/submodules/TelegramUI/TelegramUI/AnimatedStickerNode.swift @@ -83,20 +83,23 @@ private final class AnimatedStickerFrame { let width: Int let height: Int let bytesPerRow: Int + let index: Int let isLastFrame: Bool - init(data: Data, type: AnimationRendererFrameType, width: Int, height: Int, bytesPerRow: Int, isLastFrame: Bool) { + init(data: Data, type: AnimationRendererFrameType, width: Int, height: Int, bytesPerRow: Int, index: Int, isLastFrame: Bool) { self.data = data self.type = type self.width = width self.height = height self.bytesPerRow = bytesPerRow + self.index = index self.isLastFrame = isLastFrame } } private protocol AnimatedStickerFrameSource: class { var frameRate: Int { get } + var frameCount: Int { get } func takeFrame() -> AnimatedStickerFrame } @@ -110,6 +113,8 @@ private final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource let bytesPerRow: Int let height: Int let frameRate: Int + let frameCount: Int + private var frameIndex: Int private let initialOffset: Int private var offset: Int var decodeBuffer: Data @@ -119,27 +124,32 @@ private final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource self.queue = queue self.data = data self.scratchBuffer = Data(count: compression_decode_scratch_buffer_size(COMPRESSION_LZFSE)) - + var offset = 0 var width = 0 var height = 0 var bytesPerRow = 0 var frameRate = 0 + var frameCount = 0 if !self.data.withUnsafeBytes({ (bytes: UnsafePointer) -> Bool in var frameRateValue: Int32 = 0 - memcpy(&frameRateValue, bytes.advanced(by: offset), 4) - frameRate = Int(frameRateValue) - offset += 4 + var frameCountValue: Int32 = 0 var widthValue: Int32 = 0 var heightValue: Int32 = 0 var bytesPerRowValue: Int32 = 0 + memcpy(&frameRateValue, bytes.advanced(by: offset), 4) + offset += 4 + memcpy(&frameCountValue, bytes.advanced(by: offset), 4) + offset += 4 memcpy(&widthValue, bytes.advanced(by: offset), 4) offset += 4 memcpy(&heightValue, bytes.advanced(by: offset), 4) offset += 4 memcpy(&bytesPerRowValue, bytes.advanced(by: offset), 4) offset += 4 + frameRate = Int(frameRateValue) + frameCount = Int(frameCountValue) width = Int(widthValue) height = Int(heightValue) bytesPerRow = Int(bytesPerRowValue) @@ -154,7 +164,9 @@ private final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource self.width = width self.height = height self.frameRate = frameRate + self.frameCount = frameCount + self.frameIndex = 0 self.initialOffset = offset self.offset = offset @@ -178,6 +190,8 @@ private final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource let decodeBufferLength = self.decodeBuffer.count let frameBufferLength = self.frameBuffer.count + let frameIndex = self.frameIndex + self.data.withUnsafeBytes { (bytes: UnsafePointer) -> Void in var frameLength: Int32 = 0 memcpy(&frameLength, bytes.advanced(by: self.offset), 4) @@ -208,9 +222,11 @@ private final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource } } + self.frameIndex += 1 self.offset += Int(frameLength) if self.offset == dataLength { isLastFrame = true + self.frameIndex = 0 self.offset = self.initialOffset self.frameBuffer.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer) -> Void in memset(bytes, 0, frameBufferLength) @@ -218,7 +234,7 @@ private final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource } } - return AnimatedStickerFrame(data: frameData!, type: .yuva, width: self.width, height: self.height, bytesPerRow: self.bytesPerRow, isLastFrame: isLastFrame) + return AnimatedStickerFrame(data: frameData!, type: .yuva, width: self.width, height: self.height, bytesPerRow: self.bytesPerRow, index: frameIndex, isLastFrame: isLastFrame) } } @@ -228,7 +244,7 @@ private final class AnimatedStickerDirectFrameSource: AnimatedStickerFrameSource private let width: Int private let height: Int private let bytesPerRow: Int - private let frameCount: Int + let frameCount: Int let frameRate: Int private var currentFrame: Int private let animation: LottieInstance @@ -263,7 +279,7 @@ private final class AnimatedStickerDirectFrameSource: AnimatedStickerFrameSource memset(bytes, 0, self.bytesPerRow * self.height) self.animation.renderFrame(with: Int32(frameIndex), into: bytes, width: Int32(self.width), height: Int32(self.height), bytesPerRow: Int32(self.bytesPerRow)) } - return AnimatedStickerFrame(data: frameData, type: .argb, width: self.width, height: self.height, bytesPerRow: self.bytesPerRow, isLastFrame: frameIndex == self.frameCount) + return AnimatedStickerFrame(data: frameData, type: .argb, width: self.width, height: self.height, bytesPerRow: self.bytesPerRow, index: frameIndex, isLastFrame: frameIndex == self.frameCount) } } @@ -298,6 +314,18 @@ private final class AnimatedStickerFrameQueue { } } +public struct AnimatedStickerStatus: Equatable { + public let playing: Bool + public let duration: Double + public let timestamp: Double + + public init(playing: Bool, duration: Double, timestamp: Double) { + self.playing = playing + self.duration = duration + self.timestamp = timestamp + } +} + final class AnimatedStickerNode: ASDisplayNode { private let queue: Queue private var account: Account? @@ -319,6 +347,11 @@ final class AnimatedStickerNode: ASDisplayNode { private var isPlaying: Bool = false private var playbackMode: AnimatedStickerPlaybackMode = .loop + private let playbackStatus = Promise() + public var status: Signal { + return self.playbackStatus.get() + } + var visibility = false { didSet { if self.visibility != oldValue { @@ -368,7 +401,7 @@ final class AnimatedStickerNode: ASDisplayNode { self.renderer?.frame = CGRect(origin: CGPoint(), size: self.bounds.size) self.addSubnode(self.renderer!) } - + func setup(account: Account, resource: MediaResource, width: Int, height: Int, playbackMode: AnimatedStickerPlaybackMode = .loop, mode: AnimatedStickerMode) { if width < 2 || height < 2 { return @@ -377,27 +410,27 @@ final class AnimatedStickerNode: ASDisplayNode { switch mode { case .direct: self.disposable.set((account.postbox.mediaBox.resourceData(resource) - |> deliverOnMainQueue).start(next: { [weak self] data in - guard let strongSelf = self, data.complete else { - return - } - if let directData = try? Data(contentsOf: URL(fileURLWithPath: data.path), options: [.mappedRead]) { - strongSelf.directData = Tuple(directData, data.path, width, height) - } - if strongSelf.isPlaying { - strongSelf.play() - } - })) - case .cached: - self.disposable.set((chatMessageAnimationData(postbox: account.postbox, resource: resource, width: width, height: height, synchronousLoad: false) - |> deliverOnMainQueue).start(next: { [weak self] data in - if let strongSelf = self, data.complete { - strongSelf.cachedData = try? Data(contentsOf: URL(fileURLWithPath: data.path), options: [.mappedRead]) + |> deliverOnMainQueue).start(next: { [weak self] data in + guard let strongSelf = self, data.complete else { + return + } + if let directData = try? Data(contentsOf: URL(fileURLWithPath: data.path), options: [.mappedRead]) { + strongSelf.directData = Tuple(directData, data.path, width, height) + } if strongSelf.isPlaying { strongSelf.play() } - } - })) + })) + case .cached: + self.disposable.set((chatMessageAnimationData(postbox: account.postbox, resource: resource, width: width, height: height, synchronousLoad: false) + |> deliverOnMainQueue).start(next: { [weak self] data in + if let strongSelf = self, data.complete { + strongSelf.cachedData = try? Data(contentsOf: URL(fileURLWithPath: data.path), options: [.mappedRead]) + if strongSelf.isPlaying { + strongSelf.play() + } + } + })) } } @@ -440,7 +473,10 @@ final class AnimatedStickerNode: ASDisplayNode { }) timerHolder.swap(nil)?.invalidate() - let timer = SwiftSignalKit.Timer(timeout: 1.0 / Double(frameSource.frameRate), repeat: true, completion: { + let duration: Double = frameSource.frameRate > 0 ? Double(frameSource.frameCount) / Double(frameSource.frameRate) : 0 + let frameRate = frameSource.frameRate + + let timer = SwiftSignalKit.Timer(timeout: 1.0 / Double(frameRate), repeat: true, completion: { let maybeFrame = frameQueue.syncWith { frameQueue in return frameQueue.take() } @@ -449,6 +485,7 @@ final class AnimatedStickerNode: ASDisplayNode { guard let strongSelf = self else { return } + strongSelf.renderer?.render(queue: strongSelf.queue, width: frame.width, height: frame.height, bytesPerRow: frame.bytesPerRow, data: frame.data, type: frame.type, completion: { guard let strongSelf = self else { return @@ -458,10 +495,14 @@ final class AnimatedStickerNode: ASDisplayNode { strongSelf.started() } }) + if case .once = strongSelf.playbackMode, frame.isLastFrame { strongSelf.stop() strongSelf.isPlaying = false } + + let timestamp: Double = frameRate > 0 ? Double(frame.index) / Double(frameRate) : 0 + strongSelf.playbackStatus.set(.single(AnimatedStickerStatus(playing: strongSelf.isPlaying, duration: duration, timestamp: timestamp))) } } frameQueue.with { frameQueue in @@ -502,6 +543,8 @@ final class AnimatedStickerNode: ASDisplayNode { }) timerHolder.swap(nil)?.invalidate() + let duration: Double = frameSource.frameRate > 0 ? Double(frameSource.frameCount) / Double(frameSource.frameRate) : 0 + let maybeFrame = frameQueue.syncWith { frameQueue in return frameQueue.take() } @@ -510,6 +553,7 @@ final class AnimatedStickerNode: ASDisplayNode { guard let strongSelf = self else { return } + strongSelf.renderer?.render(queue: strongSelf.queue, width: frame.width, height: frame.height, bytesPerRow: frame.bytesPerRow, data: frame.data, type: frame.type, completion: { guard let strongSelf = self else { return @@ -519,10 +563,8 @@ final class AnimatedStickerNode: ASDisplayNode { strongSelf.started() } }) - if case .once = strongSelf.playbackMode, frame.isLastFrame { - strongSelf.stop() - strongSelf.isPlaying = false - } + + strongSelf.playbackStatus.set(.single(AnimatedStickerStatus(playing: false, duration: duration, timestamp: 0.0))) } } frameQueue.with { frameQueue in diff --git a/submodules/TelegramUI/TelegramUI/AnimatedStickerUtils.swift b/submodules/TelegramUI/TelegramUI/AnimatedStickerUtils.swift index bb317b7bf1..1d15924e11 100644 --- a/submodules/TelegramUI/TelegramUI/AnimatedStickerUtils.swift +++ b/submodules/TelegramUI/TelegramUI/AnimatedStickerUtils.swift @@ -196,7 +196,9 @@ func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: CGSize, var currentFrame: Int32 = 0 var fps: Int32 = player.frameRate + var frameCount: Int32 = player.frameCount let _ = fileContext.write(&fps, count: 4) + let _ = fileContext.write(&frameCount, count: 4) var widthValue: Int32 = Int32(size.width) var heightValue: Int32 = Int32(size.height) var bytesPerRowValue: Int32 = Int32(bytesPerRow) diff --git a/submodules/TelegramUI/TelegramUI/AppDelegate.swift b/submodules/TelegramUI/TelegramUI/AppDelegate.swift index 799a63c299..c11595200b 100644 --- a/submodules/TelegramUI/TelegramUI/AppDelegate.swift +++ b/submodules/TelegramUI/TelegramUI/AppDelegate.swift @@ -1119,13 +1119,13 @@ final class SharedApplicationContext { Logger.shared.log("App \(self.episodeId)", "isActive = \(value)") }) - /*if let url = launchOptions?[.url] { + if let url = launchOptions?[.url] { if let url = url as? URL, url.scheme == "tg" { self.openUrlWhenReady(url: url.absoluteString) } else if let url = url as? String, url.lowercased().hasPrefix("tg://") { self.openUrlWhenReady(url: url) } - }*/ + } if application.applicationState == .active { self.isInForegroundValue = true @@ -1843,7 +1843,7 @@ final class SharedApplicationContext { notificationCenter.getNotificationSettings(completionHandler: { settings in switch (settings.authorizationStatus, authorize) { case (.authorized, _), (.notDetermined, true): - notificationCenter.requestAuthorization(options: [.badge, .sound, .alert, .carPlay], completionHandler: { result, _ in + notificationCenter.requestAuthorization(options: [.badge, .sound, .alert], completionHandler: { result, _ in completion(result) if result { Queue.mainQueue().async { @@ -1865,7 +1865,7 @@ final class SharedApplicationContext { } var carPlayOptions = options - carPlayOptions.insert(.allowInCarPlay) + //carPlayOptions.insert(.allowInCarPlay) unknownMessageCategory = UNNotificationCategory(identifier: "unknown", actions: [], intentIdentifiers: [], hiddenPreviewsBodyPlaceholder: hiddenContentString, options: options) replyMessageCategory = UNNotificationCategory(identifier: "withReply", actions: [reply], intentIdentifiers: [INSearchForMessagesIntentIdentifier], hiddenPreviewsBodyPlaceholder: hiddenContentString, options: carPlayOptions) @@ -1876,7 +1876,7 @@ final class SharedApplicationContext { muteMessageCategory = UNNotificationCategory(identifier: "withMute", actions: [], intentIdentifiers: [], hiddenPreviewsBodyPlaceholder: hiddenContentString, options: options) muteMediaMessageCategory = UNNotificationCategory(identifier: "withMuteMedia", actions: [], intentIdentifiers: [], hiddenPreviewsBodyPlaceholder: hiddenContentString, options: options) } else { - let carPlayOptions: UNNotificationCategoryOptions = [.allowInCarPlay] + let carPlayOptions: UNNotificationCategoryOptions = [] //[.allowInCarPlay] unknownMessageCategory = UNNotificationCategory(identifier: "unknown", actions: [], intentIdentifiers: [], options: []) replyMessageCategory = UNNotificationCategory(identifier: "withReply", actions: [reply], intentIdentifiers: [INSearchForMessagesIntentIdentifier], options: carPlayOptions) diff --git a/submodules/TelegramUI/TelegramUI/AuthorizationSequenceAwaitingAccountResetControllerNode.swift b/submodules/TelegramUI/TelegramUI/AuthorizationSequenceAwaitingAccountResetControllerNode.swift index 40c388239e..6d4139f0d0 100644 --- a/submodules/TelegramUI/TelegramUI/AuthorizationSequenceAwaitingAccountResetControllerNode.swift +++ b/submodules/TelegramUI/TelegramUI/AuthorizationSequenceAwaitingAccountResetControllerNode.swift @@ -15,7 +15,7 @@ private func timerValueString(days: Int32, hours: Int32, minutes: Int32, color: var hoursString = "" if hours > 0 || days > 0 { - daysString = strings.MessageTimer_Hours(hours) + " " + hoursString = strings.MessageTimer_Hours(hours) + " " } let minutesString = strings.MessageTimer_Minutes(minutes) diff --git a/submodules/TelegramUI/TelegramUI/Bridge Audio/TGBridgeAudioEncoder.m b/submodules/TelegramUI/TelegramUI/Bridge Audio/TGBridgeAudioEncoder.m index cfec210545..af5ba77bf5 100644 --- a/submodules/TelegramUI/TelegramUI/Bridge Audio/TGBridgeAudioEncoder.m +++ b/submodules/TelegramUI/TelegramUI/Bridge Audio/TGBridgeAudioEncoder.m @@ -81,13 +81,13 @@ typedef enum { NSDictionary *outputSettings = @ { - AVFormatIDKey: @(kAudioFormatLinearPCM), - AVSampleRateKey: @(TGBridgeAudioEncoderSampleRate), - AVNumberOfChannelsKey: @1, - AVLinearPCMBitDepthKey: @16, - AVLinearPCMIsFloatKey: @false, - AVLinearPCMIsBigEndianKey: @false, - AVLinearPCMIsNonInterleaved: @false + AVFormatIDKey: @(kAudioFormatLinearPCM), + AVSampleRateKey: @(TGBridgeAudioEncoderSampleRate), + AVNumberOfChannelsKey: @1, + AVLinearPCMBitDepthKey: @16, + AVLinearPCMIsFloatKey: @false, + AVLinearPCMIsBigEndianKey: @false, + AVLinearPCMIsNonInterleaved: @false }; _readerOutput = [AVAssetReaderAudioMixOutput assetReaderAudioMixOutputWithAudioTracks:asset.tracks audioSettings:outputSettings]; @@ -114,9 +114,9 @@ typedef enum { static ATQueue *queue = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^ - { - queue = [[ATQueue alloc] initWithName:@"org.telegram.opusAudioEncoderQueue"]; - }); + { + queue = [[ATQueue alloc] initWithName:@"org.telegram.opusAudioEncoderQueue"]; + }); return queue; } @@ -124,76 +124,76 @@ typedef enum { static const int encoderPacketSizeInBytes = TGBridgeAudioEncoderSampleRate / 1000 * 60 * 2; - (void)startWithCompletion:(void (^)(NSString *, int32_t))completion -{ +{ [[TGBridgeAudioEncoder processingQueue] dispatch:^ - { - _oggWriter = [[TGOggOpusWriter alloc] init]; - if (![_oggWriter beginWithDataItem:_tempFileItem]) - { - [self cleanup]; - return; - } - - [_assetReader startReading]; - - while (_assetReader.status != AVAssetReaderStatusCompleted) - { - if (_assetReader.status == AVAssetReaderStatusReading) - { - CMSampleBufferRef nextBuffer = [_readerOutput copyNextSampleBuffer]; - if (nextBuffer) - { - AudioBufferList abl; - CMBlockBufferRef blockBuffer; - CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(nextBuffer, NULL, &abl, sizeof(abl), NULL, NULL, kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment, &blockBuffer); - - [[TGBridgeAudioEncoder processingQueue] dispatch:^ - { - [self _processBuffer:&abl.mBuffers[0]]; - - CFRelease(nextBuffer); - CFRelease(blockBuffer); - }]; - } - else - { - [[TGBridgeAudioEncoder processingQueue] dispatch:^ - { - if (_tailLength > 0) { - [_oggWriter writeFrame:(uint8_t *)_audioBuffer.bytes frameByteCount:(NSUInteger)_tailLength]; - } - }]; - break; - } - } - } - - [[TGBridgeAudioEncoder processingQueue] dispatch:^ - { - TGFileDataItem *dataItemResult = nil; - NSTimeInterval durationResult = 0.0; - - NSUInteger totalBytes = 0; - - if (_assetReader.status == AVAssetReaderStatusCompleted) - { - NSLog(@"finished"); - if (_oggWriter != nil && [_oggWriter writeFrame:NULL frameByteCount:0]) - { - dataItemResult = _tempFileItem; - durationResult = [_oggWriter encodedDuration]; - totalBytes = [_oggWriter encodedBytes]; - } - - [self cleanup]; - } - - //TGLog(@"[TGBridgeAudioEncoder#%x convert time: %f ms]", self, (CFAbsoluteTimeGetCurrent() - startTime) * 1000.0); - - if (completion != nil) - completion(dataItemResult.path, (int32_t)durationResult); - }]; - }]; + { + _oggWriter = [[TGOggOpusWriter alloc] init]; + if (![_oggWriter beginWithDataItem:_tempFileItem]) + { + [self cleanup]; + return; + } + + [_assetReader startReading]; + + while (_assetReader.status != AVAssetReaderStatusCompleted) + { + if (_assetReader.status == AVAssetReaderStatusReading) + { + CMSampleBufferRef nextBuffer = [_readerOutput copyNextSampleBuffer]; + if (nextBuffer) + { + AudioBufferList abl; + CMBlockBufferRef blockBuffer; + CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(nextBuffer, NULL, &abl, sizeof(abl), NULL, NULL, kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment, &blockBuffer); + + [[TGBridgeAudioEncoder processingQueue] dispatch:^ + { + [self _processBuffer:&abl.mBuffers[0]]; + + CFRelease(nextBuffer); + CFRelease(blockBuffer); + }]; + } + else + { + [[TGBridgeAudioEncoder processingQueue] dispatch:^ + { + if (_tailLength > 0) { + [_oggWriter writeFrame:(uint8_t *)_audioBuffer.bytes frameByteCount:(NSUInteger)_tailLength]; + } + }]; + break; + } + } + } + + [[TGBridgeAudioEncoder processingQueue] dispatch:^ + { + TGFileDataItem *dataItemResult = nil; + NSTimeInterval durationResult = 0.0; + + NSUInteger totalBytes = 0; + + if (_assetReader.status == AVAssetReaderStatusCompleted) + { + NSLog(@"finished"); + if (_oggWriter != nil) + { + dataItemResult = _tempFileItem; + durationResult = [_oggWriter encodedDuration]; + totalBytes = [_oggWriter encodedBytes]; + } + + [self cleanup]; + } + + //TGLog(@"[TGBridgeAudioEncoder#%x convert time: %f ms]", self, (CFAbsoluteTimeGetCurrent() - startTime) * 1000.0); + + if (completion != nil) + completion(dataItemResult.path, (int32_t)durationResult); + }]; + }]; } - (void)_processBuffer:(AudioBuffer const *)buffer @@ -270,7 +270,7 @@ static const int encoderPacketSizeInBytes = TGBridgeAudioEncoderSampleRate / 100 @implementation TGFileDataItem { - ATQueue *_queue; + ATQueue *_queue; } - (void)_commonInit @@ -306,11 +306,11 @@ static const int encoderPacketSizeInBytes = TGBridgeAudioEncoderSampleRate / 100 [_queue dispatch:^ - { - _fileName = filePath; - _length = [[[NSFileManager defaultManager] attributesOfItemAtPath:_fileName error:nil][NSFileSize] unsignedIntegerValue]; - _fileExists = [[NSFileManager defaultManager] fileExistsAtPath:_fileName]; - }]; + { + _fileName = filePath; + _length = [[[NSFileManager defaultManager] attributesOfItemAtPath:_fileName error:nil][NSFileSize] unsignedIntegerValue]; + _fileExists = [[NSFileManager defaultManager] fileExistsAtPath:_fileName]; + }]; } return self; } @@ -322,38 +322,38 @@ static const int encoderPacketSizeInBytes = TGBridgeAudioEncoderSampleRate / 100 - (void)moveToPath:(NSString *)path { [_queue dispatch:^ - { - [[NSFileManager defaultManager] moveItemAtPath:_fileName toPath:path error:nil]; - _fileName = path; - }]; + { + [[NSFileManager defaultManager] moveItemAtPath:_fileName toPath:path error:nil]; + _fileName = path; + }]; } - (void)remove { [_queue dispatch:^ - { - [[NSFileManager defaultManager] removeItemAtPath:_fileName error:nil]; - }]; + { + [[NSFileManager defaultManager] removeItemAtPath:_fileName error:nil]; + }]; } - (void)appendData:(NSData *)data { [_queue dispatch:^ - { - if (!_fileExists) - { - [[NSFileManager defaultManager] createFileAtPath:_fileName contents:nil attributes:nil]; - _fileExists = true; - } - NSFileHandle *file = [NSFileHandle fileHandleForUpdatingAtPath:_fileName]; - [file seekToEndOfFile]; - [file writeData:data]; - [file synchronizeFile]; - [file closeFile]; - _length += data.length; - - [_data appendData:data]; - }]; + { + if (!_fileExists) + { + [[NSFileManager defaultManager] createFileAtPath:_fileName contents:nil attributes:nil]; + _fileExists = true; + } + NSFileHandle *file = [NSFileHandle fileHandleForUpdatingAtPath:_fileName]; + [file seekToEndOfFile]; + [file writeData:data]; + [file synchronizeFile]; + [file closeFile]; + _length += data.length; + + [_data appendData:data]; + }]; } - (NSData *)readDataAtOffset:(NSUInteger)offset length:(NSUInteger)length @@ -361,14 +361,14 @@ static const int encoderPacketSizeInBytes = TGBridgeAudioEncoderSampleRate / 100 __block NSData *data = nil; [_queue dispatch:^ - { - NSFileHandle *file = [NSFileHandle fileHandleForUpdatingAtPath:_fileName]; - [file seekToFileOffset:(unsigned long long)offset]; - data = [file readDataOfLength:length]; - if (data.length != length) - //TGLog(@"Read data length mismatch"); - [file closeFile]; - } synchronous:true]; + { + NSFileHandle *file = [NSFileHandle fileHandleForUpdatingAtPath:_fileName]; + [file seekToFileOffset:(unsigned long long)offset]; + data = [file readDataOfLength:length]; + if (data.length != length) + //TGLog(@"Read data length mismatch"); + [file closeFile]; + } synchronous:true]; return data; } @@ -377,9 +377,9 @@ static const int encoderPacketSizeInBytes = TGBridgeAudioEncoderSampleRate / 100 { __block NSUInteger result = 0; [_queue dispatch:^ - { - result = _length; - } synchronous:true]; + { + result = _length; + } synchronous:true]; return result; } @@ -420,11 +420,11 @@ static const int encoderPacketSizeInBytes = TGBridgeAudioEncoderSampleRate / 100 static ATQueue *queue = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^ - { - queue = [[ATQueue alloc] init]; - queue->_nativeQueue = dispatch_get_main_queue(); - queue->_isMainQueue = true; - }); + { + queue = [[ATQueue alloc] init]; + queue->_nativeQueue = dispatch_get_main_queue(); + queue->_isMainQueue = true; + }); return queue; } @@ -434,9 +434,9 @@ static const int encoderPacketSizeInBytes = TGBridgeAudioEncoderSampleRate / 100 static ATQueue *queue = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^ - { - queue = [[ATQueue alloc] initWithNativeQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; - }); + { + queue = [[ATQueue alloc] initWithNativeQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; + }); return queue; } @@ -446,9 +446,9 @@ static const int encoderPacketSizeInBytes = TGBridgeAudioEncoderSampleRate / 100 static ATQueue *queue = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^ - { - queue = [[ATQueue alloc] initWithNativeQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)]; - }); + { + queue = [[ATQueue alloc] initWithNativeQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)]; + }); return queue; } diff --git a/submodules/TelegramUI/TelegramUI/CachedResourceRepresentations.swift b/submodules/TelegramUI/TelegramUI/CachedResourceRepresentations.swift index e962023208..fb7229d802 100644 --- a/submodules/TelegramUI/TelegramUI/CachedResourceRepresentations.swift +++ b/submodules/TelegramUI/TelegramUI/CachedResourceRepresentations.swift @@ -271,7 +271,7 @@ final class CachedAnimatedStickerRepresentation: CachedMediaResourceRepresentati let height: Int32 var uniqueId: String { - return "animated-sticker-\(self.width)x\(self.height)-v7" + return "animated-sticker-\(self.width)x\(self.height)-v8" } init(width: Int32, height: Int32) { diff --git a/submodules/TelegramUI/TelegramUI/ChatListController.swift b/submodules/TelegramUI/TelegramUI/ChatListController.swift index d8fc00a1a5..3d4e21ac60 100644 --- a/submodules/TelegramUI/TelegramUI/ChatListController.swift +++ b/submodules/TelegramUI/TelegramUI/ChatListController.swift @@ -845,6 +845,14 @@ public class ChatListController: TelegramController, UIViewControllerPreviewingD } } + self.chatListDisplayNode.isEmptyUpdated = { [weak self] isEmpty in + if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode, let validLayout = strongSelf.validLayout { + if isEmpty { + searchContentNode.updateListVisibleContentOffset(.known(0.0)) + } + } + } + self.chatListDisplayNode.toolbarActionSelected = { [weak self] action in self?.toolbarActionSelected(action: action) } diff --git a/submodules/TelegramUI/TelegramUI/ChatListControllerNode.swift b/submodules/TelegramUI/TelegramUI/ChatListControllerNode.swift index 711fab8cce..5874219e4e 100644 --- a/submodules/TelegramUI/TelegramUI/ChatListControllerNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatListControllerNode.swift @@ -60,7 +60,8 @@ final class ChatListControllerNode: ASDisplayNode { var requestOpenMessageFromSearch: ((Peer, MessageId) -> Void)? var requestAddContact: ((String) -> Void)? var dismissSelf: (() -> Void)? - + var isEmptyUpdated: ((Bool) -> Void)? + let debugListView = ListView() init(context: AccountContext, groupId: PeerGroupId, controlsHistoryPreload: Bool, presentationData: PresentationData, controller: ChatListController) { @@ -96,6 +97,7 @@ final class ChatListControllerNode: ASDisplayNode { if let (layout, navigationHeight, visualNavigationHeight) = strongSelf.containerLayout { strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, visualNavigationHeight: visualNavigationHeight, transition: .immediate) } + strongSelf.isEmptyUpdated?(true) } case .notEmpty(false): if case .group = strongSelf.groupId { diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageAnimatedStickerItemNode.swift index 910a2e6738..b28eba6d57 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageAnimatedStickerItemNode.swift @@ -13,6 +13,92 @@ private let nameFont = Font.medium(14.0) private let inlineBotPrefixFont = Font.regular(14.0) private let inlineBotNameFont = nameFont +private class ChatMessageHeartbeatHaptic { + private var hapticFeedback = HapticFeedback() + var timer: SwiftSignalKit.Timer? + var time: Double = 0 + var enabled = false { + didSet { + if !self.enabled { + self.reset() + } + } + } + + var active: Bool { + return self.timer != nil + } + + private func reset() { + if let timer = self.timer { + self.time = 0.0 + timer.invalidate() + self.timer = nil + } + } + + private func beat(time: Double) { + let epsilon = 0.1 + if fabs(0.0 - time) < epsilon || fabs(1.0 - time) < epsilon || fabs(2.0 - time) < epsilon { + self.hapticFeedback.impact(.medium) + } else if fabs(0.2 - time) < epsilon || fabs(1.2 - time) < epsilon || fabs(2.2 - time) < epsilon { + self.hapticFeedback.impact(.light) + } + } + + func start(time: Double) { + self.hapticFeedback.prepareImpact() + + if time > 2.0 { + return + } + + var startTime: Double = 0.0 + var delay: Double = 0.0 + + if time > 0.0 { + if time <= 1.0 { + startTime = 1.0 + } else if time <= 2.0 { + startTime = 2.0 + } + } + + delay = max(0.0, startTime - time) + + let block = { [weak self] in + guard let strongSelf = self, strongSelf.enabled else { + return + } + + strongSelf.time = startTime + strongSelf.beat(time: startTime) + strongSelf.timer = SwiftSignalKit.Timer(timeout: 0.2, repeat: true, completion: { [weak self] in + guard let strongSelf = self, strongSelf.enabled else { + return + } + strongSelf.time += 0.2 + strongSelf.beat(time: strongSelf.time) + + if strongSelf.time > 2.2 { + strongSelf.reset() + strongSelf.time = 0.0 + strongSelf.timer?.invalidate() + strongSelf.timer = nil + } + + }, queue: Queue.mainQueue()) + strongSelf.timer?.start() + } + + if delay > 0.0 { + Queue.mainQueue().after(delay, block) + } else { + block() + } + } +} + class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { let imageNode: TransformImageNode private let animationNode: AnimatedStickerNode @@ -39,7 +125,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { private var highlightedState: Bool = false - private var hapticFeedback: HapticFeedback? + private var heartbeatHaptic: ChatMessageHeartbeatHaptic? private var currentSwipeToReplyTranslation: CGFloat = 0.0 @@ -108,7 +194,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } self.view.addGestureRecognizer(replyRecognizer) } - + override var visibility: ListViewItemNodeVisibility { didSet { let wasVisible = oldValue != .none @@ -124,6 +210,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { didSet { if self.visibilityStatus != oldValue { self.updateVisibility() + self.heartbeatHaptic?.enabled = self.visibilityStatus } } } @@ -143,7 +230,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { break } } - + if self.telegramFile == nil, let emojiFile = item.associatedData.animatedEmojiStickers[item.message.text.trimmedEmoji]?.file { if self.emojiFile?.id != emojiFile.id { self.emojiFile = emojiFile @@ -247,23 +334,23 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { var hasAvatar = false switch item.chatLocation { - case let .peer(peerId): - if peerId != item.context.account.peerId { - if peerId.isGroupOrChannel && item.message.author != nil { - var isBroadcastChannel = false - if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = peer.info { - isBroadcastChannel = true - } - - if !isBroadcastChannel { - hasAvatar = true - } + case let .peer(peerId): + if peerId != item.context.account.peerId { + if peerId.isGroupOrChannel && item.message.author != nil { + var isBroadcastChannel = false + if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = peer.info { + isBroadcastChannel = true + } + + if !isBroadcastChannel { + hasAvatar = true } - } else if incoming { - hasAvatar = true } + } else if incoming { + hasAvatar = true + } /*case .group: - hasAvatar = true*/ + hasAvatar = true*/ } if hasAvatar { @@ -347,7 +434,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { statusType = .FreeOutgoing(.Sent(read: item.read)) } } - + var viewCount: Int? = nil for attribute in item.message.attributes { if let attribute = attribute as? ViewCountMessageAttribute { @@ -605,127 +692,128 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { @objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { switch recognizer.state { - case .ended: - if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation { - switch gesture { - case .tap: - if let avatarNode = self.accessoryItemNode as? ChatMessageAvatarAccessoryItemNode, avatarNode.frame.contains(location) { - if let item = self.item, let author = item.content.firstMessage.author { - var openPeerId = item.effectiveAuthorId ?? author.id - var navigate: ChatControllerInteractionNavigateToPeer - - if item.content.firstMessage.id.peerId == item.context.account.peerId { - navigate = .chat(textInputState: nil, messageId: nil) + case .ended: + if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation { + switch gesture { + case .tap: + if let avatarNode = self.accessoryItemNode as? ChatMessageAvatarAccessoryItemNode, avatarNode.frame.contains(location) { + if let item = self.item, let author = item.content.firstMessage.author { + var openPeerId = item.effectiveAuthorId ?? author.id + var navigate: ChatControllerInteractionNavigateToPeer + + if item.content.firstMessage.id.peerId == item.context.account.peerId { + navigate = .chat(textInputState: nil, messageId: nil) + } else { + navigate = .info + } + + for attribute in item.content.firstMessage.attributes { + if let attribute = attribute as? SourceReferenceMessageAttribute { + openPeerId = attribute.messageId.peerId + navigate = .chat(textInputState: nil, messageId: attribute.messageId) + } + } + + if item.effectiveAuthorId?.namespace == Namespaces.Peer.Empty { + item.controllerInteraction.displayMessageTooltip(item.content.firstMessage.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, self, avatarNode.frame) + } else { + if let channel = item.content.firstMessage.forwardInfo?.author as? TelegramChannel, channel.username == nil { + if case .member = channel.participationStatus { } else { - navigate = .info - } - - for attribute in item.content.firstMessage.attributes { - if let attribute = attribute as? SourceReferenceMessageAttribute { - openPeerId = attribute.messageId.peerId - navigate = .chat(textInputState: nil, messageId: attribute.messageId) - } - } - - if item.effectiveAuthorId?.namespace == Namespaces.Peer.Empty { - item.controllerInteraction.displayMessageTooltip(item.content.firstMessage.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, self, avatarNode.frame) - } else { - if let channel = item.content.firstMessage.forwardInfo?.author as? TelegramChannel, channel.username == nil { - if case .member = channel.participationStatus { - } else { - item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, self, avatarNode.frame) - return - } - } - item.controllerInteraction.openPeer(openPeerId, navigate, item.message) + item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, self, avatarNode.frame) + return } } - return + item.controllerInteraction.openPeer(openPeerId, navigate, item.message) } - - if let viaBotNode = self.viaBotNode, viaBotNode.frame.contains(location) { - if let item = self.item { - for attribute in item.message.attributes { - if let attribute = attribute as? InlineBotMessageAttribute { - var botAddressName: String? - if let peerId = attribute.peerId, let botPeer = item.message.peers[peerId], let addressName = botPeer.addressName { - botAddressName = addressName - } else { - botAddressName = attribute.title - } - - if let botAddressName = botAddressName { - item.controllerInteraction.updateInputState { textInputState in - return ChatTextInputState(inputText: NSAttributedString(string: "@" + botAddressName + " ")) - } - item.controllerInteraction.updateInputMode { _ in - return .text - } - } - return - } - } - } - } - - if let replyInfoNode = self.replyInfoNode, replyInfoNode.frame.contains(location) { - if let item = self.item { - for attribute in item.message.attributes { - if let attribute = attribute as? ReplyMessageAttribute { - item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId) - return - } - } - } - } - - if let item = self.item, self.imageNode.frame.contains(location) { - if self.telegramFile != nil { - let _ = item.controllerInteraction.openMessage(item.message, .default) - } else if let _ = self.emojiFile { - if self.animationNode.playIfNeeded() { - if self.item?.message.text == "❤️" { - let hapticFeedback: HapticFeedback - if let currentHapticFeedback = self.hapticFeedback { - hapticFeedback = currentHapticFeedback - } else { - hapticFeedback = HapticFeedback() - self.hapticFeedback = hapticFeedback - } - hapticFeedback.prepareImpact() - hapticFeedback.impact(.medium) - Queue.mainQueue().after(0.2) { - hapticFeedback.impact(.light) - Queue.mainQueue().after(0.78) { - hapticFeedback.impact(.medium) - Queue.mainQueue().after(0.2) { - hapticFeedback.impact(.light) - Queue.mainQueue().after(0.78) { - hapticFeedback.impact(.medium) - Queue.mainQueue().after(0.2) { - hapticFeedback.impact(.light) - } - } - } - } - } - } - } - } - return - } - - self.item?.controllerInteraction.clickThroughMessage() - case .longTap, .doubleTap: - if let item = self.item, self.imageNode.frame.contains(location) { - item.controllerInteraction.openMessageContextMenu(item.message, false, self, self.imageNode.frame) - } - case .hold: - break + } + return } + + if let viaBotNode = self.viaBotNode, viaBotNode.frame.contains(location) { + if let item = self.item { + for attribute in item.message.attributes { + if let attribute = attribute as? InlineBotMessageAttribute { + var botAddressName: String? + if let peerId = attribute.peerId, let botPeer = item.message.peers[peerId], let addressName = botPeer.addressName { + botAddressName = addressName + } else { + botAddressName = attribute.title + } + + if let botAddressName = botAddressName { + item.controllerInteraction.updateInputState { textInputState in + return ChatTextInputState(inputText: NSAttributedString(string: "@" + botAddressName + " ")) + } + item.controllerInteraction.updateInputMode { _ in + return .text + } + } + return + } + } + } + } + + if let replyInfoNode = self.replyInfoNode, replyInfoNode.frame.contains(location) { + if let item = self.item { + for attribute in item.message.attributes { + if let attribute = attribute as? ReplyMessageAttribute { + item.controllerInteraction.navigateToMessage(item.message.id, attribute.messageId) + return + } + } + } + } + + if let item = self.item, self.imageNode.frame.contains(location) { + if self.telegramFile != nil { + let _ = item.controllerInteraction.openMessage(item.message, .default) + } else if let _ = self.emojiFile { + var startTime: Signal + if self.animationNode.playIfNeeded() { + startTime = .single(0.0) + } else { + startTime = self.animationNode.status + |> map { $0.timestamp } + |> take(1) + |> deliverOnMainQueue + } + + if self.item?.message.text == "❤️" { + let _ = startTime.start(next: { [weak self] time in + guard let strongSelf = self else { + return + } + + let heartbeatHaptic: ChatMessageHeartbeatHaptic + if let current = strongSelf.heartbeatHaptic { + heartbeatHaptic = current + } else { + heartbeatHaptic = ChatMessageHeartbeatHaptic() + heartbeatHaptic.enabled = true + strongSelf.heartbeatHaptic = heartbeatHaptic + } + if !heartbeatHaptic.active { + heartbeatHaptic.start(time: time) + } + }) + } + } + return + } + + self.item?.controllerInteraction.clickThroughMessage() + case .longTap, .doubleTap: + if let item = self.item, self.imageNode.frame.contains(location) { + item.controllerInteraction.openMessageContextMenu(item.message, false, self, self.imageNode.frame) + } + case .hold: + break } - default: - break + } + default: + break } } diff --git a/submodules/TelegramUI/TelegramUI/EmojiUtils.swift b/submodules/TelegramUI/TelegramUI/EmojiUtils.swift index cb435f4146..37388e101e 100644 --- a/submodules/TelegramUI/TelegramUI/EmojiUtils.swift +++ b/submodules/TelegramUI/TelegramUI/EmojiUtils.swift @@ -19,6 +19,8 @@ extension UnicodeScalar { return true case 0x1f004: return true + case 0x2764: + return true case 0x270b, 0x2728: return true default: diff --git a/submodules/TelegramUI/TelegramUI/ManagedAudioRecorder.swift b/submodules/TelegramUI/TelegramUI/ManagedAudioRecorder.swift index f9c0b5a34e..c6fafa8e81 100644 --- a/submodules/TelegramUI/TelegramUI/ManagedAudioRecorder.swift +++ b/submodules/TelegramUI/TelegramUI/ManagedAudioRecorder.swift @@ -284,7 +284,7 @@ final class ManagedAudioRecorderContext { strongSelf.toneTimer?.invalidate() } } - }, queue: queue) + }, queue: queue) self.toneTimer = toneTimer toneTimer.start() } else { @@ -292,25 +292,25 @@ final class ManagedAudioRecorderContext { } /*if beginWithTone, let beginToneData = beginToneData { - self.tonePlayer = TonePlayer() - self.tonePlayer?.play(data: beginToneData, completed: { [weak self] in - queue.async { - guard let strongSelf = self else { - return - } - let toneTimer = SwiftSignalKit.Timer(timeout: 0.3, repeat: false, completion: { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.processSamples = true - }, queue: queue) - strongSelf.toneTimer = toneTimer - toneTimer.start() - } - }) - } else { - self.processSamples = true - }*/ + self.tonePlayer = TonePlayer() + self.tonePlayer?.play(data: beginToneData, completed: { [weak self] in + queue.async { + guard let strongSelf = self else { + return + } + let toneTimer = SwiftSignalKit.Timer(timeout: 0.3, repeat: false, completion: { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.processSamples = true + }, queue: queue) + strongSelf.toneTimer = toneTimer + toneTimer.start() + } + }) + } else { + self.processSamples = true + }*/ addAudioRecorderContext(self.id, self) addAudioUnitHolder(self.id, queue, self.audioUnit) @@ -319,7 +319,7 @@ final class ManagedAudioRecorderContext { self.idleTimerExtensionDisposable = (Signal { subscriber in return pushIdleTimerExtension() - } |> delay(5.0, queue: queue)).start() + } |> delay(5.0, queue: queue)).start() } deinit { @@ -406,19 +406,19 @@ final class ManagedAudioRecorderContext { strongSelf.audioSessionAcquired(headset: state.isHeadsetConnected) } } - }, deactivate: { [weak self] in - return Signal { subscriber in - queue.async { - if let strongSelf = self { - strongSelf.hasAudioSession = false - strongSelf.stop() - strongSelf.recordingState.set(.stopped) - subscriber.putCompletion() + }, deactivate: { [weak self] in + return Signal { subscriber in + queue.async { + if let strongSelf = self { + strongSelf.hasAudioSession = false + strongSelf.stop() + strongSelf.recordingState.set(.stopped) + subscriber.putCompletion() + } } + + return EmptyDisposable } - - return EmptyDisposable - } }) } } @@ -557,7 +557,7 @@ final class ManagedAudioRecorderContext { self.currentPeak = max(Int64(sample), self.currentPeak) self.currentPeakCount += 1 if self.currentPeakCount == self.peakCompressionFactor { - var compressedPeak = self.currentPeak//Int16(Float(self.currentPeak) / Float(self.peakCompressionFactor)) + var compressedPeak = self.currentPeak withUnsafeBytes(of: &compressedPeak, { buffer in self.compressedWaveformSamples.append(buffer.bindMemory(to: UInt8.self)) }) @@ -592,57 +592,53 @@ final class ManagedAudioRecorderContext { } func takeData() -> RecordedAudioData? { - if self.oggWriter.writeFrame(nil, frameByteCount: 0) { - var scaledSamplesMemory = malloc(100 * 2)! - var scaledSamples: UnsafeMutablePointer = scaledSamplesMemory.assumingMemoryBound(to: Int16.self) - defer { - free(scaledSamplesMemory) - } - memset(scaledSamples, 0, 100 * 2); - var waveform: Data? - - let count = self.compressedWaveformSamples.count / 2 - self.compressedWaveformSamples.withUnsafeMutableBytes { (samples: UnsafeMutablePointer) -> Void in - for i in 0 ..< count { - let sample = samples[i] - let index = i * 100 / count - if (scaledSamples[index] < sample) { - scaledSamples[index] = sample; - } - } - - var peak: Int16 = 0 - var sumSamples: Int64 = 0 - for i in 0 ..< 100 { - let sample = scaledSamples[i] - if peak < sample { - peak = sample - } - sumSamples += Int64(sample) - } - var calculatedPeak: UInt16 = 0 - calculatedPeak = UInt16((Double(sumSamples) * 1.8 / 100.0)) - - if calculatedPeak < 2500 { - calculatedPeak = 2500 - } - - for i in 0 ..< 100 { - let sample: UInt16 = UInt16(Int64(scaledSamples[i])) - let minPeak = min(Int64(sample), Int64(calculatedPeak)) - let resultPeak = minPeak * 31 / Int64(calculatedPeak) - scaledSamples[i] = Int16(clamping: min(31, resultPeak)) - } - - let resultWaveform = AudioWaveform(samples: Data(bytes: scaledSamplesMemory, count: 100 * 2), peak: 31) - let bitstream = resultWaveform.makeBitstream() - waveform = AudioWaveform(bitstream: bitstream, bitsPerSample: 5).makeBitstream() - } - - return RecordedAudioData(compressedData: self.dataItem.data(), duration: self.oggWriter.encodedDuration(), waveform: waveform) - } else { - return nil + var scaledSamplesMemory = malloc(100 * 2)! + var scaledSamples: UnsafeMutablePointer = scaledSamplesMemory.assumingMemoryBound(to: Int16.self) + defer { + free(scaledSamplesMemory) } + memset(scaledSamples, 0, 100 * 2); + var waveform: Data? + + let count = self.compressedWaveformSamples.count / 2 + self.compressedWaveformSamples.withUnsafeMutableBytes { (samples: UnsafeMutablePointer) -> Void in + for i in 0 ..< count { + let sample = samples[i] + let index = i * 100 / count + if (scaledSamples[index] < sample) { + scaledSamples[index] = sample; + } + } + + var peak: Int16 = 0 + var sumSamples: Int64 = 0 + for i in 0 ..< 100 { + let sample = scaledSamples[i] + if peak < sample { + peak = sample + } + sumSamples += Int64(sample) + } + var calculatedPeak: UInt16 = 0 + calculatedPeak = UInt16((Double(sumSamples) * 1.8 / 100.0)) + + if calculatedPeak < 2500 { + calculatedPeak = 2500 + } + + for i in 0 ..< 100 { + let sample: UInt16 = UInt16(Int64(scaledSamples[i])) + let minPeak = min(Int64(sample), Int64(calculatedPeak)) + let resultPeak = minPeak * 31 / Int64(calculatedPeak) + scaledSamples[i] = Int16(clamping: min(31, resultPeak)) + } + + let resultWaveform = AudioWaveform(samples: Data(bytes: scaledSamplesMemory, count: 100 * 2), peak: 31) + let bitstream = resultWaveform.makeBitstream() + waveform = AudioWaveform(bitstream: bitstream, bitsPerSample: 5).makeBitstream() + } + + return RecordedAudioData(compressedData: self.dataItem.data(), duration: self.oggWriter.encodedDuration(), waveform: waveform) } } diff --git a/submodules/TelegramUI/TelegramUI/ShareExtensionContext.swift b/submodules/TelegramUI/TelegramUI/ShareExtensionContext.swift index 6ed084b7dc..cdc491be71 100644 --- a/submodules/TelegramUI/TelegramUI/ShareExtensionContext.swift +++ b/submodules/TelegramUI/TelegramUI/ShareExtensionContext.swift @@ -292,7 +292,9 @@ public class ShareRootControllerImpl { } cancelImpl = { [weak shareController] in - shareController?.dismiss() + shareController?.dismiss(completion: { [weak self] in + self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil) + }) } if let strongSelf = self { From 40b1160bcdc954487dad284127934d1f3fc08add Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 7 Aug 2019 16:47:01 +0300 Subject: [PATCH 2/8] Animated emoji skin coloring --- .../AccountStateManagementUtils.swift | 9 +- .../ChatHistoryPreloadManager.swift | 7 + .../TelegramUI/AnimatedStickerNode.swift | 38 +- .../TelegramUI/AnimatedStickerUtils.swift | 418 +++++++++++------- .../CachedResourceRepresentations.swift | 53 ++- .../TelegramUI/ChatAnimationGalleryItem.swift | 2 +- .../ChatHistoryEntriesForView.swift | 2 +- .../TelegramUI/ChatHistoryListNode.swift | 2 +- .../ChatInterfaceInputContexts.swift | 2 +- .../ChatInterfaceStateContextQueries.swift | 2 +- .../ChatMessageAnimatedStickerItemNode.swift | 20 +- .../TelegramUI/ChatMessageItem.swift | 2 +- .../TelegramUI/TelegramUI/EmojiUtils.swift | 9 +- .../FetchCachedRepresentations.swift | 18 +- .../TelegramUI/FetchMediaUtils.swift | 6 + .../TelegramUI/PrefetchManager.swift | 217 ++++++--- .../PresentationThemeEssentialGraphics.swift | 6 +- .../StickerPaneSearchContentNode.swift | 5 +- .../TelegramUI/StickerResources.swift | 22 +- 19 files changed, 531 insertions(+), 309 deletions(-) diff --git a/submodules/TelegramCore/TelegramCore/AccountStateManagementUtils.swift b/submodules/TelegramCore/TelegramCore/AccountStateManagementUtils.swift index cc01b1c903..51e9cbf389 100644 --- a/submodules/TelegramCore/TelegramCore/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/TelegramCore/AccountStateManagementUtils.swift @@ -924,8 +924,11 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo } updatedState.addMessages([message], location: .UpperHistoryBlock) } - case let .updateServiceNotification(_, date, type, text, media, entities): - if let date = date { + case let .updateServiceNotification(flags, date, type, text, media, entities): + let popup = (flags & (1 << 0)) != 0 + if popup { + updatedState.addDisplayAlert(text, isDropAuth: type.hasPrefix("AUTH_KEY_DROP_")) + } else if let date = date { let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: 777000) if updatedState.peers[peerId] == nil { @@ -969,8 +972,6 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo let message = StoreMessage(peerId: peerId, namespace: Namespaces.Message.Local, globallyUniqueId: nil, groupingKey: nil, timestamp: date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: peerId, text: messageText, attributes: attributes, media: medias) updatedState.addMessages([message], location: .UpperHistoryBlock) } - } else { - updatedState.addDisplayAlert(text, isDropAuth: type.hasPrefix("AUTH_KEY_DROP_")) } case let .updateReadChannelInbox(_, folderId, channelId, maxId, stillUnreadCount, pts): updatedState.resetIncomingReadState(groupId: PeerGroupId(rawValue: folderId ?? 0), peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId), namespace: Namespaces.Message.Cloud, maxIncomingReadId: maxId, count: stillUnreadCount, pts: pts) diff --git a/submodules/TelegramCore/TelegramCore/ChatHistoryPreloadManager.swift b/submodules/TelegramCore/TelegramCore/ChatHistoryPreloadManager.swift index a88a5ed0c7..4534620ab3 100644 --- a/submodules/TelegramCore/TelegramCore/ChatHistoryPreloadManager.swift +++ b/submodules/TelegramCore/TelegramCore/ChatHistoryPreloadManager.swift @@ -13,6 +13,13 @@ public struct HistoryPreloadIndex: Comparable { public let isMuted: Bool public let isPriority: Bool + public init(index: ChatListIndex?, hasUnread: Bool, isMuted: Bool, isPriority: Bool) { + self.index = index + self.hasUnread = hasUnread + self.isMuted = isMuted + self.isPriority = isPriority + } + public static func <(lhs: HistoryPreloadIndex, rhs: HistoryPreloadIndex) -> Bool { if lhs.isPriority != rhs.isPriority { if lhs.isPriority { diff --git a/submodules/TelegramUI/TelegramUI/AnimatedStickerNode.swift b/submodules/TelegramUI/TelegramUI/AnimatedStickerNode.swift index 6028a4679d..a54839daef 100644 --- a/submodules/TelegramUI/TelegramUI/AnimatedStickerNode.swift +++ b/submodules/TelegramUI/TelegramUI/AnimatedStickerNode.swift @@ -402,7 +402,7 @@ final class AnimatedStickerNode: ASDisplayNode { self.addSubnode(self.renderer!) } - func setup(account: Account, resource: MediaResource, width: Int, height: Int, playbackMode: AnimatedStickerPlaybackMode = .loop, mode: AnimatedStickerMode) { + func setup(account: Account, resource: MediaResource, fitzModifier: EmojiFitzModifier? = nil, width: Int, height: Int, playbackMode: AnimatedStickerPlaybackMode = .loop, mode: AnimatedStickerMode) { if width < 2 || height < 2 { return } @@ -410,27 +410,27 @@ final class AnimatedStickerNode: ASDisplayNode { switch mode { case .direct: self.disposable.set((account.postbox.mediaBox.resourceData(resource) - |> deliverOnMainQueue).start(next: { [weak self] data in - guard let strongSelf = self, data.complete else { - return - } - if let directData = try? Data(contentsOf: URL(fileURLWithPath: data.path), options: [.mappedRead]) { - strongSelf.directData = Tuple(directData, data.path, width, height) - } + |> deliverOnMainQueue).start(next: { [weak self] data in + guard let strongSelf = self, data.complete else { + return + } + if let directData = try? Data(contentsOf: URL(fileURLWithPath: data.path), options: [.mappedRead]) { + strongSelf.directData = Tuple(directData, data.path, width, height) + } + if strongSelf.isPlaying { + strongSelf.play() + } + })) + case .cached: + self.disposable.set((chatMessageAnimationData(postbox: account.postbox, resource: resource, fitzModifier: fitzModifier, width: width, height: height, synchronousLoad: false) + |> deliverOnMainQueue).start(next: { [weak self] data in + if let strongSelf = self, data.complete { + strongSelf.cachedData = try? Data(contentsOf: URL(fileURLWithPath: data.path), options: [.mappedRead]) if strongSelf.isPlaying { strongSelf.play() } - })) - case .cached: - self.disposable.set((chatMessageAnimationData(postbox: account.postbox, resource: resource, width: width, height: height, synchronousLoad: false) - |> deliverOnMainQueue).start(next: { [weak self] data in - if let strongSelf = self, data.complete { - strongSelf.cachedData = try? Data(contentsOf: URL(fileURLWithPath: data.path), options: [.mappedRead]) - if strongSelf.isPlaying { - strongSelf.play() - } - } - })) + } + })) } } diff --git a/submodules/TelegramUI/TelegramUI/AnimatedStickerUtils.swift b/submodules/TelegramUI/TelegramUI/AnimatedStickerUtils.swift index 1d15924e11..fdda130e80 100644 --- a/submodules/TelegramUI/TelegramUI/AnimatedStickerUtils.swift +++ b/submodules/TelegramUI/TelegramUI/AnimatedStickerUtils.swift @@ -65,7 +65,85 @@ public class LocalBundleResource: TelegramMediaResource { } } -func fetchCompressedLottieFirstFrameAJpeg(data: Data, size: CGSize, cacheKey: String) -> Signal { +let colorKeyRegex = try? NSRegularExpression(pattern: "\"k\":\\[[\\d\\.]+\\,[\\d\\.]+\\,[\\d\\.]+\\,[\\d\\.]+\\]") + +private func transformedWithFitzModifier(data: Data, fitzModifier: EmojiFitzModifier?) -> Data { + if let fitzModifier = fitzModifier, var string = String(data: data, encoding: .utf8) { + let color1: UIColor + let color2: UIColor + let color3: UIColor + let color4: UIColor + + var colors: [UIColor] = [0xf77e41, 0xffb139, 0xffd140, 0xffdf79].map { UIColor(rgb: $0) } + let replacementColors: [UIColor] + switch fitzModifier { + case .type12: + replacementColors = [0xca907a, 0xedc5a5, 0xf7e3c3, 0xfbefd6].map { UIColor(rgb: $0) } + case .type3: + replacementColors = [0xaa7c60, 0xc8a987, 0xddc89f, 0xe6d6b2].map { UIColor(rgb: $0) } + case .type4: + replacementColors = [0x8c6148, 0xad8562, 0xc49e76, 0xd4b188].map { UIColor(rgb: $0) } + case .type5: + replacementColors = [0x6e3c2c, 0x925a34, 0xa16e46, 0xac7a52].map { UIColor(rgb: $0) } + case .type6: + replacementColors = [0x291c12, 0x472a22, 0x573b30, 0x68493c].map { UIColor(rgb: $0) } + } + + func colorToString(_ color: UIColor) -> String { + var r: CGFloat = 0.0 + var g: CGFloat = 0.0 + var b: CGFloat = 0.0 + if color.getRed(&r, green: &g, blue: &b, alpha: nil) { + return "\"k\":[\(r),\(g),\(b),1]" + } + return "" + } + + func match(_ a: Double, _ b: Double, eps: Double) -> Bool { + return abs(a - b) < eps + } + + var replacements: [(NSTextCheckingResult, String)] = [] + + if let colorKeyRegex = colorKeyRegex { + let results = colorKeyRegex.matches(in: string, range: NSRange(string.startIndex..., in: string)) + for result in results.reversed() { + if let range = Range(result.range, in: string) { + let substring = String(string[range]) + let color = substring[substring.index(string.startIndex, offsetBy: "\"k\":[".count) ..< substring.index(before: substring.endIndex)] + let components = color.split(separator: ",") + if components.count == 4, let r = Double(components[0]), let g = Double(components[1]), let b = Double(components[2]), let a = Double(components[3]) { + if match(a, 1.0, eps: 0.01) { + for i in 0 ..< colors.count { + let color = colors[i] + var cr: CGFloat = 0.0 + var cg: CGFloat = 0.0 + var cb: CGFloat = 0.0 + if color.getRed(&cr, green: &cg, blue: &cb, alpha: nil) { + if match(r, Double(cr), eps: 0.01) && match(g, Double(cg), eps: 0.01) && match(b, Double(cb), eps: 0.01) { + replacements.append((result, colorToString(replacementColors[i]))) + } + } + } + } + } + } + } + } + + for (result, text) in replacements { + if let range = Range(result.range, in: string) { + string = string.replacingCharacters(in: range, with: text) + } + } + + return string.data(using: .utf8) ?? data + } else { + return data + } +} + +func fetchCompressedLottieFirstFrameAJpeg(data: Data, size: CGSize, fitzModifier: EmojiFitzModifier? = nil, cacheKey: String) -> Signal { return Signal({ subscriber in let queue = Queue() @@ -77,72 +155,75 @@ func fetchCompressedLottieFirstFrameAJpeg(data: Data, size: CGSize, cacheKey: St } let decompressedData = TGGUnzipData(data, 8 * 1024 * 1024) - if let decompressedData = decompressedData, let player = LottieInstance(data: decompressedData, cacheKey: cacheKey) { - if cancelled.with({ $0 }) { - return - } - - let context = DrawingContext(size: size, scale: 1.0, clear: true) - player.renderFrame(with: 0, into: context.bytes.assumingMemoryBound(to: UInt8.self), width: Int32(size.width), height: Int32(size.height), bytesPerRow: Int32(context.bytesPerRow)) - - let yuvaPixelsPerAlphaRow = (Int(size.width) + 1) & (~1) - assert(yuvaPixelsPerAlphaRow % 2 == 0) - - let yuvaLength = Int(size.width) * Int(size.height) * 2 + yuvaPixelsPerAlphaRow * Int(size.height) / 2 - var yuvaFrameData = malloc(yuvaLength)! - memset(yuvaFrameData, 0, yuvaLength) - - defer { - free(yuvaFrameData) - } - - encodeRGBAToYUVA(yuvaFrameData.assumingMemoryBound(to: UInt8.self), context.bytes.assumingMemoryBound(to: UInt8.self), Int32(size.width), Int32(size.height), Int32(context.bytesPerRow)) - decodeYUVAToRGBA(yuvaFrameData.assumingMemoryBound(to: UInt8.self), context.bytes.assumingMemoryBound(to: UInt8.self), Int32(size.width), Int32(size.height), Int32(context.bytesPerRow)) - - if let colorSourceImage = context.generateImage(), let alphaImage = generateGrayscaleAlphaMaskImage(image: colorSourceImage) { - let colorContext = DrawingContext(size: size, scale: 1.0, clear: false) - colorContext.withFlippedContext { c in - c.setFillColor(UIColor.black.cgColor) - c.fill(CGRect(origin: CGPoint(), size: size)) - c.draw(colorSourceImage.cgImage!, in: CGRect(origin: CGPoint(), size: size)) - } - guard let colorImage = colorContext.generateImage() else { + if let decompressedData = decompressedData { + let decompressedData = transformedWithFitzModifier(data: decompressedData, fitzModifier: fitzModifier) + if let player = LottieInstance(data: decompressedData, cacheKey: cacheKey) { + if cancelled.with({ $0 }) { return } - let colorData = NSMutableData() - let alphaData = NSMutableData() + let context = DrawingContext(size: size, scale: 1.0, clear: true) + player.renderFrame(with: 0, into: context.bytes.assumingMemoryBound(to: UInt8.self), width: Int32(size.width), height: Int32(size.height), bytesPerRow: Int32(context.bytesPerRow)) - if let colorDestination = CGImageDestinationCreateWithData(colorData as CFMutableData, kUTTypeJPEG, 1, nil), let alphaDestination = CGImageDestinationCreateWithData(alphaData as CFMutableData, kUTTypeJPEG, 1, nil) { - CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary) - CGImageDestinationSetProperties(alphaDestination, [:] as CFDictionary) + let yuvaPixelsPerAlphaRow = (Int(size.width) + 1) & (~1) + assert(yuvaPixelsPerAlphaRow % 2 == 0) + + let yuvaLength = Int(size.width) * Int(size.height) * 2 + yuvaPixelsPerAlphaRow * Int(size.height) / 2 + var yuvaFrameData = malloc(yuvaLength)! + memset(yuvaFrameData, 0, yuvaLength) + + defer { + free(yuvaFrameData) + } + + encodeRGBAToYUVA(yuvaFrameData.assumingMemoryBound(to: UInt8.self), context.bytes.assumingMemoryBound(to: UInt8.self), Int32(size.width), Int32(size.height), Int32(context.bytesPerRow)) + decodeYUVAToRGBA(yuvaFrameData.assumingMemoryBound(to: UInt8.self), context.bytes.assumingMemoryBound(to: UInt8.self), Int32(size.width), Int32(size.height), Int32(context.bytesPerRow)) + + if let colorSourceImage = context.generateImage(), let alphaImage = generateGrayscaleAlphaMaskImage(image: colorSourceImage) { + let colorContext = DrawingContext(size: size, scale: 1.0, clear: false) + colorContext.withFlippedContext { c in + c.setFillColor(UIColor.black.cgColor) + c.fill(CGRect(origin: CGPoint(), size: size)) + c.draw(colorSourceImage.cgImage!, in: CGRect(origin: CGPoint(), size: size)) + } + guard let colorImage = colorContext.generateImage() else { + return + } - let colorQuality: Float - let alphaQuality: Float - colorQuality = 0.5 - alphaQuality = 0.4 + let colorData = NSMutableData() + let alphaData = NSMutableData() - let options = NSMutableDictionary() - options.setObject(colorQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString) - - let optionsAlpha = NSMutableDictionary() - optionsAlpha.setObject(alphaQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString) - - CGImageDestinationAddImage(colorDestination, colorImage.cgImage!, options as CFDictionary) - CGImageDestinationAddImage(alphaDestination, alphaImage.cgImage!, optionsAlpha as CFDictionary) - if CGImageDestinationFinalize(colorDestination) && CGImageDestinationFinalize(alphaDestination) { - let finalData = NSMutableData() - var colorSize: Int32 = Int32(colorData.length) - finalData.append(&colorSize, length: 4) - finalData.append(colorData as Data) - var alphaSize: Int32 = Int32(alphaData.length) - finalData.append(&alphaSize, length: 4) - finalData.append(alphaData as Data) + if let colorDestination = CGImageDestinationCreateWithData(colorData as CFMutableData, kUTTypeJPEG, 1, nil), let alphaDestination = CGImageDestinationCreateWithData(alphaData as CFMutableData, kUTTypeJPEG, 1, nil) { + CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary) + CGImageDestinationSetProperties(alphaDestination, [:] as CFDictionary) - let tempFile = TempBox.shared.tempFile(fileName: "image.ajpg") - let _ = try? finalData.write(to: URL(fileURLWithPath: tempFile.path), options: []) - subscriber.putNext(tempFile) - subscriber.putCompletion() + let colorQuality: Float + let alphaQuality: Float + colorQuality = 0.5 + alphaQuality = 0.4 + + let options = NSMutableDictionary() + options.setObject(colorQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString) + + let optionsAlpha = NSMutableDictionary() + optionsAlpha.setObject(alphaQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString) + + CGImageDestinationAddImage(colorDestination, colorImage.cgImage!, options as CFDictionary) + CGImageDestinationAddImage(alphaDestination, alphaImage.cgImage!, optionsAlpha as CFDictionary) + if CGImageDestinationFinalize(colorDestination) && CGImageDestinationFinalize(alphaDestination) { + let finalData = NSMutableData() + var colorSize: Int32 = Int32(colorData.length) + finalData.append(&colorSize, length: 4) + finalData.append(colorData as Data) + var alphaSize: Int32 = Int32(alphaData.length) + finalData.append(&alphaSize, length: 4) + finalData.append(alphaData as Data) + + let tempFile = TempBox.shared.tempFile(fileName: "image.ajpg") + let _ = try? finalData.write(to: URL(fileURLWithPath: tempFile.path), options: []) + subscriber.putNext(tempFile) + subscriber.putCompletion() + } } } } @@ -159,7 +240,7 @@ private let threadPool: ThreadPool = { }() @available(iOS 9.0, *) -func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: CGSize, cacheKey: String) -> Signal { +func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: CGSize, fitzModifier: EmojiFitzModifier? = nil, cacheKey: String) -> Signal { return Signal({ subscriber in let cancelled = Atomic(value: false) @@ -174,127 +255,130 @@ func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: CGSize, var appendingTime: Double = 0 var deltaTime: Double = 0 var compressionTime: Double = 0 - + let decompressedData = TGGUnzipData(data, 8 * 1024 * 1024) - if let decompressedData = decompressedData, let player = LottieInstance(data: decompressedData, cacheKey: cacheKey) { - let endFrame = Int(player.frameCount) - - if cancelled.with({ $0 }) { - //print("cancelled 2") - return - } - - var randomId: Int64 = 0 - arc4random_buf(&randomId, 8) - let path = NSTemporaryDirectory() + "\(randomId).lz4v" - guard let fileContext = ManagedFile(queue: nil, path: path, mode: .readwrite) else { - return - } - - let bytesPerRow = (4 * Int(size.width) + 15) & (~15) - - var currentFrame: Int32 = 0 - - var fps: Int32 = player.frameRate - var frameCount: Int32 = player.frameCount - let _ = fileContext.write(&fps, count: 4) - let _ = fileContext.write(&frameCount, count: 4) - var widthValue: Int32 = Int32(size.width) - var heightValue: Int32 = Int32(size.height) - var bytesPerRowValue: Int32 = Int32(bytesPerRow) - let _ = fileContext.write(&widthValue, count: 4) - let _ = fileContext.write(&heightValue, count: 4) - let _ = fileContext.write(&bytesPerRowValue, count: 4) - - let frameLength = bytesPerRow * Int(size.height) - assert(frameLength % 16 == 0) - - let currentFrameData = malloc(frameLength)! - memset(currentFrameData, 0, frameLength) - - let yuvaPixelsPerAlphaRow = (Int(size.width) + 1) & (~1) - assert(yuvaPixelsPerAlphaRow % 2 == 0) - - let yuvaLength = Int(size.width) * Int(size.height) * 2 + yuvaPixelsPerAlphaRow * Int(size.height) / 2 - var yuvaFrameData = malloc(yuvaLength)! - memset(yuvaFrameData, 0, yuvaLength) - - var previousYuvaFrameData = malloc(yuvaLength)! - memset(previousYuvaFrameData, 0, yuvaLength) - - defer { - free(currentFrameData) - free(previousYuvaFrameData) - free(yuvaFrameData) - } - - var compressedFrameData = Data(count: frameLength) - let compressedFrameDataLength = compressedFrameData.count - - let scratchData = malloc(compression_encode_scratch_buffer_size(COMPRESSION_LZFSE))! - defer { - free(scratchData) - } - - while currentFrame < endFrame { + if let decompressedData = decompressedData { + let decompressedData = transformedWithFitzModifier(data: decompressedData, fitzModifier: fitzModifier) + if let player = LottieInstance(data: decompressedData, cacheKey: cacheKey) { + let endFrame = Int(player.frameCount) + if cancelled.with({ $0 }) { - //print("cancelled 3") + //print("cancelled 2") return } - let drawStartTime = CACurrentMediaTime() + var randomId: Int64 = 0 + arc4random_buf(&randomId, 8) + let path = NSTemporaryDirectory() + "\(randomId).lz4v" + guard let fileContext = ManagedFile(queue: nil, path: path, mode: .readwrite) else { + return + } + + let bytesPerRow = (4 * Int(size.width) + 15) & (~15) + + var currentFrame: Int32 = 0 + + var fps: Int32 = player.frameRate + var frameCount: Int32 = player.frameCount + let _ = fileContext.write(&fps, count: 4) + let _ = fileContext.write(&frameCount, count: 4) + var widthValue: Int32 = Int32(size.width) + var heightValue: Int32 = Int32(size.height) + var bytesPerRowValue: Int32 = Int32(bytesPerRow) + let _ = fileContext.write(&widthValue, count: 4) + let _ = fileContext.write(&heightValue, count: 4) + let _ = fileContext.write(&bytesPerRowValue, count: 4) + + let frameLength = bytesPerRow * Int(size.height) + assert(frameLength % 16 == 0) + + let currentFrameData = malloc(frameLength)! memset(currentFrameData, 0, frameLength) - player.renderFrame(with: Int32(currentFrame), into: currentFrameData.assumingMemoryBound(to: UInt8.self), width: Int32(size.width), height: Int32(size.height), bytesPerRow: Int32(bytesPerRow)) - drawingTime += CACurrentMediaTime() - drawStartTime - let appendStartTime = CACurrentMediaTime() + let yuvaPixelsPerAlphaRow = (Int(size.width) + 1) & (~1) + assert(yuvaPixelsPerAlphaRow % 2 == 0) - encodeRGBAToYUVA(yuvaFrameData.assumingMemoryBound(to: UInt8.self), currentFrameData.assumingMemoryBound(to: UInt8.self), Int32(size.width), Int32(size.height), Int32(bytesPerRow)) + let yuvaLength = Int(size.width) * Int(size.height) * 2 + yuvaPixelsPerAlphaRow * Int(size.height) / 2 + var yuvaFrameData = malloc(yuvaLength)! + memset(yuvaFrameData, 0, yuvaLength) - appendingTime += CACurrentMediaTime() - appendStartTime + var previousYuvaFrameData = malloc(yuvaLength)! + memset(previousYuvaFrameData, 0, yuvaLength) - let deltaStartTime = CACurrentMediaTime() - var lhs = previousYuvaFrameData.assumingMemoryBound(to: UInt64.self) - var rhs = yuvaFrameData.assumingMemoryBound(to: UInt64.self) - for _ in 0 ..< yuvaLength / 8 { - lhs.pointee = rhs.pointee ^ lhs.pointee - lhs = lhs.advanced(by: 1) - rhs = rhs.advanced(by: 1) - } - var lhsRest = previousYuvaFrameData.assumingMemoryBound(to: UInt8.self).advanced(by: (yuvaLength / 8) * 8) - var rhsRest = yuvaFrameData.assumingMemoryBound(to: UInt8.self).advanced(by: (yuvaLength / 8) * 8) - for _ in (yuvaLength / 8) * 8 ..< yuvaLength { - lhsRest.pointee = rhsRest.pointee ^ lhsRest.pointee - lhsRest = lhsRest.advanced(by: 1) - rhsRest = rhsRest.advanced(by: 1) - } - deltaTime += CACurrentMediaTime() - deltaStartTime - - let compressionStartTime = CACurrentMediaTime() - compressedFrameData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer) -> Void in - let length = compression_encode_buffer(bytes, compressedFrameDataLength, previousYuvaFrameData.assumingMemoryBound(to: UInt8.self), yuvaLength, scratchData, COMPRESSION_LZFSE) - var frameLengthValue: Int32 = Int32(length) - let _ = fileContext.write(&frameLengthValue, count: 4) - let _ = fileContext.write(bytes, count: length) + defer { + free(currentFrameData) + free(previousYuvaFrameData) + free(yuvaFrameData) } - let tmp = previousYuvaFrameData - previousYuvaFrameData = yuvaFrameData - yuvaFrameData = tmp + var compressedFrameData = Data(count: frameLength) + let compressedFrameDataLength = compressedFrameData.count - compressionTime += CACurrentMediaTime() - compressionStartTime + let scratchData = malloc(compression_encode_scratch_buffer_size(COMPRESSION_LZFSE))! + defer { + free(scratchData) + } - currentFrame += 1 + while currentFrame < endFrame { + if cancelled.with({ $0 }) { + //print("cancelled 3") + return + } + + let drawStartTime = CACurrentMediaTime() + memset(currentFrameData, 0, frameLength) + player.renderFrame(with: Int32(currentFrame), into: currentFrameData.assumingMemoryBound(to: UInt8.self), width: Int32(size.width), height: Int32(size.height), bytesPerRow: Int32(bytesPerRow)) + drawingTime += CACurrentMediaTime() - drawStartTime + + let appendStartTime = CACurrentMediaTime() + + encodeRGBAToYUVA(yuvaFrameData.assumingMemoryBound(to: UInt8.self), currentFrameData.assumingMemoryBound(to: UInt8.self), Int32(size.width), Int32(size.height), Int32(bytesPerRow)) + + appendingTime += CACurrentMediaTime() - appendStartTime + + let deltaStartTime = CACurrentMediaTime() + var lhs = previousYuvaFrameData.assumingMemoryBound(to: UInt64.self) + var rhs = yuvaFrameData.assumingMemoryBound(to: UInt64.self) + for _ in 0 ..< yuvaLength / 8 { + lhs.pointee = rhs.pointee ^ lhs.pointee + lhs = lhs.advanced(by: 1) + rhs = rhs.advanced(by: 1) + } + var lhsRest = previousYuvaFrameData.assumingMemoryBound(to: UInt8.self).advanced(by: (yuvaLength / 8) * 8) + var rhsRest = yuvaFrameData.assumingMemoryBound(to: UInt8.self).advanced(by: (yuvaLength / 8) * 8) + for _ in (yuvaLength / 8) * 8 ..< yuvaLength { + lhsRest.pointee = rhsRest.pointee ^ lhsRest.pointee + lhsRest = lhsRest.advanced(by: 1) + rhsRest = rhsRest.advanced(by: 1) + } + deltaTime += CACurrentMediaTime() - deltaStartTime + + let compressionStartTime = CACurrentMediaTime() + compressedFrameData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer) -> Void in + let length = compression_encode_buffer(bytes, compressedFrameDataLength, previousYuvaFrameData.assumingMemoryBound(to: UInt8.self), yuvaLength, scratchData, COMPRESSION_LZFSE) + var frameLengthValue: Int32 = Int32(length) + let _ = fileContext.write(&frameLengthValue, count: 4) + let _ = fileContext.write(bytes, count: length) + } + + let tmp = previousYuvaFrameData + previousYuvaFrameData = yuvaFrameData + yuvaFrameData = tmp + + compressionTime += CACurrentMediaTime() - compressionStartTime + + currentFrame += 1 + } + + subscriber.putNext(path) + subscriber.putCompletion() + print("animation render time \(CACurrentMediaTime() - startTime)") + print("of which drawing time \(drawingTime)") + print("of which appending time \(appendingTime)") + print("of which delta time \(deltaTime)") + + print("of which compression time \(compressionTime)") } - - subscriber.putNext(path) - subscriber.putCompletion() - print("animation render time \(CACurrentMediaTime() - startTime)") - print("of which drawing time \(drawingTime)") - print("of which appending time \(appendingTime)") - print("of which delta time \(deltaTime)") - - print("of which compression time \(compressionTime)") } })) return ActionDisposable { diff --git a/submodules/TelegramUI/TelegramUI/CachedResourceRepresentations.swift b/submodules/TelegramUI/TelegramUI/CachedResourceRepresentations.swift index fb7229d802..3fe13bf712 100644 --- a/submodules/TelegramUI/TelegramUI/CachedResourceRepresentations.swift +++ b/submodules/TelegramUI/TelegramUI/CachedResourceRepresentations.swift @@ -234,19 +234,51 @@ final class CachedEmojiRepresentation: CachedMediaResourceRepresentation { } } +public enum EmojiFitzModifier: Int32, Equatable { + case type12 + case type3 + case type4 + case type5 + case type6 + + public init?(emoji: String) { + switch emoji.unicodeScalars.first?.value { + case 0x1f3fb: + self = .type12 + case 0x1f3fc: + self = .type3 + case 0x1f3fd: + self = .type4 + case 0x1f3fe: + self = .type5 + case 0x1f3ff: + self = .type6 + default: + return nil + } + } +} + final class CachedAnimatedStickerFirstFrameRepresentation: CachedMediaResourceRepresentation { let keepDuration: CachedMediaRepresentationKeepDuration = .general let width: Int32 let height: Int32 + let fitzModifier: EmojiFitzModifier? - init(width: Int32, height: Int32) { + init(width: Int32, height: Int32, fitzModifier: EmojiFitzModifier? = nil) { self.width = width self.height = height + self.fitzModifier = fitzModifier } var uniqueId: String { - return "animated-sticker-first-frame-\(self.width)x\(self.height)-v1" + let version: Int = 1 + if let fitzModifier = self.fitzModifier { + return "animated-sticker-first-frame-\(self.width)x\(self.height)-fitz\(fitzModifier.rawValue)-v\(version)" + } else { + return "animated-sticker-first-frame-\(self.width)x\(self.height)-v\(version)" + } } func isEqual(to: CachedMediaResourceRepresentation) -> Bool { @@ -257,6 +289,9 @@ final class CachedAnimatedStickerFirstFrameRepresentation: CachedMediaResourceRe if other.height != self.height { return false } + if other.fitzModifier != self.fitzModifier { + return false + } return true } else { return false @@ -269,14 +304,21 @@ final class CachedAnimatedStickerRepresentation: CachedMediaResourceRepresentati let width: Int32 let height: Int32 + let fitzModifier: EmojiFitzModifier? var uniqueId: String { - return "animated-sticker-\(self.width)x\(self.height)-v8" + let version: Int = 8 + if let fitzModifier = self.fitzModifier { + return "animated-sticker-\(self.width)x\(self.height)-fitz\(fitzModifier.rawValue)-v\(version)" + } else { + return "animated-sticker-\(self.width)x\(self.height)-v\(version)" + } } - init(width: Int32, height: Int32) { + init(width: Int32, height: Int32, fitzModifier: EmojiFitzModifier? = nil) { self.width = width self.height = height + self.fitzModifier = fitzModifier } func isEqual(to: CachedMediaResourceRepresentation) -> Bool { @@ -287,6 +329,9 @@ final class CachedAnimatedStickerRepresentation: CachedMediaResourceRepresentati if other.height != self.height { return false } + if other.fitzModifier != self.fitzModifier { + return false + } return true } else { return false diff --git a/submodules/TelegramUI/TelegramUI/ChatAnimationGalleryItem.swift b/submodules/TelegramUI/TelegramUI/ChatAnimationGalleryItem.swift index 25432b9be3..80abff6fde 100644 --- a/submodules/TelegramUI/TelegramUI/ChatAnimationGalleryItem.swift +++ b/submodules/TelegramUI/TelegramUI/ChatAnimationGalleryItem.swift @@ -133,7 +133,7 @@ final class ChatAnimationGalleryItemNode: ZoomableContentGalleryItemNode { func setFile(context: AccountContext, fileReference: FileMediaReference) { if self.contextAndMedia == nil || !self.contextAndMedia!.1.media.isEqual(to: fileReference.media) { - let signal = chatMessageAnimatedStrickerBackingData(postbox: context.account.postbox, fileReference: fileReference, synchronousLoad: false) + let signal = chatMessageAnimatedStickerBackingData(postbox: context.account.postbox, fileReference: fileReference, synchronousLoad: false) |> mapToSignal { value -> Signal in if value._1, let data = value._0 { return .single(data) diff --git a/submodules/TelegramUI/TelegramUI/ChatHistoryEntriesForView.swift b/submodules/TelegramUI/TelegramUI/ChatHistoryEntriesForView.swift index 50b44fc043..6f41313dac 100644 --- a/submodules/TelegramUI/TelegramUI/ChatHistoryEntriesForView.swift +++ b/submodules/TelegramUI/TelegramUI/ChatHistoryEntriesForView.swift @@ -39,7 +39,7 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView, var contentTypeHint: ChatMessageEntryContentType = .generic if presentationData.largeEmoji { - if let _ = associatedData.animatedEmojiStickers[entry.message.text.trimmedEmoji] { + if let _ = associatedData.animatedEmojiStickers[entry.message.text.basicEmoji.0] { contentTypeHint = .animatedEmoji } else if messageIsElligibleForLargeEmoji(entry.message) { contentTypeHint = .largeEmoji diff --git a/submodules/TelegramUI/TelegramUI/ChatHistoryListNode.swift b/submodules/TelegramUI/TelegramUI/ChatHistoryListNode.swift index 61c0929385..8638ba3b84 100644 --- a/submodules/TelegramUI/TelegramUI/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatHistoryListNode.swift @@ -545,7 +545,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { var animatedEmojiStickers: [String: StickerPackItem] = [:] for case let item as StickerPackItem in items { if let emoji = item.getStringRepresentationsOfIndexKeys().first { - animatedEmojiStickers[emoji.trimmedEmoji] = item + animatedEmojiStickers[emoji.basicEmoji.0] = item } } return animatedEmojiStickers diff --git a/submodules/TelegramUI/TelegramUI/ChatInterfaceInputContexts.swift b/submodules/TelegramUI/TelegramUI/ChatInterfaceInputContexts.swift index a03145f877..5b16351161 100644 --- a/submodules/TelegramUI/TelegramUI/ChatInterfaceInputContexts.swift +++ b/submodules/TelegramUI/TelegramUI/ChatInterfaceInputContexts.swift @@ -176,7 +176,7 @@ func inputContextQueriesForChatPresentationIntefaceState(_ chatPresentationInter for (possibleQueryRange, possibleTypes, additionalStringRange) in textInputStateContextQueryRangeAndType(inputState) { let query = inputString.substring(with: possibleQueryRange) if possibleTypes == [.emoji] { - result.append(.emoji(query.basicEmoji)) + result.append(.emoji(query.basicEmoji.0)) } else if possibleTypes == [.hashtag] { result.append(.hashtag(query)) } else if possibleTypes == [.mention] { diff --git a/submodules/TelegramUI/TelegramUI/ChatInterfaceStateContextQueries.swift b/submodules/TelegramUI/TelegramUI/ChatInterfaceStateContextQueries.swift index fa47d0ff32..826c9066aa 100644 --- a/submodules/TelegramUI/TelegramUI/ChatInterfaceStateContextQueries.swift +++ b/submodules/TelegramUI/TelegramUI/ChatInterfaceStateContextQueries.swift @@ -88,7 +88,7 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee case .installed: scope = [.installed] } - return searchStickers(account: context.account, query: query.trimmedEmoji, scope: scope) + return searchStickers(account: context.account, query: query.basicEmoji.0, scope: scope) |> introduceError(ChatContextQueryError.self) } |> map { stickers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageAnimatedStickerItemNode.swift index b28eba6d57..f8d3362104 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageAnimatedStickerItemNode.swift @@ -231,12 +231,17 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } } - if self.telegramFile == nil, let emojiFile = item.associatedData.animatedEmojiStickers[item.message.text.trimmedEmoji]?.file { + let (emoji, fitz) = item.message.text.basicEmoji + if self.telegramFile == nil, let emojiFile = item.associatedData.animatedEmojiStickers[emoji]?.file { if self.emojiFile?.id != emojiFile.id { self.emojiFile = emojiFile let dimensions = emojiFile.dimensions ?? CGSize(width: 512.0, height: 512.0) - self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: item.context.account.postbox, file: emojiFile, small: false, size: dimensions.aspectFilled(CGSize(width: 384.0, height: 384.0)), thumbnail: false)) - self.disposable.set(freeMediaFileInteractiveFetched(account: item.context.account, fileReference: .message(message: MessageReference(item.message), media: emojiFile)).start()) + var fitzModifier: EmojiFitzModifier? + if let fitz = fitz { + fitzModifier = EmojiFitzModifier(emoji: fitz) + } + self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: item.context.account.postbox, file: emojiFile, small: false, size: dimensions.aspectFilled(CGSize(width: 384.0, height: 384.0)), fitzModifier: fitzModifier, thumbnail: false)) + self.disposable.set(freeMediaFileInteractiveFetched(account: item.context.account, fileReference: .standalone(media: emojiFile)).start()) self.updateVisibility() } } @@ -269,7 +274,8 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { var file: TelegramMediaFile? var playbackMode: AnimatedStickerPlaybackMode = .loop var isEmoji = false - + var fitzModifier: EmojiFitzModifier? + if let telegramFile = self.telegramFile { file = telegramFile if !item.controllerInteraction.stickerSettings.loopAnimatedStickers { @@ -279,12 +285,16 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { isEmoji = true file = emojiFile playbackMode = .once + let (_, fitz) = item.message.text.basicEmoji + if let fitz = fitz { + fitzModifier = EmojiFitzModifier(emoji: fitz) + } } if let file = file { let dimensions = file.dimensions ?? CGSize(width: 512.0, height: 512.0) let fittedSize = isEmoji ? dimensions.aspectFilled(CGSize(width: 384.0, height: 384.0)) : dimensions.aspectFitted(CGSize(width: 384.0, height: 384.0)) - self.animationNode.setup(account: item.context.account, resource: file.resource, width: Int(fittedSize.width), height: Int(fittedSize.height), playbackMode: playbackMode, mode: .cached) + self.animationNode.setup(account: item.context.account, resource: file.resource, fitzModifier: fitzModifier, width: Int(fittedSize.width), height: Int(fittedSize.height), playbackMode: playbackMode, mode: .cached) } } } diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageItem.swift b/submodules/TelegramUI/TelegramUI/ChatMessageItem.swift index c18041ac90..c019eb9860 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageItem.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageItem.swift @@ -387,7 +387,7 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible { } if viewClassName == ChatMessageBubbleItemNode.self && self.presentationData.largeEmoji && messageIsElligibleForLargeEmoji(self.message) { - if let _ = self.associatedData.animatedEmojiStickers[self.message.text.trimmedEmoji] { + if let _ = self.associatedData.animatedEmojiStickers[self.message.text.basicEmoji.0] { viewClassName = ChatMessageAnimatedStickerItemNode.self } else { viewClassName = ChatMessageStickerItemNode.self diff --git a/submodules/TelegramUI/TelegramUI/EmojiUtils.swift b/submodules/TelegramUI/TelegramUI/EmojiUtils.swift index 37388e101e..32c3535f45 100644 --- a/submodules/TelegramUI/TelegramUI/EmojiUtils.swift +++ b/submodules/TelegramUI/TelegramUI/EmojiUtils.swift @@ -129,7 +129,7 @@ extension String { return string } - var basicEmoji: String { + var basicEmoji: (String, String?) { let fitzCodes: [UInt32] = [ 0x1f3fb, 0x1f3fc, @@ -139,13 +139,18 @@ extension String { ] var string = "" + var fitzModifier: String? for scalar in self.unicodeScalars { if fitzCodes.contains(scalar.value) { + fitzModifier = String(scalar) continue } string.unicodeScalars.append(scalar) + if scalar.value == 0x2764, self.unicodeScalars.count > 1, self.emojis.count == 1 { + break + } } - return string + return (string, fitzModifier) } var trimmedEmoji: String { diff --git a/submodules/TelegramUI/TelegramUI/FetchCachedRepresentations.swift b/submodules/TelegramUI/TelegramUI/FetchCachedRepresentations.swift index ffafc8e884..920a3d7aa6 100644 --- a/submodules/TelegramUI/TelegramUI/FetchCachedRepresentations.swift +++ b/submodules/TelegramUI/TelegramUI/FetchCachedRepresentations.swift @@ -112,19 +112,7 @@ public func fetchCachedResourceRepresentation(account: Account, resource: MediaR } else if let representation = representation as? CachedEmojiRepresentation { return fetchEmojiRepresentation(account: account, resource: resource, representation: representation) } else if let representation = representation as? CachedAnimatedStickerRepresentation { - let data: Signal -// if let resource = resource as? LocalBundleResource { -// data = Signal { subscriber in -// if let path = frameworkBundle.path(forResource: resource.name, ofType: resource.ext), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) { -// subscriber.putNext(MediaResourceData(path: path, offset: 0, size: data.count, complete: true)) -// subscriber.putCompletion() -// } -// return EmptyDisposable -// } -// } else { - data = account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false)) -// } - return data + return account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false)) |> mapToSignal { data -> Signal in if !data.complete { return .complete() @@ -908,7 +896,7 @@ private func fetchEmojiRepresentation(account: Account, resource: MediaResource, private func fetchAnimatedStickerFirstFrameRepresentation(account: Account, resource: MediaResource, resourceData: MediaResourceData, representation: CachedAnimatedStickerFirstFrameRepresentation) -> Signal { return Signal({ subscriber in if let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) { - return fetchCompressedLottieFirstFrameAJpeg(data: data, size: CGSize(width: CGFloat(representation.width), height: CGFloat(representation.height)), cacheKey: "\(resource.id.uniqueId)-\(representation.uniqueId)").start(next: { file in + return fetchCompressedLottieFirstFrameAJpeg(data: data, size: CGSize(width: CGFloat(representation.width), height: CGFloat(representation.height)), fitzModifier: representation.fitzModifier, cacheKey: "\(resource.id.uniqueId)-\(representation.uniqueId)").start(next: { file in subscriber.putNext(.tempFile(file)) subscriber.putCompletion() }) @@ -923,7 +911,7 @@ private func fetchAnimatedStickerRepresentation(account: Account, resource: Medi return Signal({ subscriber in if let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) { if #available(iOS 9.0, *) { - return experimentalConvertCompressedLottieToCombinedMp4(data: data, size: CGSize(width: CGFloat(representation.width), height: CGFloat(representation.height)), cacheKey: "\(resource.id.uniqueId)-\(representation.uniqueId)").start(next: { path in + return experimentalConvertCompressedLottieToCombinedMp4(data: data, size: CGSize(width: CGFloat(representation.width), height: CGFloat(representation.height)), fitzModifier: representation.fitzModifier, cacheKey: "\(resource.id.uniqueId)-\(representation.uniqueId)").start(next: { path in subscriber.putNext(.temporaryPath(path)) subscriber.putCompletion() }) diff --git a/submodules/TelegramUI/TelegramUI/FetchMediaUtils.swift b/submodules/TelegramUI/TelegramUI/FetchMediaUtils.swift index 25c0df6115..9fca2562e2 100644 --- a/submodules/TelegramUI/TelegramUI/FetchMediaUtils.swift +++ b/submodules/TelegramUI/TelegramUI/FetchMediaUtils.swift @@ -9,6 +9,12 @@ public func freeMediaFileInteractiveFetched(account: Account, fileReference: Fil return fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: fileReference.resourceReference(fileReference.media.resource)) } +func freeMediaFileInteractiveFetched(fetchManager: FetchManager, fileReference: FileMediaReference, priority: FetchManagerPriority) -> Signal { + let file = fileReference.media + let mediaReference = AnyMediaReference.standalone(media: fileReference.media) + return fetchManager.interactivelyFetched(category: fetchCategoryForFile(file), location: .chat(PeerId(namespace: 0, id: 0)), locationKey: .free, mediaReference: mediaReference, resourceReference: mediaReference.resourceReference(file.resource), ranges: IndexSet(integersIn: 0 ..< Int(Int32.max) as Range), statsCategory: statsCategoryForFileWithAttributes(file.attributes), elevatedPriority: false, userInitiated: false, priority: priority) +} + func freeMediaFileResourceInteractiveFetched(account: Account, fileReference: FileMediaReference, resource: MediaResource) -> Signal { return fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: fileReference.resourceReference(resource)) } diff --git a/submodules/TelegramUI/TelegramUI/PrefetchManager.swift b/submodules/TelegramUI/TelegramUI/PrefetchManager.swift index 5b03639883..b2f9c94184 100644 --- a/submodules/TelegramUI/TelegramUI/PrefetchManager.swift +++ b/submodules/TelegramUI/TelegramUI/PrefetchManager.swift @@ -11,6 +11,11 @@ private final class PrefetchMediaContext { } } +public enum PrefetchMediaItem { + case chatHistory(ChatHistoryPreloadMediaItem) + case animatedEmojiSticker(TelegramMediaFile) +} + private final class PrefetchManagerImpl { private let queue: Queue private let account: Account @@ -36,7 +41,37 @@ private final class PrefetchManagerImpl { } |> distinctUntilChanged - self.listDisposable = (combineLatest(account.viewTracker.orderedPreloadMedia, sharedContext.automaticMediaDownloadSettings, networkType) + let orderedPreloadMedia = account.viewTracker.orderedPreloadMedia + |> mapToSignal { orderedPreloadMedia in + return loadedStickerPack(postbox: account.postbox, network: account.network, reference: .animatedEmoji, forceActualized: false) + |> map { result -> [PrefetchMediaItem] in + let chatHistoryMediaItems = orderedPreloadMedia.map { PrefetchMediaItem.chatHistory($0) } + switch result { + case let .result(_, items, _): + var animatedEmojiStickers: [String: StickerPackItem] = [:] + for case let item as StickerPackItem in items { + if let emoji = item.getStringRepresentationsOfIndexKeys().first { + animatedEmojiStickers[emoji.basicEmoji.0] = item + } + } + var stickerItems: [PrefetchMediaItem] = [] + let popularEmoji = ["\u{2764}", "👍", "😳", "😒", "🥳"] + for emoji in popularEmoji { + if let sticker = animatedEmojiStickers[emoji] { + if let _ = account.postbox.mediaBox.completedResourcePath(sticker.file.resource) { + } else { + stickerItems.append(.animatedEmojiSticker(sticker.file)) + } + } + } + return stickerItems + chatHistoryMediaItems + default: + return chatHistoryMediaItems + } + } + } + + self.listDisposable = (combineLatest(orderedPreloadMedia, sharedContext.automaticMediaDownloadSettings, networkType) |> deliverOn(self.queue)).start(next: { [weak self] orderedPreloadMedia, automaticDownloadSettings, networkType in self?.updateOrderedPreloadMedia(orderedPreloadMedia, automaticDownloadSettings: automaticDownloadSettings, networkType: networkType) }) @@ -47,79 +82,119 @@ private final class PrefetchManagerImpl { self.listDisposable?.dispose() } - private func updateOrderedPreloadMedia(_ orderedPreloadMedia: [ChatHistoryPreloadMediaItem], automaticDownloadSettings: MediaAutoDownloadSettings, networkType: MediaAutoDownloadNetworkType) { + private func updateOrderedPreloadMedia(_ items: [PrefetchMediaItem], automaticDownloadSettings: MediaAutoDownloadSettings, networkType: MediaAutoDownloadNetworkType) { var validIds = Set() - for mediaItem in orderedPreloadMedia { - guard let id = mediaItem.media.media.id else { - continue - } - if validIds.contains(id) { - continue - } - - var automaticDownload: InteractiveMediaNodeAutodownloadMode = .none - let peerType: MediaAutoDownloadPeerType - if mediaItem.media.authorIsContact { - peerType = .contact - } else if let channel = mediaItem.media.peer as? TelegramChannel { - if case .group = channel.info { - peerType = .group - } else { - peerType = .channel - } - } else if mediaItem.media.peer is TelegramGroup { - peerType = .group - } else { - peerType = .otherPrivate - } - var mediaResource: MediaResource? - - if let telegramImage = mediaItem.media.media as? TelegramMediaImage { - mediaResource = largestRepresentationForPhoto(telegramImage)?.resource - if shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, authorPeerId: nil, contactsPeerIds: [], media: telegramImage) { - automaticDownload = .full - } - } else if let telegramFile = mediaItem.media.media as? TelegramMediaFile { - mediaResource = telegramFile.resource - if shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, authorPeerId: nil, contactsPeerIds: [], media: telegramFile) { - automaticDownload = .full - } else if shouldPredownloadMedia(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, media: telegramFile) { - automaticDownload = .prefetch - } - } - - if case .none = automaticDownload { - continue - } - guard let resource = mediaResource else { - continue - } - - validIds.insert(id) - let context: PrefetchMediaContext - if let current = self.contexts[id] { - context = current - } else { - context = PrefetchMediaContext() - self.contexts[id] = context - - let media = mediaItem.media.media - - let priority: FetchManagerPriority = .backgroundPrefetch(locationOrder: mediaItem.preloadIndex, localOrder: mediaItem.media.index) - - if case .full = automaticDownload { - if let image = media as? TelegramMediaImage { - context.fetchDisposable.set(messageMediaImageInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), image: image, resource: resource, userInitiated: false, priority: priority, storeToDownloadsPeerType: nil).start()) - } else if let _ = media as? TelegramMediaWebFile { - //strongSelf.fetchDisposable.set(chatMessageWebFileInteractiveFetched(account: context.account, image: image).start()) - } else if let file = media as? TelegramMediaFile { - let fetchSignal = messageMediaFileInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), file: file, userInitiated: false, priority: priority) - context.fetchDisposable.set(fetchSignal.start()) + var order: Int32 = 0 + for mediaItem in items { + switch mediaItem { + case let .chatHistory(mediaItem): + guard let id = mediaItem.media.media.id else { + continue } - } else if case .prefetch = automaticDownload, mediaItem.media.peer.id.namespace != Namespaces.Peer.SecretChat { - if let file = media as? TelegramMediaFile, let _ = file.size { - context.fetchDisposable.set(preloadVideoResource(postbox: self.account.postbox, resourceReference: FileMediaReference.message(message: MessageReference(peer: mediaItem.media.peer, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), media: file).resourceReference(file.resource), duration: 4.0).start()) + if validIds.contains(id) { + continue } + + var automaticDownload: InteractiveMediaNodeAutodownloadMode = .none + let peerType: MediaAutoDownloadPeerType + if mediaItem.media.authorIsContact { + peerType = .contact + } else if let channel = mediaItem.media.peer as? TelegramChannel { + if case .group = channel.info { + peerType = .group + } else { + peerType = .channel + } + } else if mediaItem.media.peer is TelegramGroup { + peerType = .group + } else { + peerType = .otherPrivate + } + var mediaResource: MediaResource? + + if let telegramImage = mediaItem.media.media as? TelegramMediaImage { + mediaResource = largestRepresentationForPhoto(telegramImage)?.resource + if shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, authorPeerId: nil, contactsPeerIds: [], media: telegramImage) { + automaticDownload = .full + } + } else if let telegramFile = mediaItem.media.media as? TelegramMediaFile { + mediaResource = telegramFile.resource + if shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, authorPeerId: nil, contactsPeerIds: [], media: telegramFile) { + automaticDownload = .full + } else if shouldPredownloadMedia(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, media: telegramFile) { + automaticDownload = .prefetch + } + } + + if case .none = automaticDownload { + continue + } + guard let resource = mediaResource else { + continue + } + + validIds.insert(id) + let context: PrefetchMediaContext + if let current = self.contexts[id] { + context = current + } else { + context = PrefetchMediaContext() + self.contexts[id] = context + + let media = mediaItem.media.media + + let priority: FetchManagerPriority = .backgroundPrefetch(locationOrder: mediaItem.preloadIndex, localOrder: mediaItem.media.index) + + if case .full = automaticDownload { + if let image = media as? TelegramMediaImage { + context.fetchDisposable.set(messageMediaImageInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), image: image, resource: resource, userInitiated: false, priority: priority, storeToDownloadsPeerType: nil).start()) + } else if let _ = media as? TelegramMediaWebFile { + //strongSelf.fetchDisposable.set(chatMessageWebFileInteractiveFetched(account: context.account, image: image).start()) + } else if let file = media as? TelegramMediaFile { + let fetchSignal = messageMediaFileInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), file: file, userInitiated: false, priority: priority) + context.fetchDisposable.set(fetchSignal.start()) + } + } else if case .prefetch = automaticDownload, mediaItem.media.peer.id.namespace != Namespaces.Peer.SecretChat { + if let file = media as? TelegramMediaFile, let _ = file.size { + context.fetchDisposable.set(preloadVideoResource(postbox: self.account.postbox, resourceReference: FileMediaReference.message(message: MessageReference(peer: mediaItem.media.peer, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), media: file).resourceReference(file.resource), duration: 4.0).start()) + } + } + } + case let .animatedEmojiSticker(media): + guard let id = media.id else { + continue + } + if validIds.contains(id) { + continue + } + + var automaticDownload: InteractiveMediaNodeAutodownloadMode = .none + let peerType = MediaAutoDownloadPeerType.contact + + if shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, authorPeerId: nil, contactsPeerIds: [], media: media) { + automaticDownload = .full + } + + if case .none = automaticDownload { + continue + } + + validIds.insert(id) + let context: PrefetchMediaContext + if let current = self.contexts[id] { + context = current + } else { + context = PrefetchMediaContext() + self.contexts[id] = context + + let priority: FetchManagerPriority = .backgroundPrefetch(locationOrder: HistoryPreloadIndex(index: nil, hasUnread: false, isMuted: false, isPriority: true), localOrder: MessageIndex(id: MessageId(peerId: PeerId(namespace: 0, id: 0), namespace: 0, id: order), timestamp: 0)) + + if case .full = automaticDownload { + let fetchSignal = freeMediaFileInteractiveFetched(fetchManager: self.fetchManager, fileReference: .standalone(media: media), priority: priority) + context.fetchDisposable.set(fetchSignal.start()) + } + + order += 1 } } } diff --git a/submodules/TelegramUI/TelegramUI/PresentationThemeEssentialGraphics.swift b/submodules/TelegramUI/TelegramUI/PresentationThemeEssentialGraphics.swift index e0c3fadda5..13e27f9034 100644 --- a/submodules/TelegramUI/TelegramUI/PresentationThemeEssentialGraphics.swift +++ b/submodules/TelegramUI/TelegramUI/PresentationThemeEssentialGraphics.swift @@ -134,6 +134,9 @@ public final class PrincipalThemeEssentialGraphics { let emptyImage = UIImage() if preview { self.chatMessageBackgroundIncomingImage = messageBubbleImage(incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .none) + self.chatMessageBackgroundOutgoingImage = messageBubbleImage(incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .none) + self.checkBubbleFullImage = generateCheckImage(partial: false, color: theme.message.outgoingCheckColor)! + self.checkBubblePartialImage = generateCheckImage(partial: true, color: theme.message.outgoingCheckColor)! self.chatMessageBackgroundIncomingHighlightedImage = emptyImage self.chatMessageBackgroundIncomingMergedTopImage = emptyImage self.chatMessageBackgroundIncomingMergedTopHighlightedImage = emptyImage @@ -145,7 +148,6 @@ public final class PrincipalThemeEssentialGraphics { self.chatMessageBackgroundIncomingMergedBothHighlightedImage = emptyImage self.chatMessageBackgroundIncomingMergedSideImage = emptyImage self.chatMessageBackgroundIncomingMergedSideHighlightedImage = emptyImage - self.chatMessageBackgroundOutgoingImage = messageBubbleImage(incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .none) self.chatMessageBackgroundOutgoingHighlightedImage = emptyImage self.chatMessageBackgroundOutgoingMergedTopImage = emptyImage self.chatMessageBackgroundOutgoingMergedTopHighlightedImage = emptyImage @@ -157,8 +159,6 @@ public final class PrincipalThemeEssentialGraphics { self.chatMessageBackgroundOutgoingMergedBothHighlightedImage = emptyImage self.chatMessageBackgroundOutgoingMergedSideImage = emptyImage self.chatMessageBackgroundOutgoingMergedSideHighlightedImage = emptyImage - self.checkBubbleFullImage = emptyImage - self.checkBubblePartialImage = emptyImage self.checkMediaFullImage = emptyImage self.checkMediaPartialImage = emptyImage self.checkFreeFullImage = emptyImage diff --git a/submodules/TelegramUI/TelegramUI/StickerPaneSearchContentNode.swift b/submodules/TelegramUI/TelegramUI/StickerPaneSearchContentNode.swift index fc0fcd8890..c9ec40fb20 100644 --- a/submodules/TelegramUI/TelegramUI/StickerPaneSearchContentNode.swift +++ b/submodules/TelegramUI/TelegramUI/StickerPaneSearchContentNode.swift @@ -269,8 +269,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode { let query = text.trimmingCharacters(in: .whitespacesAndNewlines) if query.isSingleEmoji { - signals = .single([searchStickers(account: account, query: text.trimmedEmoji) - //|> take(1) + signals = .single([searchStickers(account: account, query: text.basicEmoji.0) |> map { (nil, $0) }]) } else if query.count > 1, let languageCode = languageCode, !languageCode.isEmpty && languageCode != "emoji" { var signal = searchEmojiKeywords(postbox: account.postbox, inputLanguageCode: languageCode, query: query.lowercased(), completeMatch: query.count < 3) @@ -292,7 +291,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode { var signals: [Signal<(String?, [FoundStickerItem]), NoError>] = [] let emoticons = keywords.flatMap { $0.emoticons } for emoji in emoticons { - signals.append(searchStickers(account: self.context.account, query: emoji.trimmedEmoji) + signals.append(searchStickers(account: self.context.account, query: emoji.basicEmoji.0) |> take(1) |> map { (emoji, $0) }) } diff --git a/submodules/TelegramUI/TelegramUI/StickerResources.swift b/submodules/TelegramUI/TelegramUI/StickerResources.swift index 772a8c99a2..1b5636fd1b 100644 --- a/submodules/TelegramUI/TelegramUI/StickerResources.swift +++ b/submodules/TelegramUI/TelegramUI/StickerResources.swift @@ -96,11 +96,12 @@ private func chatMessageStickerDatas(postbox: Postbox, file: TelegramMediaFile, } } -func chatMessageAnimatedStickerDatas(postbox: Postbox, file: TelegramMediaFile, small: Bool, size: CGSize, fetched: Bool, onlyFullSize: Bool, synchronousLoad: Bool) -> Signal, NoError> { +public func chatMessageAnimatedStickerDatas(postbox: Postbox, file: TelegramMediaFile, small: Bool, size: CGSize, fitzModifier: EmojiFitzModifier? = nil, 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) + let firstFrameRepresentation = CachedAnimatedStickerFirstFrameRepresentation(width: Int32(size.width), height: Int32(size.height), fitzModifier: fitzModifier) + let maybeFetched = postbox.mediaBox.cachedResourceRepresentation(resource, representation: firstFrameRepresentation, complete: false, fetch: false, attemptSynchronously: synchronousLoad) return maybeFetched |> take(1) @@ -111,7 +112,7 @@ func chatMessageAnimatedStickerDatas(postbox: Postbox, file: TelegramMediaFile, return .single(Tuple(nil, loadedData, true)) } else { let thumbnailData = postbox.mediaBox.cachedResourceRepresentation(thumbnailResource, representation: CachedStickerAJpegRepresentation(size: nil), complete: false) - let fullSizeData = postbox.mediaBox.cachedResourceRepresentation(resource, representation: CachedAnimatedStickerFirstFrameRepresentation(width: Int32(size.width), height: Int32(size.height)), complete: onlyFullSize) + let fullSizeData = postbox.mediaBox.cachedResourceRepresentation(resource, representation: firstFrameRepresentation, complete: onlyFullSize) |> map { next in return (next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: .mappedIfSafe), next.complete) } @@ -162,7 +163,7 @@ private func chatMessageStickerThumbnailData(postbox: Postbox, file: TelegramMed let thumbnailData = postbox.mediaBox.cachedResourceRepresentation(thumbnailResource, representation: CachedStickerAJpegRepresentation(size: nil), complete: false) return Signal { subscriber in - var fetchThumbnail = fetchedMediaResource(mediaBox: postbox.mediaBox, reference: stickerPackFileReference(file).resourceReference(thumbnailResource)).start() + let fetchThumbnail = fetchedMediaResource(mediaBox: postbox.mediaBox, reference: stickerPackFileReference(file).resourceReference(thumbnailResource)).start() let disposable = (thumbnailData |> map { thumbnailData -> Data? in @@ -225,8 +226,9 @@ private func chatMessageStickerPackThumbnailData(postbox: Postbox, resource: Med } } -func chatMessageAnimationData(postbox: Postbox, resource: MediaResource, width: Int, height: Int, synchronousLoad: Bool) -> Signal { - let maybeFetched = postbox.mediaBox.cachedResourceRepresentation(resource, representation: CachedAnimatedStickerRepresentation(width: Int32(width), height: Int32(height)), complete: false, fetch: false, attemptSynchronously: synchronousLoad) +public func chatMessageAnimationData(postbox: Postbox, resource: MediaResource, fitzModifier: EmojiFitzModifier? = nil, width: Int, height: Int, synchronousLoad: Bool) -> Signal { + let representation = CachedAnimatedStickerRepresentation(width: Int32(width), height: Int32(height), fitzModifier: fitzModifier) + let maybeFetched = postbox.mediaBox.cachedResourceRepresentation(resource, representation: representation, complete: false, fetch: false, attemptSynchronously: synchronousLoad) return maybeFetched |> take(1) @@ -234,12 +236,12 @@ func chatMessageAnimationData(postbox: Postbox, resource: MediaResource, width: if maybeData.complete { return .single(maybeData) } else { - return postbox.mediaBox.cachedResourceRepresentation(resource, representation: CachedAnimatedStickerRepresentation(width: Int32(width), height: Int32(height)), complete: false) + return postbox.mediaBox.cachedResourceRepresentation(resource, representation: representation, complete: false) } } } -func chatMessageAnimatedStrickerBackingData(postbox: Postbox, fileReference: FileMediaReference, synchronousLoad: Bool) -> Signal, NoError> { +public func chatMessageAnimatedStickerBackingData(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) @@ -464,7 +466,7 @@ public func chatMessageSticker(postbox: Postbox, file: TelegramMediaFile, small: } } -public func chatMessageAnimatedSticker(postbox: Postbox, file: TelegramMediaFile, small: Bool, size: CGSize, fetched: Bool = false, onlyFullSize: Bool = false, thumbnail: Bool = false, synchronousLoad: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { +public func chatMessageAnimatedSticker(postbox: Postbox, file: TelegramMediaFile, small: Bool, size: CGSize, fitzModifier: EmojiFitzModifier? = nil, fetched: Bool = false, onlyFullSize: Bool = false, thumbnail: Bool = false, synchronousLoad: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { let signal: Signal, NoError> if thumbnail { signal = chatMessageStickerThumbnailData(postbox: postbox, file: file, synchronousLoad: synchronousLoad) @@ -472,7 +474,7 @@ public func chatMessageAnimatedSticker(postbox: Postbox, file: TelegramMediaFile return Tuple(data, nil, false) } } else { - signal = chatMessageAnimatedStickerDatas(postbox: postbox, file: file, small: small, size: size, fetched: fetched, onlyFullSize: onlyFullSize, synchronousLoad: synchronousLoad) + signal = chatMessageAnimatedStickerDatas(postbox: postbox, file: file, small: small, size: size, fitzModifier: fitzModifier, fetched: fetched, onlyFullSize: onlyFullSize, synchronousLoad: synchronousLoad) } return signal |> map { value in From 11ab388fc010f87be900c8153d3c8e2e379e2282 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 7 Aug 2019 16:50:26 +0300 Subject: [PATCH 3/8] Don't display animated emoji thumbnail if fitz modifier is applied [skip ci] --- submodules/TelegramUI/TelegramUI/StickerResources.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/TelegramUI/TelegramUI/StickerResources.swift b/submodules/TelegramUI/TelegramUI/StickerResources.swift index 1b5636fd1b..6c39a55506 100644 --- a/submodules/TelegramUI/TelegramUI/StickerResources.swift +++ b/submodules/TelegramUI/TelegramUI/StickerResources.swift @@ -496,7 +496,7 @@ public func chatMessageAnimatedSticker(postbox: Postbox, file: TelegramMediaFile } var thumbnailImage: (UIImage, UIImage)? - if fullSizeImage == nil, let thumbnailData = thumbnailData { + if fullSizeImage == nil, let thumbnailData = thumbnailData, fitzModifier == nil { if let image = imageFromAJpeg(data: thumbnailData) { thumbnailImage = image } From 8fbaf8d1a662b4a7808fc38f709b91f2bf6906f4 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Wed, 7 Aug 2019 20:51:36 +0300 Subject: [PATCH 4/8] Improve scrubbing --- .../Display/Display/ImmediateTextNode.swift | 3 +- submodules/Display/Display/TextNode.swift | 26 ++++--- .../project.pbxproj | 29 ++++---- .../ChatItemGalleryFooterContentNode.swift | 67 ++++++++++++++++--- .../ChatVideoGalleryItemScrubberView.swift | 2 + 5 files changed, 95 insertions(+), 32 deletions(-) diff --git a/submodules/Display/Display/ImmediateTextNode.swift b/submodules/Display/Display/ImmediateTextNode.swift index add3df7e25..a8e4154c15 100644 --- a/submodules/Display/Display/ImmediateTextNode.swift +++ b/submodules/Display/Display/ImmediateTextNode.swift @@ -13,6 +13,7 @@ public class ImmediateTextNode: TextNode { public var maximumNumberOfLines: Int = 1 public var lineSpacing: CGFloat = 0.0 public var insets: UIEdgeInsets = UIEdgeInsets() + public var textShadowColor: UIColor? private var tapRecognizer: TapLongTapOrDoubleTapGestureRecognizer? private var linkHighlightingNode: LinkHighlightingNode? @@ -34,7 +35,7 @@ public class ImmediateTextNode: TextNode { public func updateLayout(_ constrainedSize: CGSize) -> CGSize { let makeLayout = TextNode.asyncLayout(self) - let (layout, apply) = makeLayout(TextNodeLayoutArguments(attributedString: self.attributedText, backgroundColor: nil, maximumNumberOfLines: self.maximumNumberOfLines, truncationType: self.truncationType, constrainedSize: constrainedSize, alignment: self.textAlignment, lineSpacing: self.lineSpacing, cutout: nil, insets: self.insets)) + let (layout, apply) = makeLayout(TextNodeLayoutArguments(attributedString: self.attributedText, backgroundColor: nil, maximumNumberOfLines: self.maximumNumberOfLines, truncationType: self.truncationType, constrainedSize: constrainedSize, alignment: self.textAlignment, lineSpacing: self.lineSpacing, cutout: nil, insets: self.insets, textShadowColor: self.textShadowColor)) let _ = apply() if layout.numberOfLines > 1 { self.trailingLineWidth = layout.trailingLineWidth diff --git a/submodules/Display/Display/TextNode.swift b/submodules/Display/Display/TextNode.swift index a00c22c472..b1650c4eba 100644 --- a/submodules/Display/Display/TextNode.swift +++ b/submodules/Display/Display/TextNode.swift @@ -92,8 +92,9 @@ public final class TextNodeLayoutArguments { public let cutout: TextNodeCutout? public let insets: UIEdgeInsets public let lineColor: UIColor? + public let textShadowColor: UIColor? - public init(attributedString: NSAttributedString?, backgroundColor: UIColor? = nil, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, alignment: NSTextAlignment = .natural, lineSpacing: CGFloat = 0.12, cutout: TextNodeCutout? = nil, insets: UIEdgeInsets = UIEdgeInsets(), lineColor: UIColor? = nil) { + public init(attributedString: NSAttributedString?, backgroundColor: UIColor? = nil, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, alignment: NSTextAlignment = .natural, lineSpacing: CGFloat = 0.12, cutout: TextNodeCutout? = nil, insets: UIEdgeInsets = UIEdgeInsets(), lineColor: UIColor? = nil, textShadowColor: UIColor? = nil) { self.attributedString = attributedString self.backgroundColor = backgroundColor self.maximumNumberOfLines = maximumNumberOfLines @@ -104,6 +105,7 @@ public final class TextNodeLayoutArguments { self.cutout = cutout self.insets = insets self.lineColor = lineColor + self.textShadowColor = textShadowColor } } @@ -123,9 +125,10 @@ public final class TextNodeLayout: NSObject { fileprivate let lines: [TextNodeLine] fileprivate let blockQuotes: [TextNodeBlockQuote] fileprivate let lineColor: UIColor? + fileprivate let textShadowColor: UIColor? public let hasRTL: Bool - fileprivate init(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, alignment: NSTextAlignment, lineSpacing: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, size: CGSize, truncated: Bool, firstLineOffset: CGFloat, lines: [TextNodeLine], blockQuotes: [TextNodeBlockQuote], backgroundColor: UIColor?, lineColor: UIColor?) { + fileprivate init(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, alignment: NSTextAlignment, lineSpacing: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, size: CGSize, truncated: Bool, firstLineOffset: CGFloat, lines: [TextNodeLine], blockQuotes: [TextNodeBlockQuote], backgroundColor: UIColor?, lineColor: UIColor?, textShadowColor: UIColor?) { self.attributedString = attributedString self.maximumNumberOfLines = maximumNumberOfLines self.truncationType = truncationType @@ -141,6 +144,7 @@ public final class TextNodeLayout: NSObject { self.blockQuotes = blockQuotes self.backgroundColor = backgroundColor self.lineColor = lineColor + self.textShadowColor = textShadowColor var hasRTL = false for line in lines { if line.isRTL { @@ -575,7 +579,7 @@ public class TextNode: ASDisplayNode { } } - private class func calculateLayout(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, backgroundColor: UIColor?, constrainedSize: CGSize, alignment: NSTextAlignment, lineSpacingFactor: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, lineColor: UIColor?) -> TextNodeLayout { + private class func calculateLayout(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, backgroundColor: UIColor?, constrainedSize: CGSize, alignment: NSTextAlignment, lineSpacingFactor: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, lineColor: UIColor?, textShadowColor: UIColor?) -> TextNodeLayout { if let attributedString = attributedString { let stringLength = attributedString.length @@ -601,7 +605,7 @@ public class TextNode: ASDisplayNode { var maybeTypesetter: CTTypesetter? maybeTypesetter = CTTypesetterCreateWithAttributedString(attributedString as CFAttributedString) if maybeTypesetter == nil { - return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor) + return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor) } let typesetter = maybeTypesetter! @@ -793,9 +797,9 @@ public class TextNode: ASDisplayNode { } } - return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(width: ceil(layoutSize.width) + insets.left + insets.right, height: ceil(layoutSize.height) + insets.top + insets.bottom), truncated: truncated, firstLineOffset: firstLineOffset, lines: lines, blockQuotes: blockQuotes, backgroundColor: backgroundColor, lineColor: lineColor) + return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(width: ceil(layoutSize.width) + insets.left + insets.right, height: ceil(layoutSize.height) + insets.top + insets.bottom), truncated: truncated, firstLineOffset: firstLineOffset, lines: lines, blockQuotes: blockQuotes, backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor) } else { - return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor) + return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor) } } @@ -828,11 +832,15 @@ public class TextNode: ASDisplayNode { context.fill(bounds) } + if let textShadowColor = layout.textShadowColor { + context.setTextDrawingMode(.fill) + context.setShadow(offset: CGSize(width: 0.0, height: 1.0), blur: 0.0, color: textShadowColor.cgColor) + } + let textMatrix = context.textMatrix let textPosition = context.textPosition context.textMatrix = CGAffineTransform(scaleX: 1.0, y: -1.0) - let alignment = layout.alignment let offset = CGPoint(x: layout.insets.left, y: layout.insets.top) @@ -927,11 +935,11 @@ public class TextNode: ASDisplayNode { if stringMatch { layout = existingLayout } else { - layout = TextNode.calculateLayout(attributedString: arguments.attributedString, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor) + layout = TextNode.calculateLayout(attributedString: arguments.attributedString, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor) updated = true } } else { - layout = TextNode.calculateLayout(attributedString: arguments.attributedString, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor) + layout = TextNode.calculateLayout(attributedString: arguments.attributedString, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor) updated = true } diff --git a/submodules/MtProtoKit/MtProtoKit_Xcode.xcodeproj/project.pbxproj b/submodules/MtProtoKit/MtProtoKit_Xcode.xcodeproj/project.pbxproj index b321ed0361..60b6cfeae6 100644 --- a/submodules/MtProtoKit/MtProtoKit_Xcode.xcodeproj/project.pbxproj +++ b/submodules/MtProtoKit/MtProtoKit_Xcode.xcodeproj/project.pbxproj @@ -1929,6 +1929,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, ); mainGroup = D05A830918AFB3F9007F1076; @@ -2492,7 +2493,7 @@ ); GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; - GCC_TREAT_WARNINGS_AS_ERRORS = YES; + GCC_TREAT_WARNINGS_AS_ERRORS = NO; HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/openssl"; INFOPLIST_FILE = MtProtoKitDynamic/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -2770,7 +2771,7 @@ "DEBUG=1", "$(inherited)", ); - GCC_TREAT_WARNINGS_AS_ERRORS = YES; + GCC_TREAT_WARNINGS_AS_ERRORS = NO; HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/openssl"; INFOPLIST_FILE = MtProtoKitDynamic/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -3018,7 +3019,7 @@ "DEBUG=1", "$(inherited)", ); - GCC_TREAT_WARNINGS_AS_ERRORS = YES; + GCC_TREAT_WARNINGS_AS_ERRORS = NO; HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/openssl"; INFOPLIST_FILE = MtProtoKitDynamic/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -3266,7 +3267,7 @@ "DEBUG=1", "$(inherited)", ); - GCC_TREAT_WARNINGS_AS_ERRORS = YES; + GCC_TREAT_WARNINGS_AS_ERRORS = NO; HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/openssl"; INFOPLIST_FILE = MtProtoKitDynamic/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -3675,7 +3676,7 @@ "DEBUG=1", "$(inherited)", ); - GCC_TREAT_WARNINGS_AS_ERRORS = YES; + GCC_TREAT_WARNINGS_AS_ERRORS = NO; HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/openssl"; INFOPLIST_FILE = MtProtoKitDynamic/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -3902,7 +3903,7 @@ "$(PROJECT_DIR)/thirdparty", ); GCC_NO_COMMON_BLOCKS = YES; - GCC_TREAT_WARNINGS_AS_ERRORS = YES; + GCC_TREAT_WARNINGS_AS_ERRORS = NO; HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/openssl"; INFOPLIST_FILE = MtProtoKitDynamic/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -4122,7 +4123,7 @@ "$(PROJECT_DIR)/thirdparty", ); GCC_NO_COMMON_BLOCKS = YES; - GCC_TREAT_WARNINGS_AS_ERRORS = YES; + GCC_TREAT_WARNINGS_AS_ERRORS = NO; HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/openssl"; INFOPLIST_FILE = MtProtoKitDynamic/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -4341,7 +4342,7 @@ "$(PROJECT_DIR)/thirdparty", ); GCC_NO_COMMON_BLOCKS = YES; - GCC_TREAT_WARNINGS_AS_ERRORS = YES; + GCC_TREAT_WARNINGS_AS_ERRORS = NO; HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/openssl"; INFOPLIST_FILE = MtProtoKitDynamic/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -4560,7 +4561,7 @@ "$(PROJECT_DIR)/thirdparty", ); GCC_NO_COMMON_BLOCKS = YES; - GCC_TREAT_WARNINGS_AS_ERRORS = YES; + GCC_TREAT_WARNINGS_AS_ERRORS = NO; HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/openssl"; INFOPLIST_FILE = MtProtoKitDynamic/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -4801,7 +4802,7 @@ "DEBUG=1", "$(inherited)", ); - GCC_TREAT_WARNINGS_AS_ERRORS = YES; + GCC_TREAT_WARNINGS_AS_ERRORS = NO; HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/openssl"; INFOPLIST_FILE = MtProtoKitDynamic/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -5139,7 +5140,7 @@ "$(PROJECT_DIR)/thirdparty", ); GCC_NO_COMMON_BLOCKS = YES; - GCC_TREAT_WARNINGS_AS_ERRORS = YES; + GCC_TREAT_WARNINGS_AS_ERRORS = NO; HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/openssl"; INFOPLIST_FILE = MtProtoKitDynamic/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -5233,7 +5234,7 @@ "DEBUG=1", "$(inherited)", ); - GCC_TREAT_WARNINGS_AS_ERRORS = YES; + GCC_TREAT_WARNINGS_AS_ERRORS = NO; HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/openssl"; INFOPLIST_FILE = MtProtoKitDynamic/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -5285,7 +5286,7 @@ "$(PROJECT_DIR)/thirdparty", ); GCC_NO_COMMON_BLOCKS = YES; - GCC_TREAT_WARNINGS_AS_ERRORS = YES; + GCC_TREAT_WARNINGS_AS_ERRORS = NO; HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/openssl"; INFOPLIST_FILE = MtProtoKitDynamic/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -5534,7 +5535,7 @@ "DEBUG=1", "$(inherited)", ); - GCC_TREAT_WARNINGS_AS_ERRORS = YES; + GCC_TREAT_WARNINGS_AS_ERRORS = NO; HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/openssl"; INFOPLIST_FILE = MtProtoKitDynamic/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; diff --git a/submodules/TelegramUI/TelegramUI/ChatItemGalleryFooterContentNode.swift b/submodules/TelegramUI/TelegramUI/ChatItemGalleryFooterContentNode.swift index c09b73d672..ed2d2d9549 100644 --- a/submodules/TelegramUI/TelegramUI/ChatItemGalleryFooterContentNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatItemGalleryFooterContentNode.swift @@ -167,7 +167,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll private let messageContextDisposable = MetaDisposable() - private var videoFramePreviewNode: ASImageNode? + private var videoFramePreviewNode: (ASImageNode, ImmediateTextNode)? private var validLayout: (CGSize, LayoutMetrics, CGFloat, CGFloat, CGFloat, CGFloat)? @@ -230,6 +230,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll } private var scrubbingHandleRelativePosition: CGFloat = 0.0 + private var scrubbingVisualTimestamp: Double? var scrubberView: ChatVideoGalleryItemScrubberView? = nil { willSet { @@ -240,6 +241,23 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll didSet { if let scrubberView = self.scrubberView { self.view.addSubview(scrubberView) + scrubberView.updateScrubbingVisual = { [weak self] value in + guard let strongSelf = self else { + return + } + if let value = value { + strongSelf.scrubbingVisualTimestamp = value + if let (videoFramePreviewNode, videoFrameTextNode) = strongSelf.videoFramePreviewNode { + videoFrameTextNode.attributedText = NSAttributedString(string: stringForDuration(Int32(value)), font: Font.regular(13.0), textColor: .white) + let textSize = videoFrameTextNode.updateLayout(CGSize(width: 100.0, height: 100.0)) + let imageFrame = videoFramePreviewNode.frame + let textOffset = (Int((imageFrame.size.width - videoFrameTextNode.bounds.width) / 2) / 2) * 2 + videoFrameTextNode.frame = CGRect(origin: CGPoint(x: CGFloat(textOffset), y: imageFrame.size.height - videoFrameTextNode.bounds.height - 5.0), size: textSize) + } + } else { + strongSelf.scrubbingVisualTimestamp = nil + } + } scrubberView.updateScrubbingHandlePosition = { [weak self] value in guard let strongSelf = self else { return @@ -638,15 +656,31 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll self.dateNode.frame = CGRect(origin: CGPoint(x: floor((width - dateSize.width) / 2.0), y: panelHeight - bottomInset - 44.0 + floor((44.0 - dateSize.height - authorNameSize.height - labelsSpacing) / 2.0) + authorNameSize.height + labelsSpacing), size: dateSize) } - if let videoFramePreviewNode = self.videoFramePreviewNode { + if let (videoFramePreviewNode, videoFrameTextNode) = self.videoFramePreviewNode { let intrinsicImageSize = videoFramePreviewNode.image?.size ?? CGSize(width: 320.0, height: 240.0) - let imageSize = intrinsicImageSize.aspectFitted(CGSize(width: 200.0, height: 200.0)) - var imageFrame = CGRect(origin: CGPoint(x: leftInset + floor(self.scrubbingHandleRelativePosition * (width - leftInset - rightInset) - imageSize.width / 2.0), y: self.scrollNode.frame.minY - 10.0 - imageSize.height), size: imageSize) + let fitSize: CGSize + if intrinsicImageSize.width < intrinsicImageSize.height { + fitSize = CGSize(width: 90.0, height: 160.0) + } else { + fitSize = CGSize(width: 160.0, height: 90.0) + } + let scrubberInset: CGFloat + if size.width > size.height { + scrubberInset = 58.0 + } else { + scrubberInset = 13.0 + } + + let imageSize = intrinsicImageSize.aspectFitted(fitSize) + var imageFrame = CGRect(origin: CGPoint(x: leftInset + scrubberInset + floor(self.scrubbingHandleRelativePosition * (width - leftInset - rightInset - scrubberInset * 2.0) - imageSize.width / 2.0), y: self.scrollNode.frame.minY - 6.0 - imageSize.height), size: imageSize) imageFrame.origin.x = min(imageFrame.origin.x, width - rightInset - 10.0 - imageSize.width) imageFrame.origin.x = max(imageFrame.origin.x, leftInset + 10.0) videoFramePreviewNode.frame = imageFrame videoFramePreviewNode.subnodes?.first?.frame = CGRect(origin: CGPoint(), size: imageFrame.size) + + let textOffset = (Int((imageFrame.size.width - videoFrameTextNode.bounds.width) / 2) / 2) * 2 + videoFrameTextNode.frame = CGRect(origin: CGPoint(x: CGFloat(textOffset), y: imageFrame.size.height - videoFrameTextNode.bounds.height - 5.0), size: videoFrameTextNode.bounds.size) } return panelHeight @@ -1023,7 +1057,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll } func setFramePreviewImageIsLoading() { - if self.videoFramePreviewNode?.image != nil { + if self.videoFramePreviewNode?.0.image != nil { //self.videoFramePreviewNode?.subnodes?.first?.alpha = 1.0 } } @@ -1031,17 +1065,34 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll func setFramePreviewImage(image: UIImage?) { if let image = image { let videoFramePreviewNode: ASImageNode + let videoFrameTextNode: ImmediateTextNode var animateIn = false if let current = self.videoFramePreviewNode { - videoFramePreviewNode = current + videoFramePreviewNode = current.0 + videoFrameTextNode = current.1 } else { videoFramePreviewNode = ASImageNode() videoFramePreviewNode.displaysAsynchronously = false videoFramePreviewNode.displayWithoutProcessing = true + videoFramePreviewNode.clipsToBounds = true + videoFramePreviewNode.cornerRadius = 6.0 + let dimNode = ASDisplayNode() dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5) videoFramePreviewNode.addSubnode(dimNode) - self.videoFramePreviewNode = videoFramePreviewNode + + videoFrameTextNode = ImmediateTextNode() + videoFrameTextNode.displaysAsynchronously = false + videoFrameTextNode.maximumNumberOfLines = 1 + videoFrameTextNode.textShadowColor = .black + if let scrubbingVisualTimestamp = self.scrubbingVisualTimestamp { + videoFrameTextNode.attributedText = NSAttributedString(string: stringForDuration(Int32(scrubbingVisualTimestamp)), font: Font.regular(13.0), textColor: .white) + } + let textSize = videoFrameTextNode.updateLayout(CGSize(width: 100.0, height: 100.0)) + videoFrameTextNode.frame = CGRect(origin: CGPoint(), size: textSize) + videoFramePreviewNode.addSubnode(videoFrameTextNode) + + self.videoFramePreviewNode = (videoFramePreviewNode, videoFrameTextNode) self.addSubnode(videoFramePreviewNode) animateIn = true } @@ -1054,7 +1105,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll if animateIn { videoFramePreviewNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) } - } else if let videoFramePreviewNode = self.videoFramePreviewNode { + } else if let (videoFramePreviewNode, _) = self.videoFramePreviewNode { self.videoFramePreviewNode = nil videoFramePreviewNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false, completion: { [weak videoFramePreviewNode] _ in videoFramePreviewNode?.removeFromSupernode() diff --git a/submodules/TelegramUI/TelegramUI/ChatVideoGalleryItemScrubberView.swift b/submodules/TelegramUI/TelegramUI/ChatVideoGalleryItemScrubberView.swift index ab588236ca..9efbcc2e9a 100644 --- a/submodules/TelegramUI/TelegramUI/ChatVideoGalleryItemScrubberView.swift +++ b/submodules/TelegramUI/TelegramUI/ChatVideoGalleryItemScrubberView.swift @@ -43,6 +43,7 @@ final class ChatVideoGalleryItemScrubberView: UIView { } var updateScrubbing: (Double?) -> Void = { _ in } + var updateScrubbingVisual: (Double?) -> Void = { _ in } var updateScrubbingHandlePosition: (CGFloat) -> Void = { _ in } var seek: (Double) -> Void = { _ in } @@ -67,6 +68,7 @@ final class ChatVideoGalleryItemScrubberView: UIView { self.scrubberNode.update = { [weak self] timestamp, position in self?.updateScrubbing(timestamp) + self?.updateScrubbingVisual(timestamp) self?.updateScrubbingHandlePosition(position) } From 7cf95a743cef5090f1f100414a6b2c894056c0db Mon Sep 17 00:00:00 2001 From: Peter <> Date: Wed, 7 Aug 2019 20:56:13 +0300 Subject: [PATCH 5/8] Fix entitlements --- .../Telegram-iOS-AppStoreLLC.entitlements | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/Telegram-iOS/Telegram-iOS-AppStoreLLC.entitlements b/Telegram-iOS/Telegram-iOS-AppStoreLLC.entitlements index cf2fe70016..896fe795c9 100644 --- a/Telegram-iOS/Telegram-iOS-AppStoreLLC.entitlements +++ b/Telegram-iOS/Telegram-iOS-AppStoreLLC.entitlements @@ -2,15 +2,15 @@ - com.apple.developer.icloud-services - - CloudDocuments - CloudKit - - com.apple.developer.icloud-container-identifiers - - iCloud.$(CFBundleIdentifier) - + com.apple.developer.icloud-services + + CloudDocuments + CloudKit + + com.apple.developer.icloud-container-identifiers + + iCloud.$(CFBundleIdentifier) + aps-environment production com.apple.developer.associated-domains @@ -33,7 +33,5 @@ merchant.privatbank.test.telergramios merchant.privatbank.prod.telergram - com.apple.developer.carplay-messaging - com.apple.developer.carplay-calling From b313383e5e35e97bac7f903d8da534b7bc9fe20f Mon Sep 17 00:00:00 2001 From: Peter <> Date: Thu, 8 Aug 2019 13:22:50 +0300 Subject: [PATCH 6/8] Fix report and leave --- .../TelegramUI/ChannelInfoController.swift | 2 +- .../TelegramUI/ChatController.swift | 10 +- .../PeerMediaCollectionController.swift | 2 +- .../TelegramUI/PeerReportController.swift | 95 ++++++++++--------- .../TelegramUI/UserInfoController.swift | 2 +- 5 files changed, 61 insertions(+), 50 deletions(-) diff --git a/submodules/TelegramUI/TelegramUI/ChannelInfoController.swift b/submodules/TelegramUI/TelegramUI/ChannelInfoController.swift index e1aa3d8747..97e590f45c 100644 --- a/submodules/TelegramUI/TelegramUI/ChannelInfoController.swift +++ b/submodules/TelegramUI/TelegramUI/ChannelInfoController.swift @@ -881,7 +881,7 @@ public func channelInfoController(context: AccountContext, peerId: PeerId) -> Vi }, reportChannel: { presentControllerImpl?(peerReportOptionsController(context: context, subject: .peer(peerId), present: { c, a in presentControllerImpl?(c, a) - }), nil) + }, completion: { _ in }), nil) }, leaveChannel: { let _ = (context.account.postbox.transaction { transaction -> Peer? in return transaction.getPeer(peerId) diff --git a/submodules/TelegramUI/TelegramUI/ChatController.swift b/submodules/TelegramUI/TelegramUI/ChatController.swift index 532e25a1f4..e1f6ff3bc6 100644 --- a/submodules/TelegramUI/TelegramUI/ChatController.swift +++ b/submodules/TelegramUI/TelegramUI/ChatController.swift @@ -2618,14 +2618,14 @@ public final class ChatController: TelegramBaseController, GalleryHiddenMediaTar if let strongSelf = self, let messageIds = strongSelf.presentationInterfaceState.interfaceState.selectionState?.selectedIds, !messageIds.isEmpty { strongSelf.present(peerReportOptionsController(context: strongSelf.context, subject: .messages(Array(messageIds).sorted()), present: { c, a in self?.present(c, in: .window(.root), with: a) - }), in: .window(.root)) + }, completion: { _ in }), in: .window(.root)) } }, reportMessages: { [weak self] messages in if let strongSelf = self, !messages.isEmpty { strongSelf.chatDisplayNode.dismissInput() strongSelf.present(peerReportOptionsController(context: strongSelf.context, subject: .messages(messages.map({ $0.id }).sorted()), present: { c, a in self?.present(c, in: .window(.root), with: a) - }), in: .window(.root)) + }, completion: { _ in }), in: .window(.root)) } }, deleteMessages: { [weak self] messages, contextController, completion in if let strongSelf = self, !messages.isEmpty { @@ -6087,6 +6087,12 @@ public final class ChatController: TelegramBaseController, GalleryHiddenMediaTar if let peer = peer as? TelegramChannel, let username = peer.username, !username.isEmpty { self.present(peerReportOptionsController(context: self.context, subject: .peer(peer.id), present: { [weak self] c, a in self?.present(c, in: .window(.root)) + }, completion: { [weak self] success in + guard let strongSelf = self, success else { + return + } + let _ = removePeerChat(account: strongSelf.context.account, peerId: chatPeer.id, reportChatSpam: false).start() + (strongSelf.navigationController as? NavigationController)?.filterController(strongSelf, animated: true) }), in: .window(.root)) } else if let _ = peer as? TelegramUser { let presentationData = self.presentationData diff --git a/submodules/TelegramUI/TelegramUI/PeerMediaCollectionController.swift b/submodules/TelegramUI/TelegramUI/PeerMediaCollectionController.swift index 1c60f92110..3164009fe3 100644 --- a/submodules/TelegramUI/TelegramUI/PeerMediaCollectionController.swift +++ b/submodules/TelegramUI/TelegramUI/PeerMediaCollectionController.swift @@ -294,7 +294,7 @@ public class PeerMediaCollectionController: TelegramBaseController { if let strongSelf = self, let messageIds = strongSelf.interfaceState.selectionState?.selectedIds, !messageIds.isEmpty { strongSelf.present(peerReportOptionsController(context: strongSelf.context, subject: .messages(Array(messageIds).sorted()), present: { c, a in self?.present(c, in: .window(.root), with: a) - }), in: .window(.root)) + }, completion: { _ in }), in: .window(.root)) } }, reportMessages: { _ in }, deleteMessages: { _, _, f in diff --git a/submodules/TelegramUI/TelegramUI/PeerReportController.swift b/submodules/TelegramUI/TelegramUI/PeerReportController.swift index fe0e527b9e..edf4060a39 100644 --- a/submodules/TelegramUI/TelegramUI/PeerReportController.swift +++ b/submodules/TelegramUI/TelegramUI/PeerReportController.swift @@ -22,7 +22,7 @@ private enum PeerReportOption { case other } -func peerReportOptionsController(context: AccountContext, subject: PeerReportSubject, present: @escaping (ViewController, Any?) -> Void) -> ViewController { +func peerReportOptionsController(context: AccountContext, subject: PeerReportSubject, present: @escaping (ViewController, Any?) -> Void, completion: @escaping (Bool) -> Void) -> ViewController { let presentationData = context.sharedContext.currentPresentationData.with { $0 } let controller = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: presentationData.theme)) @@ -75,15 +75,17 @@ func peerReportOptionsController(context: AccountContext, subject: PeerReportSub let _ = (reportPeer(account: context.account, peerId: peerId, reason: reportReason) |> deliverOnMainQueue).start(completed: { present(textAlertController(context: context, title: nil, text: presentationData.strings.ReportPeer_AlertSuccess, actions: [TextAlertAction(type: TextAlertActionType.defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) + completion(true) }) case let .messages(messageIds): let _ = (reportPeerMessages(account: context.account, messageIds: messageIds, reason: reportReason) |> deliverOnMainQueue).start(completed: { present(textAlertController(context: context, title: nil, text: presentationData.strings.ReportPeer_AlertSuccess, actions: [TextAlertAction.init(type: TextAlertActionType.defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) + completion(true) }) } } else { - controller?.present(peerReportController(context: context, subject: subject), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + controller?.present(peerReportController(context: context, subject: subject, completion: completion), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) } controller?.dismissAnimated() @@ -95,6 +97,7 @@ func peerReportOptionsController(context: AccountContext, subject: PeerReportSub ActionSheetItemGroup(items: [ ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { [weak controller] in controller?.dismissAnimated() + completion(false) }) ]) ]) @@ -187,7 +190,7 @@ private func peerReportControllerEntries(presentationData: PresentationData, sta return entries } -private func peerReportController(context: AccountContext, subject: PeerReportSubject) -> ViewController { +private func peerReportController(context: AccountContext, subject: PeerReportSubject, completion: @escaping (Bool) -> Void) -> ViewController { var dismissImpl: (() -> Void)? var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)? @@ -208,50 +211,52 @@ private func peerReportController(context: AccountContext, subject: PeerReportSu let reportDisposable = MetaDisposable() let signal = combineLatest(context.sharedContext.presentationData, statePromise.get()) - |> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, PeerReportControllerEntry.ItemGenerationArguments)) in - let rightButton: ItemListNavigationButton - if state.isReporting { - rightButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {}) - } else { - rightButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: !state.text.isEmpty, action: { - var text: String = "" - updateState { state in - var state = state - if !state.isReporting && !state.text.isEmpty { - text = state.text - state.isReporting = true - } - return state + |> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, PeerReportControllerEntry.ItemGenerationArguments)) in + let rightButton: ItemListNavigationButton + if state.isReporting { + rightButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {}) + } else { + rightButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: !state.text.isEmpty, action: { + var text: String = "" + updateState { state in + var state = state + if !state.isReporting && !state.text.isEmpty { + text = state.text + state.isReporting = true } - - if !text.isEmpty { - let completed: () -> Void = { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.ReportPeer_AlertSuccess, actions: [TextAlertAction.init(type: TextAlertActionType.defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) - dismissImpl?() - } - switch subject { - case let .peer(peerId): - reportDisposable.set((reportPeer(account: context.account, peerId: peerId, reason: .custom(text)) - |> deliverOnMainQueue).start(completed: { - completed() - })) - case let .messages(messageIds): - reportDisposable.set((reportPeerMessages(account: context.account, messageIds: messageIds, reason: .custom(text)) - |> deliverOnMainQueue).start(completed: { - completed() - })) - } + return state + } + + if !text.isEmpty { + let completed: () -> Void = { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.ReportPeer_AlertSuccess, actions: [TextAlertAction.init(type: TextAlertActionType.defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) + completion(true) + dismissImpl?() } - }) - } - - let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.ReportPeer_ReasonOther_Title), leftNavigationButton: ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { - dismissImpl?() - }), rightNavigationButton: rightButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back)) - let listState = ItemListNodeState(entries: peerReportControllerEntries(presentationData: presentationData, state: state), style: .blocks, focusItemTag: PeerReportControllerEntryTag.text) - - return (controllerState, (listState, arguments)) + switch subject { + case let .peer(peerId): + reportDisposable.set((reportPeer(account: context.account, peerId: peerId, reason: .custom(text)) + |> deliverOnMainQueue).start(completed: { + completed() + })) + case let .messages(messageIds): + reportDisposable.set((reportPeerMessages(account: context.account, messageIds: messageIds, reason: .custom(text)) + |> deliverOnMainQueue).start(completed: { + completed() + })) + } + } + }) + } + + let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.ReportPeer_ReasonOther_Title), leftNavigationButton: ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { + dismissImpl?() + completion(false) + }), rightNavigationButton: rightButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back)) + let listState = ItemListNodeState(entries: peerReportControllerEntries(presentationData: presentationData, state: state), style: .blocks, focusItemTag: PeerReportControllerEntryTag.text) + + return (controllerState, (listState, arguments)) } |> afterDisposed { reportDisposable.dispose() diff --git a/submodules/TelegramUI/TelegramUI/UserInfoController.swift b/submodules/TelegramUI/TelegramUI/UserInfoController.swift index 127d7d29c7..2fabcdb9de 100644 --- a/submodules/TelegramUI/TelegramUI/UserInfoController.swift +++ b/submodules/TelegramUI/TelegramUI/UserInfoController.swift @@ -1164,7 +1164,7 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Us }, report: { presentControllerImpl?(peerReportOptionsController(context: context, subject: .peer(peerId), present: { c, a in presentControllerImpl?(c, a) - }), nil) + }, completion: { _ in }), nil) }) let deviceContacts: Signal<[(DeviceContactStableId, DeviceContactBasicData)], NoError> = peerView.get() From 2e20138a3c6e4740c76f615e5537df2ab29a5a3f Mon Sep 17 00:00:00 2001 From: Peter <> Date: Thu, 8 Aug 2019 13:23:04 +0300 Subject: [PATCH 7/8] Fix context menu on grouped messages --- .../ChatMessageContextControllerContentSource.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageContextControllerContentSource.swift b/submodules/TelegramUI/TelegramUI/ChatMessageContextControllerContentSource.swift index d59840356c..e7014b23ac 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageContextControllerContentSource.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageContextControllerContentSource.swift @@ -26,7 +26,7 @@ final class ChatMessageContextControllerContentSource: ContextControllerContentS guard let item = itemNode.item else { return } - if item.message.stableId == self.message.stableId, let contentNode = itemNode.getMessageContextSourceNode() { + if item.content.contains(where: { $0.stableId == self.message.stableId }), let contentNode = itemNode.getMessageContextSourceNode() { result = ContextControllerTakeViewInfo(contentContainingNode: contentNode, contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil)) } } @@ -46,7 +46,7 @@ final class ChatMessageContextControllerContentSource: ContextControllerContentS guard let item = itemNode.item else { return } - if item.message.stableId == self.message.stableId { + if item.content.contains(where: { $0.stableId == self.message.stableId }) { result = ContextControllerPutBackViewInfo(contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil)) } } From 8621604cc378bf85df5a4962865d2b7c4664bf59 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Thu, 8 Aug 2019 16:54:05 +0300 Subject: [PATCH 8/8] Reactions API [skip ci] --- NotificationService/Serialization.swift | 2 +- .../Postbox/Postbox/MessageHistoryTable.swift | 28 -- submodules/TelegramApi/Sources/Api0.swift | 14 +- submodules/TelegramApi/Sources/Api1.swift | 226 ++++++++- submodules/TelegramApi/Sources/Api3.swift | 428 +++++++++++------- .../AccountIntermediateState.swift | 7 +- .../AccountStateManagementUtils.swift | 24 +- .../TelegramCore/ApplyUpdateMessage.swift | 2 +- .../TelegramCore/TelegramCore/Download.swift | 2 +- .../TelegramCore/ForwardGame.swift | 2 +- .../TelegramCore/MessageReactions.swift | 43 ++ .../TelegramCore/MultipartFetch.swift | 2 +- .../TelegramCore/PendingMessageManager.swift | 12 +- .../TelegramCore/TelegramCore/Polls.swift | 2 +- .../ReactionsMessageAttribute.swift | 91 ++++ .../TelegramCore/RequestEditMessage.swift | 4 +- .../TelegramCore/Serialization.swift | 2 +- .../TelegramCore/StandaloneSendMessage.swift | 4 +- .../TelegramCore/StoreMessage_Telegram.swift | 12 +- .../TelegramCore/UpdateMessageService.swift | 4 +- .../TelegramCore/UpdatesApiUtils.swift | 8 +- .../project.pbxproj | 12 + 22 files changed, 711 insertions(+), 220 deletions(-) create mode 100644 submodules/TelegramCore/TelegramCore/MessageReactions.swift create mode 100644 submodules/TelegramCore/TelegramCore/ReactionsMessageAttribute.swift diff --git a/NotificationService/Serialization.swift b/NotificationService/Serialization.swift index 425f455c35..f0477c29cc 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 103 + return 106 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/submodules/Postbox/Postbox/MessageHistoryTable.swift b/submodules/Postbox/Postbox/MessageHistoryTable.swift index 149766d01f..f6bb2f038a 100644 --- a/submodules/Postbox/Postbox/MessageHistoryTable.swift +++ b/submodules/Postbox/Postbox/MessageHistoryTable.swift @@ -2502,34 +2502,6 @@ final class MessageHistoryTable: Table { return (result, mediaRefs, count == 0 ? nil : lastIndex) } - func fetchBoundaries(peerId: PeerId, namespace: MessageId.Namespace, tag: MessageTags?) -> (lower: MessageIndex, upper: MessageIndex)? { - if let tag = tag { - let latest = self.tagsTable.earlierIndices(tag: tag, peerId: peerId, namespace: namespace, index: nil, includeFrom: false, count: 1) - let earliest = self.tagsTable.laterIndices(tag: tag, peerId: peerId, namespace: namespace, index: nil, includeFrom: false, count: 1) - if let latestIndex = latest.first, let earliestIndex = earliest.first { - return (earliestIndex, latestIndex) - } else { - return nil - } - } else { - var earliestIndex: MessageIndex? - self.valueBox.range(self.table, start: self.lowerBound(peerId: peerId, namespace: namespace), end: self.upperBound(peerId: peerId, namespace: namespace), keys: { key in - earliestIndex = extractKey(key) - return false - }, limit: 1) - var latestIndex: MessageIndex? - self.valueBox.range(self.table, start: self.upperBound(peerId: peerId, namespace: namespace), end: self.lowerBound(peerId: peerId, namespace: namespace), keys: { key in - latestIndex = extractKey(key) - return false - }, limit: 1) - if let latestIndex = latestIndex, let earliestIndex = earliestIndex { - return (earliestIndex, latestIndex) - } else { - return nil - } - } - } - func fetch(peerId: PeerId, namespace: MessageId.Namespace, tag: MessageTags?, from fromIndex: MessageIndex, includeFrom: Bool, to toIndex: MessageIndex, limit: Int) -> [IntermediateMessage] { precondition(fromIndex.id.peerId == toIndex.id.peerId) precondition(fromIndex.id.namespace == toIndex.id.namespace) diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 06a2721880..bf8801a83a 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -235,6 +235,9 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1667805217] = { return Api.Update.parse_updateReadHistoryInbox($0) } dict[1786671974] = { return Api.Update.parse_updatePeerSettings($0) } dict[-1263546448] = { return Api.Update.parse_updatePeerLocated($0) } + dict[967122427] = { return Api.Update.parse_updateNewScheduledMessage($0) } + dict[-1870238482] = { return Api.Update.parse_updateDeleteScheduledMessages($0) } + dict[357013699] = { return Api.Update.parse_updateMessageReactions($0) } dict[1558266229] = { return Api.PopularContact.parse_popularContact($0) } dict[-373643672] = { return Api.FolderPeer.parse_folderPeer($0) } dict[367766557] = { return Api.ChannelParticipant.parse_channelParticipant($0) } @@ -272,6 +275,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[471437699] = { return Api.account.WallPapers.parse_wallPapersNotModified($0) } dict[1881892265] = { return Api.account.WallPapers.parse_wallPapers($0) } dict[1158290442] = { return Api.messages.FoundGifs.parse_foundGifs($0) } + dict[-1199954735] = { return Api.MessageReactions.parse_messageReactions($0) } dict[-1132476723] = { return Api.FileLocation.parse_fileLocationToBeDeprecated($0) } dict[-716006138] = { return Api.Poll.parse_poll($0) } dict[423314455] = { return Api.InputNotifyPeer.parse_inputNotifyUsers($0) } @@ -416,6 +420,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-2128640689] = { return Api.account.SentEmailCode.parse_sentEmailCode($0) } dict[-1038136962] = { return Api.EncryptedFile.parse_encryptedFileEmpty($0) } dict[1248893260] = { return Api.EncryptedFile.parse_encryptedFile($0) } + dict[-557924733] = { return Api.CodeSettings.parse_codeSettings($0) } dict[-391902247] = { return Api.SecureValueError.parse_secureValueErrorData($0) } dict[12467706] = { return Api.SecureValueError.parse_secureValueErrorFrontSide($0) } dict[-2037765467] = { return Api.SecureValueError.parse_secureValueErrorReverseSide($0) } @@ -547,7 +552,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[773059779] = { return Api.User.parse_user($0) } dict[-2082087340] = { return Api.Message.parse_messageEmpty($0) } dict[-1642487306] = { return Api.Message.parse_messageService($0) } - dict[1157215293] = { return Api.Message.parse_message($0) } + dict[-1186706133] = { return Api.Message.parse_message($0) } dict[186120336] = { return Api.messages.RecentStickers.parse_recentStickersNotModified($0) } dict[586395571] = { return Api.messages.RecentStickers.parse_recentStickers($0) } dict[-182231723] = { return Api.InputFileLocation.parse_inputEncryptedFileLocation($0) } @@ -601,6 +606,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1625153079] = { return Api.InputWebFileLocation.parse_inputWebFileGeoPointLocation($0) } dict[-332168592] = { return Api.MessageFwdHeader.parse_messageFwdHeader($0) } dict[398898678] = { return Api.help.Support.parse_support($0) } + dict[1873957073] = { return Api.ReactionCount.parse_reactionCount($0) } dict[1474492012] = { return Api.MessagesFilter.parse_inputMessagesFilterEmpty($0) } dict[-1777752804] = { return Api.MessagesFilter.parse_inputMessagesFilterPhotos($0) } dict[-1614803355] = { return Api.MessagesFilter.parse_inputMessagesFilterVideo($0) } @@ -950,6 +956,8 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.messages.FoundGifs: _1.serialize(buffer, boxed) + case let _1 as Api.MessageReactions: + _1.serialize(buffer, boxed) case let _1 as Api.FileLocation: _1.serialize(buffer, boxed) case let _1 as Api.Poll: @@ -1060,6 +1068,8 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.EncryptedFile: _1.serialize(buffer, boxed) + case let _1 as Api.CodeSettings: + _1.serialize(buffer, boxed) case let _1 as Api.SecureValueError: _1.serialize(buffer, boxed) case let _1 as Api.NotifyPeer: @@ -1236,6 +1246,8 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.help.Support: _1.serialize(buffer, boxed) + case let _1 as Api.ReactionCount: + _1.serialize(buffer, boxed) case let _1 as Api.MessagesFilter: _1.serialize(buffer, boxed) case let _1 as Api.messages.Dialogs: diff --git a/submodules/TelegramApi/Sources/Api1.swift b/submodules/TelegramApi/Sources/Api1.swift index 391a88b56b..c0c9dda062 100644 --- a/submodules/TelegramApi/Sources/Api1.swift +++ b/submodules/TelegramApi/Sources/Api1.swift @@ -4019,6 +4019,9 @@ public extension Api { case updateReadHistoryInbox(flags: Int32, folderId: Int32?, peer: Api.Peer, maxId: Int32, stillUnreadCount: Int32, pts: Int32, ptsCount: Int32) case updatePeerSettings(peer: Api.Peer, settings: Api.PeerSettings) case updatePeerLocated(peers: [Api.PeerLocated]) + case updateNewScheduledMessage(message: Api.Message) + case updateDeleteScheduledMessages(peer: Api.Peer, messages: [Int32]) + case updateMessageReactions(peer: Api.Peer, msgId: Int32, reactions: Api.MessageReactions) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -4620,6 +4623,31 @@ public extension Api { item.serialize(buffer, true) } break + case .updateNewScheduledMessage(let message): + if boxed { + buffer.appendInt32(967122427) + } + message.serialize(buffer, true) + break + case .updateDeleteScheduledMessages(let peer, let messages): + if boxed { + buffer.appendInt32(-1870238482) + } + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(messages.count)) + for item in messages { + serializeInt32(item, buffer: buffer, boxed: false) + } + break + case .updateMessageReactions(let peer, let msgId, let reactions): + if boxed { + buffer.appendInt32(357013699) + } + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + reactions.serialize(buffer, true) + break } } @@ -4767,6 +4795,12 @@ public extension Api { return ("updatePeerSettings", [("peer", peer), ("settings", settings)]) case .updatePeerLocated(let peers): return ("updatePeerLocated", [("peers", peers)]) + case .updateNewScheduledMessage(let message): + return ("updateNewScheduledMessage", [("message", message)]) + case .updateDeleteScheduledMessages(let peer, let messages): + return ("updateDeleteScheduledMessages", [("peer", peer), ("messages", messages)]) + case .updateMessageReactions(let peer, let msgId, let reactions): + return ("updateMessageReactions", [("peer", peer), ("msgId", msgId), ("reactions", reactions)]) } } @@ -5981,6 +6015,58 @@ public extension Api { return nil } } + public static func parse_updateNewScheduledMessage(_ reader: BufferReader) -> Update? { + var _1: Api.Message? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Message + } + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateNewScheduledMessage(message: _1!) + } + else { + return nil + } + } + public static func parse_updateDeleteScheduledMessages(_ reader: BufferReader) -> Update? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: [Int32]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateDeleteScheduledMessages(peer: _1!, messages: _2!) + } + else { + return nil + } + } + public static func parse_updateMessageReactions(_ reader: BufferReader) -> Update? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Int32? + _2 = reader.readInt32() + var _3: Api.MessageReactions? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.MessageReactions + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateMessageReactions(peer: _1!, msgId: _2!, reactions: _3!) + } + else { + return nil + } + } } public enum PopularContact: TypeConstructorDescription { @@ -6878,6 +6964,50 @@ public extension Api { } } + } + public enum MessageReactions: TypeConstructorDescription { + case messageReactions(flags: Int32, results: [Api.ReactionCount]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .messageReactions(let flags, let results): + if boxed { + buffer.appendInt32(-1199954735) + } + serializeInt32(flags, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(results.count)) + for item in results { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .messageReactions(let flags, let results): + return ("messageReactions", [("flags", flags), ("results", results)]) + } + } + + public static func parse_messageReactions(_ reader: BufferReader) -> MessageReactions? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.ReactionCount]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ReactionCount.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageReactions.messageReactions(flags: _1!, results: _2!) + } + else { + return nil + } + } + } public enum FileLocation: TypeConstructorDescription { case fileLocationToBeDeprecated(volumeId: Int64, localId: Int32) @@ -10300,6 +10430,40 @@ public extension Api { } } + } + public enum CodeSettings: TypeConstructorDescription { + case codeSettings(flags: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .codeSettings(let flags): + if boxed { + buffer.appendInt32(-557924733) + } + serializeInt32(flags, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .codeSettings(let flags): + return ("codeSettings", [("flags", flags)]) + } + } + + public static func parse_codeSettings(_ reader: BufferReader) -> CodeSettings? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.CodeSettings.codeSettings(flags: _1!) + } + else { + return nil + } + } + } public enum SecureValueError: TypeConstructorDescription { case secureValueErrorData(type: Api.SecureValueType, dataHash: Buffer, field: String, text: String) @@ -13928,7 +14092,7 @@ public extension Api { public enum Message: TypeConstructorDescription { case messageEmpty(id: Int32) case messageService(flags: Int32, id: Int32, fromId: Int32?, toId: Api.Peer, replyToMsgId: Int32?, date: Int32, action: Api.MessageAction) - case message(flags: Int32, id: Int32, fromId: Int32?, toId: Api.Peer, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int32?, replyToMsgId: Int32?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, editDate: Int32?, postAuthor: String?, groupedId: Int64?) + case message(flags: Int32, id: Int32, fromId: Int32?, toId: Api.Peer, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int32?, replyToMsgId: Int32?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, reactions: Api.MessageReactions?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -13950,9 +14114,9 @@ public extension Api { serializeInt32(date, buffer: buffer, boxed: false) action.serialize(buffer, true) break - case .message(let flags, let id, let fromId, let toId, let fwdFrom, let viaBotId, let replyToMsgId, let date, let message, let media, let replyMarkup, let entities, let views, let editDate, let postAuthor, let groupedId): + case .message(let flags, let id, let fromId, let toId, let fwdFrom, let viaBotId, let replyToMsgId, let date, let message, let media, let replyMarkup, let entities, let views, let editDate, let postAuthor, let groupedId, let reactions): if boxed { - buffer.appendInt32(1157215293) + buffer.appendInt32(-1186706133) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(id, buffer: buffer, boxed: false) @@ -13974,6 +14138,7 @@ public extension Api { if Int(flags) & Int(1 << 15) != 0 {serializeInt32(editDate!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 16) != 0 {serializeString(postAuthor!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 17) != 0 {serializeInt64(groupedId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 20) != 0 {reactions!.serialize(buffer, true)} break } } @@ -13984,8 +14149,8 @@ public extension Api { return ("messageEmpty", [("id", id)]) case .messageService(let flags, let id, let fromId, let toId, let replyToMsgId, let date, let action): return ("messageService", [("flags", flags), ("id", id), ("fromId", fromId), ("toId", toId), ("replyToMsgId", replyToMsgId), ("date", date), ("action", action)]) - case .message(let flags, let id, let fromId, let toId, let fwdFrom, let viaBotId, let replyToMsgId, let date, let message, let media, let replyMarkup, let entities, let views, let editDate, let postAuthor, let groupedId): - return ("message", [("flags", flags), ("id", id), ("fromId", fromId), ("toId", toId), ("fwdFrom", fwdFrom), ("viaBotId", viaBotId), ("replyToMsgId", replyToMsgId), ("date", date), ("message", message), ("media", media), ("replyMarkup", replyMarkup), ("entities", entities), ("views", views), ("editDate", editDate), ("postAuthor", postAuthor), ("groupedId", groupedId)]) + case .message(let flags, let id, let fromId, let toId, let fwdFrom, let viaBotId, let replyToMsgId, let date, let message, let media, let replyMarkup, let entities, let views, let editDate, let postAuthor, let groupedId, let reactions): + return ("message", [("flags", flags), ("id", id), ("fromId", fromId), ("toId", toId), ("fwdFrom", fwdFrom), ("viaBotId", viaBotId), ("replyToMsgId", replyToMsgId), ("date", date), ("message", message), ("media", media), ("replyMarkup", replyMarkup), ("entities", entities), ("views", views), ("editDate", editDate), ("postAuthor", postAuthor), ("groupedId", groupedId), ("reactions", reactions)]) } } @@ -14076,6 +14241,10 @@ public extension Api { if Int(_1!) & Int(1 << 16) != 0 {_15 = parseString(reader) } var _16: Int64? if Int(_1!) & Int(1 << 17) != 0 {_16 = reader.readInt64() } + var _17: Api.MessageReactions? + if Int(_1!) & Int(1 << 20) != 0 {if let signature = reader.readInt32() { + _17 = Api.parse(reader, signature: signature) as? Api.MessageReactions + } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 8) == 0) || _3 != nil @@ -14092,8 +14261,9 @@ public extension Api { let _c14 = (Int(_1!) & Int(1 << 15) == 0) || _14 != nil let _c15 = (Int(_1!) & Int(1 << 16) == 0) || _15 != nil let _c16 = (Int(_1!) & Int(1 << 17) == 0) || _16 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 { - return Api.Message.message(flags: _1!, id: _2!, fromId: _3, toId: _4!, fwdFrom: _5, viaBotId: _6, replyToMsgId: _7, date: _8!, message: _9!, media: _10, replyMarkup: _11, entities: _12, views: _13, editDate: _14, postAuthor: _15, groupedId: _16) + let _c17 = (Int(_1!) & Int(1 << 20) == 0) || _17 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 { + return Api.Message.message(flags: _1!, id: _2!, fromId: _3, toId: _4!, fwdFrom: _5, viaBotId: _6, replyToMsgId: _7, date: _8!, message: _9!, media: _10, replyMarkup: _11, entities: _12, views: _13, editDate: _14, postAuthor: _15, groupedId: _16, reactions: _17) } else { return nil @@ -15586,6 +15756,48 @@ public extension Api { } } + } + public enum ReactionCount: TypeConstructorDescription { + case reactionCount(flags: Int32, reaction: String, count: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .reactionCount(let flags, let reaction, let count): + if boxed { + buffer.appendInt32(1873957073) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(reaction, buffer: buffer, boxed: false) + serializeInt32(count, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .reactionCount(let flags, let reaction, let count): + return ("reactionCount", [("flags", flags), ("reaction", reaction), ("count", count)]) + } + } + + public static func parse_reactionCount(_ reader: BufferReader) -> ReactionCount? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.ReactionCount.reactionCount(flags: _1!, reaction: _2!, count: _3!) + } + else { + return nil + } + } + } public enum MessagesFilter: TypeConstructorDescription { case inputMessagesFilterEmpty diff --git a/submodules/TelegramApi/Sources/Api3.swift b/submodules/TelegramApi/Sources/Api3.swift index d5a1815eb7..2748d65e98 100644 --- a/submodules/TelegramApi/Sources/Api3.swift +++ b/submodules/TelegramApi/Sources/Api3.swift @@ -1211,30 +1211,6 @@ public extension Api { }) } - public static func sendMessage(flags: Int32, peer: Api.InputPeer, replyToMsgId: Int32?, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-91733382) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)} - serializeString(message, buffer: buffer, boxed: false) - serializeInt64(randomId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {replyMarkup!.serialize(buffer, true)} - if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) - }} - return (FunctionDescription(name: "messages.sendMessage", parameters: [("flags", flags), ("peer", peer), ("replyToMsgId", replyToMsgId), ("message", message), ("randomId", randomId), ("replyMarkup", replyMarkup), ("entities", entities)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } - public static func reportSpam(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() buffer.appendInt32(-820669733) @@ -1850,25 +1826,6 @@ public extension Api { }) } - public static func sendInlineBotResult(flags: Int32, peer: Api.InputPeer, replyToMsgId: Int32?, randomId: Int64, queryId: Int64, id: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1318189314) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)} - serializeInt64(randomId, buffer: buffer, boxed: false) - serializeInt64(queryId, buffer: buffer, boxed: false) - serializeString(id, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.sendInlineBotResult", parameters: [("flags", flags), ("peer", peer), ("replyToMsgId", replyToMsgId), ("randomId", randomId), ("queryId", queryId), ("id", id)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } - public static func getMessageEditData(peer: Api.InputPeer, id: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() buffer.appendInt32(-39416522) @@ -2321,53 +2278,6 @@ public extension Api { }) } - public static func sendMultiMedia(flags: Int32, peer: Api.InputPeer, replyToMsgId: Int32?, multiMedia: [Api.InputSingleMedia]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(546656559) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)} - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(multiMedia.count)) - for item in multiMedia { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "messages.sendMultiMedia", parameters: [("flags", flags), ("peer", peer), ("replyToMsgId", replyToMsgId), ("multiMedia", multiMedia)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } - - public static func forwardMessages(flags: Int32, fromPeer: Api.InputPeer, id: [Int32], randomId: [Int64], toPeer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1888354709) - serializeInt32(flags, buffer: buffer, boxed: false) - fromPeer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(randomId.count)) - for item in randomId { - serializeInt64(item, buffer: buffer, boxed: false) - } - toPeer.serialize(buffer, true) - return (FunctionDescription(name: "messages.forwardMessages", parameters: [("flags", flags), ("fromPeer", fromPeer), ("id", id), ("randomId", randomId), ("toPeer", toPeer)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } - public static func uploadEncryptedFile(peer: Api.InputEncryptedChat, file: Api.InputEncryptedFile) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() buffer.appendInt32(1347929239) @@ -2403,31 +2313,6 @@ public extension Api { }) } - public static func sendMedia(flags: Int32, peer: Api.InputPeer, replyToMsgId: Int32?, media: Api.InputMedia, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1194252757) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)} - media.serialize(buffer, true) - serializeString(message, buffer: buffer, boxed: false) - serializeInt64(randomId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {replyMarkup!.serialize(buffer, true)} - if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) - }} - return (FunctionDescription(name: "messages.sendMedia", parameters: [("flags", flags), ("peer", peer), ("replyToMsgId", replyToMsgId), ("media", media), ("message", message), ("randomId", randomId), ("replyMarkup", replyMarkup), ("entities", entities)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } - public static func getMessages(id: [Api.InputMessage]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() buffer.appendInt32(1673946374) @@ -2682,30 +2567,6 @@ public extension Api { }) } - public static func editMessage(flags: Int32, peer: Api.InputPeer, id: Int32, message: String?, media: Api.InputMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-787025122) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 11) != 0 {serializeString(message!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 14) != 0 {media!.serialize(buffer, true)} - if Int(flags) & Int(1 << 2) != 0 {replyMarkup!.serialize(buffer, true)} - if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) - }} - return (FunctionDescription(name: "messages.editMessage", parameters: [("flags", flags), ("peer", peer), ("id", id), ("message", message), ("media", media), ("replyMarkup", replyMarkup), ("entities", entities)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } - public static func editInlineBotMessage(flags: Int32, id: Api.InputBotInlineMessageID, message: String?, media: Api.InputMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() buffer.appendInt32(-2091549254) @@ -2955,6 +2816,262 @@ public extension Api { return result }) } + + public static func sendMessage(flags: Int32, peer: Api.InputPeer, replyToMsgId: Int32?, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1376532592) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)} + serializeString(message, buffer: buffer, boxed: false) + serializeInt64(randomId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {replyMarkup!.serialize(buffer, true)} + if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "messages.sendMessage", parameters: [("flags", flags), ("peer", peer), ("replyToMsgId", replyToMsgId), ("message", message), ("randomId", randomId), ("replyMarkup", replyMarkup), ("entities", entities), ("scheduleDate", scheduleDate)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } + + public static func sendMedia(flags: Int32, peer: Api.InputPeer, replyToMsgId: Int32?, media: Api.InputMedia, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(881978281) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)} + media.serialize(buffer, true) + serializeString(message, buffer: buffer, boxed: false) + serializeInt64(randomId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {replyMarkup!.serialize(buffer, true)} + if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "messages.sendMedia", parameters: [("flags", flags), ("peer", peer), ("replyToMsgId", replyToMsgId), ("media", media), ("message", message), ("randomId", randomId), ("replyMarkup", replyMarkup), ("entities", entities), ("scheduleDate", scheduleDate)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } + + public static func sendInlineBotResult(flags: Int32, peer: Api.InputPeer, replyToMsgId: Int32?, randomId: Int64, queryId: Int64, id: String, scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(570955184) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)} + serializeInt64(randomId, buffer: buffer, boxed: false) + serializeInt64(queryId, buffer: buffer, boxed: false) + serializeString(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "messages.sendInlineBotResult", parameters: [("flags", flags), ("peer", peer), ("replyToMsgId", replyToMsgId), ("randomId", randomId), ("queryId", queryId), ("id", id), ("scheduleDate", scheduleDate)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } + + public static func sendMultiMedia(flags: Int32, peer: Api.InputPeer, replyToMsgId: Int32?, multiMedia: [Api.InputSingleMedia], scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-872345397) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)} + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(multiMedia.count)) + for item in multiMedia { + item.serialize(buffer, true) + } + if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "messages.sendMultiMedia", parameters: [("flags", flags), ("peer", peer), ("replyToMsgId", replyToMsgId), ("multiMedia", multiMedia), ("scheduleDate", scheduleDate)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } + + public static func forwardMessages(flags: Int32, fromPeer: Api.InputPeer, id: [Int32], randomId: [Int64], toPeer: Api.InputPeer, scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-637606386) + serializeInt32(flags, buffer: buffer, boxed: false) + fromPeer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomId.count)) + for item in randomId { + serializeInt64(item, buffer: buffer, boxed: false) + } + toPeer.serialize(buffer, true) + if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "messages.forwardMessages", parameters: [("flags", flags), ("fromPeer", fromPeer), ("id", id), ("randomId", randomId), ("toPeer", toPeer), ("scheduleDate", scheduleDate)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } + + public static func getScheduledHistory(peer: Api.InputPeer, hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-490575781) + peer.serialize(buffer, true) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getScheduledHistory", parameters: [("peer", peer), ("hash", hash)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } + + public static func getScheduledMessages(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1111817116) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.getScheduledMessages", parameters: [("peer", peer), ("id", id)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } + + public static func sendScheduledMessages(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1120369398) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.sendScheduledMessages", parameters: [("peer", peer), ("id", id)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } + + public static func deleteScheduledMessages(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1504586518) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.deleteScheduledMessages", parameters: [("peer", peer), ("id", id)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } + + public static func editMessage(flags: Int32, peer: Api.InputPeer, id: Int32, message: String?, media: Api.InputMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1224152952) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 11) != 0 {serializeString(message!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 14) != 0 {media!.serialize(buffer, true)} + if Int(flags) & Int(1 << 2) != 0 {replyMarkup!.serialize(buffer, true)} + if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + }} + if Int(flags) & Int(1 << 15) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "messages.editMessage", parameters: [("flags", flags), ("peer", peer), ("id", id), ("message", message), ("media", media), ("replyMarkup", replyMarkup), ("entities", entities), ("scheduleDate", scheduleDate)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } + + public static func sendReaction(peer: Api.InputPeer, msgId: Int32, reaction: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(666939980) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(reaction.count)) + for item in reaction { + serializeString(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.sendReaction", parameters: [("peer", peer), ("msgId", msgId), ("reaction", reaction)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } + + public static func getMessagesReactions(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1950707482) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.getMessagesReactions", parameters: [("peer", peer), ("id", id)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } } public struct channels { public static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { @@ -4596,22 +4713,6 @@ public extension Api { }) } - public static func getFile(location: Api.InputFileLocation, offset: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-475607115) - location.serialize(buffer, true) - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "upload.getFile", parameters: [("location", location), ("offset", offset), ("limit", limit)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.upload.File? in - let reader = BufferReader(buffer) - var result: Api.upload.File? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.upload.File - } - return result - }) - } - public static func saveBigFilePart(fileId: Int64, filePart: Int32, fileTotalParts: Int32, bytes: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() buffer.appendInt32(-562337987) @@ -4705,6 +4806,23 @@ public extension Api { return result }) } + + public static func getFile(flags: Int32, location: Api.InputFileLocation, offset: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1319462148) + serializeInt32(flags, buffer: buffer, boxed: false) + location.serialize(buffer, true) + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "upload.getFile", parameters: [("flags", flags), ("location", location), ("offset", offset), ("limit", limit)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.upload.File? in + let reader = BufferReader(buffer) + var result: Api.upload.File? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.upload.File + } + return result + }) + } } public struct account { public static func updateNotifySettings(peer: Api.InputNotifyPeer, settings: Api.InputPeerNotifySettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { diff --git a/submodules/TelegramCore/TelegramCore/AccountIntermediateState.swift b/submodules/TelegramCore/TelegramCore/AccountIntermediateState.swift index 3464682d33..447163567f 100644 --- a/submodules/TelegramCore/TelegramCore/AccountIntermediateState.swift +++ b/submodules/TelegramCore/TelegramCore/AccountIntermediateState.swift @@ -70,6 +70,7 @@ enum AccountStateMutationOperation { case DeleteMessages([MessageId]) case EditMessage(MessageId, StoreMessage) case UpdateMessagePoll(MediaId, Api.Poll?, Api.PollResults) + case UpdateMessageReactions(MessageId, Api.MessageReactions) case UpdateMedia(MediaId, Media?) case ReadInbox(MessageId) case ReadOutbox(MessageId, Int32?) @@ -219,6 +220,10 @@ struct AccountMutableState { self.addOperation(.UpdateMessagePoll(id, poll, results)) } + mutating func updateMessageReactions(_ messageId: MessageId, reactions: Api.MessageReactions) { + self.addOperation(.UpdateMessageReactions(messageId, reactions)) + } + mutating func updateMedia(_ id: MediaId, media: Media?) { self.addOperation(.UpdateMedia(id, media)) } @@ -398,7 +403,7 @@ struct AccountMutableState { mutating func addOperation(_ operation: AccountStateMutationOperation) { switch operation { - case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby: + case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby: break case let .AddMessages(messages, location): for message in messages { diff --git a/submodules/TelegramCore/TelegramCore/AccountStateManagementUtils.swift b/submodules/TelegramCore/TelegramCore/AccountStateManagementUtils.swift index 51e9cbf389..49393ae99b 100644 --- a/submodules/TelegramCore/TelegramCore/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/TelegramCore/AccountStateManagementUtils.swift @@ -1287,6 +1287,8 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo updatedState.updateLangPack(langCode: langCode, difference: difference) case let .updateMessagePoll(_, pollId, poll, results): updatedState.updateMessagePoll(MediaId(namespace: Namespaces.Media.CloudPoll, id: pollId), poll: poll, results: results) + case let .updateMessageReactions(peer, msgId, reactions): + updatedState.updateMessageReactions(MessageId(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, id: msgId), reactions: reactions) case let .updateFolderPeers(folderPeers, _, _): for folderPeer in folderPeers { switch folderPeer { @@ -2013,7 +2015,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation]) var currentAddMessages: OptimizeAddMessagesState? for operation in operations { switch operation { - case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby: + case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby: if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty { result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location)) } @@ -2282,6 +2284,26 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP updatedPoll = updatedPoll.withUpdatedResults(TelegramMediaPollResults(apiResults: results), min: resultsMin) updateMessageMedia(transaction: transaction, id: pollId, media: updatedPoll) } + case let .UpdateMessageReactions(messageId, reactions): + transaction.updateMessage(messageId, update: { + var storeForwardInfo: StoreMessageForwardInfo? + if let forwardInfo = currentMessage.forwardInfo { + storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature) + } + var attributes = currentMessage.attributes + var found = false + loop: for j in 0 ..< attributes.count { + if let attribute = attributes[j] as? ReactionsMessageAttribute { + attributes[j] = attribute.withUpdatedResults(reactions) + found = true + break loop + } + } + if !found { + attributes.append(ReactionsMessageAttribute(apiReactions: reactions)) + } + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + }) case let .UpdateMedia(id, media): if let media = media as? TelegramMediaWebpage { updatedWebpages[id] = media diff --git a/submodules/TelegramCore/TelegramCore/ApplyUpdateMessage.swift b/submodules/TelegramCore/TelegramCore/ApplyUpdateMessage.swift index 851095631b..b629f7dc2b 100644 --- a/submodules/TelegramCore/TelegramCore/ApplyUpdateMessage.swift +++ b/submodules/TelegramCore/TelegramCore/ApplyUpdateMessage.swift @@ -57,7 +57,7 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes var updatedTimestamp: Int32? if let apiMessage = apiMessage { switch apiMessage { - case let .message(_, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _): updatedTimestamp = date case .messageEmpty: break diff --git a/submodules/TelegramCore/TelegramCore/Download.swift b/submodules/TelegramCore/TelegramCore/Download.swift index 9b1f814f11..506637a87c 100644 --- a/submodules/TelegramCore/TelegramCore/Download.swift +++ b/submodules/TelegramCore/TelegramCore/Download.swift @@ -196,7 +196,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate { updatedLength += 1 } - let data = Api.functions.upload.getFile(location: location, offset: Int32(offset), limit: Int32(updatedLength)) + let data = Api.functions.upload.getFile(flags: 0, location: location, offset: Int32(offset), limit: Int32(updatedLength)) request.setPayload(data.1.makeData() as Data, metadata: WrappedRequestMetadata(metadata: WrappedFunctionDescription(data.0), tag: nil), shortMetadata: WrappedRequestShortMetadata(shortMetadata: WrappedShortFunctionDescription(data.0)), responseParser: { response in if let result = data.2.parse(Buffer(data: response)) { diff --git a/submodules/TelegramCore/TelegramCore/ForwardGame.swift b/submodules/TelegramCore/TelegramCore/ForwardGame.swift index d782cc890e..c2facd228f 100644 --- a/submodules/TelegramCore/TelegramCore/ForwardGame.swift +++ b/submodules/TelegramCore/TelegramCore/ForwardGame.swift @@ -12,7 +12,7 @@ import Foundation public func forwardGameWithScore(account: Account, messageId: MessageId, to peerId: PeerId) -> Signal { return account.postbox.transaction { transaction -> Signal in if let message = transaction.getMessage(messageId), let fromPeer = transaction.getPeer(messageId.peerId), let fromInputPeer = apiInputPeer(fromPeer), let toPeer = transaction.getPeer(peerId), let toInputPeer = apiInputPeer(toPeer) { - return account.network.request(Api.functions.messages.forwardMessages(flags: 1 << 8, fromPeer: fromInputPeer, id: [messageId.id], randomId: [arc4random64()], toPeer: toInputPeer)) + return account.network.request(Api.functions.messages.forwardMessages(flags: 1 << 8, fromPeer: fromInputPeer, id: [messageId.id], randomId: [arc4random64()], toPeer: toInputPeer, scheduleDate: nil)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) diff --git a/submodules/TelegramCore/TelegramCore/MessageReactions.swift b/submodules/TelegramCore/TelegramCore/MessageReactions.swift new file mode 100644 index 0000000000..b229541251 --- /dev/null +++ b/submodules/TelegramCore/TelegramCore/MessageReactions.swift @@ -0,0 +1,43 @@ +import Foundation +#if os(macOS) +import PostboxMac +import SwiftSignalKitMac +import MtProtoKitMac +import TelegramApiMac +#else +import Postbox +import SwiftSignalKit +import TelegramApi +#if BUCK +import MtProtoKit +#else +import MtProtoKitDynamic +#endif +#endif + + +public enum RequestUpdateMessageReactionError { + case generic +} + +public func requestUpdateMessageReaction(account: Account, messageId: MessageId, reactions: [String]) -> Signal { + return account.postbox.loadedPeerWithId(messageId.peerId) + |> take(1) + |> introduceError(RequestUpdateMessageReactionError.self) + |> mapToSignal { peer in + guard let inputPeer = apiInputPeer(peer) else { + return .fail(.generic) + } + if messageId.namespace != Namespaces.Message.Cloud { + return .fail(.generic) + } + return account.network.request(Api.functions.messages.sendReaction(peer: inputPeer, msgId: messageId.id, reaction: reactions)) + |> mapError { _ -> RequestUpdateMessageReactionError in + return .generic + } + |> mapToSignal { result -> Signal in + account.stateManager.addUpdates(result) + return .complete() + } + } +} diff --git a/submodules/TelegramCore/TelegramCore/MultipartFetch.swift b/submodules/TelegramCore/TelegramCore/MultipartFetch.swift index 50f4861c58..c9d058a265 100644 --- a/submodules/TelegramCore/TelegramCore/MultipartFetch.swift +++ b/submodules/TelegramCore/TelegramCore/MultipartFetch.swift @@ -300,7 +300,7 @@ private enum MultipartFetchSource { case .revalidate: return .fail(.revalidateMediaReference) case let .location(parsedLocation): - return download.request(Api.functions.upload.getFile(location: parsedLocation, offset: offset, limit: Int32(limit)), tag: tag, continueInBackground: continueInBackground) + return download.request(Api.functions.upload.getFile(flags: 0, location: parsedLocation, offset: offset, limit: Int32(limit)), tag: tag, continueInBackground: continueInBackground) |> mapError { error -> MultipartFetchDownloadError in if error.errorDescription.hasPrefix("FILEREF_INVALID") || error.errorDescription.hasPrefix("FILE_REFERENCE_") { return .revalidateMediaReference diff --git a/submodules/TelegramCore/TelegramCore/PendingMessageManager.swift b/submodules/TelegramCore/TelegramCore/PendingMessageManager.swift index f3b3013918..62c9bce109 100644 --- a/submodules/TelegramCore/TelegramCore/PendingMessageManager.swift +++ b/submodules/TelegramCore/TelegramCore/PendingMessageManager.swift @@ -663,7 +663,7 @@ public final class PendingMessageManager { } else if let inputSourcePeerId = forwardPeerIds.first, let inputSourcePeer = transaction.getPeer(inputSourcePeerId).flatMap(apiInputPeer) { let dependencyTag = PendingMessageRequestDependencyTag(messageId: messages[0].0.id) - sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: inputSourcePeer, id: forwardIds.map { $0.0.id }, randomId: forwardIds.map { $0.1 }, toPeer: inputPeer), tag: dependencyTag) + sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: inputSourcePeer, id: forwardIds.map { $0.0.id }, randomId: forwardIds.map { $0.1 }, toPeer: inputPeer, scheduleDate: nil), tag: dependencyTag) } else { assertionFailure() sendMessageRequest = .fail(MTRpcError(errorCode: 400, errorDescription: "Invalid forward source")) @@ -707,7 +707,7 @@ public final class PendingMessageManager { } } - sendMessageRequest = network.request(Api.functions.messages.sendMultiMedia(flags: flags, peer: inputPeer, replyToMsgId: replyMessageId, multiMedia: singleMedias)) + sendMessageRequest = network.request(Api.functions.messages.sendMultiMedia(flags: flags, peer: inputPeer, replyToMsgId: replyMessageId, multiMedia: singleMedias, scheduleDate: nil)) } return sendMessageRequest @@ -917,13 +917,13 @@ public final class PendingMessageManager { let sendMessageRequest: Signal, MTRpcError> switch content.content { case .text: - sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyToMsgId: replyMessageId, message: message.text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities), info: .acknowledgement, tag: dependencyTag) + sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyToMsgId: replyMessageId, message: message.text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: nil), info: .acknowledgement, tag: dependencyTag) case let .media(inputMedia, text): - sendMessageRequest = network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyToMsgId: replyMessageId, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities), tag: dependencyTag) + sendMessageRequest = network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyToMsgId: replyMessageId, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: nil), tag: dependencyTag) |> map(NetworkRequestResult.result) case let .forward(sourceInfo): if let forwardSourceInfoAttribute = forwardSourceInfoAttribute, let sourcePeer = transaction.getPeer(forwardSourceInfoAttribute.messageId.peerId), let sourceInputPeer = apiInputPeer(sourcePeer) { - sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: sourceInputPeer, id: [sourceInfo.messageId.id], randomId: [uniqueId], toPeer: inputPeer), tag: dependencyTag) + sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: sourceInputPeer, id: [sourceInfo.messageId.id], randomId: [uniqueId], toPeer: inputPeer, scheduleDate: nil), tag: dependencyTag) |> map(NetworkRequestResult.result) } else { sendMessageRequest = .fail(MTRpcError(errorCode: 400, errorDescription: "internal")) @@ -932,7 +932,7 @@ public final class PendingMessageManager { if chatContextResult.hideVia { flags |= Int32(1 << 11) } - sendMessageRequest = network.request(Api.functions.messages.sendInlineBotResult(flags: flags, peer: inputPeer, replyToMsgId: replyMessageId, randomId: uniqueId, queryId: chatContextResult.queryId, id: chatContextResult.id)) + sendMessageRequest = network.request(Api.functions.messages.sendInlineBotResult(flags: flags, peer: inputPeer, replyToMsgId: replyMessageId, randomId: uniqueId, queryId: chatContextResult.queryId, id: chatContextResult.id, scheduleDate: nil)) |> map(NetworkRequestResult.result) case .messageScreenshot: sendMessageRequest = network.request(Api.functions.messages.sendScreenshotNotification(peer: inputPeer, replyToMsgId: replyMessageId ?? 0, randomId: uniqueId)) diff --git a/submodules/TelegramCore/TelegramCore/Polls.swift b/submodules/TelegramCore/TelegramCore/Polls.swift index c6223c533d..155aa5c311 100644 --- a/submodules/TelegramCore/TelegramCore/Polls.swift +++ b/submodules/TelegramCore/TelegramCore/Polls.swift @@ -60,7 +60,7 @@ public func requestClosePoll(postbox: Postbox, network: Network, stateManager: A } var flags: Int32 = 0 flags |= 1 << 14 - return network.request(Api.functions.messages.editMessage(flags: flags, peer: inputPeer, id: messageId.id, message: nil, media: .inputMediaPoll(poll: .poll(id: poll.pollId.id, flags: 1 << 0, question: poll.text, answers: poll.options.map({ $0.apiOption }))), replyMarkup: nil, entities: nil)) + return network.request(Api.functions.messages.editMessage(flags: flags, peer: inputPeer, id: messageId.id, message: nil, media: .inputMediaPoll(poll: .poll(id: poll.pollId.id, flags: 1 << 0, question: poll.text, answers: poll.options.map({ $0.apiOption }))), replyMarkup: nil, entities: nil, scheduleDate: nil)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) diff --git a/submodules/TelegramCore/TelegramCore/ReactionsMessageAttribute.swift b/submodules/TelegramCore/TelegramCore/ReactionsMessageAttribute.swift new file mode 100644 index 0000000000..45ce41b4a3 --- /dev/null +++ b/submodules/TelegramCore/TelegramCore/ReactionsMessageAttribute.swift @@ -0,0 +1,91 @@ +import Foundation +#if os(macOS) +import PostboxMac +#else +import Postbox +#endif +import TelegramApi + +public struct MessageReaction: Equatable, PostboxCoding { + public var value: String + public var count: Int32 + public var isSelected: Bool + + public init(value: String, count: Int32, isSelected: Bool) { + self.value = value + self.count = count + self.isSelected = isSelected + } + + public init(decoder: PostboxDecoder) { + self.value = decoder.decodeStringForKey("v", orElse: "") + self.count = decoder.decodeInt32ForKey("c", orElse: 0) + self.isSelected = decoder.decodeInt32ForKey("s", orElse: 0) != 0 + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeString(self.value, forKey: "v") + encoder.encodeInt32(self.count, forKey: "c") + encoder.encodeInt32(self.isSelected ? 1 : 0, forKey: "s") + } +} + +public class ReactionsMessageAttribute: MessageAttribute { + public let reactions: [MessageReaction] + + init(reactions: [MessageReaction]) { + self.reactions = reactions + } + + required public init(decoder: PostboxDecoder) { + self.reactions = decoder.decodeObjectArrayWithDecoderForKey("r") + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeObjectArray(self.reactions, forKey: "r") + } + + func withUpdatedResults(_ reactions: Api.MessageReactions) -> ReactionsMessageAttribute { + switch reactions { + case let .messageReactions(flags, results): + let min = (flags & (1 << 0)) != 0 + var reactions = results.map { result -> MessageReaction in + switch result { + case let .reactionCount(flags, reaction, count): + return MessageReaction(value: reaction, count: count, isSelected: (flags & (1 << 0)) != 0) + } + } + if min { + var currentSelectedReaction: String? + for reaction in self.reactions { + if reaction.isSelected { + currentSelectedReaction = reaction.value + break + } + } + if let currentSelectedReaction = currentSelectedReaction { + for i in 0 ..< reactions.count { + if reactions[i].value == currentSelectedReaction { + reactions[i].isSelected = true + } + } + } + } + return ReactionsMessageAttribute(reactions: reactions) + } + } +} + +extension ReactionsMessageAttribute { + convenience init(apiReactions: Api.MessageReactions) { + switch apiReactions { + case let .messageReactions(_, results): + self.init(reactions: results.map { result in + switch result { + case let .reactionCount(flags, reaction, count): + return MessageReaction(value: reaction, count: count, isSelected: (flags & (1 << 0)) != 0) + } + }) + } + } +} diff --git a/submodules/TelegramCore/TelegramCore/RequestEditMessage.swift b/submodules/TelegramCore/TelegramCore/RequestEditMessage.swift index 3cb90b9ed0..71e3e66f96 100644 --- a/submodules/TelegramCore/TelegramCore/RequestEditMessage.swift +++ b/submodules/TelegramCore/TelegramCore/RequestEditMessage.swift @@ -150,7 +150,7 @@ private func requestEditMessageInternal(account: Account, messageId: MessageId, flags |= Int32(1 << 14) } - return account.network.request(Api.functions.messages.editMessage(flags: flags, peer: inputPeer, id: messageId.id, message: text, media: inputMedia, replyMarkup: nil, entities: apiEntities)) + return account.network.request(Api.functions.messages.editMessage(flags: flags, peer: inputPeer, id: messageId.id, message: text, media: inputMedia, replyMarkup: nil, entities: apiEntities, scheduleDate: nil)) |> map { result -> Api.Updates? in return result } @@ -223,7 +223,7 @@ public func requestEditLiveLocation(postbox: Postbox, network: Network, stateMan } else { inputMedia = .inputMediaGeoLive(flags: 1 << 0, geoPoint: .inputGeoPoint(lat: media.latitude, long: media.longitude), period: nil) } - return network.request(Api.functions.messages.editMessage(flags: 1 << 14, peer: inputPeer, id: messageId.id, message: nil, media: inputMedia, replyMarkup: nil, entities: nil)) + return network.request(Api.functions.messages.editMessage(flags: 1 << 14, peer: inputPeer, id: messageId.id, message: nil, media: inputMedia, replyMarkup: nil, entities: nil, scheduleDate: nil)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) diff --git a/submodules/TelegramCore/TelegramCore/Serialization.swift b/submodules/TelegramCore/TelegramCore/Serialization.swift index 2a78e8b6c2..acd34716b8 100644 --- a/submodules/TelegramCore/TelegramCore/Serialization.swift +++ b/submodules/TelegramCore/TelegramCore/Serialization.swift @@ -220,7 +220,7 @@ public class BoxedMessage: NSObject { public class Serialization: NSObject, MTSerialization { public func currentLayer() -> UInt { - return 104 + return 106 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/submodules/TelegramCore/TelegramCore/StandaloneSendMessage.swift b/submodules/TelegramCore/TelegramCore/StandaloneSendMessage.swift index 692a5f5916..a576fd9e12 100644 --- a/submodules/TelegramCore/TelegramCore/StandaloneSendMessage.swift +++ b/submodules/TelegramCore/TelegramCore/StandaloneSendMessage.swift @@ -118,12 +118,12 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M let sendMessageRequest: Signal switch content { case let .text(text): - sendMessageRequest = account.network.request(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyToMsgId: replyMessageId, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities)) + sendMessageRequest = account.network.request(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyToMsgId: replyMessageId, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: nil)) |> `catch` { _ -> Signal in return .complete() } case let .media(inputMedia, text): - sendMessageRequest = account.network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyToMsgId: replyMessageId, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities)) + sendMessageRequest = account.network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyToMsgId: replyMessageId, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: nil)) |> `catch` { _ -> Signal in return .complete() } diff --git a/submodules/TelegramCore/TelegramCore/StoreMessage_Telegram.swift b/submodules/TelegramCore/TelegramCore/StoreMessage_Telegram.swift index b6c467fac7..ccdafffa3a 100644 --- a/submodules/TelegramCore/TelegramCore/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/TelegramCore/StoreMessage_Telegram.swift @@ -111,7 +111,7 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute], func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? { switch messsage { - case let .message(flags, _, fromId, toId, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(flags, _, fromId, toId, _, _, _, _, _, _, _, _, _, _, _, _, _): switch toId { case let .peerUser(userId): return PeerId(namespace: Namespaces.Peer.CloudUser, id: (flags & Int32(2)) != 0 ? userId : (fromId ?? userId)) @@ -136,7 +136,7 @@ func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? { func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { switch message { - case let .message(flags, _, fromId, toId, fwdHeader, viaBotId, _, _, _, media, _, entities, _, _, _, _): + case let .message(flags, _, fromId, toId, fwdHeader, viaBotId, _, _, _, media, _, entities, _, _, _, _, _): let peerId: PeerId switch toId { case let .peerUser(userId): @@ -240,7 +240,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { func apiMessageAssociatedMessageIds(_ message: Api.Message) -> [MessageId]? { switch message { - case let .message(flags, _, fromId, toId, _, _, replyToMsgId, _, _, _, _, _, _, _, _, _): + case let .message(flags, _, fromId, toId, _, _, replyToMsgId, _, _, _, _, _, _, _, _, _, _): if let replyToMsgId = replyToMsgId { let peerId: PeerId switch toId { @@ -382,7 +382,7 @@ func messageTextEntitiesFromApiEntities(_ entities: [Api.MessageEntity]) -> [Mes extension StoreMessage { convenience init?(apiMessage: Api.Message) { switch apiMessage { - case let .message(flags, id, fromId, toId, fwdFrom, viaBotId, replyToMsgId, date, message, media, replyMarkup, entities, views, editDate, postAuthor, groupingId): + case let .message(flags, id, fromId, toId, fwdFrom, viaBotId, replyToMsgId, date, message, media, replyMarkup, entities, views, editDate, postAuthor, groupingId, reactions): let peerId: PeerId var authorId: PeerId? switch toId { @@ -537,6 +537,10 @@ extension StoreMessage { attributes.append(ContentRequiresValidationMessageAttribute()) } + if let reactions = reactions { + attributes.append(ReactionsMessageAttribute(apiReactions: reactions)) + } + var storeFlags = StoreMessageFlags() if let replyMarkup = replyMarkup { diff --git a/submodules/TelegramCore/TelegramCore/UpdateMessageService.swift b/submodules/TelegramCore/TelegramCore/UpdateMessageService.swift index 0f1676ecd2..37ad1d3396 100644 --- a/submodules/TelegramCore/TelegramCore/UpdateMessageService.swift +++ b/submodules/TelegramCore/TelegramCore/UpdateMessageService.swift @@ -69,7 +69,7 @@ class UpdateMessageService: NSObject, MTMessageService { self.putNext(groups) } case let .updateShortChatMessage(flags, id, fromId, chatId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyToMsgId, entities): - let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: fromId, toId: Api.Peer.peerChat(chatId: chatId), fwdFrom: fwdFrom, viaBotId: viaBotId, replyToMsgId: replyToMsgId, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, editDate: nil, postAuthor: nil, groupedId: nil) + let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: fromId, toId: Api.Peer.peerChat(chatId: chatId), fwdFrom: fwdFrom, viaBotId: viaBotId, replyToMsgId: replyToMsgId, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil) let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount) let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil) if groups.count != 0 { @@ -86,7 +86,7 @@ class UpdateMessageService: NSObject, MTMessageService { generatedToId = Api.Peer.peerUser(userId: self.peerId.id) } - let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: generatedFromId, toId: generatedToId, fwdFrom: fwdFrom, viaBotId: viaBotId, replyToMsgId: replyToMsgId, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, editDate: nil, postAuthor: nil, groupedId: nil) + let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: generatedFromId, toId: generatedToId, fwdFrom: fwdFrom, viaBotId: viaBotId, replyToMsgId: replyToMsgId, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil) let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount) let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil) if groups.count != 0 { diff --git a/submodules/TelegramCore/TelegramCore/UpdatesApiUtils.swift b/submodules/TelegramCore/TelegramCore/UpdatesApiUtils.swift index a44ed98572..768d0b53d5 100644 --- a/submodules/TelegramCore/TelegramCore/UpdatesApiUtils.swift +++ b/submodules/TelegramCore/TelegramCore/UpdatesApiUtils.swift @@ -100,7 +100,7 @@ extension Api.MessageMedia { extension Api.Message { var rawId: Int32 { switch self { - case let .message(_, id, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, id, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return id case let .messageEmpty(id): return id @@ -111,7 +111,7 @@ extension Api.Message { var id: MessageId? { switch self { - case let .message(flags, id, fromId, toId, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(flags, id, fromId, toId, _, _, _, _, _, _, _, _, _, _, _, _, _): let peerId: PeerId switch toId { case let .peerUser(userId): @@ -141,7 +141,7 @@ extension Api.Message { var timestamp: Int32? { switch self { - case let .message(_, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _): return date case let .messageService(_, _, _, _, _, date, _): return date @@ -152,7 +152,7 @@ extension Api.Message { var preCachedResources: [(MediaResource, Data)]? { switch self { - case let .message(_, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _): return media?.preCachedResources default: return nil diff --git a/submodules/TelegramCore/TelegramCore_Xcode.xcodeproj/project.pbxproj b/submodules/TelegramCore/TelegramCore_Xcode.xcodeproj/project.pbxproj index 0f29a052f2..c1fcaf8628 100644 --- a/submodules/TelegramCore/TelegramCore_Xcode.xcodeproj/project.pbxproj +++ b/submodules/TelegramCore/TelegramCore_Xcode.xcodeproj/project.pbxproj @@ -206,6 +206,10 @@ D02DADC12139A1FC00116225 /* ContactSyncManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02DADC02139A1FC00116225 /* ContactSyncManager.swift */; }; D02DADC22139A1FC00116225 /* ContactSyncManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02DADC02139A1FC00116225 /* ContactSyncManager.swift */; }; D03121021DA57E93006A2A60 /* TelegramPeerNotificationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03121011DA57E93006A2A60 /* TelegramPeerNotificationSettings.swift */; }; + D0329EA222FC5A7C00F9F071 /* MessageReactions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0329EA122FC5A7C00F9F071 /* MessageReactions.swift */; }; + D0329EA322FC5A7C00F9F071 /* MessageReactions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0329EA122FC5A7C00F9F071 /* MessageReactions.swift */; }; + D0329EA522FC5A9600F9F071 /* ReactionsMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0329EA422FC5A9600F9F071 /* ReactionsMessageAttribute.swift */; }; + D0329EA622FC5A9600F9F071 /* ReactionsMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0329EA422FC5A9600F9F071 /* ReactionsMessageAttribute.swift */; }; D032F5BC20EF84FD00037B6C /* FetchedMediaResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D032F5BB20EF84FD00037B6C /* FetchedMediaResource.swift */; }; D032F5BD20EF84FD00037B6C /* FetchedMediaResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D032F5BB20EF84FD00037B6C /* FetchedMediaResource.swift */; }; D0338740223BD48B007A2CE4 /* ContactsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D033873F223BD48B007A2CE4 /* ContactsSettings.swift */; }; @@ -924,6 +928,8 @@ D02D60AA206BA64100FEFE1E /* VerifySecureIdValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerifySecureIdValue.swift; sourceTree = ""; }; D02DADC02139A1FC00116225 /* ContactSyncManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactSyncManager.swift; sourceTree = ""; }; D03121011DA57E93006A2A60 /* TelegramPeerNotificationSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramPeerNotificationSettings.swift; sourceTree = ""; }; + D0329EA122FC5A7C00F9F071 /* MessageReactions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageReactions.swift; sourceTree = ""; }; + D0329EA422FC5A9600F9F071 /* ReactionsMessageAttribute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionsMessageAttribute.swift; sourceTree = ""; }; D032F5BB20EF84FD00037B6C /* FetchedMediaResource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchedMediaResource.swift; sourceTree = ""; }; D033873F223BD48B007A2CE4 /* ContactsSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsSettings.swift; sourceTree = ""; }; D0338742223BD532007A2CE4 /* InitializeAccountAfterLogin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitializeAccountAfterLogin.swift; sourceTree = ""; }; @@ -1498,6 +1504,7 @@ C28725411EF967E700613564 /* NotificationInfoMessageAttribute.swift */, C210DD611FBDB90800F673D8 /* SourceReferenceMessageAttribute.swift */, D0439B5F228EDE430067E026 /* ContentRequiresValidationMessageAttribute.swift */, + D0329EA422FC5A9600F9F071 /* ReactionsMessageAttribute.swift */, ); name = Attributes; sourceTree = ""; @@ -1681,6 +1688,7 @@ D0EC55992101ED0800D1992C /* DeleteMessages.swift */, D01AC91C1DD5DA5E00E8160F /* RequestMessageActionCallback.swift */, D0AB262A21C3CE80008F6685 /* Polls.swift */, + D0329EA122FC5A7C00F9F071 /* MessageReactions.swift */, D01AC9201DD5E7E500E8160F /* RequestEditMessage.swift */, D0DC354D1DE368F7000195EB /* RequestChatContextResults.swift */, D0DC354F1DE36900000195EB /* ChatContextResult.swift */, @@ -2442,6 +2450,7 @@ D0561DE31E5737FC00E6B9E9 /* UpdatePeerInfo.swift in Sources */, D0DF0C8A1D819C7E008AEB01 /* JoinChannel.swift in Sources */, D051DB14215EC5A300F30F92 /* AppChangelogState.swift in Sources */, + D0329EA222FC5A7C00F9F071 /* MessageReactions.swift in Sources */, D04554A621B43440007A6DD9 /* CancelAccountReset.swift in Sources */, D04CAA5A1E83310D0047E51F /* MD5.swift in Sources */, D0E817492010E7E300B82BBB /* ChannelAdminEventLogContext.swift in Sources */, @@ -2487,6 +2496,7 @@ D0B844531DAC0773005F29E1 /* TelegramUserPresence.swift in Sources */, D08F4A661E79CC4A00A2AA15 /* SynchronizeInstalledStickerPacksOperations.swift in Sources */, D05A32E71E6F0B5C002760B4 /* RecentAccountSession.swift in Sources */, + D0329EA522FC5A9600F9F071 /* ReactionsMessageAttribute.swift in Sources */, D0E41301206B9E6E00BEE4A2 /* SecureIdAddressValue.swift in Sources */, D0B843871DA6F705005F29E1 /* UpdateCachedPeerData.swift in Sources */, D0E412F1206B9BB700BEE4A2 /* SecureIdPassportValue.swift in Sources */, @@ -2632,6 +2642,7 @@ 9F4EEF9F21DCF6E7002C3B33 /* ManagedAppConfigurationUpdates.swift in Sources */, 9F4EEFA021DCF6E7002C3B33 /* SynchronizeAppLogEventsOperation.swift in Sources */, 9F4EEFA121DCF6E7002C3B33 /* ManagedSynchronizeAppLogEventsOperations.swift in Sources */, + D0329EA622FC5A9600F9F071 /* ReactionsMessageAttribute.swift in Sources */, 9F4EEF9B21DCF66F002C3B33 /* JSON.swift in Sources */, 9F4EEF9C21DCF66F002C3B33 /* AppConfiguration.swift in Sources */, 9F4EEF9D21DCF66F002C3B33 /* SearchBotsConfiguration.swift in Sources */, @@ -2720,6 +2731,7 @@ D0448CA31E291B14005A61A7 /* FetchSecretFileResource.swift in Sources */, D0B8442F1DAB91E0005F29E1 /* NBMetadataHelper.m in Sources */, D0B8444C1DAB91FD005F29E1 /* UpdateCachedPeerData.swift in Sources */, + D0329EA322FC5A7C00F9F071 /* MessageReactions.swift in Sources */, D0FA8B9F1E1F973B001E855B /* SecretChatIncomingEncryptedOperation.swift in Sources */, D03C536C1DAD5CA9004C17B3 /* TelegramChannel.swift in Sources */, D0B418951D7E0580004562A4 /* TelegramMediaContact.swift in Sources */,