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 be875994d8..9a94725591 100644 --- a/submodules/TelegramUI/TelegramUI/AnimatedStickerNode.swift +++ b/submodules/TelegramUI/TelegramUI/AnimatedStickerNode.swift @@ -126,7 +126,7 @@ 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 @@ -403,7 +403,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 @@ -412,27 +412,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() + } + } + })) } } @@ -565,7 +565,7 @@ final class AnimatedStickerNode: ASDisplayNode { strongSelf.started() } }) - + strongSelf.playbackStatus.set(.single(AnimatedStickerStatus(playing: false, duration: duration, timestamp: 0.0))) } } diff --git a/submodules/TelegramUI/TelegramUI/AppDelegate.swift b/submodules/TelegramUI/TelegramUI/AppDelegate.swift index a49b99e764..c4f5048640 100644 --- a/submodules/TelegramUI/TelegramUI/AppDelegate.swift +++ b/submodules/TelegramUI/TelegramUI/AppDelegate.swift @@ -1121,13 +1121,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 @@ -1845,7 +1845,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 { @@ -1867,7 +1867,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) @@ -1878,7 +1878,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 a2518f2d52..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) - { - 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/ChatListController.swift b/submodules/TelegramUI/TelegramUI/ChatListController.swift index e9ae5fcd73..290af94f6f 100644 --- a/submodules/TelegramUI/TelegramUI/ChatListController.swift +++ b/submodules/TelegramUI/TelegramUI/ChatListController.swift @@ -846,6 +846,14 @@ public class ChatListController: TelegramBaseController, UIViewControllerPreview } } + 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 493229bf61..d11298fbc2 100644 --- a/submodules/TelegramUI/TelegramUI/ChatListControllerNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatListControllerNode.swift @@ -63,7 +63,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) { @@ -99,6 +100,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 ca331616de..e6f5443e94 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageAnimatedStickerItemNode.swift @@ -56,7 +56,7 @@ private class ChatMessageHeartbeatHaptic { if time > 2.0 { return } - + var startTime: Double = 0.0 var delay: Double = 0.0 @@ -201,7 +201,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } self.view.addGestureRecognizer(replyRecognizer) } - + override var visibility: ListViewItemNodeVisibility { didSet { let wasVisible = oldValue != .none @@ -237,7 +237,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 @@ -341,23 +341,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 { @@ -441,7 +441,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 { @@ -704,128 +704,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 { - 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 + } + 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 832f990d33..69f0b29f54 100644 --- a/submodules/TelegramUI/TelegramUI/ManagedAudioRecorder.swift +++ b/submodules/TelegramUI/TelegramUI/ManagedAudioRecorder.swift @@ -279,7 +279,7 @@ final class ManagedAudioRecorderContext { strongSelf.toneTimer?.invalidate() } } - }, queue: queue) + }, queue: queue) self.toneTimer = toneTimer toneTimer.start() } else { @@ -287,25 +287,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) @@ -314,7 +314,7 @@ final class ManagedAudioRecorderContext { self.idleTimerExtensionDisposable = (Signal { subscriber in return pushIdleTimerExtension() - } |> delay(5.0, queue: queue)).start() + } |> delay(5.0, queue: queue)).start() } deinit { @@ -401,19 +401,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 - } }) } } diff --git a/submodules/TelegramUI/TelegramUI/ShareExtensionContext.swift b/submodules/TelegramUI/TelegramUI/ShareExtensionContext.swift index d749e430b2..b4863bc731 100644 --- a/submodules/TelegramUI/TelegramUI/ShareExtensionContext.swift +++ b/submodules/TelegramUI/TelegramUI/ShareExtensionContext.swift @@ -294,7 +294,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 {