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