mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-07-31 07:30:40 +00:00
Merge commit 'd1bcfbc670080b90e187aa42075670febd43685e'
This commit is contained in:
commit
558bf41c4a
@ -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)!)
|
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)
|
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<Account?, NoError> in
|
|> mapToSignal { account -> Signal<Account?, NoError> in
|
||||||
if let account = account {
|
if let account = account {
|
||||||
switch account {
|
switch account {
|
||||||
case .upgrading:
|
case .upgrading:
|
||||||
return .complete()
|
return .complete()
|
||||||
case let .authorized(account):
|
case let .authorized(account):
|
||||||
return applicationSettings(accountManager: accountManager)
|
return applicationSettings(accountManager: accountManager)
|
||||||
|> deliverOnMainQueue
|
|> deliverOnMainQueue
|
||||||
|> map { settings -> Account in
|
|> map { settings -> Account in
|
||||||
accountCache = account
|
accountCache = account
|
||||||
Logger.shared.logToFile = settings.logging.logToFile
|
Logger.shared.logToFile = settings.logging.logToFile
|
||||||
Logger.shared.logToConsole = settings.logging.logToConsole
|
Logger.shared.logToConsole = settings.logging.logToConsole
|
||||||
|
|
||||||
Logger.shared.redactSensitiveData = settings.logging.redactSensitiveData
|
Logger.shared.redactSensitiveData = settings.logging.redactSensitiveData
|
||||||
return account
|
return account
|
||||||
}
|
}
|
||||||
case .unauthorized:
|
case .unauthorized:
|
||||||
return .complete()
|
return .complete()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return .single(nil)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return .single(nil)
|
|
||||||
}
|
}
|
||||||
}
|
|> take(1)
|
||||||
|> take(1)
|
|
||||||
}
|
}
|
||||||
self.accountPromise.set(account)
|
self.accountPromise.set(account)
|
||||||
}
|
}
|
||||||
@ -135,14 +135,48 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
|||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
|
|
||||||
private func resolve(persons: [INPerson]?, with completion: @escaping ([INPersonResolutionResult]) -> Void) {
|
enum ResolveResult {
|
||||||
guard CNContactStore.authorizationStatus(for: .contacts) == .authorized else {
|
case success(INPerson)
|
||||||
completion([INPersonResolutionResult.notRequired()])
|
case disambiguation([INPerson])
|
||||||
return
|
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 {
|
guard let initialPersons = persons, !initialPersons.isEmpty else {
|
||||||
completion([INPersonResolutionResult.needsValue()])
|
completion([.needsValue])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,12 +198,12 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
|||||||
}
|
}
|
||||||
|
|
||||||
if filteredPersons.isEmpty {
|
if filteredPersons.isEmpty {
|
||||||
completion([INPersonResolutionResult.needsValue()])
|
completion([.noResult])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if filteredPersons.count > 1 {
|
if filteredPersons.count > 1 {
|
||||||
completion([INPersonResolutionResult.disambiguation(with: filteredPersons)])
|
completion([.disambiguation(filteredPersons)])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,7 +216,7 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
|||||||
}
|
}
|
||||||
|
|
||||||
if allPersonsAlreadyMatched {
|
if allPersonsAlreadyMatched {
|
||||||
completion([INPersonResolutionResult.success(with: filteredPersons[0])])
|
completion([.success(filteredPersons[0])])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,74 +239,84 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
|||||||
let account = self.accountPromise.get()
|
let account = self.accountPromise.get()
|
||||||
|
|
||||||
let signal = matchingDeviceContacts(stableIds: stableIds)
|
let signal = matchingDeviceContacts(stableIds: stableIds)
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> mapToSignal { matchedContacts in
|
|> mapToSignal { matchedContacts in
|
||||||
return account
|
return account
|
||||||
|> introduceError(IntentContactsError.self)
|
|
||||||
|> mapToSignal { account -> Signal<[(String, TelegramUser)], IntentContactsError> in
|
|
||||||
if let account = account {
|
|
||||||
return matchingCloudContacts(postbox: account.postbox, contacts: matchedContacts)
|
|
||||||
|> introduceError(IntentContactsError.self)
|
|> introduceError(IntentContactsError.self)
|
||||||
} else {
|
|> mapToSignal { account -> Signal<[(String, TelegramUser)], IntentContactsError> in
|
||||||
return .fail(.generic)
|
if let account = account {
|
||||||
|
return matchingCloudContacts(postbox: account.postbox, contacts: matchedContacts)
|
||||||
|
|> introduceError(IntentContactsError.self)
|
||||||
|
} else {
|
||||||
|
return .fail(.generic)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
self.resolvePersonsDisposable.set((signal
|
self.resolvePersonsDisposable.set((signal
|
||||||
|> deliverOnMainQueue).start(next: { peers in
|
|> deliverOnMainQueue).start(next: { peers in
|
||||||
if peers.isEmpty {
|
if peers.isEmpty {
|
||||||
completion([INPersonResolutionResult.needsValue()])
|
completion([.needsValue])
|
||||||
} else {
|
} else {
|
||||||
completion(peers.map { stableId, user in
|
completion(peers.map { .success(personWithUser(stableId: $0, user: $1)) })
|
||||||
let person = personWithUser(stableId: stableId, user: user)
|
}
|
||||||
return INPersonResolutionResult.success(with: person)
|
}, error: { error in
|
||||||
})
|
completion([.skip])
|
||||||
}
|
}))
|
||||||
}, error: { error in
|
|
||||||
completion([INPersonResolutionResult.unsupported()])
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - INSendMessageIntentHandling
|
// MARK: - INSendMessageIntentHandling
|
||||||
|
|
||||||
func resolveRecipients(for intent: INSendMessageIntent, with completion: @escaping ([INPersonResolutionResult]) -> Void) {
|
func resolveRecipients(for intent: INSendMessageIntent, with completion: @escaping ([INPersonResolutionResult]) -> Void) {
|
||||||
if #available(iOSApplicationExtension 11.0, *) {
|
guard CNContactStore.authorizationStatus(for: .contacts) == .authorized else {
|
||||||
if let peerId = intent.conversationIdentifier.flatMap(Int64.init) {
|
completion([INPersonResolutionResult.notRequired()])
|
||||||
let account = self.accountPromise.get()
|
return
|
||||||
|
}
|
||||||
let signal = account
|
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)
|
|> introduceError(IntentHandlingError.self)
|
||||||
|> mapToSignal { account -> Signal<INPerson?, IntentHandlingError> in
|
|> mapToSignal { account -> Signal<INPerson?, IntentHandlingError> in
|
||||||
if let account = account {
|
if let account = account {
|
||||||
return matchingCloudContact(postbox: account.postbox, peerId: PeerId(peerId))
|
return matchingCloudContact(postbox: account.postbox, peerId: PeerId(peerId))
|
||||||
|> introduceError(IntentHandlingError.self)
|
|> introduceError(IntentHandlingError.self)
|
||||||
|> map { user -> INPerson? in
|
|> map { user -> INPerson? in
|
||||||
if let user = user {
|
if let user = user {
|
||||||
return personWithUser(stableId: "tg\(peerId)", user: user)
|
return personWithUser(stableId: "tg\(peerId)", user: user)
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return .fail(.generic)
|
return .fail(.generic)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.resolvePersonsDisposable.set((signal
|
self.resolvePersonsDisposable.set((signal
|
||||||
|> deliverOnMainQueue).start(next: { person in
|
|> deliverOnMainQueue).start(next: { person in
|
||||||
if let person = person {
|
if let person = person {
|
||||||
completion([INPersonResolutionResult.success(with: person)])
|
completion([INSendMessageRecipientResolutionResult.success(with: person)])
|
||||||
} else {
|
} else {
|
||||||
completion([INPersonResolutionResult.needsValue()])
|
completion([INSendMessageRecipientResolutionResult.needsValue()])
|
||||||
}
|
}
|
||||||
}, error: { error in
|
}, error: { error in
|
||||||
completion([INPersonResolutionResult.notRequired()])
|
completion([INSendMessageRecipientResolutionResult.unsupported(forReason: .noAccount)])
|
||||||
}))
|
}))
|
||||||
} else {
|
|
||||||
self.resolve(persons: intent.recipients, with: completion)
|
|
||||||
}
|
|
||||||
} else {
|
} 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) {
|
func handle(intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Void) {
|
||||||
self.actionDisposable.set((self.accountPromise.get()
|
self.actionDisposable.set((self.accountPromise.get()
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> mapError { _ -> IntentHandlingError in
|
|
||||||
return .generic
|
|
||||||
}
|
|
||||||
|> mapToSignal { account -> Signal<Void, IntentHandlingError> 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
|
|> mapError { _ -> IntentHandlingError in
|
||||||
return .generic
|
return .generic
|
||||||
}
|
}
|
||||||
|> mapToSignal { _ -> Signal<Void, IntentHandlingError> in
|
|> mapToSignal { account -> Signal<Void, IntentHandlingError> in
|
||||||
return .complete()
|
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<Void, IntentHandlingError> in
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
|> afterDisposed {
|
||||||
|
account.shouldBeServiceTaskMaster.set(.single(.never))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|> afterDisposed {
|
|> deliverOnMainQueue).start(error: { _ in
|
||||||
account.shouldBeServiceTaskMaster.set(.single(.never))
|
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self))
|
||||||
}
|
let response = INSendMessageIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity)
|
||||||
}
|
completion(response)
|
||||||
|> deliverOnMainQueue).start(error: { _ in
|
}, completed: {
|
||||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self))
|
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self))
|
||||||
let response = INSendMessageIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity)
|
let response = INSendMessageIntentResponse(code: .success, userActivity: userActivity)
|
||||||
completion(response)
|
completion(response)
|
||||||
}, completed: {
|
}))
|
||||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self))
|
|
||||||
let response = INSendMessageIntentResponse(code: .success, userActivity: userActivity)
|
|
||||||
completion(response)
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - INSearchForMessagesIntentHandling
|
// MARK: - INSearchForMessagesIntentHandling
|
||||||
@ -353,42 +397,42 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
|||||||
|
|
||||||
func handle(intent: INSearchForMessagesIntent, completion: @escaping (INSearchForMessagesIntentResponse) -> Void) {
|
func handle(intent: INSearchForMessagesIntent, completion: @escaping (INSearchForMessagesIntentResponse) -> Void) {
|
||||||
self.actionDisposable.set((self.accountPromise.get()
|
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<Void, NoError> = account.stateManager.pollStateUpdateCompletion()
|
|
||||||
|> map { _ in
|
|
||||||
return Void()
|
|
||||||
}
|
|
||||||
|
|
||||||
return (completion |> timeout(4.0, queue: Queue.mainQueue(), alternate: .single(Void())))
|
|
||||||
|> introduceError(IntentHandlingError.self)
|
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> mapToSignal { _ -> Signal<[INMessage], IntentHandlingError> in
|
|> introduceError(IntentHandlingError.self)
|
||||||
return unreadMessages(account: account)
|
|> mapToSignal { account -> Signal<[INMessage], IntentHandlingError> in
|
||||||
|> introduceError(IntentHandlingError.self)
|
guard let account = account else {
|
||||||
|> afterDisposed {
|
return .fail(.generic)
|
||||||
account.shouldBeServiceTaskMaster.set(.single(.never))
|
}
|
||||||
|
|
||||||
|
account.shouldBeServiceTaskMaster.set(.single(.now))
|
||||||
|
account.resetStateManagement()
|
||||||
|
|
||||||
|
let completion: Signal<Void, NoError> = 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
|
||||||
|> deliverOnMainQueue).start(next: { messages in
|
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchForMessagesIntent.self))
|
||||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchForMessagesIntent.self))
|
let response = INSearchForMessagesIntentResponse(code: .success, userActivity: userActivity)
|
||||||
let response = INSearchForMessagesIntentResponse(code: .success, userActivity: userActivity)
|
response.messages = messages
|
||||||
response.messages = messages
|
completion(response)
|
||||||
completion(response)
|
}, error: { _ in
|
||||||
}, error: { _ in
|
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchForMessagesIntent.self))
|
||||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchForMessagesIntent.self))
|
let response = INSearchForMessagesIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity)
|
||||||
let response = INSearchForMessagesIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity)
|
completion(response)
|
||||||
completion(response)
|
}))
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - INSetMessageAttributeIntentHandling
|
// MARK: - INSetMessageAttributeIntentHandling
|
||||||
@ -408,101 +452,107 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
|||||||
|
|
||||||
func handle(intent: INSetMessageAttributeIntent, completion: @escaping (INSetMessageAttributeIntentResponse) -> Void) {
|
func handle(intent: INSetMessageAttributeIntent, completion: @escaping (INSetMessageAttributeIntentResponse) -> Void) {
|
||||||
self.actionDisposable.set((self.accountPromise.get()
|
self.actionDisposable.set((self.accountPromise.get()
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> mapError { _ -> IntentHandlingError in
|
|> mapError { _ -> IntentHandlingError in
|
||||||
return .generic
|
return .generic
|
||||||
}
|
|
||||||
|> mapToSignal { account -> Signal<Void, IntentHandlingError> in
|
|
||||||
guard let account = account else {
|
|
||||||
return .fail(.generic)
|
|
||||||
}
|
}
|
||||||
|
|> mapToSignal { account -> Signal<Void, IntentHandlingError> in
|
||||||
var signals: [Signal<Void, IntentHandlingError>] = []
|
guard let account = account else {
|
||||||
var maxMessageIdsToApply: [PeerId: MessageId] = [:]
|
return .fail(.generic)
|
||||||
if let identifiers = intent.identifiers {
|
}
|
||||||
for identifier in identifiers {
|
|
||||||
let components = identifier.components(separatedBy: "_")
|
var signals: [Signal<Void, IntentHandlingError>] = []
|
||||||
if let first = components.first, let peerId = Int64(first), let namespace = Int32(components[1]), let id = Int32(components[2]) {
|
var maxMessageIdsToApply: [PeerId: MessageId] = [:]
|
||||||
let peerId = PeerId(peerId)
|
if let identifiers = intent.identifiers {
|
||||||
let messageId = MessageId(peerId: peerId, namespace: namespace, id: id)
|
for identifier in identifiers {
|
||||||
if let currentMessageId = maxMessageIdsToApply[peerId] {
|
let components = identifier.components(separatedBy: "_")
|
||||||
if currentMessageId < messageId {
|
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
|
maxMessageIdsToApply[peerId] = messageId
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
maxMessageIdsToApply[peerId] = messageId
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
for (_, messageId) in maxMessageIdsToApply {
|
||||||
for (_, messageId) in maxMessageIdsToApply {
|
signals.append(applyMaxReadIndexInteractively(postbox: account.postbox, stateManager: account.stateManager, index: MessageIndex(id: messageId, timestamp: 0))
|
||||||
signals.append(applyMaxReadIndexInteractively(postbox: account.postbox, stateManager: account.stateManager, index: MessageIndex(id: messageId, timestamp: 0))
|
|> introduceError(IntentHandlingError.self))
|
||||||
|> introduceError(IntentHandlingError.self))
|
}
|
||||||
}
|
|
||||||
|
if signals.isEmpty {
|
||||||
if signals.isEmpty {
|
|
||||||
return .complete()
|
|
||||||
} else {
|
|
||||||
account.shouldBeServiceTaskMaster.set(.single(.now))
|
|
||||||
return combineLatest(signals)
|
|
||||||
|> mapToSignal { _ -> Signal<Void, IntentHandlingError> in
|
|
||||||
return .complete()
|
return .complete()
|
||||||
}
|
} else {
|
||||||
|> afterDisposed {
|
account.shouldBeServiceTaskMaster.set(.single(.now))
|
||||||
account.shouldBeServiceTaskMaster.set(.single(.never))
|
return combineLatest(signals)
|
||||||
|
|> mapToSignal { _ -> Signal<Void, IntentHandlingError> in
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
|> afterDisposed {
|
||||||
|
account.shouldBeServiceTaskMaster.set(.single(.never))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|> deliverOnMainQueue).start(error: { _ in
|
||||||
|> deliverOnMainQueue).start(error: { _ in
|
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSetMessageAttributeIntent.self))
|
||||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSetMessageAttributeIntent.self))
|
let response = INSetMessageAttributeIntentResponse(code: .failure, userActivity: userActivity)
|
||||||
let response = INSetMessageAttributeIntentResponse(code: .failure, userActivity: userActivity)
|
completion(response)
|
||||||
completion(response)
|
}, completed: {
|
||||||
}, completed: {
|
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSetMessageAttributeIntent.self))
|
||||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSetMessageAttributeIntent.self))
|
let response = INSetMessageAttributeIntentResponse(code: .success, userActivity: userActivity)
|
||||||
let response = INSetMessageAttributeIntentResponse(code: .success, userActivity: userActivity)
|
completion(response)
|
||||||
completion(response)
|
}))
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - INStartAudioCallIntentHandling
|
// MARK: - INStartAudioCallIntentHandling
|
||||||
|
|
||||||
func resolveContacts(for intent: INStartAudioCallIntent, with completion: @escaping ([INPersonResolutionResult]) -> Void) {
|
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) {
|
func handle(intent: INStartAudioCallIntent, completion: @escaping (INStartAudioCallIntentResponse) -> Void) {
|
||||||
self.actionDisposable.set((self.accountPromise.get()
|
self.actionDisposable.set((self.accountPromise.get()
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> mapError { _ -> IntentHandlingError in
|
|> mapError { _ -> IntentHandlingError in
|
||||||
return .generic
|
return .generic
|
||||||
}
|
|
||||||
|> mapToSignal { account -> Signal<PeerId, IntentHandlingError> in
|
|
||||||
guard let contact = intent.contacts?.first, let customIdentifier = contact.customIdentifier, customIdentifier.hasPrefix("tg") else {
|
|
||||||
return .fail(.generic)
|
|
||||||
}
|
}
|
||||||
|
|> mapToSignal { account -> Signal<PeerId, IntentHandlingError> in
|
||||||
guard let peerIdValue = Int64(String(customIdentifier[customIdentifier.index(customIdentifier.startIndex, offsetBy: 2)...])) else {
|
guard let contact = intent.contacts?.first, let customIdentifier = contact.customIdentifier, customIdentifier.hasPrefix("tg") else {
|
||||||
return .fail(.generic)
|
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)
|
||||||
}
|
}
|
||||||
|
|> deliverOnMainQueue).start(next: { peerId in
|
||||||
let peerId = PeerId(peerIdValue)
|
let userActivity = NSUserActivity(activityType: NSStringFromClass(INStartAudioCallIntent.self))
|
||||||
if peerId.namespace != Namespaces.Peer.CloudUser {
|
userActivity.userInfo = ["handle": "TGCA\(peerId.toInt64())"]
|
||||||
return .fail(.generic)
|
let response = INStartAudioCallIntentResponse(code: .continueInApp, userActivity: userActivity)
|
||||||
}
|
completion(response)
|
||||||
|
}, error: { _ in
|
||||||
return .single(peerId)
|
let userActivity = NSUserActivity(activityType: NSStringFromClass(INStartAudioCallIntent.self))
|
||||||
}
|
let response = INStartAudioCallIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity)
|
||||||
|> deliverOnMainQueue).start(next: { peerId in
|
completion(response)
|
||||||
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
|
// MARK: - INSearchCallHistoryIntentHandling
|
||||||
@ -518,34 +568,34 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
|||||||
|
|
||||||
func handle(intent: INSearchCallHistoryIntent, completion: @escaping (INSearchCallHistoryIntentResponse) -> Void) {
|
func handle(intent: INSearchCallHistoryIntent, completion: @escaping (INSearchCallHistoryIntentResponse) -> Void) {
|
||||||
self.actionDisposable.set((self.accountPromise.get()
|
self.actionDisposable.set((self.accountPromise.get()
|
||||||
|> take(1)
|
|> 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)
|
|> introduceError(IntentHandlingError.self)
|
||||||
|> afterDisposed {
|
|> mapToSignal { account -> Signal<[CallRecord], IntentHandlingError> in
|
||||||
account.shouldBeServiceTaskMaster.set(.single(.never))
|
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
|
||||||
|> deliverOnMainQueue).start(next: { calls in
|
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchCallHistoryIntent.self))
|
||||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchCallHistoryIntent.self))
|
let response: INSearchCallHistoryIntentResponse
|
||||||
let response: INSearchCallHistoryIntentResponse
|
if #available(iOSApplicationExtension 11.0, *) {
|
||||||
if #available(iOSApplicationExtension 11.0, *) {
|
response = INSearchCallHistoryIntentResponse(code: .success, userActivity: userActivity)
|
||||||
response = INSearchCallHistoryIntentResponse(code: .success, userActivity: userActivity)
|
response.callRecords = calls.map { $0.intentCall }
|
||||||
response.callRecords = calls.map { $0.intentCall }
|
} else {
|
||||||
} else {
|
response = INSearchCallHistoryIntentResponse(code: .continueInApp, userActivity: userActivity)
|
||||||
response = INSearchCallHistoryIntentResponse(code: .continueInApp, userActivity: userActivity)
|
}
|
||||||
}
|
completion(response)
|
||||||
completion(response)
|
}, error: { _ in
|
||||||
}, error: { _ in
|
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchCallHistoryIntent.self))
|
||||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchCallHistoryIntent.self))
|
let response = INSearchCallHistoryIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity)
|
||||||
let response = INSearchCallHistoryIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity)
|
completion(response)
|
||||||
completion(response)
|
}))
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,14 +95,17 @@ final class AlertControllerNode: ASDisplayNode {
|
|||||||
self.bottomDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
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.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.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.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion: { [weak self] finished in
|
||||||
self?.centerDimView.backgroundColor = nil
|
if finished {
|
||||||
self?.centerDimView.image = generateStretchableFilledCircleImage(radius: 16.0, color: nil, backgroundColor: UIColor(white: 0.0, alpha: 0.5))
|
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)
|
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) {
|
func animateOut(completion: @escaping () -> Void) {
|
||||||
|
self.containerNode.layer.removeAllAnimations()
|
||||||
self.centerDimView.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
self.centerDimView.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
||||||
self.centerDimView.image = nil
|
self.centerDimView.image = nil
|
||||||
|
|
||||||
|
@ -126,7 +126,7 @@ private final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource
|
|||||||
self.queue = queue
|
self.queue = queue
|
||||||
self.data = data
|
self.data = data
|
||||||
self.scratchBuffer = Data(count: compression_decode_scratch_buffer_size(COMPRESSION_LZFSE))
|
self.scratchBuffer = Data(count: compression_decode_scratch_buffer_size(COMPRESSION_LZFSE))
|
||||||
|
|
||||||
var offset = 0
|
var offset = 0
|
||||||
var width = 0
|
var width = 0
|
||||||
var height = 0
|
var height = 0
|
||||||
@ -403,7 +403,7 @@ final class AnimatedStickerNode: ASDisplayNode {
|
|||||||
self.renderer?.frame = CGRect(origin: CGPoint(), size: self.bounds.size)
|
self.renderer?.frame = CGRect(origin: CGPoint(), size: self.bounds.size)
|
||||||
self.addSubnode(self.renderer!)
|
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, width: Int, height: Int, playbackMode: AnimatedStickerPlaybackMode = .loop, mode: AnimatedStickerMode) {
|
||||||
if width < 2 || height < 2 {
|
if width < 2 || height < 2 {
|
||||||
return
|
return
|
||||||
@ -412,27 +412,27 @@ final class AnimatedStickerNode: ASDisplayNode {
|
|||||||
switch mode {
|
switch mode {
|
||||||
case .direct:
|
case .direct:
|
||||||
self.disposable.set((account.postbox.mediaBox.resourceData(resource)
|
self.disposable.set((account.postbox.mediaBox.resourceData(resource)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] data in
|
|> deliverOnMainQueue).start(next: { [weak self] data in
|
||||||
guard let strongSelf = self, data.complete else {
|
guard let strongSelf = self, data.complete else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let directData = try? Data(contentsOf: URL(fileURLWithPath: data.path), options: [.mappedRead]) {
|
if let directData = try? Data(contentsOf: URL(fileURLWithPath: data.path), options: [.mappedRead]) {
|
||||||
strongSelf.directData = Tuple(directData, data.path, width, height)
|
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 {
|
if strongSelf.isPlaying {
|
||||||
strongSelf.play()
|
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.started()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
strongSelf.playbackStatus.set(.single(AnimatedStickerStatus(playing: false, duration: duration, timestamp: 0.0)))
|
strongSelf.playbackStatus.set(.single(AnimatedStickerStatus(playing: false, duration: duration, timestamp: 0.0)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1121,13 +1121,13 @@ final class SharedApplicationContext {
|
|||||||
Logger.shared.log("App \(self.episodeId)", "isActive = \(value)")
|
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" {
|
if let url = url as? URL, url.scheme == "tg" {
|
||||||
self.openUrlWhenReady(url: url.absoluteString)
|
self.openUrlWhenReady(url: url.absoluteString)
|
||||||
} else if let url = url as? String, url.lowercased().hasPrefix("tg://") {
|
} else if let url = url as? String, url.lowercased().hasPrefix("tg://") {
|
||||||
self.openUrlWhenReady(url: url)
|
self.openUrlWhenReady(url: url)
|
||||||
}
|
}
|
||||||
}*/
|
}
|
||||||
|
|
||||||
if application.applicationState == .active {
|
if application.applicationState == .active {
|
||||||
self.isInForegroundValue = true
|
self.isInForegroundValue = true
|
||||||
@ -1845,7 +1845,7 @@ final class SharedApplicationContext {
|
|||||||
notificationCenter.getNotificationSettings(completionHandler: { settings in
|
notificationCenter.getNotificationSettings(completionHandler: { settings in
|
||||||
switch (settings.authorizationStatus, authorize) {
|
switch (settings.authorizationStatus, authorize) {
|
||||||
case (.authorized, _), (.notDetermined, true):
|
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)
|
completion(result)
|
||||||
if result {
|
if result {
|
||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
@ -1867,7 +1867,7 @@ final class SharedApplicationContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var carPlayOptions = options
|
var carPlayOptions = options
|
||||||
carPlayOptions.insert(.allowInCarPlay)
|
//carPlayOptions.insert(.allowInCarPlay)
|
||||||
|
|
||||||
unknownMessageCategory = UNNotificationCategory(identifier: "unknown", actions: [], intentIdentifiers: [], hiddenPreviewsBodyPlaceholder: hiddenContentString, options: options)
|
unknownMessageCategory = UNNotificationCategory(identifier: "unknown", actions: [], intentIdentifiers: [], hiddenPreviewsBodyPlaceholder: hiddenContentString, options: options)
|
||||||
replyMessageCategory = UNNotificationCategory(identifier: "withReply", actions: [reply], intentIdentifiers: [INSearchForMessagesIntentIdentifier], hiddenPreviewsBodyPlaceholder: hiddenContentString, options: carPlayOptions)
|
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)
|
muteMessageCategory = UNNotificationCategory(identifier: "withMute", actions: [], intentIdentifiers: [], hiddenPreviewsBodyPlaceholder: hiddenContentString, options: options)
|
||||||
muteMediaMessageCategory = UNNotificationCategory(identifier: "withMuteMedia", actions: [], intentIdentifiers: [], hiddenPreviewsBodyPlaceholder: hiddenContentString, options: options)
|
muteMediaMessageCategory = UNNotificationCategory(identifier: "withMuteMedia", actions: [], intentIdentifiers: [], hiddenPreviewsBodyPlaceholder: hiddenContentString, options: options)
|
||||||
} else {
|
} else {
|
||||||
let carPlayOptions: UNNotificationCategoryOptions = [.allowInCarPlay]
|
let carPlayOptions: UNNotificationCategoryOptions = [] //[.allowInCarPlay]
|
||||||
|
|
||||||
unknownMessageCategory = UNNotificationCategory(identifier: "unknown", actions: [], intentIdentifiers: [], options: [])
|
unknownMessageCategory = UNNotificationCategory(identifier: "unknown", actions: [], intentIdentifiers: [], options: [])
|
||||||
replyMessageCategory = UNNotificationCategory(identifier: "withReply", actions: [reply], intentIdentifiers: [INSearchForMessagesIntentIdentifier], options: carPlayOptions)
|
replyMessageCategory = UNNotificationCategory(identifier: "withReply", actions: [reply], intentIdentifiers: [INSearchForMessagesIntentIdentifier], options: carPlayOptions)
|
||||||
|
@ -15,7 +15,7 @@ private func timerValueString(days: Int32, hours: Int32, minutes: Int32, color:
|
|||||||
|
|
||||||
var hoursString = ""
|
var hoursString = ""
|
||||||
if hours > 0 || days > 0 {
|
if hours > 0 || days > 0 {
|
||||||
daysString = strings.MessageTimer_Hours(hours) + " "
|
hoursString = strings.MessageTimer_Hours(hours) + " "
|
||||||
}
|
}
|
||||||
|
|
||||||
let minutesString = strings.MessageTimer_Minutes(minutes)
|
let minutesString = strings.MessageTimer_Minutes(minutes)
|
||||||
|
@ -81,13 +81,13 @@ typedef enum {
|
|||||||
|
|
||||||
NSDictionary *outputSettings = @
|
NSDictionary *outputSettings = @
|
||||||
{
|
{
|
||||||
AVFormatIDKey: @(kAudioFormatLinearPCM),
|
AVFormatIDKey: @(kAudioFormatLinearPCM),
|
||||||
AVSampleRateKey: @(TGBridgeAudioEncoderSampleRate),
|
AVSampleRateKey: @(TGBridgeAudioEncoderSampleRate),
|
||||||
AVNumberOfChannelsKey: @1,
|
AVNumberOfChannelsKey: @1,
|
||||||
AVLinearPCMBitDepthKey: @16,
|
AVLinearPCMBitDepthKey: @16,
|
||||||
AVLinearPCMIsFloatKey: @false,
|
AVLinearPCMIsFloatKey: @false,
|
||||||
AVLinearPCMIsBigEndianKey: @false,
|
AVLinearPCMIsBigEndianKey: @false,
|
||||||
AVLinearPCMIsNonInterleaved: @false
|
AVLinearPCMIsNonInterleaved: @false
|
||||||
};
|
};
|
||||||
|
|
||||||
_readerOutput = [AVAssetReaderAudioMixOutput assetReaderAudioMixOutputWithAudioTracks:asset.tracks audioSettings:outputSettings];
|
_readerOutput = [AVAssetReaderAudioMixOutput assetReaderAudioMixOutputWithAudioTracks:asset.tracks audioSettings:outputSettings];
|
||||||
@ -114,9 +114,9 @@ typedef enum {
|
|||||||
static ATQueue *queue = nil;
|
static ATQueue *queue = nil;
|
||||||
static dispatch_once_t onceToken;
|
static dispatch_once_t onceToken;
|
||||||
dispatch_once(&onceToken, ^
|
dispatch_once(&onceToken, ^
|
||||||
{
|
{
|
||||||
queue = [[ATQueue alloc] initWithName:@"org.telegram.opusAudioEncoderQueue"];
|
queue = [[ATQueue alloc] initWithName:@"org.telegram.opusAudioEncoderQueue"];
|
||||||
});
|
});
|
||||||
|
|
||||||
return queue;
|
return queue;
|
||||||
}
|
}
|
||||||
@ -124,76 +124,76 @@ typedef enum {
|
|||||||
static const int encoderPacketSizeInBytes = TGBridgeAudioEncoderSampleRate / 1000 * 60 * 2;
|
static const int encoderPacketSizeInBytes = TGBridgeAudioEncoderSampleRate / 1000 * 60 * 2;
|
||||||
|
|
||||||
- (void)startWithCompletion:(void (^)(NSString *, int32_t))completion
|
- (void)startWithCompletion:(void (^)(NSString *, int32_t))completion
|
||||||
{
|
{
|
||||||
[[TGBridgeAudioEncoder processingQueue] dispatch:^
|
[[TGBridgeAudioEncoder processingQueue] dispatch:^
|
||||||
{
|
{
|
||||||
_oggWriter = [[TGOggOpusWriter alloc] init];
|
_oggWriter = [[TGOggOpusWriter alloc] init];
|
||||||
if (![_oggWriter beginWithDataItem:_tempFileItem])
|
if (![_oggWriter beginWithDataItem:_tempFileItem])
|
||||||
{
|
{
|
||||||
[self cleanup];
|
[self cleanup];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
[_assetReader startReading];
|
[_assetReader startReading];
|
||||||
|
|
||||||
while (_assetReader.status != AVAssetReaderStatusCompleted)
|
while (_assetReader.status != AVAssetReaderStatusCompleted)
|
||||||
{
|
{
|
||||||
if (_assetReader.status == AVAssetReaderStatusReading)
|
if (_assetReader.status == AVAssetReaderStatusReading)
|
||||||
{
|
{
|
||||||
CMSampleBufferRef nextBuffer = [_readerOutput copyNextSampleBuffer];
|
CMSampleBufferRef nextBuffer = [_readerOutput copyNextSampleBuffer];
|
||||||
if (nextBuffer)
|
if (nextBuffer)
|
||||||
{
|
{
|
||||||
AudioBufferList abl;
|
AudioBufferList abl;
|
||||||
CMBlockBufferRef blockBuffer;
|
CMBlockBufferRef blockBuffer;
|
||||||
CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(nextBuffer, NULL, &abl, sizeof(abl), NULL, NULL, kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment, &blockBuffer);
|
CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(nextBuffer, NULL, &abl, sizeof(abl), NULL, NULL, kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment, &blockBuffer);
|
||||||
|
|
||||||
[[TGBridgeAudioEncoder processingQueue] dispatch:^
|
[[TGBridgeAudioEncoder processingQueue] dispatch:^
|
||||||
{
|
{
|
||||||
[self _processBuffer:&abl.mBuffers[0]];
|
[self _processBuffer:&abl.mBuffers[0]];
|
||||||
|
|
||||||
CFRelease(nextBuffer);
|
CFRelease(nextBuffer);
|
||||||
CFRelease(blockBuffer);
|
CFRelease(blockBuffer);
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
[[TGBridgeAudioEncoder processingQueue] dispatch:^
|
[[TGBridgeAudioEncoder processingQueue] dispatch:^
|
||||||
{
|
{
|
||||||
if (_tailLength > 0) {
|
if (_tailLength > 0) {
|
||||||
[_oggWriter writeFrame:(uint8_t *)_audioBuffer.bytes frameByteCount:(NSUInteger)_tailLength];
|
[_oggWriter writeFrame:(uint8_t *)_audioBuffer.bytes frameByteCount:(NSUInteger)_tailLength];
|
||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[[TGBridgeAudioEncoder processingQueue] dispatch:^
|
[[TGBridgeAudioEncoder processingQueue] dispatch:^
|
||||||
{
|
{
|
||||||
TGFileDataItem *dataItemResult = nil;
|
TGFileDataItem *dataItemResult = nil;
|
||||||
NSTimeInterval durationResult = 0.0;
|
NSTimeInterval durationResult = 0.0;
|
||||||
|
|
||||||
NSUInteger totalBytes = 0;
|
NSUInteger totalBytes = 0;
|
||||||
|
|
||||||
if (_assetReader.status == AVAssetReaderStatusCompleted)
|
if (_assetReader.status == AVAssetReaderStatusCompleted)
|
||||||
{
|
{
|
||||||
NSLog(@"finished");
|
NSLog(@"finished");
|
||||||
if (_oggWriter != nil)
|
if (_oggWriter != nil)
|
||||||
{
|
{
|
||||||
dataItemResult = _tempFileItem;
|
dataItemResult = _tempFileItem;
|
||||||
durationResult = [_oggWriter encodedDuration];
|
durationResult = [_oggWriter encodedDuration];
|
||||||
totalBytes = [_oggWriter encodedBytes];
|
totalBytes = [_oggWriter encodedBytes];
|
||||||
}
|
}
|
||||||
|
|
||||||
[self cleanup];
|
[self cleanup];
|
||||||
}
|
}
|
||||||
|
|
||||||
//TGLog(@"[TGBridgeAudioEncoder#%x convert time: %f ms]", self, (CFAbsoluteTimeGetCurrent() - startTime) * 1000.0);
|
//TGLog(@"[TGBridgeAudioEncoder#%x convert time: %f ms]", self, (CFAbsoluteTimeGetCurrent() - startTime) * 1000.0);
|
||||||
|
|
||||||
if (completion != nil)
|
if (completion != nil)
|
||||||
completion(dataItemResult.path, (int32_t)durationResult);
|
completion(dataItemResult.path, (int32_t)durationResult);
|
||||||
}];
|
}];
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)_processBuffer:(AudioBuffer const *)buffer
|
- (void)_processBuffer:(AudioBuffer const *)buffer
|
||||||
@ -270,7 +270,7 @@ static const int encoderPacketSizeInBytes = TGBridgeAudioEncoderSampleRate / 100
|
|||||||
|
|
||||||
@implementation TGFileDataItem
|
@implementation TGFileDataItem
|
||||||
{
|
{
|
||||||
ATQueue *_queue;
|
ATQueue *_queue;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)_commonInit
|
- (void)_commonInit
|
||||||
@ -306,11 +306,11 @@ static const int encoderPacketSizeInBytes = TGBridgeAudioEncoderSampleRate / 100
|
|||||||
|
|
||||||
|
|
||||||
[_queue dispatch:^
|
[_queue dispatch:^
|
||||||
{
|
{
|
||||||
_fileName = filePath;
|
_fileName = filePath;
|
||||||
_length = [[[NSFileManager defaultManager] attributesOfItemAtPath:_fileName error:nil][NSFileSize] unsignedIntegerValue];
|
_length = [[[NSFileManager defaultManager] attributesOfItemAtPath:_fileName error:nil][NSFileSize] unsignedIntegerValue];
|
||||||
_fileExists = [[NSFileManager defaultManager] fileExistsAtPath:_fileName];
|
_fileExists = [[NSFileManager defaultManager] fileExistsAtPath:_fileName];
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
@ -322,38 +322,38 @@ static const int encoderPacketSizeInBytes = TGBridgeAudioEncoderSampleRate / 100
|
|||||||
- (void)moveToPath:(NSString *)path
|
- (void)moveToPath:(NSString *)path
|
||||||
{
|
{
|
||||||
[_queue dispatch:^
|
[_queue dispatch:^
|
||||||
{
|
{
|
||||||
[[NSFileManager defaultManager] moveItemAtPath:_fileName toPath:path error:nil];
|
[[NSFileManager defaultManager] moveItemAtPath:_fileName toPath:path error:nil];
|
||||||
_fileName = path;
|
_fileName = path;
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)remove
|
- (void)remove
|
||||||
{
|
{
|
||||||
[_queue dispatch:^
|
[_queue dispatch:^
|
||||||
{
|
{
|
||||||
[[NSFileManager defaultManager] removeItemAtPath:_fileName error:nil];
|
[[NSFileManager defaultManager] removeItemAtPath:_fileName error:nil];
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)appendData:(NSData *)data
|
- (void)appendData:(NSData *)data
|
||||||
{
|
{
|
||||||
[_queue dispatch:^
|
[_queue dispatch:^
|
||||||
{
|
{
|
||||||
if (!_fileExists)
|
if (!_fileExists)
|
||||||
{
|
{
|
||||||
[[NSFileManager defaultManager] createFileAtPath:_fileName contents:nil attributes:nil];
|
[[NSFileManager defaultManager] createFileAtPath:_fileName contents:nil attributes:nil];
|
||||||
_fileExists = true;
|
_fileExists = true;
|
||||||
}
|
}
|
||||||
NSFileHandle *file = [NSFileHandle fileHandleForUpdatingAtPath:_fileName];
|
NSFileHandle *file = [NSFileHandle fileHandleForUpdatingAtPath:_fileName];
|
||||||
[file seekToEndOfFile];
|
[file seekToEndOfFile];
|
||||||
[file writeData:data];
|
[file writeData:data];
|
||||||
[file synchronizeFile];
|
[file synchronizeFile];
|
||||||
[file closeFile];
|
[file closeFile];
|
||||||
_length += data.length;
|
_length += data.length;
|
||||||
|
|
||||||
[_data appendData:data];
|
[_data appendData:data];
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSData *)readDataAtOffset:(NSUInteger)offset length:(NSUInteger)length
|
- (NSData *)readDataAtOffset:(NSUInteger)offset length:(NSUInteger)length
|
||||||
@ -361,14 +361,14 @@ static const int encoderPacketSizeInBytes = TGBridgeAudioEncoderSampleRate / 100
|
|||||||
__block NSData *data = nil;
|
__block NSData *data = nil;
|
||||||
|
|
||||||
[_queue dispatch:^
|
[_queue dispatch:^
|
||||||
{
|
{
|
||||||
NSFileHandle *file = [NSFileHandle fileHandleForUpdatingAtPath:_fileName];
|
NSFileHandle *file = [NSFileHandle fileHandleForUpdatingAtPath:_fileName];
|
||||||
[file seekToFileOffset:(unsigned long long)offset];
|
[file seekToFileOffset:(unsigned long long)offset];
|
||||||
data = [file readDataOfLength:length];
|
data = [file readDataOfLength:length];
|
||||||
if (data.length != length)
|
if (data.length != length)
|
||||||
//TGLog(@"Read data length mismatch");
|
//TGLog(@"Read data length mismatch");
|
||||||
[file closeFile];
|
[file closeFile];
|
||||||
} synchronous:true];
|
} synchronous:true];
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@ -377,9 +377,9 @@ static const int encoderPacketSizeInBytes = TGBridgeAudioEncoderSampleRate / 100
|
|||||||
{
|
{
|
||||||
__block NSUInteger result = 0;
|
__block NSUInteger result = 0;
|
||||||
[_queue dispatch:^
|
[_queue dispatch:^
|
||||||
{
|
{
|
||||||
result = _length;
|
result = _length;
|
||||||
} synchronous:true];
|
} synchronous:true];
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -420,11 +420,11 @@ static const int encoderPacketSizeInBytes = TGBridgeAudioEncoderSampleRate / 100
|
|||||||
static ATQueue *queue = nil;
|
static ATQueue *queue = nil;
|
||||||
static dispatch_once_t onceToken;
|
static dispatch_once_t onceToken;
|
||||||
dispatch_once(&onceToken, ^
|
dispatch_once(&onceToken, ^
|
||||||
{
|
{
|
||||||
queue = [[ATQueue alloc] init];
|
queue = [[ATQueue alloc] init];
|
||||||
queue->_nativeQueue = dispatch_get_main_queue();
|
queue->_nativeQueue = dispatch_get_main_queue();
|
||||||
queue->_isMainQueue = true;
|
queue->_isMainQueue = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
return queue;
|
return queue;
|
||||||
}
|
}
|
||||||
@ -434,9 +434,9 @@ static const int encoderPacketSizeInBytes = TGBridgeAudioEncoderSampleRate / 100
|
|||||||
static ATQueue *queue = nil;
|
static ATQueue *queue = nil;
|
||||||
static dispatch_once_t onceToken;
|
static dispatch_once_t onceToken;
|
||||||
dispatch_once(&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;
|
return queue;
|
||||||
}
|
}
|
||||||
@ -446,9 +446,9 @@ static const int encoderPacketSizeInBytes = TGBridgeAudioEncoderSampleRate / 100
|
|||||||
static ATQueue *queue = nil;
|
static ATQueue *queue = nil;
|
||||||
static dispatch_once_t onceToken;
|
static dispatch_once_t onceToken;
|
||||||
dispatch_once(&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;
|
return queue;
|
||||||
}
|
}
|
||||||
|
@ -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.chatListDisplayNode.toolbarActionSelected = { [weak self] action in
|
||||||
self?.toolbarActionSelected(action: action)
|
self?.toolbarActionSelected(action: action)
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,8 @@ final class ChatListControllerNode: ASDisplayNode {
|
|||||||
var requestOpenMessageFromSearch: ((Peer, MessageId) -> Void)?
|
var requestOpenMessageFromSearch: ((Peer, MessageId) -> Void)?
|
||||||
var requestAddContact: ((String) -> Void)?
|
var requestAddContact: ((String) -> Void)?
|
||||||
var dismissSelf: (() -> Void)?
|
var dismissSelf: (() -> Void)?
|
||||||
|
var isEmptyUpdated: ((Bool) -> Void)?
|
||||||
|
|
||||||
let debugListView = ListView()
|
let debugListView = ListView()
|
||||||
|
|
||||||
init(context: AccountContext, groupId: PeerGroupId, controlsHistoryPreload: Bool, presentationData: PresentationData, controller: ChatListController) {
|
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 {
|
if let (layout, navigationHeight, visualNavigationHeight) = strongSelf.containerLayout {
|
||||||
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, visualNavigationHeight: visualNavigationHeight, transition: .immediate)
|
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, visualNavigationHeight: visualNavigationHeight, transition: .immediate)
|
||||||
}
|
}
|
||||||
|
strongSelf.isEmptyUpdated?(true)
|
||||||
}
|
}
|
||||||
case .notEmpty(false):
|
case .notEmpty(false):
|
||||||
if case .group = strongSelf.groupId {
|
if case .group = strongSelf.groupId {
|
||||||
|
@ -56,7 +56,7 @@ private class ChatMessageHeartbeatHaptic {
|
|||||||
if time > 2.0 {
|
if time > 2.0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var startTime: Double = 0.0
|
var startTime: Double = 0.0
|
||||||
var delay: Double = 0.0
|
var delay: Double = 0.0
|
||||||
|
|
||||||
@ -201,7 +201,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
self.view.addGestureRecognizer(replyRecognizer)
|
self.view.addGestureRecognizer(replyRecognizer)
|
||||||
}
|
}
|
||||||
|
|
||||||
override var visibility: ListViewItemNodeVisibility {
|
override var visibility: ListViewItemNodeVisibility {
|
||||||
didSet {
|
didSet {
|
||||||
let wasVisible = oldValue != .none
|
let wasVisible = oldValue != .none
|
||||||
@ -237,7 +237,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.telegramFile == nil, let emojiFile = item.associatedData.animatedEmojiStickers[item.message.text.trimmedEmoji]?.file {
|
if self.telegramFile == nil, let emojiFile = item.associatedData.animatedEmojiStickers[item.message.text.trimmedEmoji]?.file {
|
||||||
if self.emojiFile?.id != emojiFile.id {
|
if self.emojiFile?.id != emojiFile.id {
|
||||||
self.emojiFile = emojiFile
|
self.emojiFile = emojiFile
|
||||||
@ -341,23 +341,23 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
var hasAvatar = false
|
var hasAvatar = false
|
||||||
|
|
||||||
switch item.chatLocation {
|
switch item.chatLocation {
|
||||||
case let .peer(peerId):
|
case let .peer(peerId):
|
||||||
if peerId != item.context.account.peerId {
|
if peerId != item.context.account.peerId {
|
||||||
if peerId.isGroupOrChannel && item.message.author != nil {
|
if peerId.isGroupOrChannel && item.message.author != nil {
|
||||||
var isBroadcastChannel = false
|
var isBroadcastChannel = false
|
||||||
if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = peer.info {
|
if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = peer.info {
|
||||||
isBroadcastChannel = true
|
isBroadcastChannel = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isBroadcastChannel {
|
if !isBroadcastChannel {
|
||||||
hasAvatar = true
|
hasAvatar = true
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if incoming {
|
|
||||||
hasAvatar = true
|
|
||||||
}
|
}
|
||||||
|
} else if incoming {
|
||||||
|
hasAvatar = true
|
||||||
|
}
|
||||||
/*case .group:
|
/*case .group:
|
||||||
hasAvatar = true*/
|
hasAvatar = true*/
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasAvatar {
|
if hasAvatar {
|
||||||
@ -441,7 +441,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
statusType = .FreeOutgoing(.Sent(read: item.read))
|
statusType = .FreeOutgoing(.Sent(read: item.read))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var viewCount: Int? = nil
|
var viewCount: Int? = nil
|
||||||
for attribute in item.message.attributes {
|
for attribute in item.message.attributes {
|
||||||
if let attribute = attribute as? ViewCountMessageAttribute {
|
if let attribute = attribute as? ViewCountMessageAttribute {
|
||||||
@ -704,128 +704,128 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
|
|
||||||
@objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
@objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
||||||
switch recognizer.state {
|
switch recognizer.state {
|
||||||
case .ended:
|
case .ended:
|
||||||
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
|
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
|
||||||
switch gesture {
|
switch gesture {
|
||||||
case .tap:
|
case .tap:
|
||||||
if let avatarNode = self.accessoryItemNode as? ChatMessageAvatarAccessoryItemNode, avatarNode.frame.contains(location) {
|
if let avatarNode = self.accessoryItemNode as? ChatMessageAvatarAccessoryItemNode, avatarNode.frame.contains(location) {
|
||||||
if let item = self.item, let author = item.content.firstMessage.author {
|
if let item = self.item, let author = item.content.firstMessage.author {
|
||||||
var openPeerId = item.effectiveAuthorId ?? author.id
|
var openPeerId = item.effectiveAuthorId ?? author.id
|
||||||
var navigate: ChatControllerInteractionNavigateToPeer
|
var navigate: ChatControllerInteractionNavigateToPeer
|
||||||
|
|
||||||
if item.content.firstMessage.id.peerId == item.context.account.peerId {
|
if item.content.firstMessage.id.peerId == item.context.account.peerId {
|
||||||
navigate = .chat(textInputState: nil, messageId: nil)
|
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 {
|
} else {
|
||||||
navigate = .info
|
item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, self, avatarNode.frame)
|
||||||
}
|
return
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
item.controllerInteraction.openPeer(openPeerId, navigate, item.message)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if let viaBotNode = self.viaBotNode, viaBotNode.frame.contains(location) {
|
return
|
||||||
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<Double, NoError>
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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<Double, NoError>
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,8 @@ extension UnicodeScalar {
|
|||||||
return true
|
return true
|
||||||
case 0x1f004:
|
case 0x1f004:
|
||||||
return true
|
return true
|
||||||
|
case 0x2764:
|
||||||
|
return true
|
||||||
case 0x270b, 0x2728:
|
case 0x270b, 0x2728:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
|
@ -279,7 +279,7 @@ final class ManagedAudioRecorderContext {
|
|||||||
strongSelf.toneTimer?.invalidate()
|
strongSelf.toneTimer?.invalidate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, queue: queue)
|
}, queue: queue)
|
||||||
self.toneTimer = toneTimer
|
self.toneTimer = toneTimer
|
||||||
toneTimer.start()
|
toneTimer.start()
|
||||||
} else {
|
} else {
|
||||||
@ -287,25 +287,25 @@ final class ManagedAudioRecorderContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*if beginWithTone, let beginToneData = beginToneData {
|
/*if beginWithTone, let beginToneData = beginToneData {
|
||||||
self.tonePlayer = TonePlayer()
|
self.tonePlayer = TonePlayer()
|
||||||
self.tonePlayer?.play(data: beginToneData, completed: { [weak self] in
|
self.tonePlayer?.play(data: beginToneData, completed: { [weak self] in
|
||||||
queue.async {
|
queue.async {
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let toneTimer = SwiftSignalKit.Timer(timeout: 0.3, repeat: false, completion: { [weak self] in
|
let toneTimer = SwiftSignalKit.Timer(timeout: 0.3, repeat: false, completion: { [weak self] in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.processSamples = true
|
strongSelf.processSamples = true
|
||||||
}, queue: queue)
|
}, queue: queue)
|
||||||
strongSelf.toneTimer = toneTimer
|
strongSelf.toneTimer = toneTimer
|
||||||
toneTimer.start()
|
toneTimer.start()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
self.processSamples = true
|
self.processSamples = true
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
addAudioRecorderContext(self.id, self)
|
addAudioRecorderContext(self.id, self)
|
||||||
addAudioUnitHolder(self.id, queue, self.audioUnit)
|
addAudioUnitHolder(self.id, queue, self.audioUnit)
|
||||||
@ -314,7 +314,7 @@ final class ManagedAudioRecorderContext {
|
|||||||
|
|
||||||
self.idleTimerExtensionDisposable = (Signal<Void, NoError> { subscriber in
|
self.idleTimerExtensionDisposable = (Signal<Void, NoError> { subscriber in
|
||||||
return pushIdleTimerExtension()
|
return pushIdleTimerExtension()
|
||||||
} |> delay(5.0, queue: queue)).start()
|
} |> delay(5.0, queue: queue)).start()
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
@ -401,19 +401,19 @@ final class ManagedAudioRecorderContext {
|
|||||||
strongSelf.audioSessionAcquired(headset: state.isHeadsetConnected)
|
strongSelf.audioSessionAcquired(headset: state.isHeadsetConnected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, deactivate: { [weak self] in
|
}, deactivate: { [weak self] in
|
||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
queue.async {
|
queue.async {
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.hasAudioSession = false
|
strongSelf.hasAudioSession = false
|
||||||
strongSelf.stop()
|
strongSelf.stop()
|
||||||
strongSelf.recordingState.set(.stopped)
|
strongSelf.recordingState.set(.stopped)
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return EmptyDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
return EmptyDisposable
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -294,7 +294,9 @@ public class ShareRootControllerImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cancelImpl = { [weak shareController] in
|
cancelImpl = { [weak shareController] in
|
||||||
shareController?.dismiss()
|
shareController?.dismiss(completion: { [weak self] in
|
||||||
|
self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user