mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-08 08:31:13 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
adf3c696f6
@ -14,7 +14,7 @@ public class BoxedMessage: NSObject {
|
|||||||
|
|
||||||
public class Serialization: NSObject, MTSerialization {
|
public class Serialization: NSObject, MTSerialization {
|
||||||
public func currentLayer() -> UInt {
|
public func currentLayer() -> UInt {
|
||||||
return 103
|
return 106
|
||||||
}
|
}
|
||||||
|
|
||||||
public func parseMessage(_ data: Data!) -> Any! {
|
public func parseMessage(_ data: Data!) -> Any! {
|
||||||
|
@ -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)
|
}))
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,15 +2,15 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.developer.icloud-services</key>
|
<key>com.apple.developer.icloud-services</key>
|
||||||
<array>
|
<array>
|
||||||
<string>CloudDocuments</string>
|
<string>CloudDocuments</string>
|
||||||
<string>CloudKit</string>
|
<string>CloudKit</string>
|
||||||
</array>
|
</array>
|
||||||
<key>com.apple.developer.icloud-container-identifiers</key>
|
<key>com.apple.developer.icloud-container-identifiers</key>
|
||||||
<array>
|
<array>
|
||||||
<string>iCloud.$(CFBundleIdentifier)</string>
|
<string>iCloud.$(CFBundleIdentifier)</string>
|
||||||
</array>
|
</array>
|
||||||
<key>aps-environment</key>
|
<key>aps-environment</key>
|
||||||
<string>production</string>
|
<string>production</string>
|
||||||
<key>com.apple.developer.associated-domains</key>
|
<key>com.apple.developer.associated-domains</key>
|
||||||
@ -33,7 +33,5 @@
|
|||||||
<string>merchant.privatbank.test.telergramios</string>
|
<string>merchant.privatbank.test.telergramios</string>
|
||||||
<string>merchant.privatbank.prod.telergram</string>
|
<string>merchant.privatbank.prod.telergram</string>
|
||||||
</array>
|
</array>
|
||||||
<key>com.apple.developer.carplay-messaging</key><true/>
|
|
||||||
<key>com.apple.developer.carplay-calling</key><true/>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ public class ImmediateTextNode: TextNode {
|
|||||||
public var maximumNumberOfLines: Int = 1
|
public var maximumNumberOfLines: Int = 1
|
||||||
public var lineSpacing: CGFloat = 0.0
|
public var lineSpacing: CGFloat = 0.0
|
||||||
public var insets: UIEdgeInsets = UIEdgeInsets()
|
public var insets: UIEdgeInsets = UIEdgeInsets()
|
||||||
|
public var textShadowColor: UIColor?
|
||||||
|
|
||||||
private var tapRecognizer: TapLongTapOrDoubleTapGestureRecognizer?
|
private var tapRecognizer: TapLongTapOrDoubleTapGestureRecognizer?
|
||||||
private var linkHighlightingNode: LinkHighlightingNode?
|
private var linkHighlightingNode: LinkHighlightingNode?
|
||||||
@ -34,7 +35,7 @@ public class ImmediateTextNode: TextNode {
|
|||||||
|
|
||||||
public func updateLayout(_ constrainedSize: CGSize) -> CGSize {
|
public func updateLayout(_ constrainedSize: CGSize) -> CGSize {
|
||||||
let makeLayout = TextNode.asyncLayout(self)
|
let makeLayout = TextNode.asyncLayout(self)
|
||||||
let (layout, apply) = makeLayout(TextNodeLayoutArguments(attributedString: self.attributedText, backgroundColor: nil, maximumNumberOfLines: self.maximumNumberOfLines, truncationType: self.truncationType, constrainedSize: constrainedSize, alignment: self.textAlignment, lineSpacing: self.lineSpacing, cutout: nil, insets: self.insets))
|
let (layout, apply) = makeLayout(TextNodeLayoutArguments(attributedString: self.attributedText, backgroundColor: nil, maximumNumberOfLines: self.maximumNumberOfLines, truncationType: self.truncationType, constrainedSize: constrainedSize, alignment: self.textAlignment, lineSpacing: self.lineSpacing, cutout: nil, insets: self.insets, textShadowColor: self.textShadowColor))
|
||||||
let _ = apply()
|
let _ = apply()
|
||||||
if layout.numberOfLines > 1 {
|
if layout.numberOfLines > 1 {
|
||||||
self.trailingLineWidth = layout.trailingLineWidth
|
self.trailingLineWidth = layout.trailingLineWidth
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
NSObjectAssociationPolicyRetain = 0,
|
NSObjectAssociationPolicyRetain = 0,
|
||||||
@ -10,6 +11,7 @@ typedef enum {
|
|||||||
+ (void)swizzleInstanceMethodOfClass:(Class)targetClass currentSelector:(SEL)currentSelector newSelector:(SEL)newSelector;
|
+ (void)swizzleInstanceMethodOfClass:(Class)targetClass currentSelector:(SEL)currentSelector newSelector:(SEL)newSelector;
|
||||||
+ (void)swizzleInstanceMethodOfClass:(Class)targetClass currentSelector:(SEL)currentSelector withAnotherClass:(Class)anotherClass newSelector:(SEL)newSelector;
|
+ (void)swizzleInstanceMethodOfClass:(Class)targetClass currentSelector:(SEL)currentSelector withAnotherClass:(Class)anotherClass newSelector:(SEL)newSelector;
|
||||||
+ (void)swizzleClassMethodOfClass:(Class)targetClass currentSelector:(SEL)currentSelector newSelector:(SEL)newSelector;
|
+ (void)swizzleClassMethodOfClass:(Class)targetClass currentSelector:(SEL)currentSelector newSelector:(SEL)newSelector;
|
||||||
|
+ (CALayer * _Nonnull)makeLayerHostCopy:(CALayer * _Nonnull)another;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@ -2,8 +2,21 @@
|
|||||||
|
|
||||||
#import <objc/runtime.h>
|
#import <objc/runtime.h>
|
||||||
|
|
||||||
|
@interface CALayer ()
|
||||||
|
|
||||||
|
- (unsigned int)contextId;
|
||||||
|
- (void)setContextId:(unsigned int)contextId;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
@implementation RuntimeUtils
|
@implementation RuntimeUtils
|
||||||
|
|
||||||
|
+ (CALayer * _Nonnull)makeLayerHostCopy:(CALayer * _Nonnull)another {
|
||||||
|
CALayer *result = [[NSClassFromString(@"CALayerHost") alloc] init];
|
||||||
|
[result setContextId:[another contextId]];
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
+ (void)swizzleInstanceMethodOfClass:(Class)targetClass currentSelector:(SEL)currentSelector newSelector:(SEL)newSelector {
|
+ (void)swizzleInstanceMethodOfClass:(Class)targetClass currentSelector:(SEL)currentSelector newSelector:(SEL)newSelector {
|
||||||
Method origMethod = nil, newMethod = nil;
|
Method origMethod = nil, newMethod = nil;
|
||||||
|
|
||||||
|
@ -92,8 +92,9 @@ public final class TextNodeLayoutArguments {
|
|||||||
public let cutout: TextNodeCutout?
|
public let cutout: TextNodeCutout?
|
||||||
public let insets: UIEdgeInsets
|
public let insets: UIEdgeInsets
|
||||||
public let lineColor: UIColor?
|
public let lineColor: UIColor?
|
||||||
|
public let textShadowColor: UIColor?
|
||||||
|
|
||||||
public init(attributedString: NSAttributedString?, backgroundColor: UIColor? = nil, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, alignment: NSTextAlignment = .natural, lineSpacing: CGFloat = 0.12, cutout: TextNodeCutout? = nil, insets: UIEdgeInsets = UIEdgeInsets(), lineColor: UIColor? = nil) {
|
public init(attributedString: NSAttributedString?, backgroundColor: UIColor? = nil, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, alignment: NSTextAlignment = .natural, lineSpacing: CGFloat = 0.12, cutout: TextNodeCutout? = nil, insets: UIEdgeInsets = UIEdgeInsets(), lineColor: UIColor? = nil, textShadowColor: UIColor? = nil) {
|
||||||
self.attributedString = attributedString
|
self.attributedString = attributedString
|
||||||
self.backgroundColor = backgroundColor
|
self.backgroundColor = backgroundColor
|
||||||
self.maximumNumberOfLines = maximumNumberOfLines
|
self.maximumNumberOfLines = maximumNumberOfLines
|
||||||
@ -104,6 +105,7 @@ public final class TextNodeLayoutArguments {
|
|||||||
self.cutout = cutout
|
self.cutout = cutout
|
||||||
self.insets = insets
|
self.insets = insets
|
||||||
self.lineColor = lineColor
|
self.lineColor = lineColor
|
||||||
|
self.textShadowColor = textShadowColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,9 +125,10 @@ public final class TextNodeLayout: NSObject {
|
|||||||
fileprivate let lines: [TextNodeLine]
|
fileprivate let lines: [TextNodeLine]
|
||||||
fileprivate let blockQuotes: [TextNodeBlockQuote]
|
fileprivate let blockQuotes: [TextNodeBlockQuote]
|
||||||
fileprivate let lineColor: UIColor?
|
fileprivate let lineColor: UIColor?
|
||||||
|
fileprivate let textShadowColor: UIColor?
|
||||||
public let hasRTL: Bool
|
public let hasRTL: Bool
|
||||||
|
|
||||||
fileprivate init(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, alignment: NSTextAlignment, lineSpacing: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, size: CGSize, truncated: Bool, firstLineOffset: CGFloat, lines: [TextNodeLine], blockQuotes: [TextNodeBlockQuote], backgroundColor: UIColor?, lineColor: UIColor?) {
|
fileprivate init(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, alignment: NSTextAlignment, lineSpacing: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, size: CGSize, truncated: Bool, firstLineOffset: CGFloat, lines: [TextNodeLine], blockQuotes: [TextNodeBlockQuote], backgroundColor: UIColor?, lineColor: UIColor?, textShadowColor: UIColor?) {
|
||||||
self.attributedString = attributedString
|
self.attributedString = attributedString
|
||||||
self.maximumNumberOfLines = maximumNumberOfLines
|
self.maximumNumberOfLines = maximumNumberOfLines
|
||||||
self.truncationType = truncationType
|
self.truncationType = truncationType
|
||||||
@ -141,6 +144,7 @@ public final class TextNodeLayout: NSObject {
|
|||||||
self.blockQuotes = blockQuotes
|
self.blockQuotes = blockQuotes
|
||||||
self.backgroundColor = backgroundColor
|
self.backgroundColor = backgroundColor
|
||||||
self.lineColor = lineColor
|
self.lineColor = lineColor
|
||||||
|
self.textShadowColor = textShadowColor
|
||||||
var hasRTL = false
|
var hasRTL = false
|
||||||
for line in lines {
|
for line in lines {
|
||||||
if line.isRTL {
|
if line.isRTL {
|
||||||
@ -575,7 +579,7 @@ public class TextNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class func calculateLayout(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, backgroundColor: UIColor?, constrainedSize: CGSize, alignment: NSTextAlignment, lineSpacingFactor: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, lineColor: UIColor?) -> TextNodeLayout {
|
private class func calculateLayout(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, backgroundColor: UIColor?, constrainedSize: CGSize, alignment: NSTextAlignment, lineSpacingFactor: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, lineColor: UIColor?, textShadowColor: UIColor?) -> TextNodeLayout {
|
||||||
if let attributedString = attributedString {
|
if let attributedString = attributedString {
|
||||||
let stringLength = attributedString.length
|
let stringLength = attributedString.length
|
||||||
|
|
||||||
@ -601,7 +605,7 @@ public class TextNode: ASDisplayNode {
|
|||||||
var maybeTypesetter: CTTypesetter?
|
var maybeTypesetter: CTTypesetter?
|
||||||
maybeTypesetter = CTTypesetterCreateWithAttributedString(attributedString as CFAttributedString)
|
maybeTypesetter = CTTypesetterCreateWithAttributedString(attributedString as CFAttributedString)
|
||||||
if maybeTypesetter == nil {
|
if maybeTypesetter == nil {
|
||||||
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor)
|
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
let typesetter = maybeTypesetter!
|
let typesetter = maybeTypesetter!
|
||||||
@ -793,9 +797,9 @@ public class TextNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(width: ceil(layoutSize.width) + insets.left + insets.right, height: ceil(layoutSize.height) + insets.top + insets.bottom), truncated: truncated, firstLineOffset: firstLineOffset, lines: lines, blockQuotes: blockQuotes, backgroundColor: backgroundColor, lineColor: lineColor)
|
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(width: ceil(layoutSize.width) + insets.left + insets.right, height: ceil(layoutSize.height) + insets.top + insets.bottom), truncated: truncated, firstLineOffset: firstLineOffset, lines: lines, blockQuotes: blockQuotes, backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor)
|
||||||
} else {
|
} else {
|
||||||
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor)
|
return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -828,11 +832,15 @@ public class TextNode: ASDisplayNode {
|
|||||||
context.fill(bounds)
|
context.fill(bounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let textShadowColor = layout.textShadowColor {
|
||||||
|
context.setTextDrawingMode(.fill)
|
||||||
|
context.setShadow(offset: CGSize(width: 0.0, height: 1.0), blur: 0.0, color: textShadowColor.cgColor)
|
||||||
|
}
|
||||||
|
|
||||||
let textMatrix = context.textMatrix
|
let textMatrix = context.textMatrix
|
||||||
let textPosition = context.textPosition
|
let textPosition = context.textPosition
|
||||||
context.textMatrix = CGAffineTransform(scaleX: 1.0, y: -1.0)
|
context.textMatrix = CGAffineTransform(scaleX: 1.0, y: -1.0)
|
||||||
|
|
||||||
|
|
||||||
let alignment = layout.alignment
|
let alignment = layout.alignment
|
||||||
let offset = CGPoint(x: layout.insets.left, y: layout.insets.top)
|
let offset = CGPoint(x: layout.insets.left, y: layout.insets.top)
|
||||||
|
|
||||||
@ -927,11 +935,11 @@ public class TextNode: ASDisplayNode {
|
|||||||
if stringMatch {
|
if stringMatch {
|
||||||
layout = existingLayout
|
layout = existingLayout
|
||||||
} else {
|
} else {
|
||||||
layout = TextNode.calculateLayout(attributedString: arguments.attributedString, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor)
|
layout = TextNode.calculateLayout(attributedString: arguments.attributedString, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor)
|
||||||
updated = true
|
updated = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
layout = TextNode.calculateLayout(attributedString: arguments.attributedString, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor)
|
layout = TextNode.calculateLayout(attributedString: arguments.attributedString, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor)
|
||||||
updated = true
|
updated = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -285,6 +285,11 @@ public final class MediaPlayerNode: ASDisplayNode {
|
|||||||
strongSelf.updateLayout()
|
strongSelf.updateLayout()
|
||||||
|
|
||||||
strongSelf.layer.addSublayer(videoLayer)
|
strongSelf.layer.addSublayer(videoLayer)
|
||||||
|
|
||||||
|
/*let testLayer = RuntimeUtils.makeLayerHostCopy(videoLayer.sublayers![0].sublayers![0])*/
|
||||||
|
//testLayer.frame = CGRect(origin: CGPoint(x: -500.0, y: -300.0), size: CGSize(width: 60.0, height: 60.0))
|
||||||
|
//strongSelf.layer.addSublayer(testLayer)
|
||||||
|
|
||||||
strongSelf.updateState()
|
strongSelf.updateState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -240,19 +240,51 @@ public final class CachedEmojiRepresentation: CachedMediaResourceRepresentation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum EmojiFitzModifier: Int32, Equatable {
|
||||||
|
case type12
|
||||||
|
case type3
|
||||||
|
case type4
|
||||||
|
case type5
|
||||||
|
case type6
|
||||||
|
|
||||||
|
public init?(emoji: String) {
|
||||||
|
switch emoji.unicodeScalars.first?.value {
|
||||||
|
case 0x1f3fb:
|
||||||
|
self = .type12
|
||||||
|
case 0x1f3fc:
|
||||||
|
self = .type3
|
||||||
|
case 0x1f3fd:
|
||||||
|
self = .type4
|
||||||
|
case 0x1f3fe:
|
||||||
|
self = .type5
|
||||||
|
case 0x1f3ff:
|
||||||
|
self = .type6
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public final class CachedAnimatedStickerFirstFrameRepresentation: CachedMediaResourceRepresentation {
|
public final class CachedAnimatedStickerFirstFrameRepresentation: CachedMediaResourceRepresentation {
|
||||||
public let keepDuration: CachedMediaRepresentationKeepDuration = .general
|
public let keepDuration: CachedMediaRepresentationKeepDuration = .general
|
||||||
|
|
||||||
public let width: Int32
|
public let width: Int32
|
||||||
public let height: Int32
|
public let height: Int32
|
||||||
|
public let fitzModifier: EmojiFitzModifier?
|
||||||
|
|
||||||
public init(width: Int32, height: Int32) {
|
public init(width: Int32, height: Int32, fitzModifier: EmojiFitzModifier? = nil) {
|
||||||
self.width = width
|
self.width = width
|
||||||
self.height = height
|
self.height = height
|
||||||
|
self.fitzModifier = fitzModifier
|
||||||
}
|
}
|
||||||
|
|
||||||
public var uniqueId: String {
|
public var uniqueId: String {
|
||||||
return "animated-sticker-first-frame-\(self.width)x\(self.height)-v1"
|
let version: Int = 1
|
||||||
|
if let fitzModifier = self.fitzModifier {
|
||||||
|
return "animated-sticker-first-frame-\(self.width)x\(self.height)-fitz\(fitzModifier.rawValue)-v\(version)"
|
||||||
|
} else {
|
||||||
|
return "animated-sticker-first-frame-\(self.width)x\(self.height)-v\(version)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func isEqual(to: CachedMediaResourceRepresentation) -> Bool {
|
public func isEqual(to: CachedMediaResourceRepresentation) -> Bool {
|
||||||
@ -263,6 +295,9 @@ public final class CachedAnimatedStickerFirstFrameRepresentation: CachedMediaRes
|
|||||||
if other.height != self.height {
|
if other.height != self.height {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if other.fitzModifier != self.fitzModifier {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
@ -275,14 +310,21 @@ public final class CachedAnimatedStickerRepresentation: CachedMediaResourceRepre
|
|||||||
|
|
||||||
public let width: Int32
|
public let width: Int32
|
||||||
public let height: Int32
|
public let height: Int32
|
||||||
|
public let fitzModifier: EmojiFitzModifier?
|
||||||
|
|
||||||
public var uniqueId: String {
|
public var uniqueId: String {
|
||||||
return "animated-sticker-\(self.width)x\(self.height)-v8"
|
let version: Int = 8
|
||||||
|
if let fitzModifier = self.fitzModifier {
|
||||||
|
return "animated-sticker-\(self.width)x\(self.height)-fitz\(fitzModifier.rawValue)-v\(version)"
|
||||||
|
} else {
|
||||||
|
return "animated-sticker-\(self.width)x\(self.height)-v\(version)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(width: Int32, height: Int32) {
|
public init(width: Int32, height: Int32, fitzModifier: EmojiFitzModifier? = nil) {
|
||||||
self.width = width
|
self.width = width
|
||||||
self.height = height
|
self.height = height
|
||||||
|
self.fitzModifier = fitzModifier
|
||||||
}
|
}
|
||||||
|
|
||||||
public func isEqual(to: CachedMediaResourceRepresentation) -> Bool {
|
public func isEqual(to: CachedMediaResourceRepresentation) -> Bool {
|
||||||
@ -293,6 +335,9 @@ public final class CachedAnimatedStickerRepresentation: CachedMediaResourceRepre
|
|||||||
if other.height != self.height {
|
if other.height != self.height {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if other.fitzModifier != self.fitzModifier {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
|
@ -2502,34 +2502,6 @@ final class MessageHistoryTable: Table {
|
|||||||
return (result, mediaRefs, count == 0 ? nil : lastIndex)
|
return (result, mediaRefs, count == 0 ? nil : lastIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchBoundaries(peerId: PeerId, namespace: MessageId.Namespace, tag: MessageTags?) -> (lower: MessageIndex, upper: MessageIndex)? {
|
|
||||||
if let tag = tag {
|
|
||||||
let latest = self.tagsTable.earlierIndices(tag: tag, peerId: peerId, namespace: namespace, index: nil, includeFrom: false, count: 1)
|
|
||||||
let earliest = self.tagsTable.laterIndices(tag: tag, peerId: peerId, namespace: namespace, index: nil, includeFrom: false, count: 1)
|
|
||||||
if let latestIndex = latest.first, let earliestIndex = earliest.first {
|
|
||||||
return (earliestIndex, latestIndex)
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var earliestIndex: MessageIndex?
|
|
||||||
self.valueBox.range(self.table, start: self.lowerBound(peerId: peerId, namespace: namespace), end: self.upperBound(peerId: peerId, namespace: namespace), keys: { key in
|
|
||||||
earliestIndex = extractKey(key)
|
|
||||||
return false
|
|
||||||
}, limit: 1)
|
|
||||||
var latestIndex: MessageIndex?
|
|
||||||
self.valueBox.range(self.table, start: self.upperBound(peerId: peerId, namespace: namespace), end: self.lowerBound(peerId: peerId, namespace: namespace), keys: { key in
|
|
||||||
latestIndex = extractKey(key)
|
|
||||||
return false
|
|
||||||
}, limit: 1)
|
|
||||||
if let latestIndex = latestIndex, let earliestIndex = earliestIndex {
|
|
||||||
return (earliestIndex, latestIndex)
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetch(peerId: PeerId, namespace: MessageId.Namespace, tag: MessageTags?, from fromIndex: MessageIndex, includeFrom: Bool, to toIndex: MessageIndex, limit: Int) -> [IntermediateMessage] {
|
func fetch(peerId: PeerId, namespace: MessageId.Namespace, tag: MessageTags?, from fromIndex: MessageIndex, includeFrom: Bool, to toIndex: MessageIndex, limit: Int) -> [IntermediateMessage] {
|
||||||
precondition(fromIndex.id.peerId == toIndex.id.peerId)
|
precondition(fromIndex.id.peerId == toIndex.id.peerId)
|
||||||
precondition(fromIndex.id.namespace == toIndex.id.namespace)
|
precondition(fromIndex.id.namespace == toIndex.id.namespace)
|
||||||
|
@ -98,11 +98,12 @@ private func chatMessageStickerDatas(postbox: Postbox, file: TelegramMediaFile,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func chatMessageAnimatedStickerDatas(postbox: Postbox, file: TelegramMediaFile, small: Bool, size: CGSize, fetched: Bool, onlyFullSize: Bool, synchronousLoad: Bool) -> Signal<Tuple3<Data?, Data?, Bool>, NoError> {
|
public func chatMessageAnimatedStickerDatas(postbox: Postbox, file: TelegramMediaFile, small: Bool, size: CGSize, fitzModifier: EmojiFitzModifier? = nil, fetched: Bool, onlyFullSize: Bool, synchronousLoad: Bool) -> Signal<Tuple3<Data?, Data?, Bool>, NoError> {
|
||||||
let thumbnailResource = chatMessageStickerResource(file: file, small: true)
|
let thumbnailResource = chatMessageStickerResource(file: file, small: true)
|
||||||
let resource = chatMessageStickerResource(file: file, small: small)
|
let resource = chatMessageStickerResource(file: file, small: small)
|
||||||
|
|
||||||
let maybeFetched = postbox.mediaBox.cachedResourceRepresentation(resource, representation: CachedAnimatedStickerFirstFrameRepresentation(width: Int32(size.width), height: Int32(size.height)), complete: false, fetch: false, attemptSynchronously: synchronousLoad)
|
let firstFrameRepresentation = CachedAnimatedStickerFirstFrameRepresentation(width: Int32(size.width), height: Int32(size.height), fitzModifier: fitzModifier)
|
||||||
|
let maybeFetched = postbox.mediaBox.cachedResourceRepresentation(resource, representation: firstFrameRepresentation, complete: false, fetch: false, attemptSynchronously: synchronousLoad)
|
||||||
|
|
||||||
return maybeFetched
|
return maybeFetched
|
||||||
|> take(1)
|
|> take(1)
|
||||||
@ -113,7 +114,7 @@ public func chatMessageAnimatedStickerDatas(postbox: Postbox, file: TelegramMedi
|
|||||||
return .single(Tuple(nil, loadedData, true))
|
return .single(Tuple(nil, loadedData, true))
|
||||||
} else {
|
} else {
|
||||||
let thumbnailData = postbox.mediaBox.cachedResourceRepresentation(thumbnailResource, representation: CachedStickerAJpegRepresentation(size: nil), complete: false)
|
let thumbnailData = postbox.mediaBox.cachedResourceRepresentation(thumbnailResource, representation: CachedStickerAJpegRepresentation(size: nil), complete: false)
|
||||||
let fullSizeData = postbox.mediaBox.cachedResourceRepresentation(resource, representation: CachedAnimatedStickerFirstFrameRepresentation(width: Int32(size.width), height: Int32(size.height)), complete: onlyFullSize)
|
let fullSizeData = postbox.mediaBox.cachedResourceRepresentation(resource, representation: firstFrameRepresentation, complete: onlyFullSize)
|
||||||
|> map { next in
|
|> map { next in
|
||||||
return (next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: .mappedIfSafe), next.complete)
|
return (next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: .mappedIfSafe), next.complete)
|
||||||
}
|
}
|
||||||
@ -164,7 +165,7 @@ private func chatMessageStickerThumbnailData(postbox: Postbox, file: TelegramMed
|
|||||||
let thumbnailData = postbox.mediaBox.cachedResourceRepresentation(thumbnailResource, representation: CachedStickerAJpegRepresentation(size: nil), complete: false)
|
let thumbnailData = postbox.mediaBox.cachedResourceRepresentation(thumbnailResource, representation: CachedStickerAJpegRepresentation(size: nil), complete: false)
|
||||||
|
|
||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
var fetchThumbnail = fetchedMediaResource(mediaBox: postbox.mediaBox, reference: stickerPackFileReference(file).resourceReference(thumbnailResource)).start()
|
let fetchThumbnail = fetchedMediaResource(mediaBox: postbox.mediaBox, reference: stickerPackFileReference(file).resourceReference(thumbnailResource)).start()
|
||||||
|
|
||||||
let disposable = (thumbnailData
|
let disposable = (thumbnailData
|
||||||
|> map { thumbnailData -> Data? in
|
|> map { thumbnailData -> Data? in
|
||||||
@ -227,8 +228,9 @@ private func chatMessageStickerPackThumbnailData(postbox: Postbox, resource: Med
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func chatMessageAnimationData(postbox: Postbox, resource: MediaResource, width: Int, height: Int, synchronousLoad: Bool) -> Signal<MediaResourceData, NoError> {
|
public func chatMessageAnimationData(postbox: Postbox, resource: MediaResource, fitzModifier: EmojiFitzModifier? = nil, width: Int, height: Int, synchronousLoad: Bool) -> Signal<MediaResourceData, NoError> {
|
||||||
let maybeFetched = postbox.mediaBox.cachedResourceRepresentation(resource, representation: CachedAnimatedStickerRepresentation(width: Int32(width), height: Int32(height)), complete: false, fetch: false, attemptSynchronously: synchronousLoad)
|
let representation = CachedAnimatedStickerRepresentation(width: Int32(width), height: Int32(height), fitzModifier: fitzModifier)
|
||||||
|
let maybeFetched = postbox.mediaBox.cachedResourceRepresentation(resource, representation: representation, complete: false, fetch: false, attemptSynchronously: synchronousLoad)
|
||||||
|
|
||||||
return maybeFetched
|
return maybeFetched
|
||||||
|> take(1)
|
|> take(1)
|
||||||
@ -236,12 +238,12 @@ public func chatMessageAnimationData(postbox: Postbox, resource: MediaResource,
|
|||||||
if maybeData.complete {
|
if maybeData.complete {
|
||||||
return .single(maybeData)
|
return .single(maybeData)
|
||||||
} else {
|
} else {
|
||||||
return postbox.mediaBox.cachedResourceRepresentation(resource, representation: CachedAnimatedStickerRepresentation(width: Int32(width), height: Int32(height)), complete: false)
|
return postbox.mediaBox.cachedResourceRepresentation(resource, representation: representation, complete: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func chatMessageAnimatedStrickerBackingData(postbox: Postbox, fileReference: FileMediaReference, synchronousLoad: Bool) -> Signal<Tuple2<Data?, Bool>, NoError> {
|
public func chatMessageAnimatedStickerBackingData(postbox: Postbox, fileReference: FileMediaReference, synchronousLoad: Bool) -> Signal<Tuple2<Data?, Bool>, NoError> {
|
||||||
let resource = fileReference.media.resource
|
let resource = fileReference.media.resource
|
||||||
|
|
||||||
let maybeFetched = postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad)
|
let maybeFetched = postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad)
|
||||||
@ -466,7 +468,7 @@ public func chatMessageSticker(postbox: Postbox, file: TelegramMediaFile, small:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func chatMessageAnimatedSticker(postbox: Postbox, file: TelegramMediaFile, small: Bool, size: CGSize, fetched: Bool = false, onlyFullSize: Bool = false, thumbnail: Bool = false, synchronousLoad: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
public func chatMessageAnimatedSticker(postbox: Postbox, file: TelegramMediaFile, small: Bool, size: CGSize, fitzModifier: EmojiFitzModifier? = nil, fetched: Bool = false, onlyFullSize: Bool = false, thumbnail: Bool = false, synchronousLoad: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
||||||
let signal: Signal<Tuple3<Data?, Data?, Bool>, NoError>
|
let signal: Signal<Tuple3<Data?, Data?, Bool>, NoError>
|
||||||
if thumbnail {
|
if thumbnail {
|
||||||
signal = chatMessageStickerThumbnailData(postbox: postbox, file: file, synchronousLoad: synchronousLoad)
|
signal = chatMessageStickerThumbnailData(postbox: postbox, file: file, synchronousLoad: synchronousLoad)
|
||||||
@ -474,7 +476,7 @@ public func chatMessageAnimatedSticker(postbox: Postbox, file: TelegramMediaFile
|
|||||||
return Tuple(data, nil, false)
|
return Tuple(data, nil, false)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
signal = chatMessageAnimatedStickerDatas(postbox: postbox, file: file, small: small, size: size, fetched: fetched, onlyFullSize: onlyFullSize, synchronousLoad: synchronousLoad)
|
signal = chatMessageAnimatedStickerDatas(postbox: postbox, file: file, small: small, size: size, fitzModifier: fitzModifier, fetched: fetched, onlyFullSize: onlyFullSize, synchronousLoad: synchronousLoad)
|
||||||
}
|
}
|
||||||
return signal
|
return signal
|
||||||
|> map { value in
|
|> map { value in
|
||||||
@ -496,7 +498,7 @@ public func chatMessageAnimatedSticker(postbox: Postbox, file: TelegramMediaFile
|
|||||||
}
|
}
|
||||||
|
|
||||||
var thumbnailImage: (UIImage, UIImage)?
|
var thumbnailImage: (UIImage, UIImage)?
|
||||||
if fullSizeImage == nil, let thumbnailData = thumbnailData {
|
if fullSizeImage == nil, let thumbnailData = thumbnailData, fitzModifier == nil {
|
||||||
if let image = imageFromAJpeg(data: thumbnailData) {
|
if let image = imageFromAJpeg(data: thumbnailData) {
|
||||||
thumbnailImage = image
|
thumbnailImage = image
|
||||||
}
|
}
|
||||||
|
@ -235,6 +235,9 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[-1667805217] = { return Api.Update.parse_updateReadHistoryInbox($0) }
|
dict[-1667805217] = { return Api.Update.parse_updateReadHistoryInbox($0) }
|
||||||
dict[1786671974] = { return Api.Update.parse_updatePeerSettings($0) }
|
dict[1786671974] = { return Api.Update.parse_updatePeerSettings($0) }
|
||||||
dict[-1263546448] = { return Api.Update.parse_updatePeerLocated($0) }
|
dict[-1263546448] = { return Api.Update.parse_updatePeerLocated($0) }
|
||||||
|
dict[967122427] = { return Api.Update.parse_updateNewScheduledMessage($0) }
|
||||||
|
dict[-1870238482] = { return Api.Update.parse_updateDeleteScheduledMessages($0) }
|
||||||
|
dict[357013699] = { return Api.Update.parse_updateMessageReactions($0) }
|
||||||
dict[1558266229] = { return Api.PopularContact.parse_popularContact($0) }
|
dict[1558266229] = { return Api.PopularContact.parse_popularContact($0) }
|
||||||
dict[-373643672] = { return Api.FolderPeer.parse_folderPeer($0) }
|
dict[-373643672] = { return Api.FolderPeer.parse_folderPeer($0) }
|
||||||
dict[367766557] = { return Api.ChannelParticipant.parse_channelParticipant($0) }
|
dict[367766557] = { return Api.ChannelParticipant.parse_channelParticipant($0) }
|
||||||
@ -272,6 +275,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[471437699] = { return Api.account.WallPapers.parse_wallPapersNotModified($0) }
|
dict[471437699] = { return Api.account.WallPapers.parse_wallPapersNotModified($0) }
|
||||||
dict[1881892265] = { return Api.account.WallPapers.parse_wallPapers($0) }
|
dict[1881892265] = { return Api.account.WallPapers.parse_wallPapers($0) }
|
||||||
dict[1158290442] = { return Api.messages.FoundGifs.parse_foundGifs($0) }
|
dict[1158290442] = { return Api.messages.FoundGifs.parse_foundGifs($0) }
|
||||||
|
dict[-1199954735] = { return Api.MessageReactions.parse_messageReactions($0) }
|
||||||
dict[-1132476723] = { return Api.FileLocation.parse_fileLocationToBeDeprecated($0) }
|
dict[-1132476723] = { return Api.FileLocation.parse_fileLocationToBeDeprecated($0) }
|
||||||
dict[-716006138] = { return Api.Poll.parse_poll($0) }
|
dict[-716006138] = { return Api.Poll.parse_poll($0) }
|
||||||
dict[423314455] = { return Api.InputNotifyPeer.parse_inputNotifyUsers($0) }
|
dict[423314455] = { return Api.InputNotifyPeer.parse_inputNotifyUsers($0) }
|
||||||
@ -416,6 +420,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[-2128640689] = { return Api.account.SentEmailCode.parse_sentEmailCode($0) }
|
dict[-2128640689] = { return Api.account.SentEmailCode.parse_sentEmailCode($0) }
|
||||||
dict[-1038136962] = { return Api.EncryptedFile.parse_encryptedFileEmpty($0) }
|
dict[-1038136962] = { return Api.EncryptedFile.parse_encryptedFileEmpty($0) }
|
||||||
dict[1248893260] = { return Api.EncryptedFile.parse_encryptedFile($0) }
|
dict[1248893260] = { return Api.EncryptedFile.parse_encryptedFile($0) }
|
||||||
|
dict[-557924733] = { return Api.CodeSettings.parse_codeSettings($0) }
|
||||||
dict[-391902247] = { return Api.SecureValueError.parse_secureValueErrorData($0) }
|
dict[-391902247] = { return Api.SecureValueError.parse_secureValueErrorData($0) }
|
||||||
dict[12467706] = { return Api.SecureValueError.parse_secureValueErrorFrontSide($0) }
|
dict[12467706] = { return Api.SecureValueError.parse_secureValueErrorFrontSide($0) }
|
||||||
dict[-2037765467] = { return Api.SecureValueError.parse_secureValueErrorReverseSide($0) }
|
dict[-2037765467] = { return Api.SecureValueError.parse_secureValueErrorReverseSide($0) }
|
||||||
@ -547,7 +552,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[773059779] = { return Api.User.parse_user($0) }
|
dict[773059779] = { return Api.User.parse_user($0) }
|
||||||
dict[-2082087340] = { return Api.Message.parse_messageEmpty($0) }
|
dict[-2082087340] = { return Api.Message.parse_messageEmpty($0) }
|
||||||
dict[-1642487306] = { return Api.Message.parse_messageService($0) }
|
dict[-1642487306] = { return Api.Message.parse_messageService($0) }
|
||||||
dict[1157215293] = { return Api.Message.parse_message($0) }
|
dict[-1186706133] = { return Api.Message.parse_message($0) }
|
||||||
dict[186120336] = { return Api.messages.RecentStickers.parse_recentStickersNotModified($0) }
|
dict[186120336] = { return Api.messages.RecentStickers.parse_recentStickersNotModified($0) }
|
||||||
dict[586395571] = { return Api.messages.RecentStickers.parse_recentStickers($0) }
|
dict[586395571] = { return Api.messages.RecentStickers.parse_recentStickers($0) }
|
||||||
dict[-182231723] = { return Api.InputFileLocation.parse_inputEncryptedFileLocation($0) }
|
dict[-182231723] = { return Api.InputFileLocation.parse_inputEncryptedFileLocation($0) }
|
||||||
@ -601,6 +606,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[-1625153079] = { return Api.InputWebFileLocation.parse_inputWebFileGeoPointLocation($0) }
|
dict[-1625153079] = { return Api.InputWebFileLocation.parse_inputWebFileGeoPointLocation($0) }
|
||||||
dict[-332168592] = { return Api.MessageFwdHeader.parse_messageFwdHeader($0) }
|
dict[-332168592] = { return Api.MessageFwdHeader.parse_messageFwdHeader($0) }
|
||||||
dict[398898678] = { return Api.help.Support.parse_support($0) }
|
dict[398898678] = { return Api.help.Support.parse_support($0) }
|
||||||
|
dict[1873957073] = { return Api.ReactionCount.parse_reactionCount($0) }
|
||||||
dict[1474492012] = { return Api.MessagesFilter.parse_inputMessagesFilterEmpty($0) }
|
dict[1474492012] = { return Api.MessagesFilter.parse_inputMessagesFilterEmpty($0) }
|
||||||
dict[-1777752804] = { return Api.MessagesFilter.parse_inputMessagesFilterPhotos($0) }
|
dict[-1777752804] = { return Api.MessagesFilter.parse_inputMessagesFilterPhotos($0) }
|
||||||
dict[-1614803355] = { return Api.MessagesFilter.parse_inputMessagesFilterVideo($0) }
|
dict[-1614803355] = { return Api.MessagesFilter.parse_inputMessagesFilterVideo($0) }
|
||||||
@ -950,6 +956,8 @@ public struct Api {
|
|||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.messages.FoundGifs:
|
case let _1 as Api.messages.FoundGifs:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
|
case let _1 as Api.MessageReactions:
|
||||||
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.FileLocation:
|
case let _1 as Api.FileLocation:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.Poll:
|
case let _1 as Api.Poll:
|
||||||
@ -1060,6 +1068,8 @@ public struct Api {
|
|||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.EncryptedFile:
|
case let _1 as Api.EncryptedFile:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
|
case let _1 as Api.CodeSettings:
|
||||||
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.SecureValueError:
|
case let _1 as Api.SecureValueError:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.NotifyPeer:
|
case let _1 as Api.NotifyPeer:
|
||||||
@ -1236,6 +1246,8 @@ public struct Api {
|
|||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.help.Support:
|
case let _1 as Api.help.Support:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
|
case let _1 as Api.ReactionCount:
|
||||||
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.MessagesFilter:
|
case let _1 as Api.MessagesFilter:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.messages.Dialogs:
|
case let _1 as Api.messages.Dialogs:
|
||||||
|
@ -4019,6 +4019,9 @@ public extension Api {
|
|||||||
case updateReadHistoryInbox(flags: Int32, folderId: Int32?, peer: Api.Peer, maxId: Int32, stillUnreadCount: Int32, pts: Int32, ptsCount: Int32)
|
case updateReadHistoryInbox(flags: Int32, folderId: Int32?, peer: Api.Peer, maxId: Int32, stillUnreadCount: Int32, pts: Int32, ptsCount: Int32)
|
||||||
case updatePeerSettings(peer: Api.Peer, settings: Api.PeerSettings)
|
case updatePeerSettings(peer: Api.Peer, settings: Api.PeerSettings)
|
||||||
case updatePeerLocated(peers: [Api.PeerLocated])
|
case updatePeerLocated(peers: [Api.PeerLocated])
|
||||||
|
case updateNewScheduledMessage(message: Api.Message)
|
||||||
|
case updateDeleteScheduledMessages(peer: Api.Peer, messages: [Int32])
|
||||||
|
case updateMessageReactions(peer: Api.Peer, msgId: Int32, reactions: Api.MessageReactions)
|
||||||
|
|
||||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
switch self {
|
switch self {
|
||||||
@ -4620,6 +4623,31 @@ public extension Api {
|
|||||||
item.serialize(buffer, true)
|
item.serialize(buffer, true)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
case .updateNewScheduledMessage(let message):
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(967122427)
|
||||||
|
}
|
||||||
|
message.serialize(buffer, true)
|
||||||
|
break
|
||||||
|
case .updateDeleteScheduledMessages(let peer, let messages):
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(-1870238482)
|
||||||
|
}
|
||||||
|
peer.serialize(buffer, true)
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(messages.count))
|
||||||
|
for item in messages {
|
||||||
|
serializeInt32(item, buffer: buffer, boxed: false)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case .updateMessageReactions(let peer, let msgId, let reactions):
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(357013699)
|
||||||
|
}
|
||||||
|
peer.serialize(buffer, true)
|
||||||
|
serializeInt32(msgId, buffer: buffer, boxed: false)
|
||||||
|
reactions.serialize(buffer, true)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4767,6 +4795,12 @@ public extension Api {
|
|||||||
return ("updatePeerSettings", [("peer", peer), ("settings", settings)])
|
return ("updatePeerSettings", [("peer", peer), ("settings", settings)])
|
||||||
case .updatePeerLocated(let peers):
|
case .updatePeerLocated(let peers):
|
||||||
return ("updatePeerLocated", [("peers", peers)])
|
return ("updatePeerLocated", [("peers", peers)])
|
||||||
|
case .updateNewScheduledMessage(let message):
|
||||||
|
return ("updateNewScheduledMessage", [("message", message)])
|
||||||
|
case .updateDeleteScheduledMessages(let peer, let messages):
|
||||||
|
return ("updateDeleteScheduledMessages", [("peer", peer), ("messages", messages)])
|
||||||
|
case .updateMessageReactions(let peer, let msgId, let reactions):
|
||||||
|
return ("updateMessageReactions", [("peer", peer), ("msgId", msgId), ("reactions", reactions)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5981,6 +6015,58 @@ public extension Api {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public static func parse_updateNewScheduledMessage(_ reader: BufferReader) -> Update? {
|
||||||
|
var _1: Api.Message?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
_1 = Api.parse(reader, signature: signature) as? Api.Message
|
||||||
|
}
|
||||||
|
let _c1 = _1 != nil
|
||||||
|
if _c1 {
|
||||||
|
return Api.Update.updateNewScheduledMessage(message: _1!)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static func parse_updateDeleteScheduledMessages(_ reader: BufferReader) -> Update? {
|
||||||
|
var _1: Api.Peer?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
_1 = Api.parse(reader, signature: signature) as? Api.Peer
|
||||||
|
}
|
||||||
|
var _2: [Int32]?
|
||||||
|
if let _ = reader.readInt32() {
|
||||||
|
_2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self)
|
||||||
|
}
|
||||||
|
let _c1 = _1 != nil
|
||||||
|
let _c2 = _2 != nil
|
||||||
|
if _c1 && _c2 {
|
||||||
|
return Api.Update.updateDeleteScheduledMessages(peer: _1!, messages: _2!)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static func parse_updateMessageReactions(_ reader: BufferReader) -> Update? {
|
||||||
|
var _1: Api.Peer?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
_1 = Api.parse(reader, signature: signature) as? Api.Peer
|
||||||
|
}
|
||||||
|
var _2: Int32?
|
||||||
|
_2 = reader.readInt32()
|
||||||
|
var _3: Api.MessageReactions?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
_3 = Api.parse(reader, signature: signature) as? Api.MessageReactions
|
||||||
|
}
|
||||||
|
let _c1 = _1 != nil
|
||||||
|
let _c2 = _2 != nil
|
||||||
|
let _c3 = _3 != nil
|
||||||
|
if _c1 && _c2 && _c3 {
|
||||||
|
return Api.Update.updateMessageReactions(peer: _1!, msgId: _2!, reactions: _3!)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
public enum PopularContact: TypeConstructorDescription {
|
public enum PopularContact: TypeConstructorDescription {
|
||||||
@ -6878,6 +6964,50 @@ public extension Api {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
public enum MessageReactions: TypeConstructorDescription {
|
||||||
|
case messageReactions(flags: Int32, results: [Api.ReactionCount])
|
||||||
|
|
||||||
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
|
switch self {
|
||||||
|
case .messageReactions(let flags, let results):
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(-1199954735)
|
||||||
|
}
|
||||||
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(results.count))
|
||||||
|
for item in results {
|
||||||
|
item.serialize(buffer, true)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
|
switch self {
|
||||||
|
case .messageReactions(let flags, let results):
|
||||||
|
return ("messageReactions", [("flags", flags), ("results", results)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func parse_messageReactions(_ reader: BufferReader) -> MessageReactions? {
|
||||||
|
var _1: Int32?
|
||||||
|
_1 = reader.readInt32()
|
||||||
|
var _2: [Api.ReactionCount]?
|
||||||
|
if let _ = reader.readInt32() {
|
||||||
|
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ReactionCount.self)
|
||||||
|
}
|
||||||
|
let _c1 = _1 != nil
|
||||||
|
let _c2 = _2 != nil
|
||||||
|
if _c1 && _c2 {
|
||||||
|
return Api.MessageReactions.messageReactions(flags: _1!, results: _2!)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
public enum FileLocation: TypeConstructorDescription {
|
public enum FileLocation: TypeConstructorDescription {
|
||||||
case fileLocationToBeDeprecated(volumeId: Int64, localId: Int32)
|
case fileLocationToBeDeprecated(volumeId: Int64, localId: Int32)
|
||||||
@ -10300,6 +10430,40 @@ public extension Api {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
public enum CodeSettings: TypeConstructorDescription {
|
||||||
|
case codeSettings(flags: Int32)
|
||||||
|
|
||||||
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
|
switch self {
|
||||||
|
case .codeSettings(let flags):
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(-557924733)
|
||||||
|
}
|
||||||
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
|
switch self {
|
||||||
|
case .codeSettings(let flags):
|
||||||
|
return ("codeSettings", [("flags", flags)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func parse_codeSettings(_ reader: BufferReader) -> CodeSettings? {
|
||||||
|
var _1: Int32?
|
||||||
|
_1 = reader.readInt32()
|
||||||
|
let _c1 = _1 != nil
|
||||||
|
if _c1 {
|
||||||
|
return Api.CodeSettings.codeSettings(flags: _1!)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
public enum SecureValueError: TypeConstructorDescription {
|
public enum SecureValueError: TypeConstructorDescription {
|
||||||
case secureValueErrorData(type: Api.SecureValueType, dataHash: Buffer, field: String, text: String)
|
case secureValueErrorData(type: Api.SecureValueType, dataHash: Buffer, field: String, text: String)
|
||||||
@ -13928,7 +14092,7 @@ public extension Api {
|
|||||||
public enum Message: TypeConstructorDescription {
|
public enum Message: TypeConstructorDescription {
|
||||||
case messageEmpty(id: Int32)
|
case messageEmpty(id: Int32)
|
||||||
case messageService(flags: Int32, id: Int32, fromId: Int32?, toId: Api.Peer, replyToMsgId: Int32?, date: Int32, action: Api.MessageAction)
|
case messageService(flags: Int32, id: Int32, fromId: Int32?, toId: Api.Peer, replyToMsgId: Int32?, date: Int32, action: Api.MessageAction)
|
||||||
case message(flags: Int32, id: Int32, fromId: Int32?, toId: Api.Peer, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int32?, replyToMsgId: Int32?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, editDate: Int32?, postAuthor: String?, groupedId: Int64?)
|
case message(flags: Int32, id: Int32, fromId: Int32?, toId: Api.Peer, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int32?, replyToMsgId: Int32?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, reactions: Api.MessageReactions?)
|
||||||
|
|
||||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
switch self {
|
switch self {
|
||||||
@ -13950,9 +14114,9 @@ public extension Api {
|
|||||||
serializeInt32(date, buffer: buffer, boxed: false)
|
serializeInt32(date, buffer: buffer, boxed: false)
|
||||||
action.serialize(buffer, true)
|
action.serialize(buffer, true)
|
||||||
break
|
break
|
||||||
case .message(let flags, let id, let fromId, let toId, let fwdFrom, let viaBotId, let replyToMsgId, let date, let message, let media, let replyMarkup, let entities, let views, let editDate, let postAuthor, let groupedId):
|
case .message(let flags, let id, let fromId, let toId, let fwdFrom, let viaBotId, let replyToMsgId, let date, let message, let media, let replyMarkup, let entities, let views, let editDate, let postAuthor, let groupedId, let reactions):
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(1157215293)
|
buffer.appendInt32(-1186706133)
|
||||||
}
|
}
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
serializeInt32(id, buffer: buffer, boxed: false)
|
serializeInt32(id, buffer: buffer, boxed: false)
|
||||||
@ -13974,6 +14138,7 @@ public extension Api {
|
|||||||
if Int(flags) & Int(1 << 15) != 0 {serializeInt32(editDate!, buffer: buffer, boxed: false)}
|
if Int(flags) & Int(1 << 15) != 0 {serializeInt32(editDate!, buffer: buffer, boxed: false)}
|
||||||
if Int(flags) & Int(1 << 16) != 0 {serializeString(postAuthor!, buffer: buffer, boxed: false)}
|
if Int(flags) & Int(1 << 16) != 0 {serializeString(postAuthor!, buffer: buffer, boxed: false)}
|
||||||
if Int(flags) & Int(1 << 17) != 0 {serializeInt64(groupedId!, buffer: buffer, boxed: false)}
|
if Int(flags) & Int(1 << 17) != 0 {serializeInt64(groupedId!, buffer: buffer, boxed: false)}
|
||||||
|
if Int(flags) & Int(1 << 20) != 0 {reactions!.serialize(buffer, true)}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -13984,8 +14149,8 @@ public extension Api {
|
|||||||
return ("messageEmpty", [("id", id)])
|
return ("messageEmpty", [("id", id)])
|
||||||
case .messageService(let flags, let id, let fromId, let toId, let replyToMsgId, let date, let action):
|
case .messageService(let flags, let id, let fromId, let toId, let replyToMsgId, let date, let action):
|
||||||
return ("messageService", [("flags", flags), ("id", id), ("fromId", fromId), ("toId", toId), ("replyToMsgId", replyToMsgId), ("date", date), ("action", action)])
|
return ("messageService", [("flags", flags), ("id", id), ("fromId", fromId), ("toId", toId), ("replyToMsgId", replyToMsgId), ("date", date), ("action", action)])
|
||||||
case .message(let flags, let id, let fromId, let toId, let fwdFrom, let viaBotId, let replyToMsgId, let date, let message, let media, let replyMarkup, let entities, let views, let editDate, let postAuthor, let groupedId):
|
case .message(let flags, let id, let fromId, let toId, let fwdFrom, let viaBotId, let replyToMsgId, let date, let message, let media, let replyMarkup, let entities, let views, let editDate, let postAuthor, let groupedId, let reactions):
|
||||||
return ("message", [("flags", flags), ("id", id), ("fromId", fromId), ("toId", toId), ("fwdFrom", fwdFrom), ("viaBotId", viaBotId), ("replyToMsgId", replyToMsgId), ("date", date), ("message", message), ("media", media), ("replyMarkup", replyMarkup), ("entities", entities), ("views", views), ("editDate", editDate), ("postAuthor", postAuthor), ("groupedId", groupedId)])
|
return ("message", [("flags", flags), ("id", id), ("fromId", fromId), ("toId", toId), ("fwdFrom", fwdFrom), ("viaBotId", viaBotId), ("replyToMsgId", replyToMsgId), ("date", date), ("message", message), ("media", media), ("replyMarkup", replyMarkup), ("entities", entities), ("views", views), ("editDate", editDate), ("postAuthor", postAuthor), ("groupedId", groupedId), ("reactions", reactions)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -14076,6 +14241,10 @@ public extension Api {
|
|||||||
if Int(_1!) & Int(1 << 16) != 0 {_15 = parseString(reader) }
|
if Int(_1!) & Int(1 << 16) != 0 {_15 = parseString(reader) }
|
||||||
var _16: Int64?
|
var _16: Int64?
|
||||||
if Int(_1!) & Int(1 << 17) != 0 {_16 = reader.readInt64() }
|
if Int(_1!) & Int(1 << 17) != 0 {_16 = reader.readInt64() }
|
||||||
|
var _17: Api.MessageReactions?
|
||||||
|
if Int(_1!) & Int(1 << 20) != 0 {if let signature = reader.readInt32() {
|
||||||
|
_17 = Api.parse(reader, signature: signature) as? Api.MessageReactions
|
||||||
|
} }
|
||||||
let _c1 = _1 != nil
|
let _c1 = _1 != nil
|
||||||
let _c2 = _2 != nil
|
let _c2 = _2 != nil
|
||||||
let _c3 = (Int(_1!) & Int(1 << 8) == 0) || _3 != nil
|
let _c3 = (Int(_1!) & Int(1 << 8) == 0) || _3 != nil
|
||||||
@ -14092,8 +14261,9 @@ public extension Api {
|
|||||||
let _c14 = (Int(_1!) & Int(1 << 15) == 0) || _14 != nil
|
let _c14 = (Int(_1!) & Int(1 << 15) == 0) || _14 != nil
|
||||||
let _c15 = (Int(_1!) & Int(1 << 16) == 0) || _15 != nil
|
let _c15 = (Int(_1!) & Int(1 << 16) == 0) || _15 != nil
|
||||||
let _c16 = (Int(_1!) & Int(1 << 17) == 0) || _16 != nil
|
let _c16 = (Int(_1!) & Int(1 << 17) == 0) || _16 != nil
|
||||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 {
|
let _c17 = (Int(_1!) & Int(1 << 20) == 0) || _17 != nil
|
||||||
return Api.Message.message(flags: _1!, id: _2!, fromId: _3, toId: _4!, fwdFrom: _5, viaBotId: _6, replyToMsgId: _7, date: _8!, message: _9!, media: _10, replyMarkup: _11, entities: _12, views: _13, editDate: _14, postAuthor: _15, groupedId: _16)
|
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 {
|
||||||
|
return Api.Message.message(flags: _1!, id: _2!, fromId: _3, toId: _4!, fwdFrom: _5, viaBotId: _6, replyToMsgId: _7, date: _8!, message: _9!, media: _10, replyMarkup: _11, entities: _12, views: _13, editDate: _14, postAuthor: _15, groupedId: _16, reactions: _17)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return nil
|
return nil
|
||||||
@ -15586,6 +15756,48 @@ public extension Api {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
public enum ReactionCount: TypeConstructorDescription {
|
||||||
|
case reactionCount(flags: Int32, reaction: String, count: Int32)
|
||||||
|
|
||||||
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
|
switch self {
|
||||||
|
case .reactionCount(let flags, let reaction, let count):
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(1873957073)
|
||||||
|
}
|
||||||
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
|
serializeString(reaction, buffer: buffer, boxed: false)
|
||||||
|
serializeInt32(count, buffer: buffer, boxed: false)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
|
switch self {
|
||||||
|
case .reactionCount(let flags, let reaction, let count):
|
||||||
|
return ("reactionCount", [("flags", flags), ("reaction", reaction), ("count", count)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func parse_reactionCount(_ reader: BufferReader) -> ReactionCount? {
|
||||||
|
var _1: Int32?
|
||||||
|
_1 = reader.readInt32()
|
||||||
|
var _2: String?
|
||||||
|
_2 = parseString(reader)
|
||||||
|
var _3: Int32?
|
||||||
|
_3 = reader.readInt32()
|
||||||
|
let _c1 = _1 != nil
|
||||||
|
let _c2 = _2 != nil
|
||||||
|
let _c3 = _3 != nil
|
||||||
|
if _c1 && _c2 && _c3 {
|
||||||
|
return Api.ReactionCount.reactionCount(flags: _1!, reaction: _2!, count: _3!)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
public enum MessagesFilter: TypeConstructorDescription {
|
public enum MessagesFilter: TypeConstructorDescription {
|
||||||
case inputMessagesFilterEmpty
|
case inputMessagesFilterEmpty
|
||||||
|
@ -1211,30 +1211,6 @@ public extension Api {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func sendMessage(flags: Int32, peer: Api.InputPeer, replyToMsgId: Int32?, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
|
||||||
let buffer = Buffer()
|
|
||||||
buffer.appendInt32(-91733382)
|
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
|
||||||
peer.serialize(buffer, true)
|
|
||||||
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)}
|
|
||||||
serializeString(message, buffer: buffer, boxed: false)
|
|
||||||
serializeInt64(randomId, buffer: buffer, boxed: false)
|
|
||||||
if Int(flags) & Int(1 << 2) != 0 {replyMarkup!.serialize(buffer, true)}
|
|
||||||
if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261)
|
|
||||||
buffer.appendInt32(Int32(entities!.count))
|
|
||||||
for item in entities! {
|
|
||||||
item.serialize(buffer, true)
|
|
||||||
}}
|
|
||||||
return (FunctionDescription(name: "messages.sendMessage", parameters: [("flags", flags), ("peer", peer), ("replyToMsgId", replyToMsgId), ("message", message), ("randomId", randomId), ("replyMarkup", replyMarkup), ("entities", entities)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
|
||||||
let reader = BufferReader(buffer)
|
|
||||||
var result: Api.Updates?
|
|
||||||
if let signature = reader.readInt32() {
|
|
||||||
result = Api.parse(reader, signature: signature) as? Api.Updates
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func reportSpam(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
public static func reportSpam(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
buffer.appendInt32(-820669733)
|
buffer.appendInt32(-820669733)
|
||||||
@ -1850,25 +1826,6 @@ public extension Api {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func sendInlineBotResult(flags: Int32, peer: Api.InputPeer, replyToMsgId: Int32?, randomId: Int64, queryId: Int64, id: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
|
||||||
let buffer = Buffer()
|
|
||||||
buffer.appendInt32(-1318189314)
|
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
|
||||||
peer.serialize(buffer, true)
|
|
||||||
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)}
|
|
||||||
serializeInt64(randomId, buffer: buffer, boxed: false)
|
|
||||||
serializeInt64(queryId, buffer: buffer, boxed: false)
|
|
||||||
serializeString(id, buffer: buffer, boxed: false)
|
|
||||||
return (FunctionDescription(name: "messages.sendInlineBotResult", parameters: [("flags", flags), ("peer", peer), ("replyToMsgId", replyToMsgId), ("randomId", randomId), ("queryId", queryId), ("id", id)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
|
||||||
let reader = BufferReader(buffer)
|
|
||||||
var result: Api.Updates?
|
|
||||||
if let signature = reader.readInt32() {
|
|
||||||
result = Api.parse(reader, signature: signature) as? Api.Updates
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func getMessageEditData(peer: Api.InputPeer, id: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.MessageEditData>) {
|
public static func getMessageEditData(peer: Api.InputPeer, id: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.MessageEditData>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
buffer.appendInt32(-39416522)
|
buffer.appendInt32(-39416522)
|
||||||
@ -2321,53 +2278,6 @@ public extension Api {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func sendMultiMedia(flags: Int32, peer: Api.InputPeer, replyToMsgId: Int32?, multiMedia: [Api.InputSingleMedia]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
|
||||||
let buffer = Buffer()
|
|
||||||
buffer.appendInt32(546656559)
|
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
|
||||||
peer.serialize(buffer, true)
|
|
||||||
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)}
|
|
||||||
buffer.appendInt32(481674261)
|
|
||||||
buffer.appendInt32(Int32(multiMedia.count))
|
|
||||||
for item in multiMedia {
|
|
||||||
item.serialize(buffer, true)
|
|
||||||
}
|
|
||||||
return (FunctionDescription(name: "messages.sendMultiMedia", parameters: [("flags", flags), ("peer", peer), ("replyToMsgId", replyToMsgId), ("multiMedia", multiMedia)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
|
||||||
let reader = BufferReader(buffer)
|
|
||||||
var result: Api.Updates?
|
|
||||||
if let signature = reader.readInt32() {
|
|
||||||
result = Api.parse(reader, signature: signature) as? Api.Updates
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func forwardMessages(flags: Int32, fromPeer: Api.InputPeer, id: [Int32], randomId: [Int64], toPeer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
|
||||||
let buffer = Buffer()
|
|
||||||
buffer.appendInt32(1888354709)
|
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
|
||||||
fromPeer.serialize(buffer, true)
|
|
||||||
buffer.appendInt32(481674261)
|
|
||||||
buffer.appendInt32(Int32(id.count))
|
|
||||||
for item in id {
|
|
||||||
serializeInt32(item, buffer: buffer, boxed: false)
|
|
||||||
}
|
|
||||||
buffer.appendInt32(481674261)
|
|
||||||
buffer.appendInt32(Int32(randomId.count))
|
|
||||||
for item in randomId {
|
|
||||||
serializeInt64(item, buffer: buffer, boxed: false)
|
|
||||||
}
|
|
||||||
toPeer.serialize(buffer, true)
|
|
||||||
return (FunctionDescription(name: "messages.forwardMessages", parameters: [("flags", flags), ("fromPeer", fromPeer), ("id", id), ("randomId", randomId), ("toPeer", toPeer)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
|
||||||
let reader = BufferReader(buffer)
|
|
||||||
var result: Api.Updates?
|
|
||||||
if let signature = reader.readInt32() {
|
|
||||||
result = Api.parse(reader, signature: signature) as? Api.Updates
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func uploadEncryptedFile(peer: Api.InputEncryptedChat, file: Api.InputEncryptedFile) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.EncryptedFile>) {
|
public static func uploadEncryptedFile(peer: Api.InputEncryptedChat, file: Api.InputEncryptedFile) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.EncryptedFile>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
buffer.appendInt32(1347929239)
|
buffer.appendInt32(1347929239)
|
||||||
@ -2403,31 +2313,6 @@ public extension Api {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func sendMedia(flags: Int32, peer: Api.InputPeer, replyToMsgId: Int32?, media: Api.InputMedia, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
|
||||||
let buffer = Buffer()
|
|
||||||
buffer.appendInt32(-1194252757)
|
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
|
||||||
peer.serialize(buffer, true)
|
|
||||||
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)}
|
|
||||||
media.serialize(buffer, true)
|
|
||||||
serializeString(message, buffer: buffer, boxed: false)
|
|
||||||
serializeInt64(randomId, buffer: buffer, boxed: false)
|
|
||||||
if Int(flags) & Int(1 << 2) != 0 {replyMarkup!.serialize(buffer, true)}
|
|
||||||
if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261)
|
|
||||||
buffer.appendInt32(Int32(entities!.count))
|
|
||||||
for item in entities! {
|
|
||||||
item.serialize(buffer, true)
|
|
||||||
}}
|
|
||||||
return (FunctionDescription(name: "messages.sendMedia", parameters: [("flags", flags), ("peer", peer), ("replyToMsgId", replyToMsgId), ("media", media), ("message", message), ("randomId", randomId), ("replyMarkup", replyMarkup), ("entities", entities)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
|
||||||
let reader = BufferReader(buffer)
|
|
||||||
var result: Api.Updates?
|
|
||||||
if let signature = reader.readInt32() {
|
|
||||||
result = Api.parse(reader, signature: signature) as? Api.Updates
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func getMessages(id: [Api.InputMessage]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.Messages>) {
|
public static func getMessages(id: [Api.InputMessage]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.Messages>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
buffer.appendInt32(1673946374)
|
buffer.appendInt32(1673946374)
|
||||||
@ -2682,30 +2567,6 @@ public extension Api {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func editMessage(flags: Int32, peer: Api.InputPeer, id: Int32, message: String?, media: Api.InputMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
|
||||||
let buffer = Buffer()
|
|
||||||
buffer.appendInt32(-787025122)
|
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
|
||||||
peer.serialize(buffer, true)
|
|
||||||
serializeInt32(id, buffer: buffer, boxed: false)
|
|
||||||
if Int(flags) & Int(1 << 11) != 0 {serializeString(message!, buffer: buffer, boxed: false)}
|
|
||||||
if Int(flags) & Int(1 << 14) != 0 {media!.serialize(buffer, true)}
|
|
||||||
if Int(flags) & Int(1 << 2) != 0 {replyMarkup!.serialize(buffer, true)}
|
|
||||||
if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261)
|
|
||||||
buffer.appendInt32(Int32(entities!.count))
|
|
||||||
for item in entities! {
|
|
||||||
item.serialize(buffer, true)
|
|
||||||
}}
|
|
||||||
return (FunctionDescription(name: "messages.editMessage", parameters: [("flags", flags), ("peer", peer), ("id", id), ("message", message), ("media", media), ("replyMarkup", replyMarkup), ("entities", entities)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
|
||||||
let reader = BufferReader(buffer)
|
|
||||||
var result: Api.Updates?
|
|
||||||
if let signature = reader.readInt32() {
|
|
||||||
result = Api.parse(reader, signature: signature) as? Api.Updates
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func editInlineBotMessage(flags: Int32, id: Api.InputBotInlineMessageID, message: String?, media: Api.InputMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
public static func editInlineBotMessage(flags: Int32, id: Api.InputBotInlineMessageID, message: String?, media: Api.InputMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
buffer.appendInt32(-2091549254)
|
buffer.appendInt32(-2091549254)
|
||||||
@ -2955,6 +2816,262 @@ public extension Api {
|
|||||||
return result
|
return result
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static func sendMessage(flags: Int32, peer: Api.InputPeer, replyToMsgId: Int32?, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||||
|
let buffer = Buffer()
|
||||||
|
buffer.appendInt32(1376532592)
|
||||||
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
|
peer.serialize(buffer, true)
|
||||||
|
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)}
|
||||||
|
serializeString(message, buffer: buffer, boxed: false)
|
||||||
|
serializeInt64(randomId, buffer: buffer, boxed: false)
|
||||||
|
if Int(flags) & Int(1 << 2) != 0 {replyMarkup!.serialize(buffer, true)}
|
||||||
|
if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(entities!.count))
|
||||||
|
for item in entities! {
|
||||||
|
item.serialize(buffer, true)
|
||||||
|
}}
|
||||||
|
if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)}
|
||||||
|
return (FunctionDescription(name: "messages.sendMessage", parameters: [("flags", flags), ("peer", peer), ("replyToMsgId", replyToMsgId), ("message", message), ("randomId", randomId), ("replyMarkup", replyMarkup), ("entities", entities), ("scheduleDate", scheduleDate)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
||||||
|
let reader = BufferReader(buffer)
|
||||||
|
var result: Api.Updates?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
result = Api.parse(reader, signature: signature) as? Api.Updates
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func sendMedia(flags: Int32, peer: Api.InputPeer, replyToMsgId: Int32?, media: Api.InputMedia, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||||
|
let buffer = Buffer()
|
||||||
|
buffer.appendInt32(881978281)
|
||||||
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
|
peer.serialize(buffer, true)
|
||||||
|
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)}
|
||||||
|
media.serialize(buffer, true)
|
||||||
|
serializeString(message, buffer: buffer, boxed: false)
|
||||||
|
serializeInt64(randomId, buffer: buffer, boxed: false)
|
||||||
|
if Int(flags) & Int(1 << 2) != 0 {replyMarkup!.serialize(buffer, true)}
|
||||||
|
if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(entities!.count))
|
||||||
|
for item in entities! {
|
||||||
|
item.serialize(buffer, true)
|
||||||
|
}}
|
||||||
|
if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)}
|
||||||
|
return (FunctionDescription(name: "messages.sendMedia", parameters: [("flags", flags), ("peer", peer), ("replyToMsgId", replyToMsgId), ("media", media), ("message", message), ("randomId", randomId), ("replyMarkup", replyMarkup), ("entities", entities), ("scheduleDate", scheduleDate)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
||||||
|
let reader = BufferReader(buffer)
|
||||||
|
var result: Api.Updates?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
result = Api.parse(reader, signature: signature) as? Api.Updates
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func sendInlineBotResult(flags: Int32, peer: Api.InputPeer, replyToMsgId: Int32?, randomId: Int64, queryId: Int64, id: String, scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||||
|
let buffer = Buffer()
|
||||||
|
buffer.appendInt32(570955184)
|
||||||
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
|
peer.serialize(buffer, true)
|
||||||
|
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)}
|
||||||
|
serializeInt64(randomId, buffer: buffer, boxed: false)
|
||||||
|
serializeInt64(queryId, buffer: buffer, boxed: false)
|
||||||
|
serializeString(id, buffer: buffer, boxed: false)
|
||||||
|
if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)}
|
||||||
|
return (FunctionDescription(name: "messages.sendInlineBotResult", parameters: [("flags", flags), ("peer", peer), ("replyToMsgId", replyToMsgId), ("randomId", randomId), ("queryId", queryId), ("id", id), ("scheduleDate", scheduleDate)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
||||||
|
let reader = BufferReader(buffer)
|
||||||
|
var result: Api.Updates?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
result = Api.parse(reader, signature: signature) as? Api.Updates
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func sendMultiMedia(flags: Int32, peer: Api.InputPeer, replyToMsgId: Int32?, multiMedia: [Api.InputSingleMedia], scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||||
|
let buffer = Buffer()
|
||||||
|
buffer.appendInt32(-872345397)
|
||||||
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
|
peer.serialize(buffer, true)
|
||||||
|
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)}
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(multiMedia.count))
|
||||||
|
for item in multiMedia {
|
||||||
|
item.serialize(buffer, true)
|
||||||
|
}
|
||||||
|
if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)}
|
||||||
|
return (FunctionDescription(name: "messages.sendMultiMedia", parameters: [("flags", flags), ("peer", peer), ("replyToMsgId", replyToMsgId), ("multiMedia", multiMedia), ("scheduleDate", scheduleDate)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
||||||
|
let reader = BufferReader(buffer)
|
||||||
|
var result: Api.Updates?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
result = Api.parse(reader, signature: signature) as? Api.Updates
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func forwardMessages(flags: Int32, fromPeer: Api.InputPeer, id: [Int32], randomId: [Int64], toPeer: Api.InputPeer, scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||||
|
let buffer = Buffer()
|
||||||
|
buffer.appendInt32(-637606386)
|
||||||
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
|
fromPeer.serialize(buffer, true)
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(id.count))
|
||||||
|
for item in id {
|
||||||
|
serializeInt32(item, buffer: buffer, boxed: false)
|
||||||
|
}
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(randomId.count))
|
||||||
|
for item in randomId {
|
||||||
|
serializeInt64(item, buffer: buffer, boxed: false)
|
||||||
|
}
|
||||||
|
toPeer.serialize(buffer, true)
|
||||||
|
if Int(flags) & Int(1 << 10) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)}
|
||||||
|
return (FunctionDescription(name: "messages.forwardMessages", parameters: [("flags", flags), ("fromPeer", fromPeer), ("id", id), ("randomId", randomId), ("toPeer", toPeer), ("scheduleDate", scheduleDate)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
||||||
|
let reader = BufferReader(buffer)
|
||||||
|
var result: Api.Updates?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
result = Api.parse(reader, signature: signature) as? Api.Updates
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func getScheduledHistory(peer: Api.InputPeer, hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.Messages>) {
|
||||||
|
let buffer = Buffer()
|
||||||
|
buffer.appendInt32(-490575781)
|
||||||
|
peer.serialize(buffer, true)
|
||||||
|
serializeInt32(hash, buffer: buffer, boxed: false)
|
||||||
|
return (FunctionDescription(name: "messages.getScheduledHistory", parameters: [("peer", peer), ("hash", hash)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in
|
||||||
|
let reader = BufferReader(buffer)
|
||||||
|
var result: Api.messages.Messages?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
result = Api.parse(reader, signature: signature) as? Api.messages.Messages
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func getScheduledMessages(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.Messages>) {
|
||||||
|
let buffer = Buffer()
|
||||||
|
buffer.appendInt32(-1111817116)
|
||||||
|
peer.serialize(buffer, true)
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(id.count))
|
||||||
|
for item in id {
|
||||||
|
serializeInt32(item, buffer: buffer, boxed: false)
|
||||||
|
}
|
||||||
|
return (FunctionDescription(name: "messages.getScheduledMessages", parameters: [("peer", peer), ("id", id)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in
|
||||||
|
let reader = BufferReader(buffer)
|
||||||
|
var result: Api.messages.Messages?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
result = Api.parse(reader, signature: signature) as? Api.messages.Messages
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func sendScheduledMessages(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||||
|
let buffer = Buffer()
|
||||||
|
buffer.appendInt32(-1120369398)
|
||||||
|
peer.serialize(buffer, true)
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(id.count))
|
||||||
|
for item in id {
|
||||||
|
serializeInt32(item, buffer: buffer, boxed: false)
|
||||||
|
}
|
||||||
|
return (FunctionDescription(name: "messages.sendScheduledMessages", parameters: [("peer", peer), ("id", id)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
||||||
|
let reader = BufferReader(buffer)
|
||||||
|
var result: Api.Updates?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
result = Api.parse(reader, signature: signature) as? Api.Updates
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func deleteScheduledMessages(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||||
|
let buffer = Buffer()
|
||||||
|
buffer.appendInt32(1504586518)
|
||||||
|
peer.serialize(buffer, true)
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(id.count))
|
||||||
|
for item in id {
|
||||||
|
serializeInt32(item, buffer: buffer, boxed: false)
|
||||||
|
}
|
||||||
|
return (FunctionDescription(name: "messages.deleteScheduledMessages", parameters: [("peer", peer), ("id", id)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
||||||
|
let reader = BufferReader(buffer)
|
||||||
|
var result: Api.Updates?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
result = Api.parse(reader, signature: signature) as? Api.Updates
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func editMessage(flags: Int32, peer: Api.InputPeer, id: Int32, message: String?, media: Api.InputMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||||
|
let buffer = Buffer()
|
||||||
|
buffer.appendInt32(1224152952)
|
||||||
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
|
peer.serialize(buffer, true)
|
||||||
|
serializeInt32(id, buffer: buffer, boxed: false)
|
||||||
|
if Int(flags) & Int(1 << 11) != 0 {serializeString(message!, buffer: buffer, boxed: false)}
|
||||||
|
if Int(flags) & Int(1 << 14) != 0 {media!.serialize(buffer, true)}
|
||||||
|
if Int(flags) & Int(1 << 2) != 0 {replyMarkup!.serialize(buffer, true)}
|
||||||
|
if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(entities!.count))
|
||||||
|
for item in entities! {
|
||||||
|
item.serialize(buffer, true)
|
||||||
|
}}
|
||||||
|
if Int(flags) & Int(1 << 15) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)}
|
||||||
|
return (FunctionDescription(name: "messages.editMessage", parameters: [("flags", flags), ("peer", peer), ("id", id), ("message", message), ("media", media), ("replyMarkup", replyMarkup), ("entities", entities), ("scheduleDate", scheduleDate)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
||||||
|
let reader = BufferReader(buffer)
|
||||||
|
var result: Api.Updates?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
result = Api.parse(reader, signature: signature) as? Api.Updates
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func sendReaction(peer: Api.InputPeer, msgId: Int32, reaction: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||||
|
let buffer = Buffer()
|
||||||
|
buffer.appendInt32(666939980)
|
||||||
|
peer.serialize(buffer, true)
|
||||||
|
serializeInt32(msgId, buffer: buffer, boxed: false)
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(reaction.count))
|
||||||
|
for item in reaction {
|
||||||
|
serializeString(item, buffer: buffer, boxed: false)
|
||||||
|
}
|
||||||
|
return (FunctionDescription(name: "messages.sendReaction", parameters: [("peer", peer), ("msgId", msgId), ("reaction", reaction)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
||||||
|
let reader = BufferReader(buffer)
|
||||||
|
var result: Api.Updates?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
result = Api.parse(reader, signature: signature) as? Api.Updates
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func getMessagesReactions(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||||
|
let buffer = Buffer()
|
||||||
|
buffer.appendInt32(-1950707482)
|
||||||
|
peer.serialize(buffer, true)
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(id.count))
|
||||||
|
for item in id {
|
||||||
|
serializeInt32(item, buffer: buffer, boxed: false)
|
||||||
|
}
|
||||||
|
return (FunctionDescription(name: "messages.getMessagesReactions", parameters: [("peer", peer), ("id", id)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
||||||
|
let reader = BufferReader(buffer)
|
||||||
|
var result: Api.Updates?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
result = Api.parse(reader, signature: signature) as? Api.Updates
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public struct channels {
|
public struct channels {
|
||||||
public static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
public static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||||
@ -4596,22 +4713,6 @@ public extension Api {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func getFile(location: Api.InputFileLocation, offset: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.upload.File>) {
|
|
||||||
let buffer = Buffer()
|
|
||||||
buffer.appendInt32(-475607115)
|
|
||||||
location.serialize(buffer, true)
|
|
||||||
serializeInt32(offset, buffer: buffer, boxed: false)
|
|
||||||
serializeInt32(limit, buffer: buffer, boxed: false)
|
|
||||||
return (FunctionDescription(name: "upload.getFile", parameters: [("location", location), ("offset", offset), ("limit", limit)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.upload.File? in
|
|
||||||
let reader = BufferReader(buffer)
|
|
||||||
var result: Api.upload.File?
|
|
||||||
if let signature = reader.readInt32() {
|
|
||||||
result = Api.parse(reader, signature: signature) as? Api.upload.File
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func saveBigFilePart(fileId: Int64, filePart: Int32, fileTotalParts: Int32, bytes: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
public static func saveBigFilePart(fileId: Int64, filePart: Int32, fileTotalParts: Int32, bytes: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
buffer.appendInt32(-562337987)
|
buffer.appendInt32(-562337987)
|
||||||
@ -4705,6 +4806,23 @@ public extension Api {
|
|||||||
return result
|
return result
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static func getFile(flags: Int32, location: Api.InputFileLocation, offset: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.upload.File>) {
|
||||||
|
let buffer = Buffer()
|
||||||
|
buffer.appendInt32(-1319462148)
|
||||||
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
|
location.serialize(buffer, true)
|
||||||
|
serializeInt32(offset, buffer: buffer, boxed: false)
|
||||||
|
serializeInt32(limit, buffer: buffer, boxed: false)
|
||||||
|
return (FunctionDescription(name: "upload.getFile", parameters: [("flags", flags), ("location", location), ("offset", offset), ("limit", limit)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.upload.File? in
|
||||||
|
let reader = BufferReader(buffer)
|
||||||
|
var result: Api.upload.File?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
result = Api.parse(reader, signature: signature) as? Api.upload.File
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public struct account {
|
public struct account {
|
||||||
public static func updateNotifySettings(peer: Api.InputNotifyPeer, settings: Api.InputPeerNotifySettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
public static func updateNotifySettings(peer: Api.InputNotifyPeer, settings: Api.InputPeerNotifySettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||||
|
@ -70,6 +70,7 @@ enum AccountStateMutationOperation {
|
|||||||
case DeleteMessages([MessageId])
|
case DeleteMessages([MessageId])
|
||||||
case EditMessage(MessageId, StoreMessage)
|
case EditMessage(MessageId, StoreMessage)
|
||||||
case UpdateMessagePoll(MediaId, Api.Poll?, Api.PollResults)
|
case UpdateMessagePoll(MediaId, Api.Poll?, Api.PollResults)
|
||||||
|
case UpdateMessageReactions(MessageId, Api.MessageReactions)
|
||||||
case UpdateMedia(MediaId, Media?)
|
case UpdateMedia(MediaId, Media?)
|
||||||
case ReadInbox(MessageId)
|
case ReadInbox(MessageId)
|
||||||
case ReadOutbox(MessageId, Int32?)
|
case ReadOutbox(MessageId, Int32?)
|
||||||
@ -219,6 +220,10 @@ struct AccountMutableState {
|
|||||||
self.addOperation(.UpdateMessagePoll(id, poll, results))
|
self.addOperation(.UpdateMessagePoll(id, poll, results))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mutating func updateMessageReactions(_ messageId: MessageId, reactions: Api.MessageReactions) {
|
||||||
|
self.addOperation(.UpdateMessageReactions(messageId, reactions))
|
||||||
|
}
|
||||||
|
|
||||||
mutating func updateMedia(_ id: MediaId, media: Media?) {
|
mutating func updateMedia(_ id: MediaId, media: Media?) {
|
||||||
self.addOperation(.UpdateMedia(id, media))
|
self.addOperation(.UpdateMedia(id, media))
|
||||||
}
|
}
|
||||||
@ -398,7 +403,7 @@ struct AccountMutableState {
|
|||||||
|
|
||||||
mutating func addOperation(_ operation: AccountStateMutationOperation) {
|
mutating func addOperation(_ operation: AccountStateMutationOperation) {
|
||||||
switch operation {
|
switch operation {
|
||||||
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby:
|
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby:
|
||||||
break
|
break
|
||||||
case let .AddMessages(messages, location):
|
case let .AddMessages(messages, location):
|
||||||
for message in messages {
|
for message in messages {
|
||||||
|
@ -924,8 +924,11 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
|
|||||||
}
|
}
|
||||||
updatedState.addMessages([message], location: .UpperHistoryBlock)
|
updatedState.addMessages([message], location: .UpperHistoryBlock)
|
||||||
}
|
}
|
||||||
case let .updateServiceNotification(_, date, type, text, media, entities):
|
case let .updateServiceNotification(flags, date, type, text, media, entities):
|
||||||
if let date = date {
|
let popup = (flags & (1 << 0)) != 0
|
||||||
|
if popup {
|
||||||
|
updatedState.addDisplayAlert(text, isDropAuth: type.hasPrefix("AUTH_KEY_DROP_"))
|
||||||
|
} else if let date = date {
|
||||||
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: 777000)
|
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: 777000)
|
||||||
|
|
||||||
if updatedState.peers[peerId] == nil {
|
if updatedState.peers[peerId] == nil {
|
||||||
@ -969,8 +972,6 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
|
|||||||
let message = StoreMessage(peerId: peerId, namespace: Namespaces.Message.Local, globallyUniqueId: nil, groupingKey: nil, timestamp: date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: peerId, text: messageText, attributes: attributes, media: medias)
|
let message = StoreMessage(peerId: peerId, namespace: Namespaces.Message.Local, globallyUniqueId: nil, groupingKey: nil, timestamp: date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: peerId, text: messageText, attributes: attributes, media: medias)
|
||||||
updatedState.addMessages([message], location: .UpperHistoryBlock)
|
updatedState.addMessages([message], location: .UpperHistoryBlock)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
updatedState.addDisplayAlert(text, isDropAuth: type.hasPrefix("AUTH_KEY_DROP_"))
|
|
||||||
}
|
}
|
||||||
case let .updateReadChannelInbox(_, folderId, channelId, maxId, stillUnreadCount, pts):
|
case let .updateReadChannelInbox(_, folderId, channelId, maxId, stillUnreadCount, pts):
|
||||||
updatedState.resetIncomingReadState(groupId: PeerGroupId(rawValue: folderId ?? 0), peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId), namespace: Namespaces.Message.Cloud, maxIncomingReadId: maxId, count: stillUnreadCount, pts: pts)
|
updatedState.resetIncomingReadState(groupId: PeerGroupId(rawValue: folderId ?? 0), peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId), namespace: Namespaces.Message.Cloud, maxIncomingReadId: maxId, count: stillUnreadCount, pts: pts)
|
||||||
@ -1286,6 +1287,8 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
|
|||||||
updatedState.updateLangPack(langCode: langCode, difference: difference)
|
updatedState.updateLangPack(langCode: langCode, difference: difference)
|
||||||
case let .updateMessagePoll(_, pollId, poll, results):
|
case let .updateMessagePoll(_, pollId, poll, results):
|
||||||
updatedState.updateMessagePoll(MediaId(namespace: Namespaces.Media.CloudPoll, id: pollId), poll: poll, results: results)
|
updatedState.updateMessagePoll(MediaId(namespace: Namespaces.Media.CloudPoll, id: pollId), poll: poll, results: results)
|
||||||
|
case let .updateMessageReactions(peer, msgId, reactions):
|
||||||
|
updatedState.updateMessageReactions(MessageId(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, id: msgId), reactions: reactions)
|
||||||
case let .updateFolderPeers(folderPeers, _, _):
|
case let .updateFolderPeers(folderPeers, _, _):
|
||||||
for folderPeer in folderPeers {
|
for folderPeer in folderPeers {
|
||||||
switch folderPeer {
|
switch folderPeer {
|
||||||
@ -2012,7 +2015,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation])
|
|||||||
var currentAddMessages: OptimizeAddMessagesState?
|
var currentAddMessages: OptimizeAddMessagesState?
|
||||||
for operation in operations {
|
for operation in operations {
|
||||||
switch operation {
|
switch operation {
|
||||||
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby:
|
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby:
|
||||||
if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty {
|
if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty {
|
||||||
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
|
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
|
||||||
}
|
}
|
||||||
@ -2281,6 +2284,26 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
|
|||||||
updatedPoll = updatedPoll.withUpdatedResults(TelegramMediaPollResults(apiResults: results), min: resultsMin)
|
updatedPoll = updatedPoll.withUpdatedResults(TelegramMediaPollResults(apiResults: results), min: resultsMin)
|
||||||
updateMessageMedia(transaction: transaction, id: pollId, media: updatedPoll)
|
updateMessageMedia(transaction: transaction, id: pollId, media: updatedPoll)
|
||||||
}
|
}
|
||||||
|
case let .UpdateMessageReactions(messageId, reactions):
|
||||||
|
transaction.updateMessage(messageId, update: {
|
||||||
|
var storeForwardInfo: StoreMessageForwardInfo?
|
||||||
|
if let forwardInfo = currentMessage.forwardInfo {
|
||||||
|
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature)
|
||||||
|
}
|
||||||
|
var attributes = currentMessage.attributes
|
||||||
|
var found = false
|
||||||
|
loop: for j in 0 ..< attributes.count {
|
||||||
|
if let attribute = attributes[j] as? ReactionsMessageAttribute {
|
||||||
|
attributes[j] = attribute.withUpdatedResults(reactions)
|
||||||
|
found = true
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
attributes.append(ReactionsMessageAttribute(apiReactions: reactions))
|
||||||
|
}
|
||||||
|
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
||||||
|
})
|
||||||
case let .UpdateMedia(id, media):
|
case let .UpdateMedia(id, media):
|
||||||
if let media = media as? TelegramMediaWebpage {
|
if let media = media as? TelegramMediaWebpage {
|
||||||
updatedWebpages[id] = media
|
updatedWebpages[id] = media
|
||||||
|
@ -57,7 +57,7 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes
|
|||||||
var updatedTimestamp: Int32?
|
var updatedTimestamp: Int32?
|
||||||
if let apiMessage = apiMessage {
|
if let apiMessage = apiMessage {
|
||||||
switch apiMessage {
|
switch apiMessage {
|
||||||
case let .message(_, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _):
|
case let .message(_, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _):
|
||||||
updatedTimestamp = date
|
updatedTimestamp = date
|
||||||
case .messageEmpty:
|
case .messageEmpty:
|
||||||
break
|
break
|
||||||
|
@ -13,6 +13,13 @@ public struct HistoryPreloadIndex: Comparable {
|
|||||||
public let isMuted: Bool
|
public let isMuted: Bool
|
||||||
public let isPriority: Bool
|
public let isPriority: Bool
|
||||||
|
|
||||||
|
public init(index: ChatListIndex?, hasUnread: Bool, isMuted: Bool, isPriority: Bool) {
|
||||||
|
self.index = index
|
||||||
|
self.hasUnread = hasUnread
|
||||||
|
self.isMuted = isMuted
|
||||||
|
self.isPriority = isPriority
|
||||||
|
}
|
||||||
|
|
||||||
public static func <(lhs: HistoryPreloadIndex, rhs: HistoryPreloadIndex) -> Bool {
|
public static func <(lhs: HistoryPreloadIndex, rhs: HistoryPreloadIndex) -> Bool {
|
||||||
if lhs.isPriority != rhs.isPriority {
|
if lhs.isPriority != rhs.isPriority {
|
||||||
if lhs.isPriority {
|
if lhs.isPriority {
|
||||||
|
@ -196,7 +196,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
|
|||||||
updatedLength += 1
|
updatedLength += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = Api.functions.upload.getFile(location: location, offset: Int32(offset), limit: Int32(updatedLength))
|
let data = Api.functions.upload.getFile(flags: 0, location: location, offset: Int32(offset), limit: Int32(updatedLength))
|
||||||
|
|
||||||
request.setPayload(data.1.makeData() as Data, metadata: WrappedRequestMetadata(metadata: WrappedFunctionDescription(data.0), tag: nil), shortMetadata: WrappedRequestShortMetadata(shortMetadata: WrappedShortFunctionDescription(data.0)), responseParser: { response in
|
request.setPayload(data.1.makeData() as Data, metadata: WrappedRequestMetadata(metadata: WrappedFunctionDescription(data.0), tag: nil), shortMetadata: WrappedRequestShortMetadata(shortMetadata: WrappedShortFunctionDescription(data.0)), responseParser: { response in
|
||||||
if let result = data.2.parse(Buffer(data: response)) {
|
if let result = data.2.parse(Buffer(data: response)) {
|
||||||
|
@ -12,7 +12,7 @@ import Foundation
|
|||||||
public func forwardGameWithScore(account: Account, messageId: MessageId, to peerId: PeerId) -> Signal<Void, NoError> {
|
public func forwardGameWithScore(account: Account, messageId: MessageId, to peerId: PeerId) -> Signal<Void, NoError> {
|
||||||
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
|
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||||
if let message = transaction.getMessage(messageId), let fromPeer = transaction.getPeer(messageId.peerId), let fromInputPeer = apiInputPeer(fromPeer), let toPeer = transaction.getPeer(peerId), let toInputPeer = apiInputPeer(toPeer) {
|
if let message = transaction.getMessage(messageId), let fromPeer = transaction.getPeer(messageId.peerId), let fromInputPeer = apiInputPeer(fromPeer), let toPeer = transaction.getPeer(peerId), let toInputPeer = apiInputPeer(toPeer) {
|
||||||
return account.network.request(Api.functions.messages.forwardMessages(flags: 1 << 8, fromPeer: fromInputPeer, id: [messageId.id], randomId: [arc4random64()], toPeer: toInputPeer))
|
return account.network.request(Api.functions.messages.forwardMessages(flags: 1 << 8, fromPeer: fromInputPeer, id: [messageId.id], randomId: [arc4random64()], toPeer: toInputPeer, scheduleDate: nil))
|
||||||
|> map(Optional.init)
|
|> map(Optional.init)
|
||||||
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
|
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
|
43
submodules/TelegramCore/TelegramCore/MessageReactions.swift
Normal file
43
submodules/TelegramCore/TelegramCore/MessageReactions.swift
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import Foundation
|
||||||
|
#if os(macOS)
|
||||||
|
import PostboxMac
|
||||||
|
import SwiftSignalKitMac
|
||||||
|
import MtProtoKitMac
|
||||||
|
import TelegramApiMac
|
||||||
|
#else
|
||||||
|
import Postbox
|
||||||
|
import SwiftSignalKit
|
||||||
|
import TelegramApi
|
||||||
|
#if BUCK
|
||||||
|
import MtProtoKit
|
||||||
|
#else
|
||||||
|
import MtProtoKitDynamic
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
public enum RequestUpdateMessageReactionError {
|
||||||
|
case generic
|
||||||
|
}
|
||||||
|
|
||||||
|
public func requestUpdateMessageReaction(account: Account, messageId: MessageId, reactions: [String]) -> Signal<Never, RequestUpdateMessageReactionError> {
|
||||||
|
return account.postbox.loadedPeerWithId(messageId.peerId)
|
||||||
|
|> take(1)
|
||||||
|
|> introduceError(RequestUpdateMessageReactionError.self)
|
||||||
|
|> mapToSignal { peer in
|
||||||
|
guard let inputPeer = apiInputPeer(peer) else {
|
||||||
|
return .fail(.generic)
|
||||||
|
}
|
||||||
|
if messageId.namespace != Namespaces.Message.Cloud {
|
||||||
|
return .fail(.generic)
|
||||||
|
}
|
||||||
|
return account.network.request(Api.functions.messages.sendReaction(peer: inputPeer, msgId: messageId.id, reaction: reactions))
|
||||||
|
|> mapError { _ -> RequestUpdateMessageReactionError in
|
||||||
|
return .generic
|
||||||
|
}
|
||||||
|
|> mapToSignal { result -> Signal<Never, RequestUpdateMessageReactionError> in
|
||||||
|
account.stateManager.addUpdates(result)
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -300,7 +300,7 @@ private enum MultipartFetchSource {
|
|||||||
case .revalidate:
|
case .revalidate:
|
||||||
return .fail(.revalidateMediaReference)
|
return .fail(.revalidateMediaReference)
|
||||||
case let .location(parsedLocation):
|
case let .location(parsedLocation):
|
||||||
return download.request(Api.functions.upload.getFile(location: parsedLocation, offset: offset, limit: Int32(limit)), tag: tag, continueInBackground: continueInBackground)
|
return download.request(Api.functions.upload.getFile(flags: 0, location: parsedLocation, offset: offset, limit: Int32(limit)), tag: tag, continueInBackground: continueInBackground)
|
||||||
|> mapError { error -> MultipartFetchDownloadError in
|
|> mapError { error -> MultipartFetchDownloadError in
|
||||||
if error.errorDescription.hasPrefix("FILEREF_INVALID") || error.errorDescription.hasPrefix("FILE_REFERENCE_") {
|
if error.errorDescription.hasPrefix("FILEREF_INVALID") || error.errorDescription.hasPrefix("FILE_REFERENCE_") {
|
||||||
return .revalidateMediaReference
|
return .revalidateMediaReference
|
||||||
|
@ -663,7 +663,7 @@ public final class PendingMessageManager {
|
|||||||
} else if let inputSourcePeerId = forwardPeerIds.first, let inputSourcePeer = transaction.getPeer(inputSourcePeerId).flatMap(apiInputPeer) {
|
} else if let inputSourcePeerId = forwardPeerIds.first, let inputSourcePeer = transaction.getPeer(inputSourcePeerId).flatMap(apiInputPeer) {
|
||||||
let dependencyTag = PendingMessageRequestDependencyTag(messageId: messages[0].0.id)
|
let dependencyTag = PendingMessageRequestDependencyTag(messageId: messages[0].0.id)
|
||||||
|
|
||||||
sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: inputSourcePeer, id: forwardIds.map { $0.0.id }, randomId: forwardIds.map { $0.1 }, toPeer: inputPeer), tag: dependencyTag)
|
sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: inputSourcePeer, id: forwardIds.map { $0.0.id }, randomId: forwardIds.map { $0.1 }, toPeer: inputPeer, scheduleDate: nil), tag: dependencyTag)
|
||||||
} else {
|
} else {
|
||||||
assertionFailure()
|
assertionFailure()
|
||||||
sendMessageRequest = .fail(MTRpcError(errorCode: 400, errorDescription: "Invalid forward source"))
|
sendMessageRequest = .fail(MTRpcError(errorCode: 400, errorDescription: "Invalid forward source"))
|
||||||
@ -707,7 +707,7 @@ public final class PendingMessageManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMessageRequest = network.request(Api.functions.messages.sendMultiMedia(flags: flags, peer: inputPeer, replyToMsgId: replyMessageId, multiMedia: singleMedias))
|
sendMessageRequest = network.request(Api.functions.messages.sendMultiMedia(flags: flags, peer: inputPeer, replyToMsgId: replyMessageId, multiMedia: singleMedias, scheduleDate: nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
return sendMessageRequest
|
return sendMessageRequest
|
||||||
@ -917,13 +917,13 @@ public final class PendingMessageManager {
|
|||||||
let sendMessageRequest: Signal<NetworkRequestResult<Api.Updates>, MTRpcError>
|
let sendMessageRequest: Signal<NetworkRequestResult<Api.Updates>, MTRpcError>
|
||||||
switch content.content {
|
switch content.content {
|
||||||
case .text:
|
case .text:
|
||||||
sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyToMsgId: replyMessageId, message: message.text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities), info: .acknowledgement, tag: dependencyTag)
|
sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyToMsgId: replyMessageId, message: message.text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: nil), info: .acknowledgement, tag: dependencyTag)
|
||||||
case let .media(inputMedia, text):
|
case let .media(inputMedia, text):
|
||||||
sendMessageRequest = network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyToMsgId: replyMessageId, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities), tag: dependencyTag)
|
sendMessageRequest = network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyToMsgId: replyMessageId, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: nil), tag: dependencyTag)
|
||||||
|> map(NetworkRequestResult.result)
|
|> map(NetworkRequestResult.result)
|
||||||
case let .forward(sourceInfo):
|
case let .forward(sourceInfo):
|
||||||
if let forwardSourceInfoAttribute = forwardSourceInfoAttribute, let sourcePeer = transaction.getPeer(forwardSourceInfoAttribute.messageId.peerId), let sourceInputPeer = apiInputPeer(sourcePeer) {
|
if let forwardSourceInfoAttribute = forwardSourceInfoAttribute, let sourcePeer = transaction.getPeer(forwardSourceInfoAttribute.messageId.peerId), let sourceInputPeer = apiInputPeer(sourcePeer) {
|
||||||
sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: sourceInputPeer, id: [sourceInfo.messageId.id], randomId: [uniqueId], toPeer: inputPeer), tag: dependencyTag)
|
sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: sourceInputPeer, id: [sourceInfo.messageId.id], randomId: [uniqueId], toPeer: inputPeer, scheduleDate: nil), tag: dependencyTag)
|
||||||
|> map(NetworkRequestResult.result)
|
|> map(NetworkRequestResult.result)
|
||||||
} else {
|
} else {
|
||||||
sendMessageRequest = .fail(MTRpcError(errorCode: 400, errorDescription: "internal"))
|
sendMessageRequest = .fail(MTRpcError(errorCode: 400, errorDescription: "internal"))
|
||||||
@ -932,7 +932,7 @@ public final class PendingMessageManager {
|
|||||||
if chatContextResult.hideVia {
|
if chatContextResult.hideVia {
|
||||||
flags |= Int32(1 << 11)
|
flags |= Int32(1 << 11)
|
||||||
}
|
}
|
||||||
sendMessageRequest = network.request(Api.functions.messages.sendInlineBotResult(flags: flags, peer: inputPeer, replyToMsgId: replyMessageId, randomId: uniqueId, queryId: chatContextResult.queryId, id: chatContextResult.id))
|
sendMessageRequest = network.request(Api.functions.messages.sendInlineBotResult(flags: flags, peer: inputPeer, replyToMsgId: replyMessageId, randomId: uniqueId, queryId: chatContextResult.queryId, id: chatContextResult.id, scheduleDate: nil))
|
||||||
|> map(NetworkRequestResult.result)
|
|> map(NetworkRequestResult.result)
|
||||||
case .messageScreenshot:
|
case .messageScreenshot:
|
||||||
sendMessageRequest = network.request(Api.functions.messages.sendScreenshotNotification(peer: inputPeer, replyToMsgId: replyMessageId ?? 0, randomId: uniqueId))
|
sendMessageRequest = network.request(Api.functions.messages.sendScreenshotNotification(peer: inputPeer, replyToMsgId: replyMessageId ?? 0, randomId: uniqueId))
|
||||||
|
@ -60,7 +60,7 @@ public func requestClosePoll(postbox: Postbox, network: Network, stateManager: A
|
|||||||
}
|
}
|
||||||
var flags: Int32 = 0
|
var flags: Int32 = 0
|
||||||
flags |= 1 << 14
|
flags |= 1 << 14
|
||||||
return network.request(Api.functions.messages.editMessage(flags: flags, peer: inputPeer, id: messageId.id, message: nil, media: .inputMediaPoll(poll: .poll(id: poll.pollId.id, flags: 1 << 0, question: poll.text, answers: poll.options.map({ $0.apiOption }))), replyMarkup: nil, entities: nil))
|
return network.request(Api.functions.messages.editMessage(flags: flags, peer: inputPeer, id: messageId.id, message: nil, media: .inputMediaPoll(poll: .poll(id: poll.pollId.id, flags: 1 << 0, question: poll.text, answers: poll.options.map({ $0.apiOption }))), replyMarkup: nil, entities: nil, scheduleDate: nil))
|
||||||
|> map(Optional.init)
|
|> map(Optional.init)
|
||||||
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
|
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
|
@ -0,0 +1,91 @@
|
|||||||
|
import Foundation
|
||||||
|
#if os(macOS)
|
||||||
|
import PostboxMac
|
||||||
|
#else
|
||||||
|
import Postbox
|
||||||
|
#endif
|
||||||
|
import TelegramApi
|
||||||
|
|
||||||
|
public struct MessageReaction: Equatable, PostboxCoding {
|
||||||
|
public var value: String
|
||||||
|
public var count: Int32
|
||||||
|
public var isSelected: Bool
|
||||||
|
|
||||||
|
public init(value: String, count: Int32, isSelected: Bool) {
|
||||||
|
self.value = value
|
||||||
|
self.count = count
|
||||||
|
self.isSelected = isSelected
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(decoder: PostboxDecoder) {
|
||||||
|
self.value = decoder.decodeStringForKey("v", orElse: "")
|
||||||
|
self.count = decoder.decodeInt32ForKey("c", orElse: 0)
|
||||||
|
self.isSelected = decoder.decodeInt32ForKey("s", orElse: 0) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
public func encode(_ encoder: PostboxEncoder) {
|
||||||
|
encoder.encodeString(self.value, forKey: "v")
|
||||||
|
encoder.encodeInt32(self.count, forKey: "c")
|
||||||
|
encoder.encodeInt32(self.isSelected ? 1 : 0, forKey: "s")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ReactionsMessageAttribute: MessageAttribute {
|
||||||
|
public let reactions: [MessageReaction]
|
||||||
|
|
||||||
|
init(reactions: [MessageReaction]) {
|
||||||
|
self.reactions = reactions
|
||||||
|
}
|
||||||
|
|
||||||
|
required public init(decoder: PostboxDecoder) {
|
||||||
|
self.reactions = decoder.decodeObjectArrayWithDecoderForKey("r")
|
||||||
|
}
|
||||||
|
|
||||||
|
public func encode(_ encoder: PostboxEncoder) {
|
||||||
|
encoder.encodeObjectArray(self.reactions, forKey: "r")
|
||||||
|
}
|
||||||
|
|
||||||
|
func withUpdatedResults(_ reactions: Api.MessageReactions) -> ReactionsMessageAttribute {
|
||||||
|
switch reactions {
|
||||||
|
case let .messageReactions(flags, results):
|
||||||
|
let min = (flags & (1 << 0)) != 0
|
||||||
|
var reactions = results.map { result -> MessageReaction in
|
||||||
|
switch result {
|
||||||
|
case let .reactionCount(flags, reaction, count):
|
||||||
|
return MessageReaction(value: reaction, count: count, isSelected: (flags & (1 << 0)) != 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if min {
|
||||||
|
var currentSelectedReaction: String?
|
||||||
|
for reaction in self.reactions {
|
||||||
|
if reaction.isSelected {
|
||||||
|
currentSelectedReaction = reaction.value
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let currentSelectedReaction = currentSelectedReaction {
|
||||||
|
for i in 0 ..< reactions.count {
|
||||||
|
if reactions[i].value == currentSelectedReaction {
|
||||||
|
reactions[i].isSelected = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ReactionsMessageAttribute(reactions: reactions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ReactionsMessageAttribute {
|
||||||
|
convenience init(apiReactions: Api.MessageReactions) {
|
||||||
|
switch apiReactions {
|
||||||
|
case let .messageReactions(_, results):
|
||||||
|
self.init(reactions: results.map { result in
|
||||||
|
switch result {
|
||||||
|
case let .reactionCount(flags, reaction, count):
|
||||||
|
return MessageReaction(value: reaction, count: count, isSelected: (flags & (1 << 0)) != 0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -150,7 +150,7 @@ private func requestEditMessageInternal(account: Account, messageId: MessageId,
|
|||||||
flags |= Int32(1 << 14)
|
flags |= Int32(1 << 14)
|
||||||
}
|
}
|
||||||
|
|
||||||
return account.network.request(Api.functions.messages.editMessage(flags: flags, peer: inputPeer, id: messageId.id, message: text, media: inputMedia, replyMarkup: nil, entities: apiEntities))
|
return account.network.request(Api.functions.messages.editMessage(flags: flags, peer: inputPeer, id: messageId.id, message: text, media: inputMedia, replyMarkup: nil, entities: apiEntities, scheduleDate: nil))
|
||||||
|> map { result -> Api.Updates? in
|
|> map { result -> Api.Updates? in
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@ -223,7 +223,7 @@ public func requestEditLiveLocation(postbox: Postbox, network: Network, stateMan
|
|||||||
} else {
|
} else {
|
||||||
inputMedia = .inputMediaGeoLive(flags: 1 << 0, geoPoint: .inputGeoPoint(lat: media.latitude, long: media.longitude), period: nil)
|
inputMedia = .inputMediaGeoLive(flags: 1 << 0, geoPoint: .inputGeoPoint(lat: media.latitude, long: media.longitude), period: nil)
|
||||||
}
|
}
|
||||||
return network.request(Api.functions.messages.editMessage(flags: 1 << 14, peer: inputPeer, id: messageId.id, message: nil, media: inputMedia, replyMarkup: nil, entities: nil))
|
return network.request(Api.functions.messages.editMessage(flags: 1 << 14, peer: inputPeer, id: messageId.id, message: nil, media: inputMedia, replyMarkup: nil, entities: nil, scheduleDate: nil))
|
||||||
|> map(Optional.init)
|
|> map(Optional.init)
|
||||||
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
|
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
|
@ -220,7 +220,7 @@ public class BoxedMessage: NSObject {
|
|||||||
|
|
||||||
public class Serialization: NSObject, MTSerialization {
|
public class Serialization: NSObject, MTSerialization {
|
||||||
public func currentLayer() -> UInt {
|
public func currentLayer() -> UInt {
|
||||||
return 104
|
return 106
|
||||||
}
|
}
|
||||||
|
|
||||||
public func parseMessage(_ data: Data!) -> Any! {
|
public func parseMessage(_ data: Data!) -> Any! {
|
||||||
|
@ -118,12 +118,12 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M
|
|||||||
let sendMessageRequest: Signal<Api.Updates, NoError>
|
let sendMessageRequest: Signal<Api.Updates, NoError>
|
||||||
switch content {
|
switch content {
|
||||||
case let .text(text):
|
case let .text(text):
|
||||||
sendMessageRequest = account.network.request(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyToMsgId: replyMessageId, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities))
|
sendMessageRequest = account.network.request(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyToMsgId: replyMessageId, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: nil))
|
||||||
|> `catch` { _ -> Signal<Api.Updates, NoError> in
|
|> `catch` { _ -> Signal<Api.Updates, NoError> in
|
||||||
return .complete()
|
return .complete()
|
||||||
}
|
}
|
||||||
case let .media(inputMedia, text):
|
case let .media(inputMedia, text):
|
||||||
sendMessageRequest = account.network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyToMsgId: replyMessageId, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities))
|
sendMessageRequest = account.network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyToMsgId: replyMessageId, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: nil))
|
||||||
|> `catch` { _ -> Signal<Api.Updates, NoError> in
|
|> `catch` { _ -> Signal<Api.Updates, NoError> in
|
||||||
return .complete()
|
return .complete()
|
||||||
}
|
}
|
||||||
|
@ -111,7 +111,7 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute],
|
|||||||
|
|
||||||
func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? {
|
func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? {
|
||||||
switch messsage {
|
switch messsage {
|
||||||
case let .message(flags, _, fromId, toId, _, _, _, _, _, _, _, _, _, _, _, _):
|
case let .message(flags, _, fromId, toId, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||||
switch toId {
|
switch toId {
|
||||||
case let .peerUser(userId):
|
case let .peerUser(userId):
|
||||||
return PeerId(namespace: Namespaces.Peer.CloudUser, id: (flags & Int32(2)) != 0 ? userId : (fromId ?? userId))
|
return PeerId(namespace: Namespaces.Peer.CloudUser, id: (flags & Int32(2)) != 0 ? userId : (fromId ?? userId))
|
||||||
@ -136,7 +136,7 @@ func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? {
|
|||||||
|
|
||||||
func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] {
|
func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] {
|
||||||
switch message {
|
switch message {
|
||||||
case let .message(flags, _, fromId, toId, fwdHeader, viaBotId, _, _, _, media, _, entities, _, _, _, _):
|
case let .message(flags, _, fromId, toId, fwdHeader, viaBotId, _, _, _, media, _, entities, _, _, _, _, _):
|
||||||
let peerId: PeerId
|
let peerId: PeerId
|
||||||
switch toId {
|
switch toId {
|
||||||
case let .peerUser(userId):
|
case let .peerUser(userId):
|
||||||
@ -240,7 +240,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] {
|
|||||||
|
|
||||||
func apiMessageAssociatedMessageIds(_ message: Api.Message) -> [MessageId]? {
|
func apiMessageAssociatedMessageIds(_ message: Api.Message) -> [MessageId]? {
|
||||||
switch message {
|
switch message {
|
||||||
case let .message(flags, _, fromId, toId, _, _, replyToMsgId, _, _, _, _, _, _, _, _, _):
|
case let .message(flags, _, fromId, toId, _, _, replyToMsgId, _, _, _, _, _, _, _, _, _, _):
|
||||||
if let replyToMsgId = replyToMsgId {
|
if let replyToMsgId = replyToMsgId {
|
||||||
let peerId: PeerId
|
let peerId: PeerId
|
||||||
switch toId {
|
switch toId {
|
||||||
@ -382,7 +382,7 @@ func messageTextEntitiesFromApiEntities(_ entities: [Api.MessageEntity]) -> [Mes
|
|||||||
extension StoreMessage {
|
extension StoreMessage {
|
||||||
convenience init?(apiMessage: Api.Message) {
|
convenience init?(apiMessage: Api.Message) {
|
||||||
switch apiMessage {
|
switch apiMessage {
|
||||||
case let .message(flags, id, fromId, toId, fwdFrom, viaBotId, replyToMsgId, date, message, media, replyMarkup, entities, views, editDate, postAuthor, groupingId):
|
case let .message(flags, id, fromId, toId, fwdFrom, viaBotId, replyToMsgId, date, message, media, replyMarkup, entities, views, editDate, postAuthor, groupingId, reactions):
|
||||||
let peerId: PeerId
|
let peerId: PeerId
|
||||||
var authorId: PeerId?
|
var authorId: PeerId?
|
||||||
switch toId {
|
switch toId {
|
||||||
@ -537,6 +537,10 @@ extension StoreMessage {
|
|||||||
attributes.append(ContentRequiresValidationMessageAttribute())
|
attributes.append(ContentRequiresValidationMessageAttribute())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let reactions = reactions {
|
||||||
|
attributes.append(ReactionsMessageAttribute(apiReactions: reactions))
|
||||||
|
}
|
||||||
|
|
||||||
var storeFlags = StoreMessageFlags()
|
var storeFlags = StoreMessageFlags()
|
||||||
|
|
||||||
if let replyMarkup = replyMarkup {
|
if let replyMarkup = replyMarkup {
|
||||||
|
@ -69,7 +69,7 @@ class UpdateMessageService: NSObject, MTMessageService {
|
|||||||
self.putNext(groups)
|
self.putNext(groups)
|
||||||
}
|
}
|
||||||
case let .updateShortChatMessage(flags, id, fromId, chatId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyToMsgId, entities):
|
case let .updateShortChatMessage(flags, id, fromId, chatId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyToMsgId, entities):
|
||||||
let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: fromId, toId: Api.Peer.peerChat(chatId: chatId), fwdFrom: fwdFrom, viaBotId: viaBotId, replyToMsgId: replyToMsgId, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, editDate: nil, postAuthor: nil, groupedId: nil)
|
let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: fromId, toId: Api.Peer.peerChat(chatId: chatId), fwdFrom: fwdFrom, viaBotId: viaBotId, replyToMsgId: replyToMsgId, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil)
|
||||||
let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount)
|
let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount)
|
||||||
let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil)
|
let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil)
|
||||||
if groups.count != 0 {
|
if groups.count != 0 {
|
||||||
@ -86,7 +86,7 @@ class UpdateMessageService: NSObject, MTMessageService {
|
|||||||
generatedToId = Api.Peer.peerUser(userId: self.peerId.id)
|
generatedToId = Api.Peer.peerUser(userId: self.peerId.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: generatedFromId, toId: generatedToId, fwdFrom: fwdFrom, viaBotId: viaBotId, replyToMsgId: replyToMsgId, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, editDate: nil, postAuthor: nil, groupedId: nil)
|
let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: generatedFromId, toId: generatedToId, fwdFrom: fwdFrom, viaBotId: viaBotId, replyToMsgId: replyToMsgId, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil)
|
||||||
let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount)
|
let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount)
|
||||||
let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil)
|
let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil)
|
||||||
if groups.count != 0 {
|
if groups.count != 0 {
|
||||||
|
@ -100,7 +100,7 @@ extension Api.MessageMedia {
|
|||||||
extension Api.Message {
|
extension Api.Message {
|
||||||
var rawId: Int32 {
|
var rawId: Int32 {
|
||||||
switch self {
|
switch self {
|
||||||
case let .message(_, id, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
case let .message(_, id, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||||
return id
|
return id
|
||||||
case let .messageEmpty(id):
|
case let .messageEmpty(id):
|
||||||
return id
|
return id
|
||||||
@ -111,7 +111,7 @@ extension Api.Message {
|
|||||||
|
|
||||||
var id: MessageId? {
|
var id: MessageId? {
|
||||||
switch self {
|
switch self {
|
||||||
case let .message(flags, id, fromId, toId, _, _, _, _, _, _, _, _, _, _, _, _):
|
case let .message(flags, id, fromId, toId, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||||
let peerId: PeerId
|
let peerId: PeerId
|
||||||
switch toId {
|
switch toId {
|
||||||
case let .peerUser(userId):
|
case let .peerUser(userId):
|
||||||
@ -141,7 +141,7 @@ extension Api.Message {
|
|||||||
|
|
||||||
var timestamp: Int32? {
|
var timestamp: Int32? {
|
||||||
switch self {
|
switch self {
|
||||||
case let .message(_, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _):
|
case let .message(_, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _):
|
||||||
return date
|
return date
|
||||||
case let .messageService(_, _, _, _, _, date, _):
|
case let .messageService(_, _, _, _, _, date, _):
|
||||||
return date
|
return date
|
||||||
@ -152,7 +152,7 @@ extension Api.Message {
|
|||||||
|
|
||||||
var preCachedResources: [(MediaResource, Data)]? {
|
var preCachedResources: [(MediaResource, Data)]? {
|
||||||
switch self {
|
switch self {
|
||||||
case let .message(_, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _):
|
case let .message(_, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _):
|
||||||
return media?.preCachedResources
|
return media?.preCachedResources
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
|
@ -207,6 +207,10 @@
|
|||||||
D02DADC12139A1FC00116225 /* ContactSyncManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02DADC02139A1FC00116225 /* ContactSyncManager.swift */; };
|
D02DADC12139A1FC00116225 /* ContactSyncManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02DADC02139A1FC00116225 /* ContactSyncManager.swift */; };
|
||||||
D02DADC22139A1FC00116225 /* ContactSyncManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02DADC02139A1FC00116225 /* ContactSyncManager.swift */; };
|
D02DADC22139A1FC00116225 /* ContactSyncManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02DADC02139A1FC00116225 /* ContactSyncManager.swift */; };
|
||||||
D03121021DA57E93006A2A60 /* TelegramPeerNotificationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03121011DA57E93006A2A60 /* TelegramPeerNotificationSettings.swift */; };
|
D03121021DA57E93006A2A60 /* TelegramPeerNotificationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03121011DA57E93006A2A60 /* TelegramPeerNotificationSettings.swift */; };
|
||||||
|
D0329EA222FC5A7C00F9F071 /* MessageReactions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0329EA122FC5A7C00F9F071 /* MessageReactions.swift */; };
|
||||||
|
D0329EA322FC5A7C00F9F071 /* MessageReactions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0329EA122FC5A7C00F9F071 /* MessageReactions.swift */; };
|
||||||
|
D0329EA522FC5A9600F9F071 /* ReactionsMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0329EA422FC5A9600F9F071 /* ReactionsMessageAttribute.swift */; };
|
||||||
|
D0329EA622FC5A9600F9F071 /* ReactionsMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0329EA422FC5A9600F9F071 /* ReactionsMessageAttribute.swift */; };
|
||||||
D032F5BC20EF84FD00037B6C /* FetchedMediaResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D032F5BB20EF84FD00037B6C /* FetchedMediaResource.swift */; };
|
D032F5BC20EF84FD00037B6C /* FetchedMediaResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D032F5BB20EF84FD00037B6C /* FetchedMediaResource.swift */; };
|
||||||
D032F5BD20EF84FD00037B6C /* FetchedMediaResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D032F5BB20EF84FD00037B6C /* FetchedMediaResource.swift */; };
|
D032F5BD20EF84FD00037B6C /* FetchedMediaResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D032F5BB20EF84FD00037B6C /* FetchedMediaResource.swift */; };
|
||||||
D0338740223BD48B007A2CE4 /* ContactsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D033873F223BD48B007A2CE4 /* ContactsSettings.swift */; };
|
D0338740223BD48B007A2CE4 /* ContactsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D033873F223BD48B007A2CE4 /* ContactsSettings.swift */; };
|
||||||
@ -925,6 +929,8 @@
|
|||||||
D02D60AA206BA64100FEFE1E /* VerifySecureIdValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerifySecureIdValue.swift; sourceTree = "<group>"; };
|
D02D60AA206BA64100FEFE1E /* VerifySecureIdValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerifySecureIdValue.swift; sourceTree = "<group>"; };
|
||||||
D02DADC02139A1FC00116225 /* ContactSyncManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactSyncManager.swift; sourceTree = "<group>"; };
|
D02DADC02139A1FC00116225 /* ContactSyncManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactSyncManager.swift; sourceTree = "<group>"; };
|
||||||
D03121011DA57E93006A2A60 /* TelegramPeerNotificationSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramPeerNotificationSettings.swift; sourceTree = "<group>"; };
|
D03121011DA57E93006A2A60 /* TelegramPeerNotificationSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramPeerNotificationSettings.swift; sourceTree = "<group>"; };
|
||||||
|
D0329EA122FC5A7C00F9F071 /* MessageReactions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageReactions.swift; sourceTree = "<group>"; };
|
||||||
|
D0329EA422FC5A9600F9F071 /* ReactionsMessageAttribute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionsMessageAttribute.swift; sourceTree = "<group>"; };
|
||||||
D032F5BB20EF84FD00037B6C /* FetchedMediaResource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchedMediaResource.swift; sourceTree = "<group>"; };
|
D032F5BB20EF84FD00037B6C /* FetchedMediaResource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchedMediaResource.swift; sourceTree = "<group>"; };
|
||||||
D033873F223BD48B007A2CE4 /* ContactsSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsSettings.swift; sourceTree = "<group>"; };
|
D033873F223BD48B007A2CE4 /* ContactsSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsSettings.swift; sourceTree = "<group>"; };
|
||||||
D0338742223BD532007A2CE4 /* InitializeAccountAfterLogin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitializeAccountAfterLogin.swift; sourceTree = "<group>"; };
|
D0338742223BD532007A2CE4 /* InitializeAccountAfterLogin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitializeAccountAfterLogin.swift; sourceTree = "<group>"; };
|
||||||
@ -1499,6 +1505,7 @@
|
|||||||
C28725411EF967E700613564 /* NotificationInfoMessageAttribute.swift */,
|
C28725411EF967E700613564 /* NotificationInfoMessageAttribute.swift */,
|
||||||
C210DD611FBDB90800F673D8 /* SourceReferenceMessageAttribute.swift */,
|
C210DD611FBDB90800F673D8 /* SourceReferenceMessageAttribute.swift */,
|
||||||
D0439B5F228EDE430067E026 /* ContentRequiresValidationMessageAttribute.swift */,
|
D0439B5F228EDE430067E026 /* ContentRequiresValidationMessageAttribute.swift */,
|
||||||
|
D0329EA422FC5A9600F9F071 /* ReactionsMessageAttribute.swift */,
|
||||||
);
|
);
|
||||||
name = Attributes;
|
name = Attributes;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -1682,6 +1689,7 @@
|
|||||||
D0EC55992101ED0800D1992C /* DeleteMessages.swift */,
|
D0EC55992101ED0800D1992C /* DeleteMessages.swift */,
|
||||||
D01AC91C1DD5DA5E00E8160F /* RequestMessageActionCallback.swift */,
|
D01AC91C1DD5DA5E00E8160F /* RequestMessageActionCallback.swift */,
|
||||||
D0AB262A21C3CE80008F6685 /* Polls.swift */,
|
D0AB262A21C3CE80008F6685 /* Polls.swift */,
|
||||||
|
D0329EA122FC5A7C00F9F071 /* MessageReactions.swift */,
|
||||||
D01AC9201DD5E7E500E8160F /* RequestEditMessage.swift */,
|
D01AC9201DD5E7E500E8160F /* RequestEditMessage.swift */,
|
||||||
D0DC354D1DE368F7000195EB /* RequestChatContextResults.swift */,
|
D0DC354D1DE368F7000195EB /* RequestChatContextResults.swift */,
|
||||||
D0DC354F1DE36900000195EB /* ChatContextResult.swift */,
|
D0DC354F1DE36900000195EB /* ChatContextResult.swift */,
|
||||||
@ -2443,6 +2451,7 @@
|
|||||||
D0561DE31E5737FC00E6B9E9 /* UpdatePeerInfo.swift in Sources */,
|
D0561DE31E5737FC00E6B9E9 /* UpdatePeerInfo.swift in Sources */,
|
||||||
D0DF0C8A1D819C7E008AEB01 /* JoinChannel.swift in Sources */,
|
D0DF0C8A1D819C7E008AEB01 /* JoinChannel.swift in Sources */,
|
||||||
D051DB14215EC5A300F30F92 /* AppChangelogState.swift in Sources */,
|
D051DB14215EC5A300F30F92 /* AppChangelogState.swift in Sources */,
|
||||||
|
D0329EA222FC5A7C00F9F071 /* MessageReactions.swift in Sources */,
|
||||||
D04554A621B43440007A6DD9 /* CancelAccountReset.swift in Sources */,
|
D04554A621B43440007A6DD9 /* CancelAccountReset.swift in Sources */,
|
||||||
D04CAA5A1E83310D0047E51F /* MD5.swift in Sources */,
|
D04CAA5A1E83310D0047E51F /* MD5.swift in Sources */,
|
||||||
D0E817492010E7E300B82BBB /* ChannelAdminEventLogContext.swift in Sources */,
|
D0E817492010E7E300B82BBB /* ChannelAdminEventLogContext.swift in Sources */,
|
||||||
@ -2488,6 +2497,7 @@
|
|||||||
D0B844531DAC0773005F29E1 /* TelegramUserPresence.swift in Sources */,
|
D0B844531DAC0773005F29E1 /* TelegramUserPresence.swift in Sources */,
|
||||||
D08F4A661E79CC4A00A2AA15 /* SynchronizeInstalledStickerPacksOperations.swift in Sources */,
|
D08F4A661E79CC4A00A2AA15 /* SynchronizeInstalledStickerPacksOperations.swift in Sources */,
|
||||||
D05A32E71E6F0B5C002760B4 /* RecentAccountSession.swift in Sources */,
|
D05A32E71E6F0B5C002760B4 /* RecentAccountSession.swift in Sources */,
|
||||||
|
D0329EA522FC5A9600F9F071 /* ReactionsMessageAttribute.swift in Sources */,
|
||||||
D0E41301206B9E6E00BEE4A2 /* SecureIdAddressValue.swift in Sources */,
|
D0E41301206B9E6E00BEE4A2 /* SecureIdAddressValue.swift in Sources */,
|
||||||
D0B843871DA6F705005F29E1 /* UpdateCachedPeerData.swift in Sources */,
|
D0B843871DA6F705005F29E1 /* UpdateCachedPeerData.swift in Sources */,
|
||||||
D0E412F1206B9BB700BEE4A2 /* SecureIdPassportValue.swift in Sources */,
|
D0E412F1206B9BB700BEE4A2 /* SecureIdPassportValue.swift in Sources */,
|
||||||
@ -2634,6 +2644,7 @@
|
|||||||
9F4EEF9F21DCF6E7002C3B33 /* ManagedAppConfigurationUpdates.swift in Sources */,
|
9F4EEF9F21DCF6E7002C3B33 /* ManagedAppConfigurationUpdates.swift in Sources */,
|
||||||
9F4EEFA021DCF6E7002C3B33 /* SynchronizeAppLogEventsOperation.swift in Sources */,
|
9F4EEFA021DCF6E7002C3B33 /* SynchronizeAppLogEventsOperation.swift in Sources */,
|
||||||
9F4EEFA121DCF6E7002C3B33 /* ManagedSynchronizeAppLogEventsOperations.swift in Sources */,
|
9F4EEFA121DCF6E7002C3B33 /* ManagedSynchronizeAppLogEventsOperations.swift in Sources */,
|
||||||
|
D0329EA622FC5A9600F9F071 /* ReactionsMessageAttribute.swift in Sources */,
|
||||||
9F4EEF9B21DCF66F002C3B33 /* JSON.swift in Sources */,
|
9F4EEF9B21DCF66F002C3B33 /* JSON.swift in Sources */,
|
||||||
9F4EEF9C21DCF66F002C3B33 /* AppConfiguration.swift in Sources */,
|
9F4EEF9C21DCF66F002C3B33 /* AppConfiguration.swift in Sources */,
|
||||||
9F4EEF9D21DCF66F002C3B33 /* SearchBotsConfiguration.swift in Sources */,
|
9F4EEF9D21DCF66F002C3B33 /* SearchBotsConfiguration.swift in Sources */,
|
||||||
@ -2722,6 +2733,7 @@
|
|||||||
D0448CA31E291B14005A61A7 /* FetchSecretFileResource.swift in Sources */,
|
D0448CA31E291B14005A61A7 /* FetchSecretFileResource.swift in Sources */,
|
||||||
D0B8442F1DAB91E0005F29E1 /* NBMetadataHelper.m in Sources */,
|
D0B8442F1DAB91E0005F29E1 /* NBMetadataHelper.m in Sources */,
|
||||||
D0B8444C1DAB91FD005F29E1 /* UpdateCachedPeerData.swift in Sources */,
|
D0B8444C1DAB91FD005F29E1 /* UpdateCachedPeerData.swift in Sources */,
|
||||||
|
D0329EA322FC5A7C00F9F071 /* MessageReactions.swift in Sources */,
|
||||||
D0FA8B9F1E1F973B001E855B /* SecretChatIncomingEncryptedOperation.swift in Sources */,
|
D0FA8B9F1E1F973B001E855B /* SecretChatIncomingEncryptedOperation.swift in Sources */,
|
||||||
D03C536C1DAD5CA9004C17B3 /* TelegramChannel.swift in Sources */,
|
D03C536C1DAD5CA9004C17B3 /* TelegramChannel.swift in Sources */,
|
||||||
D0B418951D7E0580004562A4 /* TelegramMediaContact.swift in Sources */,
|
D0B418951D7E0580004562A4 /* TelegramMediaContact.swift in Sources */,
|
||||||
|
@ -215,6 +215,10 @@ public final class PrincipalThemeEssentialGraphics {
|
|||||||
if preview {
|
if preview {
|
||||||
self.chatMessageBackgroundIncomingMaskImage = messageBubbleImage(incoming: true, fillColor: UIColor.black, strokeColor: UIColor.clear, neighbors: .none, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true)
|
self.chatMessageBackgroundIncomingMaskImage = messageBubbleImage(incoming: true, fillColor: UIColor.black, strokeColor: UIColor.clear, neighbors: .none, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true)
|
||||||
self.chatMessageBackgroundIncomingImage = messageBubbleImage(incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout)
|
self.chatMessageBackgroundIncomingImage = messageBubbleImage(incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout)
|
||||||
|
self.chatMessageBackgroundOutgoingMaskImage = messageBubbleImage(incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .none, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true)
|
||||||
|
self.chatMessageBackgroundOutgoingImage = messageBubbleImage(incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout)
|
||||||
|
self.checkBubbleFullImage = generateCheckImage(partial: false, color: theme.message.outgoingCheckColor)!
|
||||||
|
self.checkBubblePartialImage = generateCheckImage(partial: true, color: theme.message.outgoingCheckColor)!
|
||||||
self.chatMessageBackgroundIncomingHighlightedImage = emptyImage
|
self.chatMessageBackgroundIncomingHighlightedImage = emptyImage
|
||||||
self.chatMessageBackgroundIncomingMergedTopMaskImage = emptyImage
|
self.chatMessageBackgroundIncomingMergedTopMaskImage = emptyImage
|
||||||
self.chatMessageBackgroundIncomingMergedTopImage = emptyImage
|
self.chatMessageBackgroundIncomingMergedTopImage = emptyImage
|
||||||
@ -231,8 +235,6 @@ public final class PrincipalThemeEssentialGraphics {
|
|||||||
self.chatMessageBackgroundIncomingMergedSideMaskImage = emptyImage
|
self.chatMessageBackgroundIncomingMergedSideMaskImage = emptyImage
|
||||||
self.chatMessageBackgroundIncomingMergedSideImage = emptyImage
|
self.chatMessageBackgroundIncomingMergedSideImage = emptyImage
|
||||||
self.chatMessageBackgroundIncomingMergedSideHighlightedImage = emptyImage
|
self.chatMessageBackgroundIncomingMergedSideHighlightedImage = emptyImage
|
||||||
self.chatMessageBackgroundOutgoingMaskImage = messageBubbleImage(incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .none, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true)
|
|
||||||
self.chatMessageBackgroundOutgoingImage = messageBubbleImage(incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout)
|
|
||||||
self.chatMessageBackgroundOutgoingHighlightedImage = emptyImage
|
self.chatMessageBackgroundOutgoingHighlightedImage = emptyImage
|
||||||
self.chatMessageBackgroundOutgoingMergedTopMaskImage = emptyImage
|
self.chatMessageBackgroundOutgoingMergedTopMaskImage = emptyImage
|
||||||
self.chatMessageBackgroundOutgoingMergedTopImage = emptyImage
|
self.chatMessageBackgroundOutgoingMergedTopImage = emptyImage
|
||||||
@ -249,8 +251,6 @@ public final class PrincipalThemeEssentialGraphics {
|
|||||||
self.chatMessageBackgroundOutgoingMergedSideMaskImage = emptyImage
|
self.chatMessageBackgroundOutgoingMergedSideMaskImage = emptyImage
|
||||||
self.chatMessageBackgroundOutgoingMergedSideImage = emptyImage
|
self.chatMessageBackgroundOutgoingMergedSideImage = emptyImage
|
||||||
self.chatMessageBackgroundOutgoingMergedSideHighlightedImage = emptyImage
|
self.chatMessageBackgroundOutgoingMergedSideHighlightedImage = emptyImage
|
||||||
self.checkBubbleFullImage = emptyImage
|
|
||||||
self.checkBubblePartialImage = emptyImage
|
|
||||||
self.checkMediaFullImage = emptyImage
|
self.checkMediaFullImage = emptyImage
|
||||||
self.checkMediaPartialImage = emptyImage
|
self.checkMediaPartialImage = emptyImage
|
||||||
self.checkFreeFullImage = emptyImage
|
self.checkFreeFullImage = emptyImage
|
||||||
|
@ -8,6 +8,7 @@ import AsyncDisplayKit
|
|||||||
import RLottie
|
import RLottie
|
||||||
import GZip
|
import GZip
|
||||||
import Tuples
|
import Tuples
|
||||||
|
import MediaResources
|
||||||
import StickerResources
|
import StickerResources
|
||||||
|
|
||||||
private final class AnimationFrameCache {
|
private final class AnimationFrameCache {
|
||||||
@ -126,7 +127,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,8 +404,8 @@ 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, fitzModifier: EmojiFitzModifier? = nil, width: Int, height: Int, playbackMode: AnimatedStickerPlaybackMode = .loop, mode: AnimatedStickerMode) {
|
||||||
if width < 2 || height < 2 {
|
if width < 2 || height < 2 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -424,7 +425,7 @@ final class AnimatedStickerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
case .cached:
|
case .cached:
|
||||||
self.disposable.set((chatMessageAnimationData(postbox: account.postbox, resource: resource, width: width, height: height, synchronousLoad: false)
|
self.disposable.set((chatMessageAnimationData(postbox: account.postbox, resource: resource, fitzModifier: fitzModifier, width: width, height: height, synchronousLoad: false)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] data in
|
|> deliverOnMainQueue).start(next: { [weak self] data in
|
||||||
if let strongSelf = self, data.complete {
|
if let strongSelf = self, data.complete {
|
||||||
strongSelf.cachedData = try? Data(contentsOf: URL(fileURLWithPath: data.path), options: [.mappedRead])
|
strongSelf.cachedData = try? Data(contentsOf: URL(fileURLWithPath: data.path), options: [.mappedRead])
|
||||||
@ -565,7 +566,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)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import Compression
|
|||||||
import GZip
|
import GZip
|
||||||
import RLottie
|
import RLottie
|
||||||
import MobileCoreServices
|
import MobileCoreServices
|
||||||
|
import MediaResources
|
||||||
|
|
||||||
public struct LocalBundleResourceId: MediaResourceId {
|
public struct LocalBundleResourceId: MediaResourceId {
|
||||||
public let name: String
|
public let name: String
|
||||||
@ -65,7 +66,85 @@ public class LocalBundleResource: TelegramMediaResource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchCompressedLottieFirstFrameAJpeg(data: Data, size: CGSize, cacheKey: String) -> Signal<TempBoxFile, NoError> {
|
let colorKeyRegex = try? NSRegularExpression(pattern: "\"k\":\\[[\\d\\.]+\\,[\\d\\.]+\\,[\\d\\.]+\\,[\\d\\.]+\\]")
|
||||||
|
|
||||||
|
private func transformedWithFitzModifier(data: Data, fitzModifier: EmojiFitzModifier?) -> Data {
|
||||||
|
if let fitzModifier = fitzModifier, var string = String(data: data, encoding: .utf8) {
|
||||||
|
let color1: UIColor
|
||||||
|
let color2: UIColor
|
||||||
|
let color3: UIColor
|
||||||
|
let color4: UIColor
|
||||||
|
|
||||||
|
var colors: [UIColor] = [0xf77e41, 0xffb139, 0xffd140, 0xffdf79].map { UIColor(rgb: $0) }
|
||||||
|
let replacementColors: [UIColor]
|
||||||
|
switch fitzModifier {
|
||||||
|
case .type12:
|
||||||
|
replacementColors = [0xca907a, 0xedc5a5, 0xf7e3c3, 0xfbefd6].map { UIColor(rgb: $0) }
|
||||||
|
case .type3:
|
||||||
|
replacementColors = [0xaa7c60, 0xc8a987, 0xddc89f, 0xe6d6b2].map { UIColor(rgb: $0) }
|
||||||
|
case .type4:
|
||||||
|
replacementColors = [0x8c6148, 0xad8562, 0xc49e76, 0xd4b188].map { UIColor(rgb: $0) }
|
||||||
|
case .type5:
|
||||||
|
replacementColors = [0x6e3c2c, 0x925a34, 0xa16e46, 0xac7a52].map { UIColor(rgb: $0) }
|
||||||
|
case .type6:
|
||||||
|
replacementColors = [0x291c12, 0x472a22, 0x573b30, 0x68493c].map { UIColor(rgb: $0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
func colorToString(_ color: UIColor) -> String {
|
||||||
|
var r: CGFloat = 0.0
|
||||||
|
var g: CGFloat = 0.0
|
||||||
|
var b: CGFloat = 0.0
|
||||||
|
if color.getRed(&r, green: &g, blue: &b, alpha: nil) {
|
||||||
|
return "\"k\":[\(r),\(g),\(b),1]"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func match(_ a: Double, _ b: Double, eps: Double) -> Bool {
|
||||||
|
return abs(a - b) < eps
|
||||||
|
}
|
||||||
|
|
||||||
|
var replacements: [(NSTextCheckingResult, String)] = []
|
||||||
|
|
||||||
|
if let colorKeyRegex = colorKeyRegex {
|
||||||
|
let results = colorKeyRegex.matches(in: string, range: NSRange(string.startIndex..., in: string))
|
||||||
|
for result in results.reversed() {
|
||||||
|
if let range = Range(result.range, in: string) {
|
||||||
|
let substring = String(string[range])
|
||||||
|
let color = substring[substring.index(string.startIndex, offsetBy: "\"k\":[".count) ..< substring.index(before: substring.endIndex)]
|
||||||
|
let components = color.split(separator: ",")
|
||||||
|
if components.count == 4, let r = Double(components[0]), let g = Double(components[1]), let b = Double(components[2]), let a = Double(components[3]) {
|
||||||
|
if match(a, 1.0, eps: 0.01) {
|
||||||
|
for i in 0 ..< colors.count {
|
||||||
|
let color = colors[i]
|
||||||
|
var cr: CGFloat = 0.0
|
||||||
|
var cg: CGFloat = 0.0
|
||||||
|
var cb: CGFloat = 0.0
|
||||||
|
if color.getRed(&cr, green: &cg, blue: &cb, alpha: nil) {
|
||||||
|
if match(r, Double(cr), eps: 0.01) && match(g, Double(cg), eps: 0.01) && match(b, Double(cb), eps: 0.01) {
|
||||||
|
replacements.append((result, colorToString(replacementColors[i])))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (result, text) in replacements {
|
||||||
|
if let range = Range(result.range, in: string) {
|
||||||
|
string = string.replacingCharacters(in: range, with: text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.data(using: .utf8) ?? data
|
||||||
|
} else {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchCompressedLottieFirstFrameAJpeg(data: Data, size: CGSize, fitzModifier: EmojiFitzModifier? = nil, cacheKey: String) -> Signal<TempBoxFile, NoError> {
|
||||||
return Signal({ subscriber in
|
return Signal({ subscriber in
|
||||||
let queue = Queue()
|
let queue = Queue()
|
||||||
|
|
||||||
@ -77,72 +156,75 @@ func fetchCompressedLottieFirstFrameAJpeg(data: Data, size: CGSize, cacheKey: St
|
|||||||
}
|
}
|
||||||
|
|
||||||
let decompressedData = TGGUnzipData(data, 8 * 1024 * 1024)
|
let decompressedData = TGGUnzipData(data, 8 * 1024 * 1024)
|
||||||
if let decompressedData = decompressedData, let player = LottieInstance(data: decompressedData, cacheKey: cacheKey) {
|
if let decompressedData = decompressedData {
|
||||||
if cancelled.with({ $0 }) {
|
let decompressedData = transformedWithFitzModifier(data: decompressedData, fitzModifier: fitzModifier)
|
||||||
return
|
if let player = LottieInstance(data: decompressedData, cacheKey: cacheKey) {
|
||||||
}
|
if cancelled.with({ $0 }) {
|
||||||
|
|
||||||
let context = DrawingContext(size: size, scale: 1.0, clear: true)
|
|
||||||
player.renderFrame(with: 0, into: context.bytes.assumingMemoryBound(to: UInt8.self), width: Int32(size.width), height: Int32(size.height), bytesPerRow: Int32(context.bytesPerRow))
|
|
||||||
|
|
||||||
let yuvaPixelsPerAlphaRow = (Int(size.width) + 1) & (~1)
|
|
||||||
assert(yuvaPixelsPerAlphaRow % 2 == 0)
|
|
||||||
|
|
||||||
let yuvaLength = Int(size.width) * Int(size.height) * 2 + yuvaPixelsPerAlphaRow * Int(size.height) / 2
|
|
||||||
var yuvaFrameData = malloc(yuvaLength)!
|
|
||||||
memset(yuvaFrameData, 0, yuvaLength)
|
|
||||||
|
|
||||||
defer {
|
|
||||||
free(yuvaFrameData)
|
|
||||||
}
|
|
||||||
|
|
||||||
encodeRGBAToYUVA(yuvaFrameData.assumingMemoryBound(to: UInt8.self), context.bytes.assumingMemoryBound(to: UInt8.self), Int32(size.width), Int32(size.height), Int32(context.bytesPerRow))
|
|
||||||
decodeYUVAToRGBA(yuvaFrameData.assumingMemoryBound(to: UInt8.self), context.bytes.assumingMemoryBound(to: UInt8.self), Int32(size.width), Int32(size.height), Int32(context.bytesPerRow))
|
|
||||||
|
|
||||||
if let colorSourceImage = context.generateImage(), let alphaImage = generateGrayscaleAlphaMaskImage(image: colorSourceImage) {
|
|
||||||
let colorContext = DrawingContext(size: size, scale: 1.0, clear: false)
|
|
||||||
colorContext.withFlippedContext { c in
|
|
||||||
c.setFillColor(UIColor.black.cgColor)
|
|
||||||
c.fill(CGRect(origin: CGPoint(), size: size))
|
|
||||||
c.draw(colorSourceImage.cgImage!, in: CGRect(origin: CGPoint(), size: size))
|
|
||||||
}
|
|
||||||
guard let colorImage = colorContext.generateImage() else {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let colorData = NSMutableData()
|
let context = DrawingContext(size: size, scale: 1.0, clear: true)
|
||||||
let alphaData = NSMutableData()
|
player.renderFrame(with: 0, into: context.bytes.assumingMemoryBound(to: UInt8.self), width: Int32(size.width), height: Int32(size.height), bytesPerRow: Int32(context.bytesPerRow))
|
||||||
|
|
||||||
if let colorDestination = CGImageDestinationCreateWithData(colorData as CFMutableData, kUTTypeJPEG, 1, nil), let alphaDestination = CGImageDestinationCreateWithData(alphaData as CFMutableData, kUTTypeJPEG, 1, nil) {
|
let yuvaPixelsPerAlphaRow = (Int(size.width) + 1) & (~1)
|
||||||
CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary)
|
assert(yuvaPixelsPerAlphaRow % 2 == 0)
|
||||||
CGImageDestinationSetProperties(alphaDestination, [:] as CFDictionary)
|
|
||||||
|
let yuvaLength = Int(size.width) * Int(size.height) * 2 + yuvaPixelsPerAlphaRow * Int(size.height) / 2
|
||||||
|
var yuvaFrameData = malloc(yuvaLength)!
|
||||||
|
memset(yuvaFrameData, 0, yuvaLength)
|
||||||
|
|
||||||
|
defer {
|
||||||
|
free(yuvaFrameData)
|
||||||
|
}
|
||||||
|
|
||||||
|
encodeRGBAToYUVA(yuvaFrameData.assumingMemoryBound(to: UInt8.self), context.bytes.assumingMemoryBound(to: UInt8.self), Int32(size.width), Int32(size.height), Int32(context.bytesPerRow))
|
||||||
|
decodeYUVAToRGBA(yuvaFrameData.assumingMemoryBound(to: UInt8.self), context.bytes.assumingMemoryBound(to: UInt8.self), Int32(size.width), Int32(size.height), Int32(context.bytesPerRow))
|
||||||
|
|
||||||
|
if let colorSourceImage = context.generateImage(), let alphaImage = generateGrayscaleAlphaMaskImage(image: colorSourceImage) {
|
||||||
|
let colorContext = DrawingContext(size: size, scale: 1.0, clear: false)
|
||||||
|
colorContext.withFlippedContext { c in
|
||||||
|
c.setFillColor(UIColor.black.cgColor)
|
||||||
|
c.fill(CGRect(origin: CGPoint(), size: size))
|
||||||
|
c.draw(colorSourceImage.cgImage!, in: CGRect(origin: CGPoint(), size: size))
|
||||||
|
}
|
||||||
|
guard let colorImage = colorContext.generateImage() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let colorQuality: Float
|
let colorData = NSMutableData()
|
||||||
let alphaQuality: Float
|
let alphaData = NSMutableData()
|
||||||
colorQuality = 0.5
|
|
||||||
alphaQuality = 0.4
|
|
||||||
|
|
||||||
let options = NSMutableDictionary()
|
if let colorDestination = CGImageDestinationCreateWithData(colorData as CFMutableData, kUTTypeJPEG, 1, nil), let alphaDestination = CGImageDestinationCreateWithData(alphaData as CFMutableData, kUTTypeJPEG, 1, nil) {
|
||||||
options.setObject(colorQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString)
|
CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary)
|
||||||
|
CGImageDestinationSetProperties(alphaDestination, [:] as CFDictionary)
|
||||||
let optionsAlpha = NSMutableDictionary()
|
|
||||||
optionsAlpha.setObject(alphaQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString)
|
|
||||||
|
|
||||||
CGImageDestinationAddImage(colorDestination, colorImage.cgImage!, options as CFDictionary)
|
|
||||||
CGImageDestinationAddImage(alphaDestination, alphaImage.cgImage!, optionsAlpha as CFDictionary)
|
|
||||||
if CGImageDestinationFinalize(colorDestination) && CGImageDestinationFinalize(alphaDestination) {
|
|
||||||
let finalData = NSMutableData()
|
|
||||||
var colorSize: Int32 = Int32(colorData.length)
|
|
||||||
finalData.append(&colorSize, length: 4)
|
|
||||||
finalData.append(colorData as Data)
|
|
||||||
var alphaSize: Int32 = Int32(alphaData.length)
|
|
||||||
finalData.append(&alphaSize, length: 4)
|
|
||||||
finalData.append(alphaData as Data)
|
|
||||||
|
|
||||||
let tempFile = TempBox.shared.tempFile(fileName: "image.ajpg")
|
let colorQuality: Float
|
||||||
let _ = try? finalData.write(to: URL(fileURLWithPath: tempFile.path), options: [])
|
let alphaQuality: Float
|
||||||
subscriber.putNext(tempFile)
|
colorQuality = 0.5
|
||||||
subscriber.putCompletion()
|
alphaQuality = 0.4
|
||||||
|
|
||||||
|
let options = NSMutableDictionary()
|
||||||
|
options.setObject(colorQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString)
|
||||||
|
|
||||||
|
let optionsAlpha = NSMutableDictionary()
|
||||||
|
optionsAlpha.setObject(alphaQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString)
|
||||||
|
|
||||||
|
CGImageDestinationAddImage(colorDestination, colorImage.cgImage!, options as CFDictionary)
|
||||||
|
CGImageDestinationAddImage(alphaDestination, alphaImage.cgImage!, optionsAlpha as CFDictionary)
|
||||||
|
if CGImageDestinationFinalize(colorDestination) && CGImageDestinationFinalize(alphaDestination) {
|
||||||
|
let finalData = NSMutableData()
|
||||||
|
var colorSize: Int32 = Int32(colorData.length)
|
||||||
|
finalData.append(&colorSize, length: 4)
|
||||||
|
finalData.append(colorData as Data)
|
||||||
|
var alphaSize: Int32 = Int32(alphaData.length)
|
||||||
|
finalData.append(&alphaSize, length: 4)
|
||||||
|
finalData.append(alphaData as Data)
|
||||||
|
|
||||||
|
let tempFile = TempBox.shared.tempFile(fileName: "image.ajpg")
|
||||||
|
let _ = try? finalData.write(to: URL(fileURLWithPath: tempFile.path), options: [])
|
||||||
|
subscriber.putNext(tempFile)
|
||||||
|
subscriber.putCompletion()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -159,7 +241,7 @@ private let threadPool: ThreadPool = {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
@available(iOS 9.0, *)
|
@available(iOS 9.0, *)
|
||||||
func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: CGSize, cacheKey: String) -> Signal<String, NoError> {
|
func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: CGSize, fitzModifier: EmojiFitzModifier? = nil, cacheKey: String) -> Signal<String, NoError> {
|
||||||
return Signal({ subscriber in
|
return Signal({ subscriber in
|
||||||
let cancelled = Atomic<Bool>(value: false)
|
let cancelled = Atomic<Bool>(value: false)
|
||||||
|
|
||||||
@ -174,127 +256,130 @@ func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: CGSize,
|
|||||||
var appendingTime: Double = 0
|
var appendingTime: Double = 0
|
||||||
var deltaTime: Double = 0
|
var deltaTime: Double = 0
|
||||||
var compressionTime: Double = 0
|
var compressionTime: Double = 0
|
||||||
|
|
||||||
let decompressedData = TGGUnzipData(data, 8 * 1024 * 1024)
|
let decompressedData = TGGUnzipData(data, 8 * 1024 * 1024)
|
||||||
if let decompressedData = decompressedData, let player = LottieInstance(data: decompressedData, cacheKey: cacheKey) {
|
if let decompressedData = decompressedData {
|
||||||
let endFrame = Int(player.frameCount)
|
let decompressedData = transformedWithFitzModifier(data: decompressedData, fitzModifier: fitzModifier)
|
||||||
|
if let player = LottieInstance(data: decompressedData, cacheKey: cacheKey) {
|
||||||
if cancelled.with({ $0 }) {
|
let endFrame = Int(player.frameCount)
|
||||||
//print("cancelled 2")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var randomId: Int64 = 0
|
|
||||||
arc4random_buf(&randomId, 8)
|
|
||||||
let path = NSTemporaryDirectory() + "\(randomId).lz4v"
|
|
||||||
guard let fileContext = ManagedFile(queue: nil, path: path, mode: .readwrite) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let bytesPerRow = (4 * Int(size.width) + 15) & (~15)
|
|
||||||
|
|
||||||
var currentFrame: Int32 = 0
|
|
||||||
|
|
||||||
var fps: Int32 = player.frameRate
|
|
||||||
var frameCount: Int32 = player.frameCount
|
|
||||||
let _ = fileContext.write(&fps, count: 4)
|
|
||||||
let _ = fileContext.write(&frameCount, count: 4)
|
|
||||||
var widthValue: Int32 = Int32(size.width)
|
|
||||||
var heightValue: Int32 = Int32(size.height)
|
|
||||||
var bytesPerRowValue: Int32 = Int32(bytesPerRow)
|
|
||||||
let _ = fileContext.write(&widthValue, count: 4)
|
|
||||||
let _ = fileContext.write(&heightValue, count: 4)
|
|
||||||
let _ = fileContext.write(&bytesPerRowValue, count: 4)
|
|
||||||
|
|
||||||
let frameLength = bytesPerRow * Int(size.height)
|
|
||||||
assert(frameLength % 16 == 0)
|
|
||||||
|
|
||||||
let currentFrameData = malloc(frameLength)!
|
|
||||||
memset(currentFrameData, 0, frameLength)
|
|
||||||
|
|
||||||
let yuvaPixelsPerAlphaRow = (Int(size.width) + 1) & (~1)
|
|
||||||
assert(yuvaPixelsPerAlphaRow % 2 == 0)
|
|
||||||
|
|
||||||
let yuvaLength = Int(size.width) * Int(size.height) * 2 + yuvaPixelsPerAlphaRow * Int(size.height) / 2
|
|
||||||
var yuvaFrameData = malloc(yuvaLength)!
|
|
||||||
memset(yuvaFrameData, 0, yuvaLength)
|
|
||||||
|
|
||||||
var previousYuvaFrameData = malloc(yuvaLength)!
|
|
||||||
memset(previousYuvaFrameData, 0, yuvaLength)
|
|
||||||
|
|
||||||
defer {
|
|
||||||
free(currentFrameData)
|
|
||||||
free(previousYuvaFrameData)
|
|
||||||
free(yuvaFrameData)
|
|
||||||
}
|
|
||||||
|
|
||||||
var compressedFrameData = Data(count: frameLength)
|
|
||||||
let compressedFrameDataLength = compressedFrameData.count
|
|
||||||
|
|
||||||
let scratchData = malloc(compression_encode_scratch_buffer_size(COMPRESSION_LZFSE))!
|
|
||||||
defer {
|
|
||||||
free(scratchData)
|
|
||||||
}
|
|
||||||
|
|
||||||
while currentFrame < endFrame {
|
|
||||||
if cancelled.with({ $0 }) {
|
if cancelled.with({ $0 }) {
|
||||||
//print("cancelled 3")
|
//print("cancelled 2")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let drawStartTime = CACurrentMediaTime()
|
var randomId: Int64 = 0
|
||||||
|
arc4random_buf(&randomId, 8)
|
||||||
|
let path = NSTemporaryDirectory() + "\(randomId).lz4v"
|
||||||
|
guard let fileContext = ManagedFile(queue: nil, path: path, mode: .readwrite) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let bytesPerRow = (4 * Int(size.width) + 15) & (~15)
|
||||||
|
|
||||||
|
var currentFrame: Int32 = 0
|
||||||
|
|
||||||
|
var fps: Int32 = player.frameRate
|
||||||
|
var frameCount: Int32 = player.frameCount
|
||||||
|
let _ = fileContext.write(&fps, count: 4)
|
||||||
|
let _ = fileContext.write(&frameCount, count: 4)
|
||||||
|
var widthValue: Int32 = Int32(size.width)
|
||||||
|
var heightValue: Int32 = Int32(size.height)
|
||||||
|
var bytesPerRowValue: Int32 = Int32(bytesPerRow)
|
||||||
|
let _ = fileContext.write(&widthValue, count: 4)
|
||||||
|
let _ = fileContext.write(&heightValue, count: 4)
|
||||||
|
let _ = fileContext.write(&bytesPerRowValue, count: 4)
|
||||||
|
|
||||||
|
let frameLength = bytesPerRow * Int(size.height)
|
||||||
|
assert(frameLength % 16 == 0)
|
||||||
|
|
||||||
|
let currentFrameData = malloc(frameLength)!
|
||||||
memset(currentFrameData, 0, frameLength)
|
memset(currentFrameData, 0, frameLength)
|
||||||
player.renderFrame(with: Int32(currentFrame), into: currentFrameData.assumingMemoryBound(to: UInt8.self), width: Int32(size.width), height: Int32(size.height), bytesPerRow: Int32(bytesPerRow))
|
|
||||||
drawingTime += CACurrentMediaTime() - drawStartTime
|
|
||||||
|
|
||||||
let appendStartTime = CACurrentMediaTime()
|
let yuvaPixelsPerAlphaRow = (Int(size.width) + 1) & (~1)
|
||||||
|
assert(yuvaPixelsPerAlphaRow % 2 == 0)
|
||||||
|
|
||||||
encodeRGBAToYUVA(yuvaFrameData.assumingMemoryBound(to: UInt8.self), currentFrameData.assumingMemoryBound(to: UInt8.self), Int32(size.width), Int32(size.height), Int32(bytesPerRow))
|
let yuvaLength = Int(size.width) * Int(size.height) * 2 + yuvaPixelsPerAlphaRow * Int(size.height) / 2
|
||||||
|
var yuvaFrameData = malloc(yuvaLength)!
|
||||||
|
memset(yuvaFrameData, 0, yuvaLength)
|
||||||
|
|
||||||
appendingTime += CACurrentMediaTime() - appendStartTime
|
var previousYuvaFrameData = malloc(yuvaLength)!
|
||||||
|
memset(previousYuvaFrameData, 0, yuvaLength)
|
||||||
|
|
||||||
let deltaStartTime = CACurrentMediaTime()
|
defer {
|
||||||
var lhs = previousYuvaFrameData.assumingMemoryBound(to: UInt64.self)
|
free(currentFrameData)
|
||||||
var rhs = yuvaFrameData.assumingMemoryBound(to: UInt64.self)
|
free(previousYuvaFrameData)
|
||||||
for _ in 0 ..< yuvaLength / 8 {
|
free(yuvaFrameData)
|
||||||
lhs.pointee = rhs.pointee ^ lhs.pointee
|
|
||||||
lhs = lhs.advanced(by: 1)
|
|
||||||
rhs = rhs.advanced(by: 1)
|
|
||||||
}
|
|
||||||
var lhsRest = previousYuvaFrameData.assumingMemoryBound(to: UInt8.self).advanced(by: (yuvaLength / 8) * 8)
|
|
||||||
var rhsRest = yuvaFrameData.assumingMemoryBound(to: UInt8.self).advanced(by: (yuvaLength / 8) * 8)
|
|
||||||
for _ in (yuvaLength / 8) * 8 ..< yuvaLength {
|
|
||||||
lhsRest.pointee = rhsRest.pointee ^ lhsRest.pointee
|
|
||||||
lhsRest = lhsRest.advanced(by: 1)
|
|
||||||
rhsRest = rhsRest.advanced(by: 1)
|
|
||||||
}
|
|
||||||
deltaTime += CACurrentMediaTime() - deltaStartTime
|
|
||||||
|
|
||||||
let compressionStartTime = CACurrentMediaTime()
|
|
||||||
compressedFrameData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
|
|
||||||
let length = compression_encode_buffer(bytes, compressedFrameDataLength, previousYuvaFrameData.assumingMemoryBound(to: UInt8.self), yuvaLength, scratchData, COMPRESSION_LZFSE)
|
|
||||||
var frameLengthValue: Int32 = Int32(length)
|
|
||||||
let _ = fileContext.write(&frameLengthValue, count: 4)
|
|
||||||
let _ = fileContext.write(bytes, count: length)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let tmp = previousYuvaFrameData
|
var compressedFrameData = Data(count: frameLength)
|
||||||
previousYuvaFrameData = yuvaFrameData
|
let compressedFrameDataLength = compressedFrameData.count
|
||||||
yuvaFrameData = tmp
|
|
||||||
|
|
||||||
compressionTime += CACurrentMediaTime() - compressionStartTime
|
let scratchData = malloc(compression_encode_scratch_buffer_size(COMPRESSION_LZFSE))!
|
||||||
|
defer {
|
||||||
|
free(scratchData)
|
||||||
|
}
|
||||||
|
|
||||||
currentFrame += 1
|
while currentFrame < endFrame {
|
||||||
|
if cancelled.with({ $0 }) {
|
||||||
|
//print("cancelled 3")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let drawStartTime = CACurrentMediaTime()
|
||||||
|
memset(currentFrameData, 0, frameLength)
|
||||||
|
player.renderFrame(with: Int32(currentFrame), into: currentFrameData.assumingMemoryBound(to: UInt8.self), width: Int32(size.width), height: Int32(size.height), bytesPerRow: Int32(bytesPerRow))
|
||||||
|
drawingTime += CACurrentMediaTime() - drawStartTime
|
||||||
|
|
||||||
|
let appendStartTime = CACurrentMediaTime()
|
||||||
|
|
||||||
|
encodeRGBAToYUVA(yuvaFrameData.assumingMemoryBound(to: UInt8.self), currentFrameData.assumingMemoryBound(to: UInt8.self), Int32(size.width), Int32(size.height), Int32(bytesPerRow))
|
||||||
|
|
||||||
|
appendingTime += CACurrentMediaTime() - appendStartTime
|
||||||
|
|
||||||
|
let deltaStartTime = CACurrentMediaTime()
|
||||||
|
var lhs = previousYuvaFrameData.assumingMemoryBound(to: UInt64.self)
|
||||||
|
var rhs = yuvaFrameData.assumingMemoryBound(to: UInt64.self)
|
||||||
|
for _ in 0 ..< yuvaLength / 8 {
|
||||||
|
lhs.pointee = rhs.pointee ^ lhs.pointee
|
||||||
|
lhs = lhs.advanced(by: 1)
|
||||||
|
rhs = rhs.advanced(by: 1)
|
||||||
|
}
|
||||||
|
var lhsRest = previousYuvaFrameData.assumingMemoryBound(to: UInt8.self).advanced(by: (yuvaLength / 8) * 8)
|
||||||
|
var rhsRest = yuvaFrameData.assumingMemoryBound(to: UInt8.self).advanced(by: (yuvaLength / 8) * 8)
|
||||||
|
for _ in (yuvaLength / 8) * 8 ..< yuvaLength {
|
||||||
|
lhsRest.pointee = rhsRest.pointee ^ lhsRest.pointee
|
||||||
|
lhsRest = lhsRest.advanced(by: 1)
|
||||||
|
rhsRest = rhsRest.advanced(by: 1)
|
||||||
|
}
|
||||||
|
deltaTime += CACurrentMediaTime() - deltaStartTime
|
||||||
|
|
||||||
|
let compressionStartTime = CACurrentMediaTime()
|
||||||
|
compressedFrameData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
|
||||||
|
let length = compression_encode_buffer(bytes, compressedFrameDataLength, previousYuvaFrameData.assumingMemoryBound(to: UInt8.self), yuvaLength, scratchData, COMPRESSION_LZFSE)
|
||||||
|
var frameLengthValue: Int32 = Int32(length)
|
||||||
|
let _ = fileContext.write(&frameLengthValue, count: 4)
|
||||||
|
let _ = fileContext.write(bytes, count: length)
|
||||||
|
}
|
||||||
|
|
||||||
|
let tmp = previousYuvaFrameData
|
||||||
|
previousYuvaFrameData = yuvaFrameData
|
||||||
|
yuvaFrameData = tmp
|
||||||
|
|
||||||
|
compressionTime += CACurrentMediaTime() - compressionStartTime
|
||||||
|
|
||||||
|
currentFrame += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
subscriber.putNext(path)
|
||||||
|
subscriber.putCompletion()
|
||||||
|
print("animation render time \(CACurrentMediaTime() - startTime)")
|
||||||
|
print("of which drawing time \(drawingTime)")
|
||||||
|
print("of which appending time \(appendingTime)")
|
||||||
|
print("of which delta time \(deltaTime)")
|
||||||
|
|
||||||
|
print("of which compression time \(compressionTime)")
|
||||||
}
|
}
|
||||||
|
|
||||||
subscriber.putNext(path)
|
|
||||||
subscriber.putCompletion()
|
|
||||||
print("animation render time \(CACurrentMediaTime() - startTime)")
|
|
||||||
print("of which drawing time \(drawingTime)")
|
|
||||||
print("of which appending time \(appendingTime)")
|
|
||||||
print("of which delta time \(deltaTime)")
|
|
||||||
|
|
||||||
print("of which compression time \(compressionTime)")
|
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
return ActionDisposable {
|
return ActionDisposable {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -881,7 +881,7 @@ public func channelInfoController(context: AccountContext, peerId: PeerId) -> Vi
|
|||||||
}, reportChannel: {
|
}, reportChannel: {
|
||||||
presentControllerImpl?(peerReportOptionsController(context: context, subject: .peer(peerId), present: { c, a in
|
presentControllerImpl?(peerReportOptionsController(context: context, subject: .peer(peerId), present: { c, a in
|
||||||
presentControllerImpl?(c, a)
|
presentControllerImpl?(c, a)
|
||||||
}), nil)
|
}, completion: { _ in }), nil)
|
||||||
}, leaveChannel: {
|
}, leaveChannel: {
|
||||||
let _ = (context.account.postbox.transaction { transaction -> Peer? in
|
let _ = (context.account.postbox.transaction { transaction -> Peer? in
|
||||||
return transaction.getPeer(peerId)
|
return transaction.getPeer(peerId)
|
||||||
|
@ -137,7 +137,7 @@ final class ChatAnimationGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||||||
|
|
||||||
func setFile(context: AccountContext, fileReference: FileMediaReference) {
|
func setFile(context: AccountContext, fileReference: FileMediaReference) {
|
||||||
if self.contextAndMedia == nil || !self.contextAndMedia!.1.media.isEqual(to: fileReference.media) {
|
if self.contextAndMedia == nil || !self.contextAndMedia!.1.media.isEqual(to: fileReference.media) {
|
||||||
let signal = chatMessageAnimatedStrickerBackingData(postbox: context.account.postbox, fileReference: fileReference, synchronousLoad: false)
|
let signal = chatMessageAnimatedStickerBackingData(postbox: context.account.postbox, fileReference: fileReference, synchronousLoad: false)
|
||||||
|> mapToSignal { value -> Signal<Data, NoError> in
|
|> mapToSignal { value -> Signal<Data, NoError> in
|
||||||
if value._1, let data = value._0 {
|
if value._1, let data = value._0 {
|
||||||
return .single(data)
|
return .single(data)
|
||||||
|
@ -2618,14 +2618,14 @@ public final class ChatController: TelegramBaseController, GalleryHiddenMediaTar
|
|||||||
if let strongSelf = self, let messageIds = strongSelf.presentationInterfaceState.interfaceState.selectionState?.selectedIds, !messageIds.isEmpty {
|
if let strongSelf = self, let messageIds = strongSelf.presentationInterfaceState.interfaceState.selectionState?.selectedIds, !messageIds.isEmpty {
|
||||||
strongSelf.present(peerReportOptionsController(context: strongSelf.context, subject: .messages(Array(messageIds).sorted()), present: { c, a in
|
strongSelf.present(peerReportOptionsController(context: strongSelf.context, subject: .messages(Array(messageIds).sorted()), present: { c, a in
|
||||||
self?.present(c, in: .window(.root), with: a)
|
self?.present(c, in: .window(.root), with: a)
|
||||||
}), in: .window(.root))
|
}, completion: { _ in }), in: .window(.root))
|
||||||
}
|
}
|
||||||
}, reportMessages: { [weak self] messages in
|
}, reportMessages: { [weak self] messages in
|
||||||
if let strongSelf = self, !messages.isEmpty {
|
if let strongSelf = self, !messages.isEmpty {
|
||||||
strongSelf.chatDisplayNode.dismissInput()
|
strongSelf.chatDisplayNode.dismissInput()
|
||||||
strongSelf.present(peerReportOptionsController(context: strongSelf.context, subject: .messages(messages.map({ $0.id }).sorted()), present: { c, a in
|
strongSelf.present(peerReportOptionsController(context: strongSelf.context, subject: .messages(messages.map({ $0.id }).sorted()), present: { c, a in
|
||||||
self?.present(c, in: .window(.root), with: a)
|
self?.present(c, in: .window(.root), with: a)
|
||||||
}), in: .window(.root))
|
}, completion: { _ in }), in: .window(.root))
|
||||||
}
|
}
|
||||||
}, deleteMessages: { [weak self] messages, contextController, completion in
|
}, deleteMessages: { [weak self] messages, contextController, completion in
|
||||||
if let strongSelf = self, !messages.isEmpty {
|
if let strongSelf = self, !messages.isEmpty {
|
||||||
@ -6087,6 +6087,12 @@ public final class ChatController: TelegramBaseController, GalleryHiddenMediaTar
|
|||||||
if let peer = peer as? TelegramChannel, let username = peer.username, !username.isEmpty {
|
if let peer = peer as? TelegramChannel, let username = peer.username, !username.isEmpty {
|
||||||
self.present(peerReportOptionsController(context: self.context, subject: .peer(peer.id), present: { [weak self] c, a in
|
self.present(peerReportOptionsController(context: self.context, subject: .peer(peer.id), present: { [weak self] c, a in
|
||||||
self?.present(c, in: .window(.root))
|
self?.present(c, in: .window(.root))
|
||||||
|
}, completion: { [weak self] success in
|
||||||
|
guard let strongSelf = self, success else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let _ = removePeerChat(account: strongSelf.context.account, peerId: chatPeer.id, reportChatSpam: false).start()
|
||||||
|
(strongSelf.navigationController as? NavigationController)?.filterController(strongSelf, animated: true)
|
||||||
}), in: .window(.root))
|
}), in: .window(.root))
|
||||||
} else if let _ = peer as? TelegramUser {
|
} else if let _ = peer as? TelegramUser {
|
||||||
let presentationData = self.presentationData
|
let presentationData = self.presentationData
|
||||||
|
@ -40,7 +40,7 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView,
|
|||||||
|
|
||||||
var contentTypeHint: ChatMessageEntryContentType = .generic
|
var contentTypeHint: ChatMessageEntryContentType = .generic
|
||||||
if presentationData.largeEmoji {
|
if presentationData.largeEmoji {
|
||||||
if let _ = associatedData.animatedEmojiStickers[entry.message.text.trimmedEmoji] {
|
if let _ = associatedData.animatedEmojiStickers[entry.message.text.basicEmoji.0] {
|
||||||
contentTypeHint = .animatedEmoji
|
contentTypeHint = .animatedEmoji
|
||||||
} else if messageIsElligibleForLargeEmoji(entry.message) {
|
} else if messageIsElligibleForLargeEmoji(entry.message) {
|
||||||
contentTypeHint = .largeEmoji
|
contentTypeHint = .largeEmoji
|
||||||
|
@ -548,7 +548,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
var animatedEmojiStickers: [String: StickerPackItem] = [:]
|
var animatedEmojiStickers: [String: StickerPackItem] = [:]
|
||||||
for case let item as StickerPackItem in items {
|
for case let item as StickerPackItem in items {
|
||||||
if let emoji = item.getStringRepresentationsOfIndexKeys().first {
|
if let emoji = item.getStringRepresentationsOfIndexKeys().first {
|
||||||
animatedEmojiStickers[emoji.trimmedEmoji] = item
|
animatedEmojiStickers[emoji.basicEmoji.0] = item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return animatedEmojiStickers
|
return animatedEmojiStickers
|
||||||
|
@ -177,7 +177,7 @@ func inputContextQueriesForChatPresentationIntefaceState(_ chatPresentationInter
|
|||||||
for (possibleQueryRange, possibleTypes, additionalStringRange) in textInputStateContextQueryRangeAndType(inputState) {
|
for (possibleQueryRange, possibleTypes, additionalStringRange) in textInputStateContextQueryRangeAndType(inputState) {
|
||||||
let query = inputString.substring(with: possibleQueryRange)
|
let query = inputString.substring(with: possibleQueryRange)
|
||||||
if possibleTypes == [.emoji] {
|
if possibleTypes == [.emoji] {
|
||||||
result.append(.emoji(query.basicEmoji))
|
result.append(.emoji(query.basicEmoji.0))
|
||||||
} else if possibleTypes == [.hashtag] {
|
} else if possibleTypes == [.hashtag] {
|
||||||
result.append(.hashtag(query))
|
result.append(.hashtag(query))
|
||||||
} else if possibleTypes == [.mention] {
|
} else if possibleTypes == [.mention] {
|
||||||
|
@ -90,7 +90,7 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
|
|||||||
case .installed:
|
case .installed:
|
||||||
scope = [.installed]
|
scope = [.installed]
|
||||||
}
|
}
|
||||||
return searchStickers(account: context.account, query: query.trimmedEmoji, scope: scope)
|
return searchStickers(account: context.account, query: query.basicEmoji.0, scope: scope)
|
||||||
|> introduceError(ChatContextQueryError.self)
|
|> introduceError(ChatContextQueryError.self)
|
||||||
}
|
}
|
||||||
|> map { stickers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
|
|> map { stickers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
|
||||||
|
@ -172,7 +172,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
|||||||
|
|
||||||
private let messageContextDisposable = MetaDisposable()
|
private let messageContextDisposable = MetaDisposable()
|
||||||
|
|
||||||
private var videoFramePreviewNode: ASImageNode?
|
private var videoFramePreviewNode: (ASImageNode, ImmediateTextNode)?
|
||||||
|
|
||||||
private var validLayout: (CGSize, LayoutMetrics, CGFloat, CGFloat, CGFloat, CGFloat)?
|
private var validLayout: (CGSize, LayoutMetrics, CGFloat, CGFloat, CGFloat, CGFloat)?
|
||||||
|
|
||||||
@ -235,6 +235,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var scrubbingHandleRelativePosition: CGFloat = 0.0
|
private var scrubbingHandleRelativePosition: CGFloat = 0.0
|
||||||
|
private var scrubbingVisualTimestamp: Double?
|
||||||
|
|
||||||
var scrubberView: ChatVideoGalleryItemScrubberView? = nil {
|
var scrubberView: ChatVideoGalleryItemScrubberView? = nil {
|
||||||
willSet {
|
willSet {
|
||||||
@ -245,6 +246,23 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
|||||||
didSet {
|
didSet {
|
||||||
if let scrubberView = self.scrubberView {
|
if let scrubberView = self.scrubberView {
|
||||||
self.view.addSubview(scrubberView)
|
self.view.addSubview(scrubberView)
|
||||||
|
scrubberView.updateScrubbingVisual = { [weak self] value in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let value = value {
|
||||||
|
strongSelf.scrubbingVisualTimestamp = value
|
||||||
|
if let (videoFramePreviewNode, videoFrameTextNode) = strongSelf.videoFramePreviewNode {
|
||||||
|
videoFrameTextNode.attributedText = NSAttributedString(string: stringForDuration(Int32(value)), font: Font.regular(13.0), textColor: .white)
|
||||||
|
let textSize = videoFrameTextNode.updateLayout(CGSize(width: 100.0, height: 100.0))
|
||||||
|
let imageFrame = videoFramePreviewNode.frame
|
||||||
|
let textOffset = (Int((imageFrame.size.width - videoFrameTextNode.bounds.width) / 2) / 2) * 2
|
||||||
|
videoFrameTextNode.frame = CGRect(origin: CGPoint(x: CGFloat(textOffset), y: imageFrame.size.height - videoFrameTextNode.bounds.height - 5.0), size: textSize)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
strongSelf.scrubbingVisualTimestamp = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
scrubberView.updateScrubbingHandlePosition = { [weak self] value in
|
scrubberView.updateScrubbingHandlePosition = { [weak self] value in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
@ -643,15 +661,31 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
|||||||
self.dateNode.frame = CGRect(origin: CGPoint(x: floor((width - dateSize.width) / 2.0), y: panelHeight - bottomInset - 44.0 + floor((44.0 - dateSize.height - authorNameSize.height - labelsSpacing) / 2.0) + authorNameSize.height + labelsSpacing), size: dateSize)
|
self.dateNode.frame = CGRect(origin: CGPoint(x: floor((width - dateSize.width) / 2.0), y: panelHeight - bottomInset - 44.0 + floor((44.0 - dateSize.height - authorNameSize.height - labelsSpacing) / 2.0) + authorNameSize.height + labelsSpacing), size: dateSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let videoFramePreviewNode = self.videoFramePreviewNode {
|
if let (videoFramePreviewNode, videoFrameTextNode) = self.videoFramePreviewNode {
|
||||||
let intrinsicImageSize = videoFramePreviewNode.image?.size ?? CGSize(width: 320.0, height: 240.0)
|
let intrinsicImageSize = videoFramePreviewNode.image?.size ?? CGSize(width: 320.0, height: 240.0)
|
||||||
let imageSize = intrinsicImageSize.aspectFitted(CGSize(width: 200.0, height: 200.0))
|
let fitSize: CGSize
|
||||||
var imageFrame = CGRect(origin: CGPoint(x: leftInset + floor(self.scrubbingHandleRelativePosition * (width - leftInset - rightInset) - imageSize.width / 2.0), y: self.scrollNode.frame.minY - 10.0 - imageSize.height), size: imageSize)
|
if intrinsicImageSize.width < intrinsicImageSize.height {
|
||||||
|
fitSize = CGSize(width: 90.0, height: 160.0)
|
||||||
|
} else {
|
||||||
|
fitSize = CGSize(width: 160.0, height: 90.0)
|
||||||
|
}
|
||||||
|
let scrubberInset: CGFloat
|
||||||
|
if size.width > size.height {
|
||||||
|
scrubberInset = 58.0
|
||||||
|
} else {
|
||||||
|
scrubberInset = 13.0
|
||||||
|
}
|
||||||
|
|
||||||
|
let imageSize = intrinsicImageSize.aspectFitted(fitSize)
|
||||||
|
var imageFrame = CGRect(origin: CGPoint(x: leftInset + scrubberInset + floor(self.scrubbingHandleRelativePosition * (width - leftInset - rightInset - scrubberInset * 2.0) - imageSize.width / 2.0), y: self.scrollNode.frame.minY - 6.0 - imageSize.height), size: imageSize)
|
||||||
imageFrame.origin.x = min(imageFrame.origin.x, width - rightInset - 10.0 - imageSize.width)
|
imageFrame.origin.x = min(imageFrame.origin.x, width - rightInset - 10.0 - imageSize.width)
|
||||||
imageFrame.origin.x = max(imageFrame.origin.x, leftInset + 10.0)
|
imageFrame.origin.x = max(imageFrame.origin.x, leftInset + 10.0)
|
||||||
|
|
||||||
videoFramePreviewNode.frame = imageFrame
|
videoFramePreviewNode.frame = imageFrame
|
||||||
videoFramePreviewNode.subnodes?.first?.frame = CGRect(origin: CGPoint(), size: imageFrame.size)
|
videoFramePreviewNode.subnodes?.first?.frame = CGRect(origin: CGPoint(), size: imageFrame.size)
|
||||||
|
|
||||||
|
let textOffset = (Int((imageFrame.size.width - videoFrameTextNode.bounds.width) / 2) / 2) * 2
|
||||||
|
videoFrameTextNode.frame = CGRect(origin: CGPoint(x: CGFloat(textOffset), y: imageFrame.size.height - videoFrameTextNode.bounds.height - 5.0), size: videoFrameTextNode.bounds.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
return panelHeight
|
return panelHeight
|
||||||
@ -1028,7 +1062,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
|||||||
}
|
}
|
||||||
|
|
||||||
func setFramePreviewImageIsLoading() {
|
func setFramePreviewImageIsLoading() {
|
||||||
if self.videoFramePreviewNode?.image != nil {
|
if self.videoFramePreviewNode?.0.image != nil {
|
||||||
//self.videoFramePreviewNode?.subnodes?.first?.alpha = 1.0
|
//self.videoFramePreviewNode?.subnodes?.first?.alpha = 1.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1036,17 +1070,34 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
|||||||
func setFramePreviewImage(image: UIImage?) {
|
func setFramePreviewImage(image: UIImage?) {
|
||||||
if let image = image {
|
if let image = image {
|
||||||
let videoFramePreviewNode: ASImageNode
|
let videoFramePreviewNode: ASImageNode
|
||||||
|
let videoFrameTextNode: ImmediateTextNode
|
||||||
var animateIn = false
|
var animateIn = false
|
||||||
if let current = self.videoFramePreviewNode {
|
if let current = self.videoFramePreviewNode {
|
||||||
videoFramePreviewNode = current
|
videoFramePreviewNode = current.0
|
||||||
|
videoFrameTextNode = current.1
|
||||||
} else {
|
} else {
|
||||||
videoFramePreviewNode = ASImageNode()
|
videoFramePreviewNode = ASImageNode()
|
||||||
videoFramePreviewNode.displaysAsynchronously = false
|
videoFramePreviewNode.displaysAsynchronously = false
|
||||||
videoFramePreviewNode.displayWithoutProcessing = true
|
videoFramePreviewNode.displayWithoutProcessing = true
|
||||||
|
videoFramePreviewNode.clipsToBounds = true
|
||||||
|
videoFramePreviewNode.cornerRadius = 6.0
|
||||||
|
|
||||||
let dimNode = ASDisplayNode()
|
let dimNode = ASDisplayNode()
|
||||||
dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
||||||
videoFramePreviewNode.addSubnode(dimNode)
|
videoFramePreviewNode.addSubnode(dimNode)
|
||||||
self.videoFramePreviewNode = videoFramePreviewNode
|
|
||||||
|
videoFrameTextNode = ImmediateTextNode()
|
||||||
|
videoFrameTextNode.displaysAsynchronously = false
|
||||||
|
videoFrameTextNode.maximumNumberOfLines = 1
|
||||||
|
videoFrameTextNode.textShadowColor = .black
|
||||||
|
if let scrubbingVisualTimestamp = self.scrubbingVisualTimestamp {
|
||||||
|
videoFrameTextNode.attributedText = NSAttributedString(string: stringForDuration(Int32(scrubbingVisualTimestamp)), font: Font.regular(13.0), textColor: .white)
|
||||||
|
}
|
||||||
|
let textSize = videoFrameTextNode.updateLayout(CGSize(width: 100.0, height: 100.0))
|
||||||
|
videoFrameTextNode.frame = CGRect(origin: CGPoint(), size: textSize)
|
||||||
|
videoFramePreviewNode.addSubnode(videoFrameTextNode)
|
||||||
|
|
||||||
|
self.videoFramePreviewNode = (videoFramePreviewNode, videoFrameTextNode)
|
||||||
self.addSubnode(videoFramePreviewNode)
|
self.addSubnode(videoFramePreviewNode)
|
||||||
animateIn = true
|
animateIn = true
|
||||||
}
|
}
|
||||||
@ -1059,7 +1110,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
|||||||
if animateIn {
|
if animateIn {
|
||||||
videoFramePreviewNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
|
videoFramePreviewNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
|
||||||
}
|
}
|
||||||
} else if let videoFramePreviewNode = self.videoFramePreviewNode {
|
} else if let (videoFramePreviewNode, _) = self.videoFramePreviewNode {
|
||||||
self.videoFramePreviewNode = nil
|
self.videoFramePreviewNode = nil
|
||||||
videoFramePreviewNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false, completion: { [weak videoFramePreviewNode] _ in
|
videoFramePreviewNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false, completion: { [weak videoFramePreviewNode] _ in
|
||||||
videoFramePreviewNode?.removeFromSupernode()
|
videoFramePreviewNode?.removeFromSupernode()
|
||||||
|
@ -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 {
|
||||||
|
@ -10,6 +10,7 @@ import TelegramPresentationData
|
|||||||
import Compression
|
import Compression
|
||||||
import TextFormat
|
import TextFormat
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
import MediaResources
|
||||||
import StickerResources
|
import StickerResources
|
||||||
import ContextUI
|
import ContextUI
|
||||||
|
|
||||||
@ -56,7 +57,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 +202,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,13 +238,18 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.telegramFile == nil, let emojiFile = item.associatedData.animatedEmojiStickers[item.message.text.trimmedEmoji]?.file {
|
let (emoji, fitz) = item.message.text.basicEmoji
|
||||||
|
if self.telegramFile == nil, let emojiFile = item.associatedData.animatedEmojiStickers[emoji]?.file {
|
||||||
if self.emojiFile?.id != emojiFile.id {
|
if self.emojiFile?.id != emojiFile.id {
|
||||||
self.emojiFile = emojiFile
|
self.emojiFile = emojiFile
|
||||||
let dimensions = emojiFile.dimensions ?? CGSize(width: 512.0, height: 512.0)
|
let dimensions = emojiFile.dimensions ?? CGSize(width: 512.0, height: 512.0)
|
||||||
self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: item.context.account.postbox, file: emojiFile, small: false, size: dimensions.aspectFilled(CGSize(width: 384.0, height: 384.0)), thumbnail: false))
|
var fitzModifier: EmojiFitzModifier?
|
||||||
self.disposable.set(freeMediaFileInteractiveFetched(account: item.context.account, fileReference: .message(message: MessageReference(item.message), media: emojiFile)).start())
|
if let fitz = fitz {
|
||||||
|
fitzModifier = EmojiFitzModifier(emoji: fitz)
|
||||||
|
}
|
||||||
|
self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: item.context.account.postbox, file: emojiFile, small: false, size: dimensions.aspectFilled(CGSize(width: 384.0, height: 384.0)), fitzModifier: fitzModifier, thumbnail: false))
|
||||||
|
self.disposable.set(freeMediaFileInteractiveFetched(account: item.context.account, fileReference: .standalone(media: emojiFile)).start())
|
||||||
self.updateVisibility()
|
self.updateVisibility()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -276,7 +282,8 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
var file: TelegramMediaFile?
|
var file: TelegramMediaFile?
|
||||||
var playbackMode: AnimatedStickerPlaybackMode = .loop
|
var playbackMode: AnimatedStickerPlaybackMode = .loop
|
||||||
var isEmoji = false
|
var isEmoji = false
|
||||||
|
var fitzModifier: EmojiFitzModifier?
|
||||||
|
|
||||||
if let telegramFile = self.telegramFile {
|
if let telegramFile = self.telegramFile {
|
||||||
file = telegramFile
|
file = telegramFile
|
||||||
if !item.controllerInteraction.stickerSettings.loopAnimatedStickers {
|
if !item.controllerInteraction.stickerSettings.loopAnimatedStickers {
|
||||||
@ -286,12 +293,16 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
isEmoji = true
|
isEmoji = true
|
||||||
file = emojiFile
|
file = emojiFile
|
||||||
playbackMode = .once
|
playbackMode = .once
|
||||||
|
let (_, fitz) = item.message.text.basicEmoji
|
||||||
|
if let fitz = fitz {
|
||||||
|
fitzModifier = EmojiFitzModifier(emoji: fitz)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let file = file {
|
if let file = file {
|
||||||
let dimensions = file.dimensions ?? CGSize(width: 512.0, height: 512.0)
|
let dimensions = file.dimensions ?? CGSize(width: 512.0, height: 512.0)
|
||||||
let fittedSize = isEmoji ? dimensions.aspectFilled(CGSize(width: 384.0, height: 384.0)) : dimensions.aspectFitted(CGSize(width: 384.0, height: 384.0))
|
let fittedSize = isEmoji ? dimensions.aspectFilled(CGSize(width: 384.0, height: 384.0)) : dimensions.aspectFitted(CGSize(width: 384.0, height: 384.0))
|
||||||
self.animationNode.setup(account: item.context.account, resource: file.resource, width: Int(fittedSize.width), height: Int(fittedSize.height), playbackMode: playbackMode, mode: .cached)
|
self.animationNode.setup(account: item.context.account, resource: file.resource, fitzModifier: fitzModifier, width: Int(fittedSize.width), height: Int(fittedSize.height), playbackMode: playbackMode, mode: .cached)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -341,23 +352,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 +452,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 +715,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ final class ChatMessageContextControllerContentSource: ContextControllerContentS
|
|||||||
guard let item = itemNode.item else {
|
guard let item = itemNode.item else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if item.message.stableId == self.message.stableId, let contentNode = itemNode.getMessageContextSourceNode() {
|
if item.content.contains(where: { $0.stableId == self.message.stableId }), let contentNode = itemNode.getMessageContextSourceNode() {
|
||||||
result = ContextControllerTakeViewInfo(contentContainingNode: contentNode, contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil))
|
result = ContextControllerTakeViewInfo(contentContainingNode: contentNode, contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -46,7 +46,7 @@ final class ChatMessageContextControllerContentSource: ContextControllerContentS
|
|||||||
guard let item = itemNode.item else {
|
guard let item = itemNode.item else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if item.message.stableId == self.message.stableId {
|
if item.content.contains(where: { $0.stableId == self.message.stableId }) {
|
||||||
result = ContextControllerPutBackViewInfo(contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil))
|
result = ContextControllerPutBackViewInfo(contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -388,7 +388,7 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if viewClassName == ChatMessageBubbleItemNode.self && self.presentationData.largeEmoji && messageIsElligibleForLargeEmoji(self.message) {
|
if viewClassName == ChatMessageBubbleItemNode.self && self.presentationData.largeEmoji && messageIsElligibleForLargeEmoji(self.message) {
|
||||||
if let _ = self.associatedData.animatedEmojiStickers[self.message.text.trimmedEmoji] {
|
if let _ = self.associatedData.animatedEmojiStickers[self.message.text.basicEmoji.0] {
|
||||||
viewClassName = ChatMessageAnimatedStickerItemNode.self
|
viewClassName = ChatMessageAnimatedStickerItemNode.self
|
||||||
} else {
|
} else {
|
||||||
viewClassName = ChatMessageStickerItemNode.self
|
viewClassName = ChatMessageStickerItemNode.self
|
||||||
|
@ -43,6 +43,7 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var updateScrubbing: (Double?) -> Void = { _ in }
|
var updateScrubbing: (Double?) -> Void = { _ in }
|
||||||
|
var updateScrubbingVisual: (Double?) -> Void = { _ in }
|
||||||
var updateScrubbingHandlePosition: (CGFloat) -> Void = { _ in }
|
var updateScrubbingHandlePosition: (CGFloat) -> Void = { _ in }
|
||||||
var seek: (Double) -> Void = { _ in }
|
var seek: (Double) -> Void = { _ in }
|
||||||
|
|
||||||
@ -67,6 +68,7 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||||||
|
|
||||||
self.scrubberNode.update = { [weak self] timestamp, position in
|
self.scrubberNode.update = { [weak self] timestamp, position in
|
||||||
self?.updateScrubbing(timestamp)
|
self?.updateScrubbing(timestamp)
|
||||||
|
self?.updateScrubbingVisual(timestamp)
|
||||||
self?.updateScrubbingHandlePosition(position)
|
self?.updateScrubbingHandlePosition(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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:
|
||||||
@ -127,7 +129,7 @@ extension String {
|
|||||||
return string
|
return string
|
||||||
}
|
}
|
||||||
|
|
||||||
var basicEmoji: String {
|
var basicEmoji: (String, String?) {
|
||||||
let fitzCodes: [UInt32] = [
|
let fitzCodes: [UInt32] = [
|
||||||
0x1f3fb,
|
0x1f3fb,
|
||||||
0x1f3fc,
|
0x1f3fc,
|
||||||
@ -137,13 +139,18 @@ extension String {
|
|||||||
]
|
]
|
||||||
|
|
||||||
var string = ""
|
var string = ""
|
||||||
|
var fitzModifier: String?
|
||||||
for scalar in self.unicodeScalars {
|
for scalar in self.unicodeScalars {
|
||||||
if fitzCodes.contains(scalar.value) {
|
if fitzCodes.contains(scalar.value) {
|
||||||
|
fitzModifier = String(scalar)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
string.unicodeScalars.append(scalar)
|
string.unicodeScalars.append(scalar)
|
||||||
|
if scalar.value == 0x2764, self.unicodeScalars.count > 1, self.emojis.count == 1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return string
|
return (string, fitzModifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
var trimmedEmoji: String {
|
var trimmedEmoji: String {
|
||||||
|
@ -113,19 +113,7 @@ public func fetchCachedResourceRepresentation(account: Account, resource: MediaR
|
|||||||
} else if let representation = representation as? CachedEmojiRepresentation {
|
} else if let representation = representation as? CachedEmojiRepresentation {
|
||||||
return fetchEmojiRepresentation(account: account, resource: resource, representation: representation)
|
return fetchEmojiRepresentation(account: account, resource: resource, representation: representation)
|
||||||
} else if let representation = representation as? CachedAnimatedStickerRepresentation {
|
} else if let representation = representation as? CachedAnimatedStickerRepresentation {
|
||||||
let data: Signal<MediaResourceData, NoError>
|
return account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false))
|
||||||
// if let resource = resource as? LocalBundleResource {
|
|
||||||
// data = Signal { subscriber in
|
|
||||||
// if let path = frameworkBundle.path(forResource: resource.name, ofType: resource.ext), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) {
|
|
||||||
// subscriber.putNext(MediaResourceData(path: path, offset: 0, size: data.count, complete: true))
|
|
||||||
// subscriber.putCompletion()
|
|
||||||
// }
|
|
||||||
// return EmptyDisposable
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
data = account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false))
|
|
||||||
// }
|
|
||||||
return data
|
|
||||||
|> mapToSignal { data -> Signal<CachedMediaResourceRepresentationResult, NoError> in
|
|> mapToSignal { data -> Signal<CachedMediaResourceRepresentationResult, NoError> in
|
||||||
if !data.complete {
|
if !data.complete {
|
||||||
return .complete()
|
return .complete()
|
||||||
@ -909,7 +897,7 @@ private func fetchEmojiRepresentation(account: Account, resource: MediaResource,
|
|||||||
private func fetchAnimatedStickerFirstFrameRepresentation(account: Account, resource: MediaResource, resourceData: MediaResourceData, representation: CachedAnimatedStickerFirstFrameRepresentation) -> Signal<CachedMediaResourceRepresentationResult, NoError> {
|
private func fetchAnimatedStickerFirstFrameRepresentation(account: Account, resource: MediaResource, resourceData: MediaResourceData, representation: CachedAnimatedStickerFirstFrameRepresentation) -> Signal<CachedMediaResourceRepresentationResult, NoError> {
|
||||||
return Signal({ subscriber in
|
return Signal({ subscriber in
|
||||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) {
|
if let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) {
|
||||||
return fetchCompressedLottieFirstFrameAJpeg(data: data, size: CGSize(width: CGFloat(representation.width), height: CGFloat(representation.height)), cacheKey: "\(resource.id.uniqueId)-\(representation.uniqueId)").start(next: { file in
|
return fetchCompressedLottieFirstFrameAJpeg(data: data, size: CGSize(width: CGFloat(representation.width), height: CGFloat(representation.height)), fitzModifier: representation.fitzModifier, cacheKey: "\(resource.id.uniqueId)-\(representation.uniqueId)").start(next: { file in
|
||||||
subscriber.putNext(.tempFile(file))
|
subscriber.putNext(.tempFile(file))
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
})
|
})
|
||||||
@ -924,7 +912,7 @@ private func fetchAnimatedStickerRepresentation(account: Account, resource: Medi
|
|||||||
return Signal({ subscriber in
|
return Signal({ subscriber in
|
||||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) {
|
if let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) {
|
||||||
if #available(iOS 9.0, *) {
|
if #available(iOS 9.0, *) {
|
||||||
return experimentalConvertCompressedLottieToCombinedMp4(data: data, size: CGSize(width: CGFloat(representation.width), height: CGFloat(representation.height)), cacheKey: "\(resource.id.uniqueId)-\(representation.uniqueId)").start(next: { path in
|
return experimentalConvertCompressedLottieToCombinedMp4(data: data, size: CGSize(width: CGFloat(representation.width), height: CGFloat(representation.height)), fitzModifier: representation.fitzModifier, cacheKey: "\(resource.id.uniqueId)-\(representation.uniqueId)").start(next: { path in
|
||||||
subscriber.putNext(.temporaryPath(path))
|
subscriber.putNext(.temporaryPath(path))
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
})
|
})
|
||||||
|
@ -10,6 +10,12 @@ public func freeMediaFileInteractiveFetched(account: Account, fileReference: Fil
|
|||||||
return fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: fileReference.resourceReference(fileReference.media.resource))
|
return fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: fileReference.resourceReference(fileReference.media.resource))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func freeMediaFileInteractiveFetched(fetchManager: FetchManager, fileReference: FileMediaReference, priority: FetchManagerPriority) -> Signal<Void, NoError> {
|
||||||
|
let file = fileReference.media
|
||||||
|
let mediaReference = AnyMediaReference.standalone(media: fileReference.media)
|
||||||
|
return fetchManager.interactivelyFetched(category: fetchCategoryForFile(file), location: .chat(PeerId(namespace: 0, id: 0)), locationKey: .free, mediaReference: mediaReference, resourceReference: mediaReference.resourceReference(file.resource), ranges: IndexSet(integersIn: 0 ..< Int(Int32.max) as Range<Int>), statsCategory: statsCategoryForFileWithAttributes(file.attributes), elevatedPriority: false, userInitiated: false, priority: priority, storeToDownloadsPeerType: nil)
|
||||||
|
}
|
||||||
|
|
||||||
func freeMediaFileResourceInteractiveFetched(account: Account, fileReference: FileMediaReference, resource: MediaResource) -> Signal<FetchResourceSourceType, FetchResourceError> {
|
func freeMediaFileResourceInteractiveFetched(account: Account, fileReference: FileMediaReference, resource: MediaResource) -> Signal<FetchResourceSourceType, FetchResourceError> {
|
||||||
return fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: fileReference.resourceReference(resource))
|
return fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: fileReference.resourceReference(resource))
|
||||||
}
|
}
|
||||||
|
@ -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,7 @@ public class PeerMediaCollectionController: TelegramBaseController {
|
|||||||
if let strongSelf = self, let messageIds = strongSelf.interfaceState.selectionState?.selectedIds, !messageIds.isEmpty {
|
if let strongSelf = self, let messageIds = strongSelf.interfaceState.selectionState?.selectedIds, !messageIds.isEmpty {
|
||||||
strongSelf.present(peerReportOptionsController(context: strongSelf.context, subject: .messages(Array(messageIds).sorted()), present: { c, a in
|
strongSelf.present(peerReportOptionsController(context: strongSelf.context, subject: .messages(Array(messageIds).sorted()), present: { c, a in
|
||||||
self?.present(c, in: .window(.root), with: a)
|
self?.present(c, in: .window(.root), with: a)
|
||||||
}), in: .window(.root))
|
}, completion: { _ in }), in: .window(.root))
|
||||||
}
|
}
|
||||||
}, reportMessages: { _ in
|
}, reportMessages: { _ in
|
||||||
}, deleteMessages: { _, _, f in
|
}, deleteMessages: { _, _, f in
|
||||||
|
@ -22,7 +22,7 @@ private enum PeerReportOption {
|
|||||||
case other
|
case other
|
||||||
}
|
}
|
||||||
|
|
||||||
func peerReportOptionsController(context: AccountContext, subject: PeerReportSubject, present: @escaping (ViewController, Any?) -> Void) -> ViewController {
|
func peerReportOptionsController(context: AccountContext, subject: PeerReportSubject, present: @escaping (ViewController, Any?) -> Void, completion: @escaping (Bool) -> Void) -> ViewController {
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
let controller = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: presentationData.theme))
|
let controller = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: presentationData.theme))
|
||||||
|
|
||||||
@ -75,15 +75,17 @@ func peerReportOptionsController(context: AccountContext, subject: PeerReportSub
|
|||||||
let _ = (reportPeer(account: context.account, peerId: peerId, reason: reportReason)
|
let _ = (reportPeer(account: context.account, peerId: peerId, reason: reportReason)
|
||||||
|> deliverOnMainQueue).start(completed: {
|
|> deliverOnMainQueue).start(completed: {
|
||||||
present(textAlertController(context: context, title: nil, text: presentationData.strings.ReportPeer_AlertSuccess, actions: [TextAlertAction(type: TextAlertActionType.defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
present(textAlertController(context: context, title: nil, text: presentationData.strings.ReportPeer_AlertSuccess, actions: [TextAlertAction(type: TextAlertActionType.defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||||
|
completion(true)
|
||||||
})
|
})
|
||||||
case let .messages(messageIds):
|
case let .messages(messageIds):
|
||||||
let _ = (reportPeerMessages(account: context.account, messageIds: messageIds, reason: reportReason)
|
let _ = (reportPeerMessages(account: context.account, messageIds: messageIds, reason: reportReason)
|
||||||
|> deliverOnMainQueue).start(completed: {
|
|> deliverOnMainQueue).start(completed: {
|
||||||
present(textAlertController(context: context, title: nil, text: presentationData.strings.ReportPeer_AlertSuccess, actions: [TextAlertAction.init(type: TextAlertActionType.defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
present(textAlertController(context: context, title: nil, text: presentationData.strings.ReportPeer_AlertSuccess, actions: [TextAlertAction.init(type: TextAlertActionType.defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||||
|
completion(true)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
controller?.present(peerReportController(context: context, subject: subject), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
controller?.present(peerReportController(context: context, subject: subject, completion: completion), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
}
|
}
|
||||||
|
|
||||||
controller?.dismissAnimated()
|
controller?.dismissAnimated()
|
||||||
@ -95,6 +97,7 @@ func peerReportOptionsController(context: AccountContext, subject: PeerReportSub
|
|||||||
ActionSheetItemGroup(items: [
|
ActionSheetItemGroup(items: [
|
||||||
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { [weak controller] in
|
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { [weak controller] in
|
||||||
controller?.dismissAnimated()
|
controller?.dismissAnimated()
|
||||||
|
completion(false)
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
])
|
])
|
||||||
@ -187,7 +190,7 @@ private func peerReportControllerEntries(presentationData: PresentationData, sta
|
|||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
|
|
||||||
private func peerReportController(context: AccountContext, subject: PeerReportSubject) -> ViewController {
|
private func peerReportController(context: AccountContext, subject: PeerReportSubject, completion: @escaping (Bool) -> Void) -> ViewController {
|
||||||
var dismissImpl: (() -> Void)?
|
var dismissImpl: (() -> Void)?
|
||||||
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
||||||
|
|
||||||
@ -208,50 +211,52 @@ private func peerReportController(context: AccountContext, subject: PeerReportSu
|
|||||||
let reportDisposable = MetaDisposable()
|
let reportDisposable = MetaDisposable()
|
||||||
|
|
||||||
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get())
|
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get())
|
||||||
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState<PeerReportControllerEntry>, PeerReportControllerEntry.ItemGenerationArguments)) in
|
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState<PeerReportControllerEntry>, PeerReportControllerEntry.ItemGenerationArguments)) in
|
||||||
let rightButton: ItemListNavigationButton
|
let rightButton: ItemListNavigationButton
|
||||||
if state.isReporting {
|
if state.isReporting {
|
||||||
rightButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {})
|
rightButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {})
|
||||||
} else {
|
} else {
|
||||||
rightButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: !state.text.isEmpty, action: {
|
rightButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: !state.text.isEmpty, action: {
|
||||||
var text: String = ""
|
var text: String = ""
|
||||||
updateState { state in
|
updateState { state in
|
||||||
var state = state
|
var state = state
|
||||||
if !state.isReporting && !state.text.isEmpty {
|
if !state.isReporting && !state.text.isEmpty {
|
||||||
text = state.text
|
text = state.text
|
||||||
state.isReporting = true
|
state.isReporting = true
|
||||||
}
|
|
||||||
return state
|
|
||||||
}
|
}
|
||||||
|
return state
|
||||||
if !text.isEmpty {
|
}
|
||||||
let completed: () -> Void = {
|
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
if !text.isEmpty {
|
||||||
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.ReportPeer_AlertSuccess, actions: [TextAlertAction.init(type: TextAlertActionType.defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
let completed: () -> Void = {
|
||||||
dismissImpl?()
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
}
|
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.ReportPeer_AlertSuccess, actions: [TextAlertAction.init(type: TextAlertActionType.defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||||
switch subject {
|
completion(true)
|
||||||
case let .peer(peerId):
|
dismissImpl?()
|
||||||
reportDisposable.set((reportPeer(account: context.account, peerId: peerId, reason: .custom(text))
|
|
||||||
|> deliverOnMainQueue).start(completed: {
|
|
||||||
completed()
|
|
||||||
}))
|
|
||||||
case let .messages(messageIds):
|
|
||||||
reportDisposable.set((reportPeerMessages(account: context.account, messageIds: messageIds, reason: .custom(text))
|
|
||||||
|> deliverOnMainQueue).start(completed: {
|
|
||||||
completed()
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
switch subject {
|
||||||
}
|
case let .peer(peerId):
|
||||||
|
reportDisposable.set((reportPeer(account: context.account, peerId: peerId, reason: .custom(text))
|
||||||
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.ReportPeer_ReasonOther_Title), leftNavigationButton: ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
|
|> deliverOnMainQueue).start(completed: {
|
||||||
dismissImpl?()
|
completed()
|
||||||
}), rightNavigationButton: rightButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
|
}))
|
||||||
let listState = ItemListNodeState(entries: peerReportControllerEntries(presentationData: presentationData, state: state), style: .blocks, focusItemTag: PeerReportControllerEntryTag.text)
|
case let .messages(messageIds):
|
||||||
|
reportDisposable.set((reportPeerMessages(account: context.account, messageIds: messageIds, reason: .custom(text))
|
||||||
return (controllerState, (listState, arguments))
|
|> deliverOnMainQueue).start(completed: {
|
||||||
|
completed()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.ReportPeer_ReasonOther_Title), leftNavigationButton: ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
|
||||||
|
dismissImpl?()
|
||||||
|
completion(false)
|
||||||
|
}), rightNavigationButton: rightButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
|
||||||
|
let listState = ItemListNodeState(entries: peerReportControllerEntries(presentationData: presentationData, state: state), style: .blocks, focusItemTag: PeerReportControllerEntryTag.text)
|
||||||
|
|
||||||
|
return (controllerState, (listState, arguments))
|
||||||
}
|
}
|
||||||
|> afterDisposed {
|
|> afterDisposed {
|
||||||
reportDisposable.dispose()
|
reportDisposable.dispose()
|
||||||
|
@ -12,6 +12,11 @@ private final class PrefetchMediaContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum PrefetchMediaItem {
|
||||||
|
case chatHistory(ChatHistoryPreloadMediaItem)
|
||||||
|
case animatedEmojiSticker(TelegramMediaFile)
|
||||||
|
}
|
||||||
|
|
||||||
private final class PrefetchManagerImpl {
|
private final class PrefetchManagerImpl {
|
||||||
private let queue: Queue
|
private let queue: Queue
|
||||||
private let account: Account
|
private let account: Account
|
||||||
@ -37,7 +42,37 @@ private final class PrefetchManagerImpl {
|
|||||||
}
|
}
|
||||||
|> distinctUntilChanged
|
|> distinctUntilChanged
|
||||||
|
|
||||||
self.listDisposable = (combineLatest(account.viewTracker.orderedPreloadMedia, sharedContext.automaticMediaDownloadSettings, networkType)
|
let orderedPreloadMedia = account.viewTracker.orderedPreloadMedia
|
||||||
|
|> mapToSignal { orderedPreloadMedia in
|
||||||
|
return loadedStickerPack(postbox: account.postbox, network: account.network, reference: .animatedEmoji, forceActualized: false)
|
||||||
|
|> map { result -> [PrefetchMediaItem] in
|
||||||
|
let chatHistoryMediaItems = orderedPreloadMedia.map { PrefetchMediaItem.chatHistory($0) }
|
||||||
|
switch result {
|
||||||
|
case let .result(_, items, _):
|
||||||
|
var animatedEmojiStickers: [String: StickerPackItem] = [:]
|
||||||
|
for case let item as StickerPackItem in items {
|
||||||
|
if let emoji = item.getStringRepresentationsOfIndexKeys().first {
|
||||||
|
animatedEmojiStickers[emoji.basicEmoji.0] = item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var stickerItems: [PrefetchMediaItem] = []
|
||||||
|
let popularEmoji = ["\u{2764}", "👍", "😳", "😒", "🥳"]
|
||||||
|
for emoji in popularEmoji {
|
||||||
|
if let sticker = animatedEmojiStickers[emoji] {
|
||||||
|
if let _ = account.postbox.mediaBox.completedResourcePath(sticker.file.resource) {
|
||||||
|
} else {
|
||||||
|
stickerItems.append(.animatedEmojiSticker(sticker.file))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stickerItems + chatHistoryMediaItems
|
||||||
|
default:
|
||||||
|
return chatHistoryMediaItems
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.listDisposable = (combineLatest(orderedPreloadMedia, sharedContext.automaticMediaDownloadSettings, networkType)
|
||||||
|> deliverOn(self.queue)).start(next: { [weak self] orderedPreloadMedia, automaticDownloadSettings, networkType in
|
|> deliverOn(self.queue)).start(next: { [weak self] orderedPreloadMedia, automaticDownloadSettings, networkType in
|
||||||
self?.updateOrderedPreloadMedia(orderedPreloadMedia, automaticDownloadSettings: automaticDownloadSettings, networkType: networkType)
|
self?.updateOrderedPreloadMedia(orderedPreloadMedia, automaticDownloadSettings: automaticDownloadSettings, networkType: networkType)
|
||||||
})
|
})
|
||||||
@ -48,79 +83,119 @@ private final class PrefetchManagerImpl {
|
|||||||
self.listDisposable?.dispose()
|
self.listDisposable?.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateOrderedPreloadMedia(_ orderedPreloadMedia: [ChatHistoryPreloadMediaItem], automaticDownloadSettings: MediaAutoDownloadSettings, networkType: MediaAutoDownloadNetworkType) {
|
private func updateOrderedPreloadMedia(_ items: [PrefetchMediaItem], automaticDownloadSettings: MediaAutoDownloadSettings, networkType: MediaAutoDownloadNetworkType) {
|
||||||
var validIds = Set<MediaId>()
|
var validIds = Set<MediaId>()
|
||||||
for mediaItem in orderedPreloadMedia {
|
var order: Int32 = 0
|
||||||
guard let id = mediaItem.media.media.id else {
|
for mediaItem in items {
|
||||||
continue
|
switch mediaItem {
|
||||||
}
|
case let .chatHistory(mediaItem):
|
||||||
if validIds.contains(id) {
|
guard let id = mediaItem.media.media.id else {
|
||||||
continue
|
continue
|
||||||
}
|
|
||||||
|
|
||||||
var automaticDownload: InteractiveMediaNodeAutodownloadMode = .none
|
|
||||||
let peerType: MediaAutoDownloadPeerType
|
|
||||||
if mediaItem.media.authorIsContact {
|
|
||||||
peerType = .contact
|
|
||||||
} else if let channel = mediaItem.media.peer as? TelegramChannel {
|
|
||||||
if case .group = channel.info {
|
|
||||||
peerType = .group
|
|
||||||
} else {
|
|
||||||
peerType = .channel
|
|
||||||
}
|
|
||||||
} else if mediaItem.media.peer is TelegramGroup {
|
|
||||||
peerType = .group
|
|
||||||
} else {
|
|
||||||
peerType = .otherPrivate
|
|
||||||
}
|
|
||||||
var mediaResource: MediaResource?
|
|
||||||
|
|
||||||
if let telegramImage = mediaItem.media.media as? TelegramMediaImage {
|
|
||||||
mediaResource = largestRepresentationForPhoto(telegramImage)?.resource
|
|
||||||
if shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, authorPeerId: nil, contactsPeerIds: [], media: telegramImage) {
|
|
||||||
automaticDownload = .full
|
|
||||||
}
|
|
||||||
} else if let telegramFile = mediaItem.media.media as? TelegramMediaFile {
|
|
||||||
mediaResource = telegramFile.resource
|
|
||||||
if shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, authorPeerId: nil, contactsPeerIds: [], media: telegramFile) {
|
|
||||||
automaticDownload = .full
|
|
||||||
} else if shouldPredownloadMedia(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, media: telegramFile) {
|
|
||||||
automaticDownload = .prefetch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if case .none = automaticDownload {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
guard let resource = mediaResource else {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
validIds.insert(id)
|
|
||||||
let context: PrefetchMediaContext
|
|
||||||
if let current = self.contexts[id] {
|
|
||||||
context = current
|
|
||||||
} else {
|
|
||||||
context = PrefetchMediaContext()
|
|
||||||
self.contexts[id] = context
|
|
||||||
|
|
||||||
let media = mediaItem.media.media
|
|
||||||
|
|
||||||
let priority: FetchManagerPriority = .backgroundPrefetch(locationOrder: mediaItem.preloadIndex, localOrder: mediaItem.media.index)
|
|
||||||
|
|
||||||
if case .full = automaticDownload {
|
|
||||||
if let image = media as? TelegramMediaImage {
|
|
||||||
context.fetchDisposable.set(messageMediaImageInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), image: image, resource: resource, userInitiated: false, priority: priority, storeToDownloadsPeerType: nil).start())
|
|
||||||
} else if let _ = media as? TelegramMediaWebFile {
|
|
||||||
//strongSelf.fetchDisposable.set(chatMessageWebFileInteractiveFetched(account: context.account, image: image).start())
|
|
||||||
} else if let file = media as? TelegramMediaFile {
|
|
||||||
let fetchSignal = messageMediaFileInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), file: file, userInitiated: false, priority: priority)
|
|
||||||
context.fetchDisposable.set(fetchSignal.start())
|
|
||||||
}
|
}
|
||||||
} else if case .prefetch = automaticDownload, mediaItem.media.peer.id.namespace != Namespaces.Peer.SecretChat {
|
if validIds.contains(id) {
|
||||||
if let file = media as? TelegramMediaFile, let _ = file.size {
|
continue
|
||||||
context.fetchDisposable.set(preloadVideoResource(postbox: self.account.postbox, resourceReference: FileMediaReference.message(message: MessageReference(peer: mediaItem.media.peer, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), media: file).resourceReference(file.resource), duration: 4.0).start())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var automaticDownload: InteractiveMediaNodeAutodownloadMode = .none
|
||||||
|
let peerType: MediaAutoDownloadPeerType
|
||||||
|
if mediaItem.media.authorIsContact {
|
||||||
|
peerType = .contact
|
||||||
|
} else if let channel = mediaItem.media.peer as? TelegramChannel {
|
||||||
|
if case .group = channel.info {
|
||||||
|
peerType = .group
|
||||||
|
} else {
|
||||||
|
peerType = .channel
|
||||||
|
}
|
||||||
|
} else if mediaItem.media.peer is TelegramGroup {
|
||||||
|
peerType = .group
|
||||||
|
} else {
|
||||||
|
peerType = .otherPrivate
|
||||||
|
}
|
||||||
|
var mediaResource: MediaResource?
|
||||||
|
|
||||||
|
if let telegramImage = mediaItem.media.media as? TelegramMediaImage {
|
||||||
|
mediaResource = largestRepresentationForPhoto(telegramImage)?.resource
|
||||||
|
if shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, authorPeerId: nil, contactsPeerIds: [], media: telegramImage) {
|
||||||
|
automaticDownload = .full
|
||||||
|
}
|
||||||
|
} else if let telegramFile = mediaItem.media.media as? TelegramMediaFile {
|
||||||
|
mediaResource = telegramFile.resource
|
||||||
|
if shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, authorPeerId: nil, contactsPeerIds: [], media: telegramFile) {
|
||||||
|
automaticDownload = .full
|
||||||
|
} else if shouldPredownloadMedia(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, media: telegramFile) {
|
||||||
|
automaticDownload = .prefetch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if case .none = automaticDownload {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
guard let resource = mediaResource else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
validIds.insert(id)
|
||||||
|
let context: PrefetchMediaContext
|
||||||
|
if let current = self.contexts[id] {
|
||||||
|
context = current
|
||||||
|
} else {
|
||||||
|
context = PrefetchMediaContext()
|
||||||
|
self.contexts[id] = context
|
||||||
|
|
||||||
|
let media = mediaItem.media.media
|
||||||
|
|
||||||
|
let priority: FetchManagerPriority = .backgroundPrefetch(locationOrder: mediaItem.preloadIndex, localOrder: mediaItem.media.index)
|
||||||
|
|
||||||
|
if case .full = automaticDownload {
|
||||||
|
if let image = media as? TelegramMediaImage {
|
||||||
|
context.fetchDisposable.set(messageMediaImageInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), image: image, resource: resource, userInitiated: false, priority: priority, storeToDownloadsPeerType: nil).start())
|
||||||
|
} else if let _ = media as? TelegramMediaWebFile {
|
||||||
|
//strongSelf.fetchDisposable.set(chatMessageWebFileInteractiveFetched(account: context.account, image: image).start())
|
||||||
|
} else if let file = media as? TelegramMediaFile {
|
||||||
|
let fetchSignal = messageMediaFileInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), file: file, userInitiated: false, priority: priority)
|
||||||
|
context.fetchDisposable.set(fetchSignal.start())
|
||||||
|
}
|
||||||
|
} else if case .prefetch = automaticDownload, mediaItem.media.peer.id.namespace != Namespaces.Peer.SecretChat {
|
||||||
|
if let file = media as? TelegramMediaFile, let _ = file.size {
|
||||||
|
context.fetchDisposable.set(preloadVideoResource(postbox: self.account.postbox, resourceReference: FileMediaReference.message(message: MessageReference(peer: mediaItem.media.peer, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), media: file).resourceReference(file.resource), duration: 4.0).start())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case let .animatedEmojiSticker(media):
|
||||||
|
guard let id = media.id else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if validIds.contains(id) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var automaticDownload: InteractiveMediaNodeAutodownloadMode = .none
|
||||||
|
let peerType = MediaAutoDownloadPeerType.contact
|
||||||
|
|
||||||
|
if shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, authorPeerId: nil, contactsPeerIds: [], media: media) {
|
||||||
|
automaticDownload = .full
|
||||||
|
}
|
||||||
|
|
||||||
|
if case .none = automaticDownload {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
validIds.insert(id)
|
||||||
|
let context: PrefetchMediaContext
|
||||||
|
if let current = self.contexts[id] {
|
||||||
|
context = current
|
||||||
|
} else {
|
||||||
|
context = PrefetchMediaContext()
|
||||||
|
self.contexts[id] = context
|
||||||
|
|
||||||
|
let priority: FetchManagerPriority = .backgroundPrefetch(locationOrder: HistoryPreloadIndex(index: nil, hasUnread: false, isMuted: false, isPriority: true), localOrder: MessageIndex(id: MessageId(peerId: PeerId(namespace: 0, id: 0), namespace: 0, id: order), timestamp: 0))
|
||||||
|
|
||||||
|
if case .full = automaticDownload {
|
||||||
|
let fetchSignal = freeMediaFileInteractiveFetched(fetchManager: self.fetchManager, fileReference: .standalone(media: media), priority: priority)
|
||||||
|
context.fetchDisposable.set(fetchSignal.start())
|
||||||
|
}
|
||||||
|
|
||||||
|
order += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -271,8 +271,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
|
|||||||
|
|
||||||
let query = text.trimmingCharacters(in: .whitespacesAndNewlines)
|
let query = text.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
if query.isSingleEmoji {
|
if query.isSingleEmoji {
|
||||||
signals = .single([searchStickers(account: account, query: text.trimmedEmoji)
|
signals = .single([searchStickers(account: account, query: text.basicEmoji.0)
|
||||||
//|> take(1)
|
|
||||||
|> map { (nil, $0) }])
|
|> map { (nil, $0) }])
|
||||||
} else if query.count > 1, let languageCode = languageCode, !languageCode.isEmpty && languageCode != "emoji" {
|
} else if query.count > 1, let languageCode = languageCode, !languageCode.isEmpty && languageCode != "emoji" {
|
||||||
var signal = searchEmojiKeywords(postbox: account.postbox, inputLanguageCode: languageCode, query: query.lowercased(), completeMatch: query.count < 3)
|
var signal = searchEmojiKeywords(postbox: account.postbox, inputLanguageCode: languageCode, query: query.lowercased(), completeMatch: query.count < 3)
|
||||||
@ -294,7 +293,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
|
|||||||
var signals: [Signal<(String?, [FoundStickerItem]), NoError>] = []
|
var signals: [Signal<(String?, [FoundStickerItem]), NoError>] = []
|
||||||
let emoticons = keywords.flatMap { $0.emoticons }
|
let emoticons = keywords.flatMap { $0.emoticons }
|
||||||
for emoji in emoticons {
|
for emoji in emoticons {
|
||||||
signals.append(searchStickers(account: self.context.account, query: emoji.trimmedEmoji)
|
signals.append(searchStickers(account: self.context.account, query: emoji.basicEmoji.0)
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> map { (emoji, $0) })
|
|> map { (emoji, $0) })
|
||||||
}
|
}
|
||||||
|
@ -1164,7 +1164,7 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Us
|
|||||||
}, report: {
|
}, report: {
|
||||||
presentControllerImpl?(peerReportOptionsController(context: context, subject: .peer(peerId), present: { c, a in
|
presentControllerImpl?(peerReportOptionsController(context: context, subject: .peer(peerId), present: { c, a in
|
||||||
presentControllerImpl?(c, a)
|
presentControllerImpl?(c, a)
|
||||||
}), nil)
|
}, completion: { _ in }), nil)
|
||||||
})
|
})
|
||||||
|
|
||||||
let deviceContacts: Signal<[(DeviceContactStableId, DeviceContactBasicData)], NoError> = peerView.get()
|
let deviceContacts: Signal<[(DeviceContactStableId, DeviceContactBasicData)], NoError> = peerView.get()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user