diff --git a/SiriIntents/IntentContacts.swift b/SiriIntents/IntentContacts.swift index 19ae8cf1e6..cfe9f540bf 100644 --- a/SiriIntents/IntentContacts.swift +++ b/SiriIntents/IntentContacts.swift @@ -12,13 +12,17 @@ struct MatchingDeviceContact { let phoneNumbers: [String] } -func matchingDeviceContacts(stableIds: [String]) -> Signal<[MatchingDeviceContact], NoError> { +enum IntentContactsError { + case generic +} + +func matchingDeviceContacts(stableIds: [String]) -> Signal<[MatchingDeviceContact], IntentContactsError> { guard CNContactStore.authorizationStatus(for: .contacts) == .authorized else { - return .single([]) + return .fail(.generic) } let store = CNContactStore() guard let contacts = try? store.unifiedContacts(matching: CNContact.predicateForContacts(withIdentifiers: stableIds), keysToFetch: [CNContactFormatter.descriptorForRequiredKeys(for: .fullName), CNContactPhoneNumbersKey as CNKeyDescriptor]) else { - return .single([]) + return .fail(.generic) } return .single(contacts.map({ contact in @@ -34,19 +38,58 @@ func matchingDeviceContacts(stableIds: [String]) -> Signal<[MatchingDeviceContac })) } +private func matchPhoneNumbers(_ lhs: String, _ rhs: String) -> Bool { + if lhs.count < 10 && lhs.count == rhs.count { + return lhs == rhs + } else if lhs.count >= 10 && rhs.count >= 10 && lhs.suffix(10) == rhs.suffix(10) { + return true + } else { + return false + } +} + func matchingCloudContacts(postbox: Postbox, contacts: [MatchingDeviceContact]) -> Signal<[(String, TelegramUser)], NoError> { return postbox.transaction { transaction -> [(String, TelegramUser)] in var result: [(String, TelegramUser)] = [] outer: for peerId in transaction.getContactPeerIds() { - if let peer = transaction.getPeer(peerId) as? TelegramUser, let phone = peer.phone { + if let peer = transaction.getPeer(peerId) as? TelegramUser, let peerPhoneNumber = peer.phone { for contact in contacts { for phoneNumber in contact.phoneNumbers { - if arePhoneNumbersEqual(phoneNumber, phone) { + if matchPhoneNumbers(phoneNumber, peerPhoneNumber) { result.append((contact.stableId, peer)) continue outer } } } +// var parsedPhoneNumbers: [String: ParsedPhoneNumber] = [:] +// let parsedPeerPhoneNumber: ParsedPhoneNumber? +// if let number = parsedPhoneNumbers[peerPhoneNumber] { +// parsedPeerPhoneNumber = number +// } else if let number = ParsedPhoneNumber(string: peerPhoneNumber) { +// parsedPeerPhoneNumber = number +// parsedPhoneNumbers[peerPhoneNumber] = number +// } else { +// parsedPeerPhoneNumber = nil +// } +// +// for contact in contacts { +// for phoneNumber in contact.phoneNumbers { +// let parsedPhoneNumber: ParsedPhoneNumber? +// if let number = parsedPhoneNumbers[phoneNumber] { +// parsedPhoneNumber = number +// } else if let number = ParsedPhoneNumber(string: phoneNumber) { +// parsedPhoneNumber = number +// parsedPhoneNumbers[phoneNumber] = number +// } else { +// parsedPhoneNumber = nil +// } +// +// if parsedPeerPhoneNumber == parsedPhoneNumber { +// result.append((contact.stableId, peer)) +// continue outer +// } +// } +// } } } return result diff --git a/SiriIntents/IntentHandler.swift b/SiriIntents/IntentHandler.swift index b79c9b2d39..1bc5d9ab2a 100644 --- a/SiriIntents/IntentHandler.swift +++ b/SiriIntents/IntentHandler.swift @@ -4,6 +4,7 @@ import TelegramCore import Postbox import SwiftSignalKit import BuildConfig +import Contacts private var accountCache: Account? @@ -47,7 +48,7 @@ enum IntentHandlingError { } class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessagesIntentHandling, INSetMessageAttributeIntentHandling, INStartAudioCallIntentHandling, INSearchCallHistoryIntentHandling { - private let accountPromise = Promise() + private let accountPromise = Promise() private let resolvePersonsDisposable = MetaDisposable() private let actionDisposable = MetaDisposable() @@ -86,7 +87,7 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown" - let account: Signal + let account: Signal if let accountCache = accountCache { account = .single(accountCache) } else { @@ -97,7 +98,7 @@ 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 + |> mapToSignal { account -> Signal in if let account = account { switch account { case .upgrading: @@ -117,12 +118,9 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag return .complete() } } else { - return .complete() + return .single(nil) } } - |> afterNext { account in - account.resetStateManagement() - } |> take(1) } self.accountPromise.set(account) @@ -138,6 +136,11 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag } private func resolve(persons: [INPerson]?, with completion: @escaping ([INPersonResolutionResult]) -> Void) { + guard CNContactStore.authorizationStatus(for: .contacts) == .authorized else { + completion([INPersonResolutionResult.notRequired()]) + return + } + guard let initialPersons = persons, !initialPersons.isEmpty else { completion([INPersonResolutionResult.needsValue()]) return @@ -205,8 +208,14 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag |> take(1) |> mapToSignal { matchedContacts in return account - |> mapToSignal { account in - return matchingCloudContacts(postbox: account.postbox, contacts: matchedContacts) + |> introduceError(IntentContactsError.self) + |> 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 @@ -219,6 +228,8 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag return INPersonResolutionResult.success(with: person) }) } + }, error: { error in + completion([INPersonResolutionResult.unsupported()]) })) } @@ -230,14 +241,20 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag let account = self.accountPromise.get() let signal = account - |> mapToSignal { account -> Signal in - return matchingCloudContact(postbox: account.postbox, peerId: PeerId(peerId)) - |> map { user -> INPerson? in - if let user = user { - return personWithUser(stableId: "tg\(peerId)", user: user) - } else { - return nil + |> 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 + } } + } else { + return .fail(.generic) } } @@ -248,6 +265,8 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag } else { completion([INPersonResolutionResult.needsValue()]) } + }, error: { error in + completion([INPersonResolutionResult.notRequired()]) })) } else { self.resolve(persons: intent.recipients, with: completion) @@ -258,6 +277,10 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag } func resolveContent(for intent: INSendMessageIntent, with completion: @escaping (INStringResolutionResult) -> Void) { + guard CNContactStore.authorizationStatus(for: .contacts) == .authorized else { + completion(INStringResolutionResult.notRequired()) + return + } if let text = intent.content, !text.isEmpty { completion(INStringResolutionResult.success(with: text)) } else { @@ -267,6 +290,11 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag func confirm(intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Void) { let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self)) + guard CNContactStore.authorizationStatus(for: .contacts) == .authorized else { + let response = INSendMessageIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity) + completion(response) + return + } let response = INSendMessageIntentResponse(code: .ready, userActivity: userActivity) completion(response) } @@ -278,6 +306,9 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag 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) } @@ -305,7 +336,7 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag } |> deliverOnMainQueue).start(error: { _ in let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self)) - let response = INSendMessageIntentResponse(code: .failure, userActivity: userActivity) + let response = INSendMessageIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity) completion(response) }, completed: { let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self)) @@ -325,18 +356,27 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag |> 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() - return account.stateManager.pollStateUpdateCompletion() + 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)) + |> afterDisposed { + account.shouldBeServiceTaskMaster.set(.single(.never)) + } } } |> deliverOnMainQueue).start(next: { messages in @@ -346,7 +386,7 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag completion(response) }, error: { _ in let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchForMessagesIntent.self)) - let response = INSearchForMessagesIntentResponse(code: .failure, userActivity: userActivity) + let response = INSearchForMessagesIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity) completion(response) })) } @@ -373,8 +413,11 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag return .generic } |> mapToSignal { account -> Signal in - var signals: [Signal] = [] + guard let account = account else { + return .fail(.generic) + } + var signals: [Signal] = [] var maxMessageIdsToApply: [PeerId: MessageId] = [:] if let identifiers = intent.identifiers { for identifier in identifiers { @@ -478,6 +521,10 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag |> 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) |> introduceError(IntentHandlingError.self) @@ -497,7 +544,7 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag completion(response) }, error: { _ in let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchCallHistoryIntent.self)) - let response = INSearchCallHistoryIntentResponse(code: .failure, userActivity: userActivity) + let response = INSearchCallHistoryIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity) completion(response) })) } diff --git a/SiriIntents/IntentMessages.swift b/SiriIntents/IntentMessages.swift index 8e29092f39..904faa445c 100644 --- a/SiriIntents/IntentMessages.swift +++ b/SiriIntents/IntentMessages.swift @@ -13,6 +13,10 @@ func unreadMessages(account: Account) -> Signal<[INMessage], NoError> { var signals: [Signal<[INMessage], NoError>] = [] for entry in view.0.entries { if case let .MessageEntry(index, _, readState, notificationSettings, _, _, _, _) = entry { + if index.messageIndex.id.peerId.namespace != Namespaces.Peer.CloudUser { + continue + } + var hasUnread = false var fixedCombinedReadStates: MessageHistoryViewReadState? if let readState = readState { @@ -133,7 +137,7 @@ private func callWithTelegramMessage(_ telegramMessage: Message, account: Accoun } private func messageWithTelegramMessage(_ telegramMessage: Message, account: Account) -> INMessage? { - guard let author = telegramMessage.author, let user = telegramMessage.peers[author.id] as? TelegramUser else { + guard let author = telegramMessage.author, let user = telegramMessage.peers[author.id] as? TelegramUser, user.id.id != 777000 else { return nil } @@ -194,9 +198,16 @@ private func messageWithTelegramMessage(_ telegramMessage: Message, account: Acc break loop } } + + if telegramMessage.text.isEmpty && messageType == .text { + return nil + } message = INMessage(identifier: identifier, conversationIdentifier: "\(telegramMessage.id.peerId.toInt64())", content: telegramMessage.text, dateSent: date, sender: sender, recipients: [], groupName: nil, messageType: messageType) } else { + if telegramMessage.text.isEmpty { + return nil + } message = INMessage(identifier: identifier, content: telegramMessage.text, dateSent: date, sender: sender, recipients: []) } diff --git a/submodules/TelegramCore/TelegramCore/Account.swift b/submodules/TelegramCore/TelegramCore/Account.swift index bdf726a392..09546839d5 100644 --- a/submodules/TelegramCore/TelegramCore/Account.swift +++ b/submodules/TelegramCore/TelegramCore/Account.swift @@ -1304,7 +1304,10 @@ public class Account { self.managedOperationsDisposable.add(managedPendingPeerNotificationSettings(postbox: self.postbox, network: self.network).start()) self.managedOperationsDisposable.add(managedSynchronizeAppLogEventsOperations(postbox: self.postbox, network: self.network).start()) self.managedOperationsDisposable.add(managedNotificationSettingsBehaviors(postbox: self.postbox).start()) - self.managedOperationsDisposable.add(managedAnimatedEmojiUpdates(postbox: self.postbox, network: self.network).start()) + + if !self.supplementary { + self.managedOperationsDisposable.add(managedAnimatedEmojiUpdates(postbox: self.postbox, network: self.network).start()) + } let mediaBox = postbox.mediaBox self.storageSettingsDisposable = accountManager.sharedData(keys: [SharedDataKeys.cacheStorageSettings]).start(next: { [weak mediaBox] sharedData in diff --git a/submodules/TelegramCore/TelegramCore/PhoneNumbers.swift b/submodules/TelegramCore/TelegramCore/PhoneNumbers.swift index ce5c9a345d..7bcbd37500 100644 --- a/submodules/TelegramCore/TelegramCore/PhoneNumbers.swift +++ b/submodules/TelegramCore/TelegramCore/PhoneNumbers.swift @@ -16,11 +16,27 @@ public func isViablePhoneNumber(_ string: String) -> Bool { return phoneNumberUtil.isViablePhoneNumber(string) } -public func arePhoneNumbersEqual(_ lhs: String, _ rhs: String) -> Bool { - let result = phoneNumberUtil.isNumberMatch(lhs as NSString, second: rhs as NSString, error: nil) - if result != .NO_MATCH && result != .NOT_A_NUMBER { - return true - } else { - return false +public class ParsedPhoneNumber: Equatable { + let rawPhoneNumber: NBPhoneNumber? + + public init?(string: String) { + if let number = try? phoneNumberUtil.parse(string, defaultRegion: NB_UNKNOWN_REGION) { + self.rawPhoneNumber = number + } else { + return nil + } + } + + public static func == (lhs: ParsedPhoneNumber, rhs: ParsedPhoneNumber) -> Bool { + var error: NSError? + let result = phoneNumberUtil.isNumberMatch(lhs.rawPhoneNumber, second: rhs.rawPhoneNumber, error: &error) + if error != nil { + return false + } + if result != .NO_MATCH && result != .NOT_A_NUMBER { + return true + } else { + return false + } } } diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageAnimatedStickerItemNode.swift index f417745b12..8f402cdf45 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageAnimatedStickerItemNode.swift @@ -679,12 +679,12 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { hapticFeedback.impact(.heavy) Queue.mainQueue().after(0.2) { hapticFeedback.impact(.medium) - Queue.mainQueue().after(0.74) { - hapticFeedback.impact(.medium) + Queue.mainQueue().after(0.78) { + hapticFeedback.impact(.heavy) Queue.mainQueue().after(0.2) { hapticFeedback.impact(.medium) - Queue.mainQueue().after(0.74) { - hapticFeedback.impact(.medium) + Queue.mainQueue().after(0.78) { + hapticFeedback.impact(.heavy) Queue.mainQueue().after(0.2) { hapticFeedback.impact(.medium) }