mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-07 01:10:09 +00:00
Merge branch 'postbox-mac'
This commit is contained in:
commit
ab26601177
@ -56,8 +56,7 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
|||||||
override init() {
|
override init() {
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
let appBundleIdentifier = Bundle.main.bundleIdentifier!
|
guard let appBundleIdentifier = Bundle.main.bundleIdentifier, let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else {
|
||||||
guard let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,30 +97,31 @@ 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)
|
account.shouldKeepOnlinePresence.set(.single(false))
|
||||||
|> deliverOnMainQueue
|
return applicationSettings(accountManager: accountManager)
|
||||||
|> map { settings -> Account in
|
|> deliverOnMainQueue
|
||||||
accountCache = account
|
|> map { settings -> Account in
|
||||||
Logger.shared.logToFile = settings.logging.logToFile
|
accountCache = account
|
||||||
Logger.shared.logToConsole = settings.logging.logToConsole
|
Logger.shared.logToFile = settings.logging.logToFile
|
||||||
|
Logger.shared.logToConsole = settings.logging.logToConsole
|
||||||
|
|
||||||
Logger.shared.redactSensitiveData = settings.logging.redactSensitiveData
|
Logger.shared.redactSensitiveData = settings.logging.redactSensitiveData
|
||||||
return account
|
return account
|
||||||
}
|
|
||||||
case .unauthorized:
|
|
||||||
return .complete()
|
|
||||||
}
|
}
|
||||||
} else {
|
case .unauthorized:
|
||||||
return .single(nil)
|
return .complete()
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return .single(nil)
|
||||||
}
|
}
|
||||||
|> take(1)
|
}
|
||||||
|
|> take(1)
|
||||||
}
|
}
|
||||||
self.accountPromise.set(account)
|
self.accountPromise.set(account)
|
||||||
}
|
}
|
||||||
@ -145,36 +145,37 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
|||||||
@available(iOSApplicationExtension 11.0, *)
|
@available(iOSApplicationExtension 11.0, *)
|
||||||
var sendMessageRecipientResulutionResult: INSendMessageRecipientResolutionResult {
|
var sendMessageRecipientResulutionResult: INSendMessageRecipientResolutionResult {
|
||||||
switch self {
|
switch self {
|
||||||
case let .success(person):
|
case let .success(person):
|
||||||
return .success(with: person)
|
return .success(with: person)
|
||||||
case let .disambiguation(persons):
|
case let .disambiguation(persons):
|
||||||
return .disambiguation(with: persons)
|
return .disambiguation(with: persons)
|
||||||
case .needsValue:
|
case .needsValue:
|
||||||
return .needsValue()
|
return .needsValue()
|
||||||
case .noResult:
|
case .noResult:
|
||||||
return .unsupported()
|
return .unsupported()
|
||||||
case .skip:
|
case .skip:
|
||||||
return .notRequired()
|
return .notRequired()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var personResolutionResult: INPersonResolutionResult {
|
var personResolutionResult: INPersonResolutionResult {
|
||||||
switch self {
|
switch self {
|
||||||
case let .success(person):
|
case let .success(person):
|
||||||
return .success(with: person)
|
return .success(with: person)
|
||||||
case let .disambiguation(persons):
|
case let .disambiguation(persons):
|
||||||
return .disambiguation(with: persons)
|
return .disambiguation(with: persons)
|
||||||
case .needsValue:
|
case .needsValue:
|
||||||
return .needsValue()
|
return .needsValue()
|
||||||
case .noResult:
|
case .noResult:
|
||||||
return .unsupported()
|
return .unsupported()
|
||||||
case .skip:
|
case .skip:
|
||||||
return .notRequired()
|
return .notRequired()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func resolve(persons: [INPerson]?, with completion: @escaping ([ResolveResult]) -> Void) {
|
private func resolve(persons: [INPerson]?, with completion: @escaping ([ResolveResult]) -> Void) {
|
||||||
|
let account = self.accountPromise.get()
|
||||||
guard let initialPersons = persons, !initialPersons.isEmpty else {
|
guard let initialPersons = persons, !initialPersons.isEmpty else {
|
||||||
completion([.needsValue])
|
completion([.needsValue])
|
||||||
return
|
return
|
||||||
@ -231,20 +232,18 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
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)
|
|> introduceError(IntentContactsError.self)
|
||||||
|> mapToSignal { account -> Signal<[(String, TelegramUser)], IntentContactsError> in
|
|> mapToSignal { account -> Signal<[(String, TelegramUser)], IntentContactsError> in
|
||||||
if let account = account {
|
if let account = account {
|
||||||
return matchingCloudContacts(postbox: account.postbox, contacts: matchedContacts)
|
return matchingCloudContacts(postbox: account.postbox, contacts: matchedContacts)
|
||||||
|> introduceError(IntentContactsError.self)
|
|> introduceError(IntentContactsError.self)
|
||||||
} else {
|
} else {
|
||||||
return .fail(.generic)
|
return .fail(.generic)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.resolvePersonsDisposable.set((signal
|
self.resolvePersonsDisposable.set((signal
|
||||||
@ -276,36 +275,34 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
|||||||
@available(iOSApplicationExtension 11.0, *)
|
@available(iOSApplicationExtension 11.0, *)
|
||||||
func resolveRecipients(for intent: INSendMessageIntent, with completion: @escaping ([INSendMessageRecipientResolutionResult]) -> Void) {
|
func resolveRecipients(for intent: INSendMessageIntent, with completion: @escaping ([INSendMessageRecipientResolutionResult]) -> Void) {
|
||||||
if let peerId = intent.conversationIdentifier.flatMap(Int64.init) {
|
if let peerId = intent.conversationIdentifier.flatMap(Int64.init) {
|
||||||
let account = self.accountPromise.get()
|
let signal = self.accountPromise.get()
|
||||||
|
|> introduceError(IntentHandlingError.self)
|
||||||
let signal = account
|
|> mapToSignal { account -> Signal<INPerson?, IntentHandlingError> in
|
||||||
|> introduceError(IntentHandlingError.self)
|
if let account = account {
|
||||||
|> mapToSignal { account -> Signal<INPerson?, IntentHandlingError> in
|
return matchingCloudContact(postbox: account.postbox, peerId: PeerId(peerId))
|
||||||
if let account = account {
|
|> introduceError(IntentHandlingError.self)
|
||||||
return matchingCloudContact(postbox: account.postbox, peerId: PeerId(peerId))
|
|> map { user -> INPerson? in
|
||||||
|> introduceError(IntentHandlingError.self)
|
if let user = user {
|
||||||
|> map { user -> INPerson? in
|
return personWithUser(stableId: "tg\(peerId)", user: user)
|
||||||
if let user = user {
|
} else {
|
||||||
return personWithUser(stableId: "tg\(peerId)", user: user)
|
return nil
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return .fail(.generic)
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
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([INSendMessageRecipientResolutionResult.success(with: person)])
|
completion([INSendMessageRecipientResolutionResult.success(with: person)])
|
||||||
} else {
|
} else {
|
||||||
completion([INSendMessageRecipientResolutionResult.needsValue()])
|
completion([INSendMessageRecipientResolutionResult.needsValue()])
|
||||||
}
|
}
|
||||||
}, error: { error in
|
}, error: { error in
|
||||||
completion([INSendMessageRecipientResolutionResult.unsupported(forReason: .noAccount)])
|
completion([INSendMessageRecipientResolutionResult.unsupported(forReason: .noAccount)])
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
guard CNContactStore.authorizationStatus(for: .contacts) == .authorized else {
|
guard CNContactStore.authorizationStatus(for: .contacts) == .authorized else {
|
||||||
completion([INSendMessageRecipientResolutionResult.notRequired()])
|
completion([INSendMessageRecipientResolutionResult.notRequired()])
|
||||||
@ -342,48 +339,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 { account -> Signal<Void, IntentHandlingError> in
|
|> mapToSignal { _ -> Signal<Void, IntentHandlingError> in
|
||||||
guard let account = account else {
|
return .complete()
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|> deliverOnMainQueue).start(error: { _ in
|
|> afterDisposed {
|
||||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self))
|
account.shouldBeServiceTaskMaster.set(.single(.never))
|
||||||
let response = INSendMessageIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity)
|
}
|
||||||
completion(response)
|
}
|
||||||
}, completed: {
|
|> deliverOnMainQueue).start(error: { _ in
|
||||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self))
|
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self))
|
||||||
let response = INSendMessageIntentResponse(code: .success, userActivity: userActivity)
|
let response = INSendMessageIntentResponse(code: .failureRequiringAppLaunch, 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
|
||||||
@ -394,42 +391,48 @@ 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)
|
|> 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)
|
|> introduceError(IntentHandlingError.self)
|
||||||
|> mapToSignal { account -> Signal<[INMessage], IntentHandlingError> in
|
|> take(1)
|
||||||
guard let account = account else {
|
|> mapToSignal { _ -> Signal<[INMessage], IntentHandlingError> in
|
||||||
return .fail(.generic)
|
let messages: Signal<[INMessage], NoError>
|
||||||
|
if let identifiers = intent.identifiers, !identifiers.isEmpty {
|
||||||
|
messages = getMessages(account: account, ids: identifiers.compactMap(MessageId.init(string:)))
|
||||||
|
} else {
|
||||||
|
messages = unreadMessages(account: account)
|
||||||
}
|
}
|
||||||
|
return messages
|
||||||
account.shouldBeServiceTaskMaster.set(.single(.now))
|
|> introduceError(IntentHandlingError.self)
|
||||||
account.resetStateManagement()
|
|> afterDisposed {
|
||||||
|
account.shouldBeServiceTaskMaster.set(.single(.never))
|
||||||
let completion: Signal<Void, NoError> = account.stateManager.pollStateUpdateCompletion()
|
|
||||||
|> map { _ in
|
|
||||||
return Void()
|
|
||||||
}
|
|
||||||
|
|
||||||
return (completion |> timeout(4.0, queue: Queue.mainQueue(), alternate: .single(Void())))
|
|
||||||
|> introduceError(IntentHandlingError.self)
|
|
||||||
|> take(1)
|
|
||||||
|> mapToSignal { _ -> Signal<[INMessage], IntentHandlingError> in
|
|
||||||
return unreadMessages(account: account)
|
|
||||||
|> introduceError(IntentHandlingError.self)
|
|
||||||
|> afterDisposed {
|
|
||||||
account.shouldBeServiceTaskMaster.set(.single(.never))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|> deliverOnMainQueue).start(next: { messages in
|
}
|
||||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchForMessagesIntent.self))
|
|> deliverOnMainQueue).start(next: { messages in
|
||||||
let response = INSearchForMessagesIntentResponse(code: .success, userActivity: userActivity)
|
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchForMessagesIntent.self))
|
||||||
response.messages = messages
|
let response = INSearchForMessagesIntentResponse(code: .success, userActivity: userActivity)
|
||||||
completion(response)
|
response.messages = messages
|
||||||
}, error: { _ in
|
completion(response)
|
||||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchForMessagesIntent.self))
|
}, error: { _ in
|
||||||
let response = INSearchForMessagesIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity)
|
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchForMessagesIntent.self))
|
||||||
completion(response)
|
let response = INSearchForMessagesIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity)
|
||||||
}))
|
completion(response)
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - INSetMessageAttributeIntentHandling
|
// MARK: - INSetMessageAttributeIntentHandling
|
||||||
@ -449,61 +452,61 @@ 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
|
|
||||||
guard let account = account else {
|
|
||||||
return .fail(.generic)
|
|
||||||
}
|
|
||||||
|
|
||||||
var signals: [Signal<Void, IntentHandlingError>] = []
|
var signals: [Signal<Void, IntentHandlingError>] = []
|
||||||
var maxMessageIdsToApply: [PeerId: MessageId] = [:]
|
var maxMessageIdsToApply: [PeerId: MessageId] = [:]
|
||||||
if let identifiers = intent.identifiers {
|
if let identifiers = intent.identifiers {
|
||||||
for identifier in identifiers {
|
for identifier in identifiers {
|
||||||
let components = identifier.components(separatedBy: "_")
|
let components = identifier.components(separatedBy: "_")
|
||||||
if let first = components.first, let peerId = Int64(first), let namespace = Int32(components[1]), let id = Int32(components[2]) {
|
if let first = components.first, let peerId = Int64(first), let namespace = Int32(components[1]), let id = Int32(components[2]) {
|
||||||
let peerId = PeerId(peerId)
|
let peerId = PeerId(peerId)
|
||||||
let messageId = MessageId(peerId: peerId, namespace: namespace, id: id)
|
let messageId = MessageId(peerId: peerId, namespace: namespace, id: id)
|
||||||
if let currentMessageId = maxMessageIdsToApply[peerId] {
|
if let currentMessageId = maxMessageIdsToApply[peerId] {
|
||||||
if currentMessageId < messageId {
|
if currentMessageId < messageId {
|
||||||
maxMessageIdsToApply[peerId] = messageId
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
maxMessageIdsToApply[peerId] = messageId
|
maxMessageIdsToApply[peerId] = messageId
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
maxMessageIdsToApply[peerId] = messageId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (_, messageId) in maxMessageIdsToApply {
|
|
||||||
signals.append(applyMaxReadIndexInteractively(postbox: account.postbox, stateManager: account.stateManager, index: MessageIndex(id: messageId, timestamp: 0))
|
|
||||||
|> introduceError(IntentHandlingError.self))
|
|
||||||
}
|
|
||||||
|
|
||||||
if signals.isEmpty {
|
|
||||||
return .complete()
|
|
||||||
} else {
|
|
||||||
account.shouldBeServiceTaskMaster.set(.single(.now))
|
|
||||||
return combineLatest(signals)
|
|
||||||
|> mapToSignal { _ -> Signal<Void, IntentHandlingError> in
|
|
||||||
return .complete()
|
|
||||||
}
|
|
||||||
|> afterDisposed {
|
|
||||||
account.shouldBeServiceTaskMaster.set(.single(.never))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|> deliverOnMainQueue).start(error: { _ in
|
|
||||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSetMessageAttributeIntent.self))
|
for (_, messageId) in maxMessageIdsToApply {
|
||||||
let response = INSetMessageAttributeIntentResponse(code: .failure, userActivity: userActivity)
|
signals.append(applyMaxReadIndexInteractively(postbox: account.postbox, stateManager: account.stateManager, index: MessageIndex(id: messageId, timestamp: 0))
|
||||||
completion(response)
|
|> introduceError(IntentHandlingError.self))
|
||||||
}, completed: {
|
}
|
||||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSetMessageAttributeIntent.self))
|
|
||||||
let response = INSetMessageAttributeIntentResponse(code: .success, userActivity: userActivity)
|
if signals.isEmpty {
|
||||||
completion(response)
|
return .complete()
|
||||||
}))
|
} else {
|
||||||
|
account.shouldBeServiceTaskMaster.set(.single(.now))
|
||||||
|
return combineLatest(signals)
|
||||||
|
|> mapToSignal { _ -> Signal<Void, IntentHandlingError> in
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
|> afterDisposed {
|
||||||
|
account.shouldBeServiceTaskMaster.set(.single(.never))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> deliverOnMainQueue).start(error: { _ in
|
||||||
|
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSetMessageAttributeIntent.self))
|
||||||
|
let response = INSetMessageAttributeIntentResponse(code: .failure, userActivity: userActivity)
|
||||||
|
completion(response)
|
||||||
|
}, completed: {
|
||||||
|
let userActivity = NSUserActivity(activityType: NSStringFromClass(INSetMessageAttributeIntent.self))
|
||||||
|
let response = INSetMessageAttributeIntentResponse(code: .success, userActivity: userActivity)
|
||||||
|
completion(response)
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - INStartAudioCallIntentHandling
|
// MARK: - INStartAudioCallIntentHandling
|
||||||
@ -518,38 +521,43 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension 11.0, *)
|
||||||
|
func resolveDestinationType(for intent: INStartAudioCallIntent, with completion: @escaping (INCallDestinationTypeResolutionResult) -> Void) {
|
||||||
|
completion(.success(with: .normal))
|
||||||
|
}
|
||||||
|
|
||||||
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 contact = intent.contacts?.first, let customIdentifier = contact.customIdentifier, customIdentifier.hasPrefix("tg") else {
|
|
||||||
return .fail(.generic)
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let peerIdValue = Int64(String(customIdentifier[customIdentifier.index(customIdentifier.startIndex, offsetBy: 2)...])) else {
|
guard let peerIdValue = Int64(String(customIdentifier[customIdentifier.index(customIdentifier.startIndex, offsetBy: 2)...])) else {
|
||||||
return .fail(.generic)
|
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 userActivity = NSUserActivity(activityType: NSStringFromClass(INStartAudioCallIntent.self))
|
let peerId = PeerId(peerIdValue)
|
||||||
userActivity.userInfo = ["handle": "TGCA\(peerId.toInt64())"]
|
if peerId.namespace != Namespaces.Peer.CloudUser {
|
||||||
let response = INStartAudioCallIntentResponse(code: .continueInApp, userActivity: userActivity)
|
return .fail(.generic)
|
||||||
completion(response)
|
}
|
||||||
}, error: { _ in
|
|
||||||
let userActivity = NSUserActivity(activityType: NSStringFromClass(INStartAudioCallIntent.self))
|
return .single(peerId)
|
||||||
let response = INStartAudioCallIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity)
|
}
|
||||||
completion(response)
|
|> deliverOnMainQueue).start(next: { peerId in
|
||||||
}))
|
let userActivity = NSUserActivity(activityType: NSStringFromClass(INStartAudioCallIntent.self))
|
||||||
|
userActivity.userInfo = ["handle": "TGCA\(peerId.toInt64())"]
|
||||||
|
let response = INStartAudioCallIntentResponse(code: .continueInApp, userActivity: userActivity)
|
||||||
|
completion(response)
|
||||||
|
}, error: { _ in
|
||||||
|
let userActivity = NSUserActivity(activityType: NSStringFromClass(INStartAudioCallIntent.self))
|
||||||
|
let response = INStartAudioCallIntentResponse(code: .failureRequiringAppLaunch, userActivity: userActivity)
|
||||||
|
completion(response)
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - INSearchCallHistoryIntentHandling
|
// MARK: - INSearchCallHistoryIntentHandling
|
||||||
|
|||||||
@ -1,11 +1,33 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Display
|
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import Postbox
|
import Postbox
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
import Contacts
|
import Contacts
|
||||||
import Intents
|
import Intents
|
||||||
|
|
||||||
|
extension MessageId {
|
||||||
|
init?(string: String) {
|
||||||
|
let components = string.components(separatedBy: "_")
|
||||||
|
if components.count == 3, let peerIdValue = Int64(components[0]), let namespaceValue = Int32(components[1]), let idValue = Int32(components[2]) {
|
||||||
|
self.init(peerId: PeerId(peerIdValue), namespace: namespaceValue, id: idValue)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMessages(account: Account, ids: [MessageId]) -> Signal<[INMessage], NoError> {
|
||||||
|
return account.postbox.transaction { transaction -> [INMessage] in
|
||||||
|
var messages: [INMessage] = []
|
||||||
|
for id in ids {
|
||||||
|
if let message = transaction.getMessage(id).flatMap(messageWithTelegramMessage) {
|
||||||
|
messages.append(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return messages.sorted { $0.dateSent!.compare($1.dateSent!) == .orderedDescending }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func unreadMessages(account: Account) -> Signal<[INMessage], NoError> {
|
func unreadMessages(account: Account) -> Signal<[INMessage], NoError> {
|
||||||
return account.postbox.tailChatListView(groupId: .root, count: 20, summaryComponents: ChatListEntrySummaryComponents())
|
return account.postbox.tailChatListView(groupId: .root, count: 20, summaryComponents: ChatListEntrySummaryComponents())
|
||||||
|> take(1)
|
|> take(1)
|
||||||
@ -31,7 +53,7 @@ func unreadMessages(account: Account) -> Signal<[INMessage], NoError> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !isMuted && hasUnread {
|
if !isMuted && hasUnread {
|
||||||
signals.append(account.postbox.aroundMessageHistoryViewForLocation(.peer(index.messageIndex.id.peerId), anchor: .upperBound, count: 10, fixedCombinedReadStates: fixedCombinedReadStates, topTaggedMessageIdNamespaces: Set(), tagMask: nil, excludeNamespaces: [Namespaces.Message.ScheduledCloud, Namespaces.Message.ScheduledLocal], orderStatistics: .combinedLocation)
|
signals.append(account.postbox.aroundMessageHistoryViewForLocation(.peer(index.messageIndex.id.peerId), anchor: .upperBound, count: 10, fixedCombinedReadStates: fixedCombinedReadStates, topTaggedMessageIdNamespaces: Set(), tagMask: nil, namespaces: .not([Namespaces.Message.ScheduledCloud, Namespaces.Message.ScheduledLocal]), orderStatistics: .combinedLocation)
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> map { view -> [INMessage] in
|
|> map { view -> [INMessage] in
|
||||||
var messages: [INMessage] = []
|
var messages: [INMessage] = []
|
||||||
@ -42,7 +64,7 @@ func unreadMessages(account: Account) -> Signal<[INMessage], NoError> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !isRead {
|
if !isRead {
|
||||||
if let message = messageWithTelegramMessage(entry.message, account: account) {
|
if let message = messageWithTelegramMessage(entry.message) {
|
||||||
messages.append(message)
|
messages.append(message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -58,7 +80,7 @@ func unreadMessages(account: Account) -> Signal<[INMessage], NoError> {
|
|||||||
} else {
|
} else {
|
||||||
return combineLatest(signals)
|
return combineLatest(signals)
|
||||||
|> map { results -> [INMessage] in
|
|> map { results -> [INMessage] in
|
||||||
return results.flatMap { $0 }.sorted(by: { $0.dateSent!.compare($1.dateSent!) == ComparisonResult.orderedDescending })
|
return results.flatMap { $0 }.sorted { $0.dateSent!.compare($1.dateSent!) == .orderedDescending }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -94,7 +116,7 @@ func missedCalls(account: Account) -> Signal<[CallRecord], NoError> {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return calls.sorted(by: { $0.date.compare($1.date) == ComparisonResult.orderedDescending })
|
return calls.sorted { $0.date.compare($1.date) == .orderedDescending }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,7 +158,7 @@ private func callWithTelegramMessage(_ telegramMessage: Message, account: Accoun
|
|||||||
return CallRecord(identifier: identifier, date: date, caller: caller, duration: duration, unseen: true)
|
return CallRecord(identifier: identifier, date: date, caller: caller, duration: duration, unseen: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func messageWithTelegramMessage(_ telegramMessage: Message, account: Account) -> INMessage? {
|
private func messageWithTelegramMessage(_ telegramMessage: Message) -> INMessage? {
|
||||||
guard let author = telegramMessage.author, let user = telegramMessage.peers[author.id] as? TelegramUser, user.id.id != 777000 else {
|
guard let author = telegramMessage.author, let user = telegramMessage.peers[author.id] as? TelegramUser, user.id.id != 777000 else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -181,7 +203,7 @@ private func messageWithTelegramMessage(_ telegramMessage: Message, account: Acc
|
|||||||
messageType = .mediaAudio
|
messageType = .mediaAudio
|
||||||
break loop
|
break loop
|
||||||
} else if file.isVoice {
|
} else if file.isVoice {
|
||||||
messageType = .audio
|
messageType = .mediaAudio
|
||||||
break loop
|
break loop
|
||||||
} else if file.isSticker || file.isAnimatedSticker {
|
} else if file.isSticker || file.isAnimatedSticker {
|
||||||
messageType = .sticker
|
messageType = .sticker
|
||||||
@ -189,6 +211,9 @@ private func messageWithTelegramMessage(_ telegramMessage: Message, account: Acc
|
|||||||
} else if file.isAnimated {
|
} else if file.isAnimated {
|
||||||
messageType = .mediaVideo
|
messageType = .mediaVideo
|
||||||
break loop
|
break loop
|
||||||
|
} else if #available(iOSApplicationExtension 12.0, *) {
|
||||||
|
messageType = .file
|
||||||
|
break loop
|
||||||
}
|
}
|
||||||
} else if media is TelegramMediaMap {
|
} else if media is TelegramMediaMap {
|
||||||
messageType = .mediaLocation
|
messageType = .mediaLocation
|
||||||
|
|||||||
@ -33,7 +33,7 @@
|
|||||||
<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-messaging</key>
|
||||||
<key>com.apple.developer.carplay-calling</key><true/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@ -4610,6 +4610,7 @@ Any member of this group will be able to see messages in the channel.";
|
|||||||
"ScheduledMessages.EditTime" = "Reschedule";
|
"ScheduledMessages.EditTime" = "Reschedule";
|
||||||
"ScheduledMessages.ClearAll" = "Clear All";
|
"ScheduledMessages.ClearAll" = "Clear All";
|
||||||
"ScheduledMessages.Delete" = "Delete";
|
"ScheduledMessages.Delete" = "Delete";
|
||||||
|
"ScheduledMessages.EmptyPlaceholder" = "No scheduled messages here yet...";
|
||||||
|
|
||||||
"Conversation.SendMessage.SetReminder" = "Set a Reminder";
|
"Conversation.SendMessage.SetReminder" = "Set a Reminder";
|
||||||
|
|
||||||
|
|||||||
@ -368,16 +368,13 @@ public class GalleryController: ViewController {
|
|||||||
switch source {
|
switch source {
|
||||||
case .peerMessagesAtId:
|
case .peerMessagesAtId:
|
||||||
if let tags = tagsForMessage(message!) {
|
if let tags = tagsForMessage(message!) {
|
||||||
var excludeNamespaces: [MessageId.Namespace]
|
let namespaces: HistoryViewNamespaces
|
||||||
if message!.id.namespace == Namespaces.Message.ScheduledCloud {
|
if Namespaces.Message.allScheduled.contains(message!.id.namespace) {
|
||||||
excludeNamespaces = [Namespaces.Message.Cloud, Namespaces.Message.Local, Namespaces.Message.SecretIncoming]
|
namespaces = .just(Namespaces.Message.allScheduled)
|
||||||
} else {
|
} else {
|
||||||
excludeNamespaces = [Namespaces.Message.ScheduledCloud, Namespaces.Message.ScheduledLocal]
|
namespaces = .not(Namespaces.Message.allScheduled)
|
||||||
}
|
}
|
||||||
|
return context.account.postbox.aroundMessageHistoryViewForLocation(.peer(message!.id.peerId), anchor: .index(message!.index), count: 50, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tags, namespaces: namespaces, orderStatistics: [.combinedLocation])
|
||||||
let view = context.account.postbox.aroundMessageHistoryViewForLocation(.peer(message!.id.peerId), anchor: .index(message!.index), count: 50, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tags, excludeNamespaces: excludeNamespaces, orderStatistics: [.combinedLocation])
|
|
||||||
|
|
||||||
return view
|
|
||||||
|> mapToSignal { (view, _, _) -> Signal<GalleryMessageHistoryView?, NoError> in
|
|> mapToSignal { (view, _, _) -> Signal<GalleryMessageHistoryView?, NoError> in
|
||||||
let mapped = GalleryMessageHistoryView.view(view)
|
let mapped = GalleryMessageHistoryView.view(view)
|
||||||
return .single(mapped)
|
return .single(mapped)
|
||||||
|
|||||||
@ -224,10 +224,27 @@ public enum HistoryViewInputAnchor: Equatable {
|
|||||||
case unread
|
case unread
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum HistoryViewNamespaces {
|
||||||
|
case all
|
||||||
|
case just(Set<MessageId.Namespace>)
|
||||||
|
case not(Set<MessageId.Namespace>)
|
||||||
|
|
||||||
|
public func contains(_ namespace: MessageId.Namespace) -> Bool {
|
||||||
|
switch self {
|
||||||
|
case .all:
|
||||||
|
return true
|
||||||
|
case let .just(namespaces):
|
||||||
|
return namespaces.contains(namespace)
|
||||||
|
case let .not(namespaces):
|
||||||
|
return !namespaces.contains(namespace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final class MutableMessageHistoryView {
|
final class MutableMessageHistoryView {
|
||||||
private(set) var peerIds: MessageHistoryViewPeerIds
|
private(set) var peerIds: MessageHistoryViewPeerIds
|
||||||
let tag: MessageTags?
|
let tag: MessageTags?
|
||||||
let excludeNamespaces: [MessageId.Namespace]
|
let namespaces: HistoryViewNamespaces
|
||||||
private let orderStatistics: MessageHistoryViewOrderStatistics
|
private let orderStatistics: MessageHistoryViewOrderStatistics
|
||||||
private let anchor: HistoryViewInputAnchor
|
private let anchor: HistoryViewInputAnchor
|
||||||
|
|
||||||
@ -242,7 +259,7 @@ final class MutableMessageHistoryView {
|
|||||||
|
|
||||||
fileprivate(set) var sampledState: HistoryViewSample
|
fileprivate(set) var sampledState: HistoryViewSample
|
||||||
|
|
||||||
init(postbox: Postbox, orderStatistics: MessageHistoryViewOrderStatistics, peerIds: MessageHistoryViewPeerIds, anchor inputAnchor: HistoryViewInputAnchor, combinedReadStates: MessageHistoryViewReadState?, transientReadStates: MessageHistoryViewReadState?, tag: MessageTags?, excludeNamespaces: [MessageId.Namespace], count: Int, topTaggedMessages: [MessageId.Namespace: MessageHistoryTopTaggedMessage?], additionalDatas: [AdditionalMessageHistoryViewDataEntry], getMessageCountInRange: (MessageIndex, MessageIndex) -> Int32) {
|
init(postbox: Postbox, orderStatistics: MessageHistoryViewOrderStatistics, peerIds: MessageHistoryViewPeerIds, anchor inputAnchor: HistoryViewInputAnchor, combinedReadStates: MessageHistoryViewReadState?, transientReadStates: MessageHistoryViewReadState?, tag: MessageTags?, namespaces: HistoryViewNamespaces, count: Int, topTaggedMessages: [MessageId.Namespace: MessageHistoryTopTaggedMessage?], additionalDatas: [AdditionalMessageHistoryViewDataEntry], getMessageCountInRange: (MessageIndex, MessageIndex) -> Int32) {
|
||||||
self.anchor = inputAnchor
|
self.anchor = inputAnchor
|
||||||
|
|
||||||
self.orderStatistics = orderStatistics
|
self.orderStatistics = orderStatistics
|
||||||
@ -250,17 +267,17 @@ final class MutableMessageHistoryView {
|
|||||||
self.combinedReadStates = combinedReadStates
|
self.combinedReadStates = combinedReadStates
|
||||||
self.transientReadStates = transientReadStates
|
self.transientReadStates = transientReadStates
|
||||||
self.tag = tag
|
self.tag = tag
|
||||||
self.excludeNamespaces = excludeNamespaces
|
self.namespaces = namespaces
|
||||||
self.fillCount = count
|
self.fillCount = count
|
||||||
self.topTaggedMessages = topTaggedMessages
|
self.topTaggedMessages = topTaggedMessages
|
||||||
self.additionalDatas = additionalDatas
|
self.additionalDatas = additionalDatas
|
||||||
|
|
||||||
self.state = HistoryViewState(postbox: postbox, inputAnchor: inputAnchor, tag: tag, excludeNamespaces: excludeNamespaces, statistics: self.orderStatistics, halfLimit: count + 1, locations: peerIds)
|
self.state = HistoryViewState(postbox: postbox, inputAnchor: inputAnchor, tag: tag, namespaces: namespaces, statistics: self.orderStatistics, halfLimit: count + 1, locations: peerIds)
|
||||||
if case let .loading(loadingState) = self.state {
|
if case let .loading(loadingState) = self.state {
|
||||||
let sampledState = loadingState.checkAndSample(postbox: postbox)
|
let sampledState = loadingState.checkAndSample(postbox: postbox)
|
||||||
switch sampledState {
|
switch sampledState {
|
||||||
case let .ready(anchor, holes):
|
case let .ready(anchor, holes):
|
||||||
self.state = .loaded(HistoryViewLoadedState(anchor: anchor, tag: tag, excludeNamespaces: excludeNamespaces, statistics: self.orderStatistics, halfLimit: count + 1, locations: peerIds, postbox: postbox, holes: holes))
|
self.state = .loaded(HistoryViewLoadedState(anchor: anchor, tag: tag, namespaces: namespaces, statistics: self.orderStatistics, halfLimit: count + 1, locations: peerIds, postbox: postbox, holes: holes))
|
||||||
self.sampledState = self.state.sample(postbox: postbox)
|
self.sampledState = self.state.sample(postbox: postbox)
|
||||||
case .loadHole:
|
case .loadHole:
|
||||||
break
|
break
|
||||||
@ -272,12 +289,12 @@ final class MutableMessageHistoryView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func reset(postbox: Postbox) {
|
private func reset(postbox: Postbox) {
|
||||||
self.state = HistoryViewState(postbox: postbox, inputAnchor: self.anchor, tag: self.tag, excludeNamespaces: self.excludeNamespaces, statistics: self.orderStatistics, halfLimit: self.fillCount + 1, locations: self.peerIds)
|
self.state = HistoryViewState(postbox: postbox, inputAnchor: self.anchor, tag: self.tag, namespaces: self.namespaces, statistics: self.orderStatistics, halfLimit: self.fillCount + 1, locations: self.peerIds)
|
||||||
if case let .loading(loadingState) = self.state {
|
if case let .loading(loadingState) = self.state {
|
||||||
let sampledState = loadingState.checkAndSample(postbox: postbox)
|
let sampledState = loadingState.checkAndSample(postbox: postbox)
|
||||||
switch sampledState {
|
switch sampledState {
|
||||||
case let .ready(anchor, holes):
|
case let .ready(anchor, holes):
|
||||||
self.state = .loaded(HistoryViewLoadedState(anchor: anchor, tag: self.tag, excludeNamespaces: self.excludeNamespaces, statistics: self.orderStatistics, halfLimit: self.fillCount + 1, locations: self.peerIds, postbox: postbox, holes: holes))
|
self.state = .loaded(HistoryViewLoadedState(anchor: anchor, tag: self.tag, namespaces: self.namespaces, statistics: self.orderStatistics, halfLimit: self.fillCount + 1, locations: self.peerIds, postbox: postbox, holes: holes))
|
||||||
case .loadHole:
|
case .loadHole:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -286,7 +303,7 @@ final class MutableMessageHistoryView {
|
|||||||
let sampledState = loadingState.checkAndSample(postbox: postbox)
|
let sampledState = loadingState.checkAndSample(postbox: postbox)
|
||||||
switch sampledState {
|
switch sampledState {
|
||||||
case let .ready(anchor, holes):
|
case let .ready(anchor, holes):
|
||||||
self.state = .loaded(HistoryViewLoadedState(anchor: anchor, tag: self.tag, excludeNamespaces: self.excludeNamespaces, statistics: self.orderStatistics, halfLimit: self.fillCount + 1, locations: self.peerIds, postbox: postbox, holes: holes))
|
self.state = .loaded(HistoryViewLoadedState(anchor: anchor, tag: self.tag, namespaces: self.namespaces, statistics: self.orderStatistics, halfLimit: self.fillCount + 1, locations: self.peerIds, postbox: postbox, holes: holes))
|
||||||
case .loadHole:
|
case .loadHole:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -472,7 +489,7 @@ final class MutableMessageHistoryView {
|
|||||||
let sampledState = loadingState.checkAndSample(postbox: postbox)
|
let sampledState = loadingState.checkAndSample(postbox: postbox)
|
||||||
switch sampledState {
|
switch sampledState {
|
||||||
case let .ready(anchor, holes):
|
case let .ready(anchor, holes):
|
||||||
self.state = .loaded(HistoryViewLoadedState(anchor: anchor, tag: self.tag, excludeNamespaces: self.excludeNamespaces, statistics: self.orderStatistics, halfLimit: self.fillCount + 1, locations: self.peerIds, postbox: postbox, holes: holes))
|
self.state = .loaded(HistoryViewLoadedState(anchor: anchor, tag: self.tag, namespaces: self.namespaces, statistics: self.orderStatistics, halfLimit: self.fillCount + 1, locations: self.peerIds, postbox: postbox, holes: holes))
|
||||||
case .loadHole:
|
case .loadHole:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -672,6 +689,7 @@ final class MutableMessageHistoryView {
|
|||||||
|
|
||||||
public final class MessageHistoryView {
|
public final class MessageHistoryView {
|
||||||
public let tagMask: MessageTags?
|
public let tagMask: MessageTags?
|
||||||
|
public let namespaces: HistoryViewNamespaces
|
||||||
public let anchorIndex: MessageHistoryAnchorIndex
|
public let anchorIndex: MessageHistoryAnchorIndex
|
||||||
public let earlierId: MessageIndex?
|
public let earlierId: MessageIndex?
|
||||||
public let laterId: MessageIndex?
|
public let laterId: MessageIndex?
|
||||||
@ -686,6 +704,7 @@ public final class MessageHistoryView {
|
|||||||
|
|
||||||
init(_ mutableView: MutableMessageHistoryView) {
|
init(_ mutableView: MutableMessageHistoryView) {
|
||||||
self.tagMask = mutableView.tag
|
self.tagMask = mutableView.tag
|
||||||
|
self.namespaces = mutableView.namespaces
|
||||||
var entries: [MessageHistoryEntry]
|
var entries: [MessageHistoryEntry]
|
||||||
switch mutableView.sampledState {
|
switch mutableView.sampledState {
|
||||||
case .loading:
|
case .loading:
|
||||||
@ -714,7 +733,7 @@ public final class MessageHistoryView {
|
|||||||
entries = []
|
entries = []
|
||||||
if let transientReadStates = mutableView.transientReadStates, case let .peer(states) = transientReadStates {
|
if let transientReadStates = mutableView.transientReadStates, case let .peer(states) = transientReadStates {
|
||||||
for entry in state.entries {
|
for entry in state.entries {
|
||||||
if !mutableView.excludeNamespaces.contains(entry.message.id.namespace) {
|
if mutableView.namespaces.contains(entry.message.id.namespace) {
|
||||||
let read: Bool
|
let read: Bool
|
||||||
if entry.message.flags.contains(.Incoming) {
|
if entry.message.flags.contains(.Incoming) {
|
||||||
read = false
|
read = false
|
||||||
@ -728,7 +747,7 @@ public final class MessageHistoryView {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for entry in state.entries {
|
for entry in state.entries {
|
||||||
if !mutableView.excludeNamespaces.contains(entry.message.id.namespace) {
|
if mutableView.namespaces.contains(entry.message.id.namespace) {
|
||||||
entries.append(MessageHistoryEntry(message: entry.message, isRead: false, location: entry.location, monthLocation: entry.monthLocation, attributes: entry.attributes))
|
entries.append(MessageHistoryEntry(message: entry.message, isRead: false, location: entry.location, monthLocation: entry.monthLocation, attributes: entry.attributes))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -748,7 +748,7 @@ struct HistoryViewLoadedSample {
|
|||||||
final class HistoryViewLoadedState {
|
final class HistoryViewLoadedState {
|
||||||
let anchor: HistoryViewAnchor
|
let anchor: HistoryViewAnchor
|
||||||
let tag: MessageTags?
|
let tag: MessageTags?
|
||||||
let excludeNamespaces: [MessageId.Namespace]
|
let namespaces: HistoryViewNamespaces
|
||||||
let statistics: MessageHistoryViewOrderStatistics
|
let statistics: MessageHistoryViewOrderStatistics
|
||||||
let halfLimit: Int
|
let halfLimit: Int
|
||||||
let seedConfiguration: SeedConfiguration
|
let seedConfiguration: SeedConfiguration
|
||||||
@ -756,11 +756,11 @@ final class HistoryViewLoadedState {
|
|||||||
var holes: HistoryViewHoles
|
var holes: HistoryViewHoles
|
||||||
var spacesWithRemovals = Set<PeerIdAndNamespace>()
|
var spacesWithRemovals = Set<PeerIdAndNamespace>()
|
||||||
|
|
||||||
init(anchor: HistoryViewAnchor, tag: MessageTags?, excludeNamespaces: [MessageId.Namespace], statistics: MessageHistoryViewOrderStatistics, halfLimit: Int, locations: MessageHistoryViewPeerIds, postbox: Postbox, holes: HistoryViewHoles) {
|
init(anchor: HistoryViewAnchor, tag: MessageTags?, namespaces: HistoryViewNamespaces, statistics: MessageHistoryViewOrderStatistics, halfLimit: Int, locations: MessageHistoryViewPeerIds, postbox: Postbox, holes: HistoryViewHoles) {
|
||||||
precondition(halfLimit >= 3)
|
precondition(halfLimit >= 3)
|
||||||
self.anchor = anchor
|
self.anchor = anchor
|
||||||
self.tag = tag
|
self.tag = tag
|
||||||
self.excludeNamespaces = excludeNamespaces
|
self.namespaces = namespaces
|
||||||
self.statistics = statistics
|
self.statistics = statistics
|
||||||
self.halfLimit = halfLimit
|
self.halfLimit = halfLimit
|
||||||
self.seedConfiguration = postbox.seedConfiguration
|
self.seedConfiguration = postbox.seedConfiguration
|
||||||
@ -781,7 +781,7 @@ final class HistoryViewLoadedState {
|
|||||||
var spaces: [PeerIdAndNamespace] = []
|
var spaces: [PeerIdAndNamespace] = []
|
||||||
for peerId in peerIds {
|
for peerId in peerIds {
|
||||||
for namespace in postbox.messageHistoryIndexTable.existingNamespaces(peerId: peerId) {
|
for namespace in postbox.messageHistoryIndexTable.existingNamespaces(peerId: peerId) {
|
||||||
if !excludeNamespaces.contains(namespace) {
|
if namespaces.contains(namespace) {
|
||||||
spaces.append(PeerIdAndNamespace(peerId: peerId, namespace: namespace))
|
spaces.append(PeerIdAndNamespace(peerId: peerId, namespace: namespace))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1178,7 +1178,7 @@ final class HistoryViewLoadedState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func fetchHoles(postbox: Postbox, locations: MessageHistoryViewPeerIds, tag: MessageTags?, excludeNamespaces: [MessageId.Namespace]) -> [PeerIdAndNamespace: IndexSet] {
|
private func fetchHoles(postbox: Postbox, locations: MessageHistoryViewPeerIds, tag: MessageTags?, namespaces: HistoryViewNamespaces) -> [PeerIdAndNamespace: IndexSet] {
|
||||||
var holesBySpace: [PeerIdAndNamespace: IndexSet] = [:]
|
var holesBySpace: [PeerIdAndNamespace: IndexSet] = [:]
|
||||||
var peerIds: [PeerId] = []
|
var peerIds: [PeerId] = []
|
||||||
switch locations {
|
switch locations {
|
||||||
@ -1193,7 +1193,7 @@ private func fetchHoles(postbox: Postbox, locations: MessageHistoryViewPeerIds,
|
|||||||
let holeSpace = tag.flatMap(MessageHistoryHoleSpace.tag) ?? .everywhere
|
let holeSpace = tag.flatMap(MessageHistoryHoleSpace.tag) ?? .everywhere
|
||||||
for peerId in peerIds {
|
for peerId in peerIds {
|
||||||
for namespace in postbox.messageHistoryHoleIndexTable.existingNamespaces(peerId: peerId, holeSpace: holeSpace) {
|
for namespace in postbox.messageHistoryHoleIndexTable.existingNamespaces(peerId: peerId, holeSpace: holeSpace) {
|
||||||
if !excludeNamespaces.contains(namespace) {
|
if namespaces.contains(namespace) {
|
||||||
let indices = postbox.messageHistoryHoleIndexTable.closest(peerId: peerId, namespace: namespace, space: holeSpace, range: 1 ... (Int32.max - 1))
|
let indices = postbox.messageHistoryHoleIndexTable.closest(peerId: peerId, namespace: namespace, space: holeSpace, range: 1 ... (Int32.max - 1))
|
||||||
if !indices.isEmpty {
|
if !indices.isEmpty {
|
||||||
let peerIdAndNamespace = PeerIdAndNamespace(peerId: peerId, namespace: namespace)
|
let peerIdAndNamespace = PeerIdAndNamespace(peerId: peerId, namespace: namespace)
|
||||||
@ -1217,11 +1217,11 @@ final class HistoryViewLoadingState {
|
|||||||
let halfLimit: Int
|
let halfLimit: Int
|
||||||
var holes: HistoryViewHoles
|
var holes: HistoryViewHoles
|
||||||
|
|
||||||
init(postbox: Postbox, locations: MessageHistoryViewPeerIds, tag: MessageTags?, excludeNamespaces: [MessageId.Namespace], messageId: MessageId, halfLimit: Int) {
|
init(postbox: Postbox, locations: MessageHistoryViewPeerIds, tag: MessageTags?, namespaces: HistoryViewNamespaces, messageId: MessageId, halfLimit: Int) {
|
||||||
self.messageId = messageId
|
self.messageId = messageId
|
||||||
self.tag = tag
|
self.tag = tag
|
||||||
self.halfLimit = halfLimit
|
self.halfLimit = halfLimit
|
||||||
self.holes = HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, excludeNamespaces: excludeNamespaces))
|
self.holes = HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces))
|
||||||
}
|
}
|
||||||
|
|
||||||
func insertHole(space: PeerIdAndNamespace, range: ClosedRange<MessageId.Id>) -> Bool {
|
func insertHole(space: PeerIdAndNamespace, range: ClosedRange<MessageId.Id>) -> Bool {
|
||||||
@ -1261,14 +1261,14 @@ enum HistoryViewState {
|
|||||||
case loaded(HistoryViewLoadedState)
|
case loaded(HistoryViewLoadedState)
|
||||||
case loading(HistoryViewLoadingState)
|
case loading(HistoryViewLoadingState)
|
||||||
|
|
||||||
init(postbox: Postbox, inputAnchor: HistoryViewInputAnchor, tag: MessageTags?, excludeNamespaces: [MessageId.Namespace], statistics: MessageHistoryViewOrderStatistics, halfLimit: Int, locations: MessageHistoryViewPeerIds) {
|
init(postbox: Postbox, inputAnchor: HistoryViewInputAnchor, tag: MessageTags?, namespaces: HistoryViewNamespaces, statistics: MessageHistoryViewOrderStatistics, halfLimit: Int, locations: MessageHistoryViewPeerIds) {
|
||||||
switch inputAnchor {
|
switch inputAnchor {
|
||||||
case let .index(index):
|
case let .index(index):
|
||||||
self = .loaded(HistoryViewLoadedState(anchor: .index(index), tag: tag, excludeNamespaces: excludeNamespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, excludeNamespaces: excludeNamespaces))))
|
self = .loaded(HistoryViewLoadedState(anchor: .index(index), tag: tag, namespaces: namespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces))))
|
||||||
case .lowerBound:
|
case .lowerBound:
|
||||||
self = .loaded(HistoryViewLoadedState(anchor: .lowerBound, tag: tag, excludeNamespaces: excludeNamespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, excludeNamespaces: excludeNamespaces))))
|
self = .loaded(HistoryViewLoadedState(anchor: .lowerBound, tag: tag, namespaces: namespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces))))
|
||||||
case .upperBound:
|
case .upperBound:
|
||||||
self = .loaded(HistoryViewLoadedState(anchor: .upperBound, tag: tag, excludeNamespaces: excludeNamespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, excludeNamespaces: excludeNamespaces))))
|
self = .loaded(HistoryViewLoadedState(anchor: .upperBound, tag: tag, namespaces: namespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces))))
|
||||||
case .unread:
|
case .unread:
|
||||||
let anchorPeerId: PeerId
|
let anchorPeerId: PeerId
|
||||||
switch locations {
|
switch locations {
|
||||||
@ -1301,26 +1301,26 @@ enum HistoryViewState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let messageId = messageId {
|
if let messageId = messageId {
|
||||||
let loadingState = HistoryViewLoadingState(postbox: postbox, locations: locations, tag: tag, excludeNamespaces: excludeNamespaces, messageId: messageId, halfLimit: halfLimit)
|
let loadingState = HistoryViewLoadingState(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces, messageId: messageId, halfLimit: halfLimit)
|
||||||
let sampledState = loadingState.checkAndSample(postbox: postbox)
|
let sampledState = loadingState.checkAndSample(postbox: postbox)
|
||||||
switch sampledState {
|
switch sampledState {
|
||||||
case let .ready(anchor, holes):
|
case let .ready(anchor, holes):
|
||||||
self = .loaded(HistoryViewLoadedState(anchor: anchor, tag: tag, excludeNamespaces: excludeNamespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: holes))
|
self = .loaded(HistoryViewLoadedState(anchor: anchor, tag: tag, namespaces: namespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: holes))
|
||||||
case .loadHole:
|
case .loadHole:
|
||||||
self = .loading(loadingState)
|
self = .loading(loadingState)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self = .loaded(HistoryViewLoadedState(anchor: anchor ?? .upperBound, tag: tag, excludeNamespaces: excludeNamespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, excludeNamespaces: excludeNamespaces))))
|
self = .loaded(HistoryViewLoadedState(anchor: anchor ?? .upperBound, tag: tag, namespaces: namespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces))))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
preconditionFailure()
|
preconditionFailure()
|
||||||
}
|
}
|
||||||
case let .message(messageId):
|
case let .message(messageId):
|
||||||
let loadingState = HistoryViewLoadingState(postbox: postbox, locations: locations, tag: tag, excludeNamespaces: excludeNamespaces, messageId: messageId, halfLimit: halfLimit)
|
let loadingState = HistoryViewLoadingState(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces, messageId: messageId, halfLimit: halfLimit)
|
||||||
let sampledState = loadingState.checkAndSample(postbox: postbox)
|
let sampledState = loadingState.checkAndSample(postbox: postbox)
|
||||||
switch sampledState {
|
switch sampledState {
|
||||||
case let .ready(anchor, holes):
|
case let .ready(anchor, holes):
|
||||||
self = .loaded(HistoryViewLoadedState(anchor: anchor, tag: tag, excludeNamespaces: excludeNamespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: holes))
|
self = .loaded(HistoryViewLoadedState(anchor: anchor, tag: tag, namespaces: namespaces, statistics: statistics, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: holes))
|
||||||
case .loadHole:
|
case .loadHole:
|
||||||
self = .loading(loadingState)
|
self = .loading(loadingState)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -52,7 +52,7 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.anchor = anchor
|
self.anchor = anchor
|
||||||
self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], peerIds: .single(peerId), anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, excludeNamespaces: [], count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0})
|
self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], peerIds: .single(peerId), anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0})
|
||||||
let _ = self.updateFromView()
|
let _ = self.updateFromView()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,7 +117,7 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView {
|
|||||||
|
|
||||||
if self.anchor != anchor {
|
if self.anchor != anchor {
|
||||||
self.anchor = anchor
|
self.anchor = anchor
|
||||||
self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], peerIds: .single(peerId), anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, excludeNamespaces: [], count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0})
|
self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], peerIds: .single(peerId), anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0})
|
||||||
return self.updateFromView()
|
return self.updateFromView()
|
||||||
} else if self.wrappedView.replay(postbox: postbox, transaction: transaction) {
|
} else if self.wrappedView.replay(postbox: postbox, transaction: transaction) {
|
||||||
return self.updateFromView()
|
return self.updateFromView()
|
||||||
|
|||||||
@ -2219,7 +2219,7 @@ public final class Postbox {
|
|||||||
return peerIds
|
return peerIds
|
||||||
}
|
}
|
||||||
|
|
||||||
public func aroundMessageOfInterestHistoryViewForChatLocation(_ chatLocation: ChatLocation, count: Int, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tagMask: MessageTags?, excludeNamespaces: [MessageId.Namespace], orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData]) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
public func aroundMessageOfInterestHistoryViewForChatLocation(_ chatLocation: ChatLocation, count: Int, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tagMask: MessageTags?, namespaces: HistoryViewNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData]) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
||||||
return self.transactionSignal(userInteractive: true, { subscriber, transaction in
|
return self.transactionSignal(userInteractive: true, { subscriber, transaction in
|
||||||
let peerIds = self.peerIdsForLocation(chatLocation, tagMask: tagMask)
|
let peerIds = self.peerIdsForLocation(chatLocation, tagMask: tagMask)
|
||||||
|
|
||||||
@ -2263,26 +2263,26 @@ public final class Postbox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, anchor: anchor, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, excludeNamespaces: excludeNamespaces, orderStatistics: orderStatistics, additionalData: additionalData)
|
return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, anchor: anchor, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public func aroundIdMessageHistoryViewForLocation(_ chatLocation: ChatLocation, count: Int, messageId: MessageId, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tagMask: MessageTags?, excludeNamespaces: [MessageId.Namespace], orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
public func aroundIdMessageHistoryViewForLocation(_ chatLocation: ChatLocation, count: Int, messageId: MessageId, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tagMask: MessageTags?, namespaces: HistoryViewNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
||||||
return self.transactionSignal { subscriber, transaction in
|
return self.transactionSignal { subscriber, transaction in
|
||||||
let peerIds = self.peerIdsForLocation(chatLocation, tagMask: tagMask)
|
let peerIds = self.peerIdsForLocation(chatLocation, tagMask: tagMask)
|
||||||
return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, anchor: .message(messageId), fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, excludeNamespaces: excludeNamespaces, orderStatistics: orderStatistics, additionalData: additionalData)
|
return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, anchor: .message(messageId), fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func aroundMessageHistoryViewForLocation(_ chatLocation: ChatLocation, anchor: HistoryViewInputAnchor, count: Int, fixedCombinedReadStates: MessageHistoryViewReadState?, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tagMask: MessageTags?, excludeNamespaces: [MessageId.Namespace], orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
public func aroundMessageHistoryViewForLocation(_ chatLocation: ChatLocation, anchor: HistoryViewInputAnchor, count: Int, fixedCombinedReadStates: MessageHistoryViewReadState?, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tagMask: MessageTags?, namespaces: HistoryViewNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
||||||
return self.transactionSignal { subscriber, transaction in
|
return self.transactionSignal { subscriber, transaction in
|
||||||
let peerIds = self.peerIdsForLocation(chatLocation, tagMask: tagMask)
|
let peerIds = self.peerIdsForLocation(chatLocation, tagMask: tagMask)
|
||||||
|
|
||||||
return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, anchor: anchor, fixedCombinedReadStates: fixedCombinedReadStates, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, excludeNamespaces: excludeNamespaces, orderStatistics: orderStatistics, additionalData: additionalData)
|
return self.syncAroundMessageHistoryViewForPeerId(subscriber: subscriber, peerIds: peerIds, count: count, anchor: anchor, fixedCombinedReadStates: fixedCombinedReadStates, topTaggedMessageIdNamespaces: topTaggedMessageIdNamespaces, tagMask: tagMask, namespaces: namespaces, orderStatistics: orderStatistics, additionalData: additionalData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func syncAroundMessageHistoryViewForPeerId(subscriber: Subscriber<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError>, peerIds: MessageHistoryViewPeerIds, count: Int, anchor: HistoryViewInputAnchor, fixedCombinedReadStates: MessageHistoryViewReadState?, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tagMask: MessageTags?, excludeNamespaces: [MessageId.Namespace], orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData]) -> Disposable {
|
private func syncAroundMessageHistoryViewForPeerId(subscriber: Subscriber<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError>, peerIds: MessageHistoryViewPeerIds, count: Int, anchor: HistoryViewInputAnchor, fixedCombinedReadStates: MessageHistoryViewReadState?, topTaggedMessageIdNamespaces: Set<MessageId.Namespace>, tagMask: MessageTags?, namespaces: HistoryViewNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData]) -> Disposable {
|
||||||
var topTaggedMessages: [MessageId.Namespace: MessageHistoryTopTaggedMessage?] = [:]
|
var topTaggedMessages: [MessageId.Namespace: MessageHistoryTopTaggedMessage?] = [:]
|
||||||
var mainPeerId: PeerId?
|
var mainPeerId: PeerId?
|
||||||
switch peerIds {
|
switch peerIds {
|
||||||
@ -2371,7 +2371,7 @@ public final class Postbox {
|
|||||||
readStates = transientReadStates
|
readStates = transientReadStates
|
||||||
}
|
}
|
||||||
|
|
||||||
let mutableView = MutableMessageHistoryView(postbox: self, orderStatistics: orderStatistics, peerIds: peerIds, anchor: anchor, combinedReadStates: readStates, transientReadStates: transientReadStates, tag: tagMask, excludeNamespaces: excludeNamespaces, count: count, topTaggedMessages: topTaggedMessages, additionalDatas: additionalDataEntries, getMessageCountInRange: { lowerBound, upperBound in
|
let mutableView = MutableMessageHistoryView(postbox: self, orderStatistics: orderStatistics, peerIds: peerIds, anchor: anchor, combinedReadStates: readStates, transientReadStates: transientReadStates, tag: tagMask, namespaces: namespaces, count: count, topTaggedMessages: topTaggedMessages, additionalDatas: additionalDataEntries, getMessageCountInRange: { lowerBound, upperBound in
|
||||||
if let tagMask = tagMask {
|
if let tagMask = tagMask {
|
||||||
return Int32(self.messageHistoryTable.getMessageCountInRange(peerId: lowerBound.id.peerId, namespace: lowerBound.id.namespace, tag: tagMask, lowerBound: lowerBound, upperBound: upperBound))
|
return Int32(self.messageHistoryTable.getMessageCountInRange(peerId: lowerBound.id.peerId, namespace: lowerBound.id.namespace, tag: tagMask, lowerBound: lowerBound, upperBound: upperBound))
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -1243,6 +1243,7 @@ public class Account {
|
|||||||
self.managedOperationsDisposable.add(managedSynchronizeMarkAllUnseenPersonalMessagesOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
self.managedOperationsDisposable.add(managedSynchronizeMarkAllUnseenPersonalMessagesOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
||||||
self.managedOperationsDisposable.add(managedApplyPendingMessageReactionsActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
self.managedOperationsDisposable.add(managedApplyPendingMessageReactionsActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
||||||
self.managedOperationsDisposable.add(managedSynchronizeEmojiKeywordsOperations(postbox: self.postbox, network: self.network).start())
|
self.managedOperationsDisposable.add(managedSynchronizeEmojiKeywordsOperations(postbox: self.postbox, network: self.network).start())
|
||||||
|
self.managedOperationsDisposable.add(managedApplyPendingScheduledMessagesActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
||||||
|
|
||||||
let importantBackgroundOperations: [Signal<AccountRunningImportantTasks, NoError>] = [
|
let importantBackgroundOperations: [Signal<AccountRunningImportantTasks, NoError>] = [
|
||||||
managedSynchronizeChatInputStateOperations(postbox: self.postbox, network: self.network) |> map { $0 ? AccountRunningImportantTasks.other : [] },
|
managedSynchronizeChatInputStateOperations(postbox: self.postbox, network: self.network) |> map { $0 ? AccountRunningImportantTasks.other : [] },
|
||||||
|
|||||||
@ -143,7 +143,6 @@ private var declaredEncodables: Void = {
|
|||||||
declareEncodable(EmojiKeywordCollectionInfo.self, f: { EmojiKeywordCollectionInfo(decoder: $0) })
|
declareEncodable(EmojiKeywordCollectionInfo.self, f: { EmojiKeywordCollectionInfo(decoder: $0) })
|
||||||
declareEncodable(EmojiKeywordItem.self, f: { EmojiKeywordItem(decoder: $0) })
|
declareEncodable(EmojiKeywordItem.self, f: { EmojiKeywordItem(decoder: $0) })
|
||||||
declareEncodable(SynchronizeEmojiKeywordsOperation.self, f: { SynchronizeEmojiKeywordsOperation(decoder: $0) })
|
declareEncodable(SynchronizeEmojiKeywordsOperation.self, f: { SynchronizeEmojiKeywordsOperation(decoder: $0) })
|
||||||
|
|
||||||
declareEncodable(CloudPhotoSizeMediaResource.self, f: { CloudPhotoSizeMediaResource(decoder: $0) })
|
declareEncodable(CloudPhotoSizeMediaResource.self, f: { CloudPhotoSizeMediaResource(decoder: $0) })
|
||||||
declareEncodable(CloudDocumentSizeMediaResource.self, f: { CloudDocumentSizeMediaResource(decoder: $0) })
|
declareEncodable(CloudDocumentSizeMediaResource.self, f: { CloudDocumentSizeMediaResource(decoder: $0) })
|
||||||
declareEncodable(CloudPeerPhotoSizeMediaResource.self, f: { CloudPeerPhotoSizeMediaResource(decoder: $0) })
|
declareEncodable(CloudPeerPhotoSizeMediaResource.self, f: { CloudPeerPhotoSizeMediaResource(decoder: $0) })
|
||||||
@ -153,6 +152,7 @@ private var declaredEncodables: Void = {
|
|||||||
declareEncodable(OutgoingScheduleInfoMessageAttribute.self, f: { OutgoingScheduleInfoMessageAttribute(decoder: $0) })
|
declareEncodable(OutgoingScheduleInfoMessageAttribute.self, f: { OutgoingScheduleInfoMessageAttribute(decoder: $0) })
|
||||||
declareEncodable(UpdateMessageReactionsAction.self, f: { UpdateMessageReactionsAction(decoder: $0) })
|
declareEncodable(UpdateMessageReactionsAction.self, f: { UpdateMessageReactionsAction(decoder: $0) })
|
||||||
declareEncodable(RestrictedContentMessageAttribute.self, f: { RestrictedContentMessageAttribute(decoder: $0) })
|
declareEncodable(RestrictedContentMessageAttribute.self, f: { RestrictedContentMessageAttribute(decoder: $0) })
|
||||||
|
declareEncodable(SendScheduledMessageImmediatelyAction.self, f: { SendScheduledMessageImmediatelyAction(decoder: $0) })
|
||||||
|
|
||||||
return
|
return
|
||||||
}()
|
}()
|
||||||
|
|||||||
@ -189,7 +189,7 @@ private func wrappedHistoryViewAdditionalData(chatLocation: ChatLocation, additi
|
|||||||
switch chatLocation {
|
switch chatLocation {
|
||||||
case let .peer(peerId):
|
case let .peer(peerId):
|
||||||
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||||
if result.index(where: { if case .peerChatState = $0 { return true } else { return false } }) == nil {
|
if result.firstIndex(where: { if case .peerChatState = $0 { return true } else { return false } }) == nil {
|
||||||
result.append(.peerChatState(peerId))
|
result.append(.peerChatState(peerId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -872,9 +872,7 @@ public final class AccountViewTracker {
|
|||||||
strongSelf.updatePolls(viewId: viewId, messageIds: pollMessageIds, messages: pollMessageDict)
|
strongSelf.updatePolls(viewId: viewId, messageIds: pollMessageIds, messages: pollMessageDict)
|
||||||
if case let .peer(peerId) = chatLocation, peerId.namespace == Namespaces.Peer.CloudChannel {
|
if case let .peer(peerId) = chatLocation, peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||||
strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: next.0)
|
strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: next.0)
|
||||||
}/* else if case .group = chatLocation {
|
}
|
||||||
strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: next.0)
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, disposed: { [weak self] viewId in
|
}, disposed: { [weak self] viewId in
|
||||||
@ -887,8 +885,6 @@ public final class AccountViewTracker {
|
|||||||
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||||
strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: nil)
|
strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: nil)
|
||||||
}
|
}
|
||||||
/*case .group:
|
|
||||||
strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: nil)*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -915,28 +911,54 @@ public final class AccountViewTracker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func scheduledMessagesViewForLocation(_ chatLocation: ChatLocation) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
public func scheduledMessagesViewForLocation(_ chatLocation: ChatLocation) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
||||||
return self.aroundMessageHistoryViewForLocation(chatLocation, index: .upperBound, anchorIndex: .upperBound, count: 100, fixedCombinedReadStates: nil, tagMask: nil, excludeNamespaces: [Namespaces.Message.Cloud, Namespaces.Message.Local], orderStatistics: [])
|
if let account = self.account {
|
||||||
|
let signal = account.postbox.aroundMessageHistoryViewForLocation(chatLocation, anchor: .upperBound, count: 200, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: nil, namespaces: .just(Namespaces.Message.allScheduled), orderStatistics: [], additionalData: [])
|
||||||
|
return withState(signal, { [weak self] () -> Int32 in
|
||||||
|
if let strongSelf = self {
|
||||||
|
return OSAtomicIncrement32(&strongSelf.nextViewId)
|
||||||
|
} else {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}, next: { [weak self] next, viewId in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.queue.async {
|
||||||
|
let (messageIds, localWebpages) = pendingWebpages(entries: next.0.entries)
|
||||||
|
strongSelf.updatePendingWebpages(viewId: viewId, messageIds: messageIds, localWebpages: localWebpages)
|
||||||
|
strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: next.0, location: chatLocation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, disposed: { [weak self] viewId in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.queue.async {
|
||||||
|
strongSelf.updatePendingWebpages(viewId: viewId, messageIds: [], localWebpages: [:])
|
||||||
|
strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return .never()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func aroundMessageOfInterestHistoryViewForLocation(_ chatLocation: ChatLocation, count: Int, tagMask: MessageTags? = nil, excludeNamespaces: [MessageId.Namespace] = [Namespaces.Message.ScheduledCloud, Namespaces.Message.ScheduledLocal], orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
public func aroundMessageOfInterestHistoryViewForLocation(_ chatLocation: ChatLocation, count: Int, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
||||||
if let account = self.account {
|
if let account = self.account {
|
||||||
let signal = account.postbox.aroundMessageOfInterestHistoryViewForChatLocation(chatLocation, count: count, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, excludeNamespaces: excludeNamespaces, orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData))
|
let signal = account.postbox.aroundMessageOfInterestHistoryViewForChatLocation(chatLocation, count: count, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, namespaces: .not(Namespaces.Message.allScheduled), orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData))
|
||||||
return wrappedMessageHistorySignal(chatLocation: chatLocation, signal: signal)
|
return wrappedMessageHistorySignal(chatLocation: chatLocation, signal: signal)
|
||||||
} else {
|
} else {
|
||||||
return .never()
|
return .never()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func aroundIdMessageHistoryViewForLocation(_ chatLocation: ChatLocation, count: Int, messageId: MessageId, tagMask: MessageTags? = nil, excludeNamespaces: [MessageId.Namespace] = [Namespaces.Message.ScheduledCloud, Namespaces.Message.ScheduledLocal], orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
public func aroundIdMessageHistoryViewForLocation(_ chatLocation: ChatLocation, count: Int, messageId: MessageId, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
||||||
if let account = self.account {
|
if let account = self.account {
|
||||||
let signal = account.postbox.aroundIdMessageHistoryViewForLocation(chatLocation, count: count, messageId: messageId, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, excludeNamespaces: excludeNamespaces, orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData))
|
let signal = account.postbox.aroundIdMessageHistoryViewForLocation(chatLocation, count: count, messageId: messageId, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, namespaces: .not(Namespaces.Message.allScheduled), orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData))
|
||||||
return wrappedMessageHistorySignal(chatLocation: chatLocation, signal: signal)
|
return wrappedMessageHistorySignal(chatLocation: chatLocation, signal: signal)
|
||||||
} else {
|
} else {
|
||||||
return .never()
|
return .never()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func aroundMessageHistoryViewForLocation(_ chatLocation: ChatLocation, index: MessageHistoryAnchorIndex, anchorIndex: MessageHistoryAnchorIndex, count: Int, fixedCombinedReadStates: MessageHistoryViewReadState?, tagMask: MessageTags? = nil, excludeNamespaces: [MessageId.Namespace] = [Namespaces.Message.ScheduledCloud, Namespaces.Message.ScheduledLocal], orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
public func aroundMessageHistoryViewForLocation(_ chatLocation: ChatLocation, index: MessageHistoryAnchorIndex, anchorIndex: MessageHistoryAnchorIndex, count: Int, fixedCombinedReadStates: MessageHistoryViewReadState?, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
||||||
if let account = self.account {
|
if let account = self.account {
|
||||||
let inputAnchor: HistoryViewInputAnchor
|
let inputAnchor: HistoryViewInputAnchor
|
||||||
switch index {
|
switch index {
|
||||||
@ -947,7 +969,7 @@ public final class AccountViewTracker {
|
|||||||
case let .message(index):
|
case let .message(index):
|
||||||
inputAnchor = .index(index)
|
inputAnchor = .index(index)
|
||||||
}
|
}
|
||||||
let signal = account.postbox.aroundMessageHistoryViewForLocation(chatLocation, anchor: inputAnchor, count: count, fixedCombinedReadStates: fixedCombinedReadStates, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, excludeNamespaces: excludeNamespaces, orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData))
|
let signal = account.postbox.aroundMessageHistoryViewForLocation(chatLocation, anchor: inputAnchor, count: count, fixedCombinedReadStates: fixedCombinedReadStates, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, namespaces: .not(Namespaces.Message.allScheduled), orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData))
|
||||||
return wrappedMessageHistorySignal(chatLocation: chatLocation, signal: signal)
|
return wrappedMessageHistorySignal(chatLocation: chatLocation, signal: signal)
|
||||||
} else {
|
} else {
|
||||||
return .never()
|
return .never()
|
||||||
@ -1163,18 +1185,18 @@ public final class AccountViewTracker {
|
|||||||
let pendingKey: PostboxViewKey = .pendingMessageActionsSummary(type: .consumeUnseenPersonalMessage, peerId: peerId, namespace: Namespaces.Message.Cloud)
|
let pendingKey: PostboxViewKey = .pendingMessageActionsSummary(type: .consumeUnseenPersonalMessage, peerId: peerId, namespace: Namespaces.Message.Cloud)
|
||||||
let summaryKey: PostboxViewKey = .historyTagSummaryView(tag: .unseenPersonalMessage, peerId: peerId, namespace: Namespaces.Message.Cloud)
|
let summaryKey: PostboxViewKey = .historyTagSummaryView(tag: .unseenPersonalMessage, peerId: peerId, namespace: Namespaces.Message.Cloud)
|
||||||
return account.postbox.combinedView(keys: [pendingKey, summaryKey])
|
return account.postbox.combinedView(keys: [pendingKey, summaryKey])
|
||||||
|> map { views -> Int32 in
|
|> map { views -> Int32 in
|
||||||
var count: Int32 = 0
|
var count: Int32 = 0
|
||||||
if let view = views.views[pendingKey] as? PendingMessageActionsSummaryView {
|
if let view = views.views[pendingKey] as? PendingMessageActionsSummaryView {
|
||||||
count -= view.count
|
count -= view.count
|
||||||
|
}
|
||||||
|
if let view = views.views[summaryKey] as? MessageHistoryTagSummaryView {
|
||||||
|
if let unseenCount = view.count {
|
||||||
|
count += unseenCount
|
||||||
}
|
}
|
||||||
if let view = views.views[summaryKey] as? MessageHistoryTagSummaryView {
|
}
|
||||||
if let unseenCount = view.count {
|
return max(0, count)
|
||||||
count += unseenCount
|
} |> distinctUntilChanged
|
||||||
}
|
|
||||||
}
|
|
||||||
return max(0, count)
|
|
||||||
} |> distinctUntilChanged
|
|
||||||
} else {
|
} else {
|
||||||
return .never()
|
return .never()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -387,14 +387,17 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
|
|||||||
}
|
}
|
||||||
|
|
||||||
var messageNamespace = Namespaces.Message.Local
|
var messageNamespace = Namespaces.Message.Local
|
||||||
|
var effectiveTimestamp = timestamp
|
||||||
for attribute in attributes {
|
for attribute in attributes {
|
||||||
if attribute is OutgoingScheduleInfoMessageAttribute {
|
if let attribute = attribute as? OutgoingScheduleInfoMessageAttribute {
|
||||||
messageNamespace = Namespaces.Message.ScheduledLocal
|
messageNamespace = Namespaces.Message.ScheduledLocal
|
||||||
|
effectiveTimestamp = attribute.scheduleTime
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
storeMessages.append(StoreMessage(peerId: peerId, namespace: messageNamespace, globallyUniqueId: randomId, groupingKey: localGroupingKey, timestamp: timestamp, flags: flags, tags: tags, globalTags: globalTags, localTags: localTags, forwardInfo: nil, authorId: authorId, text: text, attributes: attributes, media: mediaList))
|
|
||||||
|
storeMessages.append(StoreMessage(peerId: peerId, namespace: messageNamespace, globallyUniqueId: randomId, groupingKey: localGroupingKey, timestamp: effectiveTimestamp, flags: flags, tags: tags, globalTags: globalTags, localTags: localTags, forwardInfo: nil, authorId: authorId, text: text, attributes: attributes, media: mediaList))
|
||||||
case let .forward(source, grouping, requestedAttributes):
|
case let .forward(source, grouping, requestedAttributes):
|
||||||
let sourceMessage = transaction.getMessage(source)
|
let sourceMessage = transaction.getMessage(source)
|
||||||
if let sourceMessage = sourceMessage, let author = sourceMessage.author ?? sourceMessage.peers[sourceMessage.id.peerId] {
|
if let sourceMessage = sourceMessage, let author = sourceMessage.author ?? sourceMessage.peers[sourceMessage.id.peerId] {
|
||||||
@ -506,12 +509,14 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
|
|||||||
|
|
||||||
var messageNamespace = Namespaces.Message.Local
|
var messageNamespace = Namespaces.Message.Local
|
||||||
var entitiesAttribute: TextEntitiesMessageAttribute?
|
var entitiesAttribute: TextEntitiesMessageAttribute?
|
||||||
|
var effectiveTimestamp = timestamp
|
||||||
for attribute in attributes {
|
for attribute in attributes {
|
||||||
if let attribute = attribute as? TextEntitiesMessageAttribute {
|
if let attribute = attribute as? TextEntitiesMessageAttribute {
|
||||||
entitiesAttribute = attribute
|
entitiesAttribute = attribute
|
||||||
}
|
}
|
||||||
if attribute is OutgoingScheduleInfoMessageAttribute {
|
if let attribute = attribute as? OutgoingScheduleInfoMessageAttribute {
|
||||||
messageNamespace = Namespaces.Message.ScheduledLocal
|
messageNamespace = Namespaces.Message.ScheduledLocal
|
||||||
|
effectiveTimestamp = attribute.scheduleTime
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -543,7 +548,7 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
|
|||||||
augmentedMediaList = augmentedMediaList.map(convertForwardedMediaForSecretChat)
|
augmentedMediaList = augmentedMediaList.map(convertForwardedMediaForSecretChat)
|
||||||
}
|
}
|
||||||
|
|
||||||
storeMessages.append(StoreMessage(peerId: peerId, namespace: messageNamespace, globallyUniqueId: randomId, groupingKey: localGroupingKey, timestamp: timestamp, flags: flags, tags: tags, globalTags: globalTags, localTags: [], forwardInfo: forwardInfo, authorId: authorId, text: sourceMessage.text, attributes: attributes, media: augmentedMediaList))
|
storeMessages.append(StoreMessage(peerId: peerId, namespace: messageNamespace, globallyUniqueId: randomId, groupingKey: localGroupingKey, timestamp: effectiveTimestamp, flags: flags, tags: tags, globalTags: globalTags, localTags: [], forwardInfo: forwardInfo, authorId: authorId, text: sourceMessage.text, attributes: attributes, media: augmentedMediaList))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,610 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
private final class HistoryStateValidationBatch {
|
|
||||||
private let disposable: Disposable
|
|
||||||
let invalidatedState: HistoryState
|
|
||||||
|
|
||||||
var cancelledMessageIds = Set<MessageId>()
|
|
||||||
|
|
||||||
init(disposable: Disposable, invalidatedState: HistoryState) {
|
|
||||||
self.disposable = disposable
|
|
||||||
self.invalidatedState = invalidatedState
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
disposable.dispose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class HistoryStateValidationContext {
|
|
||||||
var batchReferences: [MessageId: HistoryStateValidationBatch] = [:]
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum HistoryState {
|
|
||||||
case channel(PeerId, ChannelState)
|
|
||||||
//case group(PeerGroupId, TelegramPeerGroupState)
|
|
||||||
|
|
||||||
var hasInvalidationIndex: Bool {
|
|
||||||
switch self {
|
|
||||||
case let .channel(_, state):
|
|
||||||
return state.invalidatedPts != nil
|
|
||||||
/*case let .group(_, state):
|
|
||||||
return state.invalidatedStateIndex != nil*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func isMessageValid(_ message: Message) -> Bool {
|
|
||||||
switch self {
|
|
||||||
case let .channel(_, state):
|
|
||||||
if let invalidatedPts = state.invalidatedPts {
|
|
||||||
var messagePts: Int32?
|
|
||||||
inner: for attribute in message.attributes {
|
|
||||||
if let attribute = attribute as? ChannelMessageStateVersionAttribute {
|
|
||||||
messagePts = attribute.pts
|
|
||||||
break inner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var requiresValidation = false
|
|
||||||
if let messagePts = messagePts {
|
|
||||||
if messagePts < invalidatedPts {
|
|
||||||
requiresValidation = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
requiresValidation = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return !requiresValidation
|
|
||||||
} else {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
/*case let .group(_, state):
|
|
||||||
if let invalidatedStateIndex = state.invalidatedStateIndex {
|
|
||||||
var messageStateIndex: Int32?
|
|
||||||
inner: for attribute in message.attributes {
|
|
||||||
if let attribute = attribute as? PeerGroupMessageStateVersionAttribute {
|
|
||||||
messageStateIndex = attribute.stateIndex
|
|
||||||
break inner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var requiresValidation = false
|
|
||||||
if let messageStateIndex = messageStateIndex {
|
|
||||||
if messageStateIndex < invalidatedStateIndex {
|
|
||||||
requiresValidation = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
requiresValidation = true
|
|
||||||
}
|
|
||||||
return !requiresValidation
|
|
||||||
} else {
|
|
||||||
return true
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func matchesPeerId(_ peerId: PeerId) -> Bool {
|
|
||||||
switch self {
|
|
||||||
case let .channel(statePeerId, state):
|
|
||||||
return statePeerId == peerId
|
|
||||||
/*case .group:
|
|
||||||
return true*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func slicedForValidationMessages(_ messages: [MessageId]) -> [[MessageId]] {
|
|
||||||
let block = 64
|
|
||||||
|
|
||||||
if messages.count <= block {
|
|
||||||
return [messages]
|
|
||||||
} else {
|
|
||||||
var result: [[MessageId]] = []
|
|
||||||
var offset = 0
|
|
||||||
while offset < messages.count {
|
|
||||||
result.append(Array(messages[offset ..< min(offset + block, messages.count)]))
|
|
||||||
offset += block
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final class HistoryViewStateValidationContexts {
|
|
||||||
private let queue: Queue
|
|
||||||
private let postbox: Postbox
|
|
||||||
private let network: Network
|
|
||||||
private let accountPeerId: PeerId
|
|
||||||
|
|
||||||
private var contexts: [Int32: HistoryStateValidationContext] = [:]
|
|
||||||
|
|
||||||
init(queue: Queue, postbox: Postbox, network: Network, accountPeerId: PeerId) {
|
|
||||||
self.queue = queue
|
|
||||||
self.postbox = postbox
|
|
||||||
self.network = network
|
|
||||||
self.accountPeerId = accountPeerId
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateView(id: Int32, view: MessageHistoryView?) {
|
|
||||||
assert(self.queue.isCurrent())
|
|
||||||
guard let view = view, view.tagMask == nil || view.tagMask == MessageTags.unseenPersonalMessage else {
|
|
||||||
if self.contexts[id] != nil {
|
|
||||||
self.contexts.removeValue(forKey: id)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var historyState: HistoryState?
|
|
||||||
for entry in view.additionalData {
|
|
||||||
if case let .peerChatState(peerId, chatState) = entry {
|
|
||||||
if let chatState = chatState as? ChannelState {
|
|
||||||
historyState = .channel(peerId, chatState)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let historyState = historyState, historyState.hasInvalidationIndex {
|
|
||||||
var rangesToInvalidate: [[MessageId]] = []
|
|
||||||
let addToRange: (MessageId, inout [[MessageId]]) -> Void = { id, ranges in
|
|
||||||
if ranges.isEmpty {
|
|
||||||
ranges = [[id]]
|
|
||||||
} else {
|
|
||||||
ranges[ranges.count - 1].append(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let addRangeBreak: (inout [[MessageId]]) -> Void = { ranges in
|
|
||||||
if ranges.last?.count != 0 {
|
|
||||||
ranges.append([])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for entry in view.entries {
|
|
||||||
if historyState.matchesPeerId(entry.message.id.peerId) && entry.message.id.namespace == Namespaces.Message.Cloud {
|
|
||||||
if !historyState.isMessageValid(entry.message) {
|
|
||||||
addToRange(entry.message.id, &rangesToInvalidate)
|
|
||||||
} else {
|
|
||||||
addRangeBreak(&rangesToInvalidate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !rangesToInvalidate.isEmpty && rangesToInvalidate[rangesToInvalidate.count - 1].isEmpty {
|
|
||||||
rangesToInvalidate.removeLast()
|
|
||||||
}
|
|
||||||
|
|
||||||
var invalidatedMessageIds = Set<MessageId>()
|
|
||||||
|
|
||||||
if !rangesToInvalidate.isEmpty {
|
|
||||||
let context: HistoryStateValidationContext
|
|
||||||
if let current = self.contexts[id] {
|
|
||||||
context = current
|
|
||||||
} else {
|
|
||||||
context = HistoryStateValidationContext()
|
|
||||||
self.contexts[id] = context
|
|
||||||
}
|
|
||||||
|
|
||||||
var addedRanges: [[MessageId]] = []
|
|
||||||
for messages in rangesToInvalidate {
|
|
||||||
for id in messages {
|
|
||||||
invalidatedMessageIds.insert(id)
|
|
||||||
|
|
||||||
if context.batchReferences[id] != nil {
|
|
||||||
addRangeBreak(&addedRanges)
|
|
||||||
} else {
|
|
||||||
addToRange(id, &addedRanges)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addRangeBreak(&addedRanges)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !addedRanges.isEmpty && addedRanges[addedRanges.count - 1].isEmpty {
|
|
||||||
addedRanges.removeLast()
|
|
||||||
}
|
|
||||||
|
|
||||||
for rangeMessages in addedRanges {
|
|
||||||
for messages in slicedForValidationMessages(rangeMessages) {
|
|
||||||
let disposable = MetaDisposable()
|
|
||||||
let batch = HistoryStateValidationBatch(disposable: disposable, invalidatedState: historyState)
|
|
||||||
for messageId in messages {
|
|
||||||
context.batchReferences[messageId] = batch
|
|
||||||
}
|
|
||||||
|
|
||||||
disposable.set((validateBatch(postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, tag: view.tagMask, messageIds: messages, historyState: historyState)
|
|
||||||
|> deliverOn(self.queue)).start(completed: { [weak self, weak batch] in
|
|
||||||
if let strongSelf = self, let context = strongSelf.contexts[id], let batch = batch {
|
|
||||||
var completedMessageIds: [MessageId] = []
|
|
||||||
for (messageId, messageBatch) in context.batchReferences {
|
|
||||||
if messageBatch === batch {
|
|
||||||
completedMessageIds.append(messageId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for messageId in completedMessageIds {
|
|
||||||
context.batchReferences.removeValue(forKey: messageId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let context = self.contexts[id] {
|
|
||||||
var removeIds: [MessageId] = []
|
|
||||||
|
|
||||||
for batchMessageId in context.batchReferences.keys {
|
|
||||||
if !invalidatedMessageIds.contains(batchMessageId) {
|
|
||||||
removeIds.append(batchMessageId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for messageId in removeIds {
|
|
||||||
context.batchReferences.removeValue(forKey: messageId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func hashForMessages(_ messages: [Message], withChannelIds: Bool) -> Int32 {
|
|
||||||
var acc: UInt32 = 0
|
|
||||||
|
|
||||||
let sorted = messages.sorted(by: { $0.index > $1.index })
|
|
||||||
|
|
||||||
for message in sorted {
|
|
||||||
if withChannelIds {
|
|
||||||
acc = (acc &* 20261) &+ UInt32(message.id.peerId.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
acc = (acc &* 20261) &+ UInt32(message.id.id)
|
|
||||||
var timestamp = message.timestamp
|
|
||||||
inner: for attribute in message.attributes {
|
|
||||||
if let attribute = attribute as? EditedMessageAttribute {
|
|
||||||
timestamp = attribute.date
|
|
||||||
break inner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
acc = (acc &* 20261) &+ UInt32(timestamp)
|
|
||||||
}
|
|
||||||
return Int32(bitPattern: acc & UInt32(0x7FFFFFFF))
|
|
||||||
}
|
|
||||||
|
|
||||||
private func hashForMessages(_ messages: [StoreMessage], withChannelIds: Bool) -> Int32 {
|
|
||||||
var acc: UInt32 = 0
|
|
||||||
|
|
||||||
for message in messages {
|
|
||||||
if case let .Id(id) = message.id {
|
|
||||||
if withChannelIds {
|
|
||||||
acc = (acc &* 20261) &+ UInt32(id.peerId.id)
|
|
||||||
}
|
|
||||||
acc = (acc &* 20261) &+ UInt32(id.id)
|
|
||||||
var timestamp = message.timestamp
|
|
||||||
inner: for attribute in message.attributes {
|
|
||||||
if let attribute = attribute as? EditedMessageAttribute {
|
|
||||||
timestamp = attribute.date
|
|
||||||
break inner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
acc = (acc &* 20261) &+ UInt32(timestamp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Int32(bitPattern: acc & UInt32(0x7FFFFFFF))
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum ValidatedMessages {
|
|
||||||
case notModified
|
|
||||||
case messages([Api.Message], [Api.Chat], [Api.User], Int32?)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func validateBatch(postbox: Postbox, network: Network, accountPeerId: PeerId, tag: MessageTags?, messageIds: [MessageId], historyState: HistoryState) -> Signal<Void, NoError> {
|
|
||||||
return postbox.transaction { transaction -> Signal<Void, NoError> in
|
|
||||||
var previousMessages: [Message] = []
|
|
||||||
var previous: [MessageId: Message] = [:]
|
|
||||||
for messageId in messageIds {
|
|
||||||
if let message = transaction.getMessage(messageId) {
|
|
||||||
previousMessages.append(message)
|
|
||||||
previous[message.id] = message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var signal: Signal<ValidatedMessages, MTRpcError>
|
|
||||||
switch historyState {
|
|
||||||
case let .channel(peerId, _):
|
|
||||||
let hash = hashForMessages(previousMessages, withChannelIds: false)
|
|
||||||
Logger.shared.log("HistoryValidation", "validate batch for \(peerId): \(previousMessages.map({ $0.id }))")
|
|
||||||
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
|
||||||
let requestSignal: Signal<Api.messages.Messages, MTRpcError>
|
|
||||||
if let tag = tag {
|
|
||||||
if tag == MessageTags.unseenPersonalMessage {
|
|
||||||
requestSignal = network.request(Api.functions.messages.getUnreadMentions(peer: inputPeer, offsetId: messageIds[messageIds.count - 1].id + 1, addOffset: 0, limit: Int32(messageIds.count), maxId: messageIds[messageIds.count - 1].id + 1, minId: messageIds[0].id - 1))
|
|
||||||
} else {
|
|
||||||
assertionFailure()
|
|
||||||
requestSignal = .complete()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
requestSignal = network.request(Api.functions.messages.getHistory(peer: inputPeer, offsetId: messageIds[messageIds.count - 1].id + 1, offsetDate: 0, addOffset: 0, limit: Int32(messageIds.count), maxId: messageIds[messageIds.count - 1].id + 1, minId: messageIds[0].id - 1, hash: hash))
|
|
||||||
}
|
|
||||||
|
|
||||||
signal = requestSignal
|
|
||||||
|> map { result -> ValidatedMessages in
|
|
||||||
let messages: [Api.Message]
|
|
||||||
let chats: [Api.Chat]
|
|
||||||
let users: [Api.User]
|
|
||||||
var channelPts: Int32?
|
|
||||||
|
|
||||||
switch result {
|
|
||||||
case let .messages(messages: apiMessages, chats: apiChats, users: apiUsers):
|
|
||||||
messages = apiMessages
|
|
||||||
chats = apiChats
|
|
||||||
users = apiUsers
|
|
||||||
case let .messagesSlice(_, _, _, messages: apiMessages, chats: apiChats, users: apiUsers):
|
|
||||||
messages = apiMessages
|
|
||||||
chats = apiChats
|
|
||||||
users = apiUsers
|
|
||||||
case let .channelMessages(_, pts, _, apiMessages, apiChats, apiUsers):
|
|
||||||
messages = apiMessages
|
|
||||||
chats = apiChats
|
|
||||||
users = apiUsers
|
|
||||||
channelPts = pts
|
|
||||||
case .messagesNotModified:
|
|
||||||
return .notModified
|
|
||||||
}
|
|
||||||
return .messages(messages, chats, users, channelPts)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return .complete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return signal
|
|
||||||
|> map(Optional.init)
|
|
||||||
|> `catch` { _ -> Signal<ValidatedMessages?, NoError> in
|
|
||||||
return .single(nil)
|
|
||||||
}
|
|
||||||
|> mapToSignal { result -> Signal<Void, NoError> in
|
|
||||||
guard let result = result else {
|
|
||||||
return .complete()
|
|
||||||
}
|
|
||||||
switch result {
|
|
||||||
case let .messages(messages, chats, users, channelPts):
|
|
||||||
var storeMessages: [StoreMessage] = []
|
|
||||||
|
|
||||||
for message in messages {
|
|
||||||
if let storeMessage = StoreMessage(apiMessage: message) {
|
|
||||||
var attributes = storeMessage.attributes
|
|
||||||
|
|
||||||
if let channelPts = channelPts {
|
|
||||||
attributes.append(ChannelMessageStateVersionAttribute(pts: channelPts))
|
|
||||||
}
|
|
||||||
|
|
||||||
storeMessages.append(storeMessage.withUpdatedAttributes(attributes))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var validMessageIds = Set<MessageId>()
|
|
||||||
for message in storeMessages {
|
|
||||||
if case let .Id(id) = message.id {
|
|
||||||
validMessageIds.insert(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var maybeRemovedMessageIds: [MessageId] = []
|
|
||||||
for id in previous.keys {
|
|
||||||
if !validMessageIds.contains(id) {
|
|
||||||
maybeRemovedMessageIds.append(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let actuallyRemovedMessagesSignal: Signal<Set<MessageId>, NoError>
|
|
||||||
if maybeRemovedMessageIds.isEmpty {
|
|
||||||
actuallyRemovedMessagesSignal = .single(Set())
|
|
||||||
} else {
|
|
||||||
actuallyRemovedMessagesSignal = postbox.transaction { transaction -> Signal<Set<MessageId>, NoError> in
|
|
||||||
switch historyState {
|
|
||||||
case let .channel(peerId, _):
|
|
||||||
if let inputChannel = transaction.getPeer(peerId).flatMap(apiInputChannel) {
|
|
||||||
return network.request(Api.functions.channels.getMessages(channel: inputChannel, id: maybeRemovedMessageIds.map({ Api.InputMessage.inputMessageID(id: $0.id) })))
|
|
||||||
|> map { result -> Set<MessageId> in
|
|
||||||
let apiMessages: [Api.Message]
|
|
||||||
switch result {
|
|
||||||
case let .channelMessages(_, _, _, messages, _, _):
|
|
||||||
apiMessages = messages
|
|
||||||
case let .messages(messages, _, _):
|
|
||||||
apiMessages = messages
|
|
||||||
case let .messagesSlice(_, _, _, messages, _, _):
|
|
||||||
apiMessages = messages
|
|
||||||
case .messagesNotModified:
|
|
||||||
return Set()
|
|
||||||
}
|
|
||||||
var ids = Set<MessageId>()
|
|
||||||
for message in apiMessages {
|
|
||||||
if let parsedMessage = StoreMessage(apiMessage: message), case let .Id(id) = parsedMessage.id {
|
|
||||||
if let tag = tag {
|
|
||||||
if parsedMessage.tags.contains(tag) {
|
|
||||||
ids.insert(id)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ids.insert(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Set(maybeRemovedMessageIds).subtracting(ids)
|
|
||||||
}
|
|
||||||
|> `catch` { _ -> Signal<Set<MessageId>, NoError> in
|
|
||||||
return .single(Set(maybeRemovedMessageIds))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return .single(Set(maybeRemovedMessageIds))
|
|
||||||
}
|
|
||||||
|> switchToLatest
|
|
||||||
}
|
|
||||||
|
|
||||||
return actuallyRemovedMessagesSignal
|
|
||||||
|> mapToSignal { removedMessageIds -> Signal<Void, NoError> in
|
|
||||||
return postbox.transaction { transaction -> Void in
|
|
||||||
var validMessageIds = Set<MessageId>()
|
|
||||||
for message in storeMessages {
|
|
||||||
if case let .Id(id) = message.id {
|
|
||||||
validMessageIds.insert(id)
|
|
||||||
let previousMessage = previous[id] ?? transaction.getMessage(id)
|
|
||||||
|
|
||||||
if let previousMessage = previousMessage {
|
|
||||||
var updatedTimestamp = message.timestamp
|
|
||||||
inner: for attribute in message.attributes {
|
|
||||||
if let attribute = attribute as? EditedMessageAttribute {
|
|
||||||
updatedTimestamp = attribute.date
|
|
||||||
break inner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var timestamp = previousMessage.timestamp
|
|
||||||
inner: for attribute in previousMessage.attributes {
|
|
||||||
if let attribute = attribute as? EditedMessageAttribute {
|
|
||||||
timestamp = attribute.date
|
|
||||||
break inner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
transaction.updateMessage(id, update: { currentMessage in
|
|
||||||
if updatedTimestamp != timestamp {
|
|
||||||
var updatedLocalTags = message.localTags
|
|
||||||
if currentMessage.localTags.contains(.OutgoingLiveLocation) {
|
|
||||||
updatedLocalTags.insert(.OutgoingLiveLocation)
|
|
||||||
}
|
|
||||||
return .update(message.withUpdatedLocalTags(updatedLocalTags))
|
|
||||||
} else {
|
|
||||||
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
|
|
||||||
if let channelPts = channelPts {
|
|
||||||
for i in (0 ..< attributes.count).reversed() {
|
|
||||||
if let _ = attributes[i] as? ChannelMessageStateVersionAttribute {
|
|
||||||
attributes.remove(at: i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
attributes.append(ChannelMessageStateVersionAttribute(pts: channelPts))
|
|
||||||
}
|
|
||||||
|
|
||||||
let updatedFlags = StoreMessageFlags(currentMessage.flags)
|
|
||||||
|
|
||||||
return .update(StoreMessage(id: message.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: updatedFlags, tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if previous[id] == nil {
|
|
||||||
print("\(id) missing")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let _ = transaction.addMessages([message], location: .Random)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let tag = tag {
|
|
||||||
for (_, previousMessage) in previous {
|
|
||||||
if !validMessageIds.contains(previousMessage.id) {
|
|
||||||
transaction.updateMessage(previousMessage.id, update: { currentMessage in
|
|
||||||
var updatedTags = currentMessage.tags
|
|
||||||
updatedTags.remove(tag)
|
|
||||||
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
|
|
||||||
for i in (0 ..< attributes.count).reversed() {
|
|
||||||
switch historyState {
|
|
||||||
case .channel:
|
|
||||||
if let _ = attributes[i] as? ChannelMessageStateVersionAttribute {
|
|
||||||
attributes.remove(at: i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch historyState {
|
|
||||||
case let .channel(_, channelState):
|
|
||||||
attributes.append(ChannelMessageStateVersionAttribute(pts: channelState.pts))
|
|
||||||
}
|
|
||||||
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: updatedTags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for id in removedMessageIds {
|
|
||||||
if !validMessageIds.contains(id) {
|
|
||||||
if let tag = tag {
|
|
||||||
transaction.updateMessage(id, update: { currentMessage in
|
|
||||||
var updatedTags = currentMessage.tags
|
|
||||||
updatedTags.remove(tag)
|
|
||||||
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
|
|
||||||
for i in (0 ..< attributes.count).reversed() {
|
|
||||||
switch historyState {
|
|
||||||
case .channel:
|
|
||||||
if let _ = attributes[i] as? ChannelMessageStateVersionAttribute {
|
|
||||||
attributes.remove(at: i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch historyState {
|
|
||||||
case let .channel(_, channelState):
|
|
||||||
attributes.append(ChannelMessageStateVersionAttribute(pts: channelState.pts))
|
|
||||||
}
|
|
||||||
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: updatedTags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
switch historyState {
|
|
||||||
case .channel:
|
|
||||||
deleteMessages(transaction: transaction, mediaBox: postbox.mediaBox, ids: [id])
|
|
||||||
Logger.shared.log("HistoryValidation", "deleting message \(id) in \(id.peerId)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case .notModified:
|
|
||||||
return postbox.transaction { transaction -> Void in
|
|
||||||
for id in previous.keys {
|
|
||||||
transaction.updateMessage(id, update: { currentMessage in
|
|
||||||
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
|
|
||||||
for i in (0 ..< attributes.count).reversed() {
|
|
||||||
switch historyState {
|
|
||||||
case .channel:
|
|
||||||
if let _ = attributes[i] as? ChannelMessageStateVersionAttribute {
|
|
||||||
attributes.remove(at: i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch historyState {
|
|
||||||
case let .channel(_, channelState):
|
|
||||||
attributes.append(ChannelMessageStateVersionAttribute(pts: channelState.pts))
|
|
||||||
}
|
|
||||||
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))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} |> switchToLatest
|
|
||||||
}
|
|
||||||
@ -0,0 +1,695 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
private final class HistoryStateValidationBatch {
|
||||||
|
private let disposable: Disposable
|
||||||
|
let invalidatedState: HistoryState?
|
||||||
|
|
||||||
|
var cancelledMessageIds = Set<MessageId>()
|
||||||
|
|
||||||
|
init(disposable: Disposable, invalidatedState: HistoryState? = nil) {
|
||||||
|
self.disposable = disposable
|
||||||
|
self.invalidatedState = invalidatedState
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.disposable.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class HistoryStateValidationContext {
|
||||||
|
var batchReferences: [MessageId: HistoryStateValidationBatch] = [:]
|
||||||
|
var batch: HistoryStateValidationBatch?
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum HistoryState {
|
||||||
|
case channel(PeerId, ChannelState)
|
||||||
|
//case group(PeerGroupId, TelegramPeerGroupState)
|
||||||
|
case scheduledMessages(PeerId)
|
||||||
|
|
||||||
|
var hasInvalidationIndex: Bool {
|
||||||
|
switch self {
|
||||||
|
case let .channel(_, state):
|
||||||
|
return state.invalidatedPts != nil
|
||||||
|
/*case let .group(_, state):
|
||||||
|
return state.invalidatedStateIndex != nil*/
|
||||||
|
case .scheduledMessages:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isMessageValid(_ message: Message) -> Bool {
|
||||||
|
switch self {
|
||||||
|
case let .channel(_, state):
|
||||||
|
if let invalidatedPts = state.invalidatedPts {
|
||||||
|
var messagePts: Int32?
|
||||||
|
inner: for attribute in message.attributes {
|
||||||
|
if let attribute = attribute as? ChannelMessageStateVersionAttribute {
|
||||||
|
messagePts = attribute.pts
|
||||||
|
break inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var requiresValidation = false
|
||||||
|
if let messagePts = messagePts {
|
||||||
|
if messagePts < invalidatedPts {
|
||||||
|
requiresValidation = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
requiresValidation = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return !requiresValidation
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
/*case let .group(_, state):
|
||||||
|
if let invalidatedStateIndex = state.invalidatedStateIndex {
|
||||||
|
var messageStateIndex: Int32?
|
||||||
|
inner: for attribute in message.attributes {
|
||||||
|
if let attribute = attribute as? PeerGroupMessageStateVersionAttribute {
|
||||||
|
messageStateIndex = attribute.stateIndex
|
||||||
|
break inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var requiresValidation = false
|
||||||
|
if let messageStateIndex = messageStateIndex {
|
||||||
|
if messageStateIndex < invalidatedStateIndex {
|
||||||
|
requiresValidation = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
requiresValidation = true
|
||||||
|
}
|
||||||
|
return !requiresValidation
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}*/
|
||||||
|
case .scheduledMessages:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchesPeerId(_ peerId: PeerId) -> Bool {
|
||||||
|
switch self {
|
||||||
|
case let .channel(statePeerId, _):
|
||||||
|
return statePeerId == peerId
|
||||||
|
/*case .group:
|
||||||
|
return true*/
|
||||||
|
case let .scheduledMessages(statePeerId):
|
||||||
|
return statePeerId == peerId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func slicedForValidationMessages(_ messages: [MessageId]) -> [[MessageId]] {
|
||||||
|
let block = 64
|
||||||
|
|
||||||
|
if messages.count <= block {
|
||||||
|
return [messages]
|
||||||
|
} else {
|
||||||
|
var result: [[MessageId]] = []
|
||||||
|
var offset = 0
|
||||||
|
while offset < messages.count {
|
||||||
|
result.append(Array(messages[offset ..< min(offset + block, messages.count)]))
|
||||||
|
offset += block
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class HistoryViewStateValidationContexts {
|
||||||
|
private let queue: Queue
|
||||||
|
private let postbox: Postbox
|
||||||
|
private let network: Network
|
||||||
|
private let accountPeerId: PeerId
|
||||||
|
|
||||||
|
private var contexts: [Int32: HistoryStateValidationContext] = [:]
|
||||||
|
|
||||||
|
init(queue: Queue, postbox: Postbox, network: Network, accountPeerId: PeerId) {
|
||||||
|
self.queue = queue
|
||||||
|
self.postbox = postbox
|
||||||
|
self.network = network
|
||||||
|
self.accountPeerId = accountPeerId
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateView(id: Int32, view: MessageHistoryView?, location: ChatLocation? = nil) {
|
||||||
|
assert(self.queue.isCurrent())
|
||||||
|
guard let view = view, view.tagMask == nil || view.tagMask == MessageTags.unseenPersonalMessage else {
|
||||||
|
if self.contexts[id] != nil {
|
||||||
|
self.contexts.removeValue(forKey: id)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var historyState: HistoryState?
|
||||||
|
for entry in view.additionalData {
|
||||||
|
if case let .peerChatState(peerId, chatState) = entry {
|
||||||
|
if let chatState = chatState as? ChannelState {
|
||||||
|
historyState = .channel(peerId, chatState)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let historyState = historyState, historyState.hasInvalidationIndex {
|
||||||
|
var rangesToInvalidate: [[MessageId]] = []
|
||||||
|
let addToRange: (MessageId, inout [[MessageId]]) -> Void = { id, ranges in
|
||||||
|
if ranges.isEmpty {
|
||||||
|
ranges = [[id]]
|
||||||
|
} else {
|
||||||
|
ranges[ranges.count - 1].append(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let addRangeBreak: (inout [[MessageId]]) -> Void = { ranges in
|
||||||
|
if ranges.last?.count != 0 {
|
||||||
|
ranges.append([])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for entry in view.entries {
|
||||||
|
if historyState.matchesPeerId(entry.message.id.peerId) && entry.message.id.namespace == Namespaces.Message.Cloud {
|
||||||
|
if !historyState.isMessageValid(entry.message) {
|
||||||
|
addToRange(entry.message.id, &rangesToInvalidate)
|
||||||
|
} else {
|
||||||
|
addRangeBreak(&rangesToInvalidate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !rangesToInvalidate.isEmpty && rangesToInvalidate[rangesToInvalidate.count - 1].isEmpty {
|
||||||
|
rangesToInvalidate.removeLast()
|
||||||
|
}
|
||||||
|
|
||||||
|
var invalidatedMessageIds = Set<MessageId>()
|
||||||
|
|
||||||
|
if !rangesToInvalidate.isEmpty {
|
||||||
|
let context: HistoryStateValidationContext
|
||||||
|
if let current = self.contexts[id] {
|
||||||
|
context = current
|
||||||
|
} else {
|
||||||
|
context = HistoryStateValidationContext()
|
||||||
|
self.contexts[id] = context
|
||||||
|
}
|
||||||
|
|
||||||
|
var addedRanges: [[MessageId]] = []
|
||||||
|
for messages in rangesToInvalidate {
|
||||||
|
for id in messages {
|
||||||
|
invalidatedMessageIds.insert(id)
|
||||||
|
|
||||||
|
if context.batchReferences[id] != nil {
|
||||||
|
addRangeBreak(&addedRanges)
|
||||||
|
} else {
|
||||||
|
addToRange(id, &addedRanges)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addRangeBreak(&addedRanges)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !addedRanges.isEmpty && addedRanges[addedRanges.count - 1].isEmpty {
|
||||||
|
addedRanges.removeLast()
|
||||||
|
}
|
||||||
|
|
||||||
|
for rangeMessages in addedRanges {
|
||||||
|
for messages in slicedForValidationMessages(rangeMessages) {
|
||||||
|
let disposable = MetaDisposable()
|
||||||
|
let batch = HistoryStateValidationBatch(disposable: disposable, invalidatedState: historyState)
|
||||||
|
for messageId in messages {
|
||||||
|
context.batchReferences[messageId] = batch
|
||||||
|
}
|
||||||
|
|
||||||
|
disposable.set((validateChannelMessagesBatch(postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, tag: view.tagMask, messageIds: messages, historyState: historyState)
|
||||||
|
|> deliverOn(self.queue)).start(completed: { [weak self, weak batch] in
|
||||||
|
if let strongSelf = self, let context = strongSelf.contexts[id], let batch = batch {
|
||||||
|
var completedMessageIds: [MessageId] = []
|
||||||
|
for (messageId, messageBatch) in context.batchReferences {
|
||||||
|
if messageBatch === batch {
|
||||||
|
completedMessageIds.append(messageId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for messageId in completedMessageIds {
|
||||||
|
context.batchReferences.removeValue(forKey: messageId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let context = self.contexts[id] {
|
||||||
|
var removeIds: [MessageId] = []
|
||||||
|
|
||||||
|
for batchMessageId in context.batchReferences.keys {
|
||||||
|
if !invalidatedMessageIds.contains(batchMessageId) {
|
||||||
|
removeIds.append(batchMessageId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for messageId in removeIds {
|
||||||
|
context.batchReferences.removeValue(forKey: messageId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if view.namespaces.contains(Namespaces.Message.ScheduledCloud) {
|
||||||
|
if let _ = self.contexts[id] {
|
||||||
|
} else if let location = location, case let .peer(peerId) = location {
|
||||||
|
let context = HistoryStateValidationContext()
|
||||||
|
self.contexts[id] = context
|
||||||
|
|
||||||
|
let disposable = MetaDisposable()
|
||||||
|
let batch = HistoryStateValidationBatch(disposable: disposable)
|
||||||
|
context.batch = batch
|
||||||
|
|
||||||
|
let messages: [Message] = view.entries.map { $0.message }
|
||||||
|
|
||||||
|
disposable.set((validateScheduledMessagesBatch(postbox: self.postbox, network: self.network, accountPeerId: peerId, tag: nil, messages: messages, historyState: .scheduledMessages(peerId))
|
||||||
|
|> deliverOn(self.queue)).start(completed: { [weak self] in
|
||||||
|
if let strongSelf = self, let context = strongSelf.contexts[id] {
|
||||||
|
context.batch = nil
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func hashForMessages(_ messages: [Message], withChannelIds: Bool) -> Int32 {
|
||||||
|
var acc: UInt32 = 0
|
||||||
|
|
||||||
|
let sorted = messages.sorted(by: { $0.index > $1.index })
|
||||||
|
|
||||||
|
for message in sorted {
|
||||||
|
if withChannelIds {
|
||||||
|
acc = (acc &* 20261) &+ UInt32(message.id.peerId.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
acc = (acc &* 20261) &+ UInt32(message.id.id)
|
||||||
|
var timestamp = message.timestamp
|
||||||
|
inner: for attribute in message.attributes {
|
||||||
|
if let attribute = attribute as? EditedMessageAttribute {
|
||||||
|
timestamp = attribute.date
|
||||||
|
break inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
acc = (acc &* 20261) &+ UInt32(timestamp)
|
||||||
|
}
|
||||||
|
return Int32(bitPattern: acc & UInt32(0x7FFFFFFF))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func hashForMessages(_ messages: [StoreMessage], withChannelIds: Bool) -> Int32 {
|
||||||
|
var acc: UInt32 = 0
|
||||||
|
|
||||||
|
for message in messages {
|
||||||
|
if case let .Id(id) = message.id {
|
||||||
|
if withChannelIds {
|
||||||
|
acc = (acc &* 20261) &+ UInt32(id.peerId.id)
|
||||||
|
}
|
||||||
|
acc = (acc &* 20261) &+ UInt32(id.id)
|
||||||
|
var timestamp = message.timestamp
|
||||||
|
inner: for attribute in message.attributes {
|
||||||
|
if let attribute = attribute as? EditedMessageAttribute {
|
||||||
|
timestamp = attribute.date
|
||||||
|
break inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
acc = (acc &* 20261) &+ UInt32(timestamp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Int32(bitPattern: acc & UInt32(0x7FFFFFFF))
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum ValidatedMessages {
|
||||||
|
case notModified
|
||||||
|
case messages([Api.Message], [Api.Chat], [Api.User], Int32?)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func validateChannelMessagesBatch(postbox: Postbox, network: Network, accountPeerId: PeerId, tag: MessageTags?, messageIds: [MessageId], historyState: HistoryState) -> Signal<Void, NoError> {
|
||||||
|
return postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||||
|
var previousMessages: [Message] = []
|
||||||
|
var previous: [MessageId: Message] = [:]
|
||||||
|
for messageId in messageIds {
|
||||||
|
if let message = transaction.getMessage(messageId) {
|
||||||
|
previousMessages.append(message)
|
||||||
|
previous[message.id] = message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var signal: Signal<ValidatedMessages, MTRpcError>
|
||||||
|
switch historyState {
|
||||||
|
case let .channel(peerId, _):
|
||||||
|
let hash = hashForMessages(previousMessages, withChannelIds: false)
|
||||||
|
Logger.shared.log("HistoryValidation", "validate batch for \(peerId): \(previousMessages.map({ $0.id }))")
|
||||||
|
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||||
|
let requestSignal: Signal<Api.messages.Messages, MTRpcError>
|
||||||
|
if let tag = tag {
|
||||||
|
if tag == MessageTags.unseenPersonalMessage {
|
||||||
|
requestSignal = network.request(Api.functions.messages.getUnreadMentions(peer: inputPeer, offsetId: messageIds[messageIds.count - 1].id + 1, addOffset: 0, limit: Int32(messageIds.count), maxId: messageIds[messageIds.count - 1].id + 1, minId: messageIds[0].id - 1))
|
||||||
|
} else {
|
||||||
|
assertionFailure()
|
||||||
|
requestSignal = .complete()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
requestSignal = network.request(Api.functions.messages.getHistory(peer: inputPeer, offsetId: messageIds[messageIds.count - 1].id + 1, offsetDate: 0, addOffset: 0, limit: Int32(messageIds.count), maxId: messageIds[messageIds.count - 1].id + 1, minId: messageIds[0].id - 1, hash: hash))
|
||||||
|
}
|
||||||
|
|
||||||
|
signal = requestSignal
|
||||||
|
|> map { result -> ValidatedMessages in
|
||||||
|
let messages: [Api.Message]
|
||||||
|
let chats: [Api.Chat]
|
||||||
|
let users: [Api.User]
|
||||||
|
var channelPts: Int32?
|
||||||
|
|
||||||
|
switch result {
|
||||||
|
case let .messages(messages: apiMessages, chats: apiChats, users: apiUsers):
|
||||||
|
messages = apiMessages
|
||||||
|
chats = apiChats
|
||||||
|
users = apiUsers
|
||||||
|
case let .messagesSlice(_, _, _, messages: apiMessages, chats: apiChats, users: apiUsers):
|
||||||
|
messages = apiMessages
|
||||||
|
chats = apiChats
|
||||||
|
users = apiUsers
|
||||||
|
case let .channelMessages(_, pts, _, apiMessages, apiChats, apiUsers):
|
||||||
|
messages = apiMessages
|
||||||
|
chats = apiChats
|
||||||
|
users = apiUsers
|
||||||
|
channelPts = pts
|
||||||
|
case .messagesNotModified:
|
||||||
|
return .notModified
|
||||||
|
}
|
||||||
|
return .messages(messages, chats, users, channelPts)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
signal = .complete()
|
||||||
|
}
|
||||||
|
|
||||||
|
return validateBatch(postbox: postbox, network: network, transaction: transaction, accountPeerId: accountPeerId, tag: tag, historyState: historyState, signal: signal, previous: previous, messageNamespace: Namespaces.Message.Cloud)
|
||||||
|
} |> switchToLatest
|
||||||
|
}
|
||||||
|
|
||||||
|
private func validateScheduledMessagesBatch(postbox: Postbox, network: Network, accountPeerId: PeerId, tag: MessageTags?, messages: [Message], historyState: HistoryState) -> Signal<Void, NoError> {
|
||||||
|
return postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||||
|
var signal: Signal<ValidatedMessages, MTRpcError>
|
||||||
|
switch historyState {
|
||||||
|
case let .scheduledMessages(peerId):
|
||||||
|
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||||
|
signal = network.request(Api.functions.messages.getScheduledHistory(peer: inputPeer, hash: 0))
|
||||||
|
|> map { result -> ValidatedMessages in
|
||||||
|
let messages: [Api.Message]
|
||||||
|
let chats: [Api.Chat]
|
||||||
|
let users: [Api.User]
|
||||||
|
|
||||||
|
switch result {
|
||||||
|
case let .messages(messages: apiMessages, chats: apiChats, users: apiUsers):
|
||||||
|
messages = apiMessages
|
||||||
|
chats = apiChats
|
||||||
|
users = apiUsers
|
||||||
|
case let .messagesSlice(_, _, _, messages: apiMessages, chats: apiChats, users: apiUsers):
|
||||||
|
messages = apiMessages
|
||||||
|
chats = apiChats
|
||||||
|
users = apiUsers
|
||||||
|
case let .channelMessages(_, _, _, apiMessages, apiChats, apiUsers):
|
||||||
|
messages = apiMessages
|
||||||
|
chats = apiChats
|
||||||
|
users = apiUsers
|
||||||
|
case .messagesNotModified:
|
||||||
|
return .notModified
|
||||||
|
}
|
||||||
|
return .messages(messages, chats, users, nil)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
signal = .complete()
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
signal = .complete()
|
||||||
|
}
|
||||||
|
var previous: [MessageId: Message] = [:]
|
||||||
|
for message in messages {
|
||||||
|
previous[message.id] = message
|
||||||
|
}
|
||||||
|
return validateBatch(postbox: postbox, network: network, transaction: transaction, accountPeerId: accountPeerId, tag: tag, historyState: historyState, signal: signal, previous: previous, messageNamespace: Namespaces.Message.ScheduledCloud)
|
||||||
|
} |> switchToLatest
|
||||||
|
}
|
||||||
|
|
||||||
|
private func validateBatch(postbox: Postbox, network: Network, transaction: Transaction, accountPeerId: PeerId, tag: MessageTags?, historyState: HistoryState, signal: Signal<ValidatedMessages, MTRpcError>, previous: [MessageId: Message], messageNamespace: MessageId.Namespace) -> Signal<Void, NoError> {
|
||||||
|
return signal
|
||||||
|
|> map(Optional.init)
|
||||||
|
|> `catch` { _ -> Signal<ValidatedMessages?, NoError> in
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
|> mapToSignal { result -> Signal<Void, NoError> in
|
||||||
|
guard let result = result else {
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
switch result {
|
||||||
|
case let .messages(messages, _, users, channelPts):
|
||||||
|
var storeMessages: [StoreMessage] = []
|
||||||
|
|
||||||
|
for message in messages {
|
||||||
|
if let storeMessage = StoreMessage(apiMessage: message, namespace: messageNamespace) {
|
||||||
|
var attributes = storeMessage.attributes
|
||||||
|
if let channelPts = channelPts {
|
||||||
|
attributes.append(ChannelMessageStateVersionAttribute(pts: channelPts))
|
||||||
|
}
|
||||||
|
storeMessages.append(storeMessage.withUpdatedAttributes(attributes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var validMessageIds = Set<MessageId>()
|
||||||
|
for message in storeMessages {
|
||||||
|
if case let .Id(id) = message.id {
|
||||||
|
validMessageIds.insert(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var maybeRemovedMessageIds: [MessageId] = []
|
||||||
|
for id in previous.keys {
|
||||||
|
if !validMessageIds.contains(id) {
|
||||||
|
maybeRemovedMessageIds.append(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let actuallyRemovedMessagesSignal: Signal<Set<MessageId>, NoError>
|
||||||
|
if maybeRemovedMessageIds.isEmpty {
|
||||||
|
actuallyRemovedMessagesSignal = .single(Set())
|
||||||
|
} else {
|
||||||
|
switch historyState {
|
||||||
|
case let .channel(peerId, _):
|
||||||
|
actuallyRemovedMessagesSignal = postbox.transaction { transaction -> Signal<Set<MessageId>, NoError> in
|
||||||
|
if let inputChannel = transaction.getPeer(peerId).flatMap(apiInputChannel) {
|
||||||
|
return network.request(Api.functions.channels.getMessages(channel: inputChannel, id: maybeRemovedMessageIds.map({ Api.InputMessage.inputMessageID(id: $0.id) })))
|
||||||
|
|> map { result -> Set<MessageId> in
|
||||||
|
let apiMessages: [Api.Message]
|
||||||
|
switch result {
|
||||||
|
case let .channelMessages(_, _, _, messages, _, _):
|
||||||
|
apiMessages = messages
|
||||||
|
case let .messages(messages, _, _):
|
||||||
|
apiMessages = messages
|
||||||
|
case let .messagesSlice(_, _, _, messages, _, _):
|
||||||
|
apiMessages = messages
|
||||||
|
case .messagesNotModified:
|
||||||
|
return Set()
|
||||||
|
}
|
||||||
|
var ids = Set<MessageId>()
|
||||||
|
for message in apiMessages {
|
||||||
|
if let parsedMessage = StoreMessage(apiMessage: message), case let .Id(id) = parsedMessage.id {
|
||||||
|
if let tag = tag {
|
||||||
|
if parsedMessage.tags.contains(tag) {
|
||||||
|
ids.insert(id)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ids.insert(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Set(maybeRemovedMessageIds).subtracting(ids)
|
||||||
|
}
|
||||||
|
|> `catch` { _ -> Signal<Set<MessageId>, NoError> in
|
||||||
|
return .single(Set(maybeRemovedMessageIds))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return .single(Set(maybeRemovedMessageIds))
|
||||||
|
} |> switchToLatest
|
||||||
|
default:
|
||||||
|
actuallyRemovedMessagesSignal = .single(Set(maybeRemovedMessageIds))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return actuallyRemovedMessagesSignal
|
||||||
|
|> mapToSignal { removedMessageIds -> Signal<Void, NoError> in
|
||||||
|
return postbox.transaction { transaction -> Void in
|
||||||
|
var validMessageIds = Set<MessageId>()
|
||||||
|
for message in storeMessages {
|
||||||
|
if case let .Id(id) = message.id {
|
||||||
|
validMessageIds.insert(id)
|
||||||
|
let previousMessage = previous[id] ?? transaction.getMessage(id)
|
||||||
|
|
||||||
|
if let previousMessage = previousMessage {
|
||||||
|
var updatedTimestamp = message.timestamp
|
||||||
|
inner: for attribute in message.attributes {
|
||||||
|
if let attribute = attribute as? EditedMessageAttribute {
|
||||||
|
updatedTimestamp = attribute.date
|
||||||
|
break inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var timestamp = previousMessage.timestamp
|
||||||
|
inner: for attribute in previousMessage.attributes {
|
||||||
|
if let attribute = attribute as? EditedMessageAttribute {
|
||||||
|
timestamp = attribute.date
|
||||||
|
break inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.updateMessage(id, update: { currentMessage in
|
||||||
|
if updatedTimestamp != timestamp {
|
||||||
|
var updatedLocalTags = message.localTags
|
||||||
|
if currentMessage.localTags.contains(.OutgoingLiveLocation) {
|
||||||
|
updatedLocalTags.insert(.OutgoingLiveLocation)
|
||||||
|
}
|
||||||
|
return .update(message.withUpdatedLocalTags(updatedLocalTags))
|
||||||
|
} else {
|
||||||
|
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
|
||||||
|
if let channelPts = channelPts {
|
||||||
|
for i in (0 ..< attributes.count).reversed() {
|
||||||
|
if let _ = attributes[i] as? ChannelMessageStateVersionAttribute {
|
||||||
|
attributes.remove(at: i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
attributes.append(ChannelMessageStateVersionAttribute(pts: channelPts))
|
||||||
|
}
|
||||||
|
|
||||||
|
let updatedFlags = StoreMessageFlags(currentMessage.flags)
|
||||||
|
|
||||||
|
return .update(StoreMessage(id: message.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: updatedFlags, tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if previous[id] == nil {
|
||||||
|
print("\(id) missing")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let _ = transaction.addMessages([message], location: .Random)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let tag = tag {
|
||||||
|
for (_, previousMessage) in previous {
|
||||||
|
if !validMessageIds.contains(previousMessage.id) {
|
||||||
|
transaction.updateMessage(previousMessage.id, update: { currentMessage in
|
||||||
|
var updatedTags = currentMessage.tags
|
||||||
|
updatedTags.remove(tag)
|
||||||
|
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
|
||||||
|
for i in (0 ..< attributes.count).reversed() {
|
||||||
|
switch historyState {
|
||||||
|
case .channel:
|
||||||
|
if let _ = attributes[i] as? ChannelMessageStateVersionAttribute {
|
||||||
|
attributes.remove(at: i)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch historyState {
|
||||||
|
case let .channel(_, channelState):
|
||||||
|
attributes.append(ChannelMessageStateVersionAttribute(pts: channelState.pts))
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: updatedTags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for id in removedMessageIds {
|
||||||
|
if !validMessageIds.contains(id) {
|
||||||
|
if let tag = tag {
|
||||||
|
transaction.updateMessage(id, update: { currentMessage in
|
||||||
|
var updatedTags = currentMessage.tags
|
||||||
|
updatedTags.remove(tag)
|
||||||
|
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
|
||||||
|
for i in (0 ..< attributes.count).reversed() {
|
||||||
|
switch historyState {
|
||||||
|
case .channel:
|
||||||
|
if let _ = attributes[i] as? ChannelMessageStateVersionAttribute {
|
||||||
|
attributes.remove(at: i)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch historyState {
|
||||||
|
case let .channel(_, channelState):
|
||||||
|
attributes.append(ChannelMessageStateVersionAttribute(pts: channelState.pts))
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: updatedTags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
deleteMessages(transaction: transaction, mediaBox: postbox.mediaBox, ids: [id])
|
||||||
|
Logger.shared.log("HistoryValidation", "deleting message \(id) in \(id.peerId)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case .notModified:
|
||||||
|
return postbox.transaction { transaction -> Void in
|
||||||
|
for id in previous.keys {
|
||||||
|
transaction.updateMessage(id, update: { currentMessage in
|
||||||
|
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
|
||||||
|
for i in (0 ..< attributes.count).reversed() {
|
||||||
|
switch historyState {
|
||||||
|
case .channel:
|
||||||
|
if let _ = attributes[i] as? ChannelMessageStateVersionAttribute {
|
||||||
|
attributes.remove(at: i)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch historyState {
|
||||||
|
case let .channel(_, channelState):
|
||||||
|
attributes.append(ChannelMessageStateVersionAttribute(pts: channelState.pts))
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
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))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -151,16 +151,57 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH
|
|||||||
var implicitelyFillHole = false
|
var implicitelyFillHole = false
|
||||||
let minMaxRange: ClosedRange<MessageId.Id>
|
let minMaxRange: ClosedRange<MessageId.Id>
|
||||||
|
|
||||||
if namespace == Namespaces.Message.ScheduledCloud {
|
switch space {
|
||||||
switch direction {
|
case .everywhere:
|
||||||
case .aroundId, .range:
|
let offsetId: Int32
|
||||||
implicitelyFillHole = true
|
let addOffset: Int32
|
||||||
}
|
let selectedLimit = limit
|
||||||
minMaxRange = 1 ... (Int32.max - 1)
|
let maxId: Int32
|
||||||
request = source.request(Api.functions.messages.getScheduledHistory(peer: inputPeer, hash: 0))
|
let minId: Int32
|
||||||
} else {
|
|
||||||
switch space {
|
switch direction {
|
||||||
case .everywhere:
|
case let .range(start, end):
|
||||||
|
if start.id <= end.id {
|
||||||
|
offsetId = start.id <= 1 ? 1 : (start.id - 1)
|
||||||
|
addOffset = Int32(-selectedLimit)
|
||||||
|
maxId = end.id
|
||||||
|
minId = start.id - 1
|
||||||
|
|
||||||
|
let rangeStartId = start.id
|
||||||
|
let rangeEndId = min(end.id, Int32.max - 1)
|
||||||
|
if rangeStartId <= rangeEndId {
|
||||||
|
minMaxRange = rangeStartId ... rangeEndId
|
||||||
|
} else {
|
||||||
|
minMaxRange = rangeStartId ... rangeStartId
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
offsetId = start.id == Int32.max ? start.id : (start.id + 1)
|
||||||
|
addOffset = 0
|
||||||
|
maxId = start.id == Int32.max ? start.id : (start.id + 1)
|
||||||
|
minId = end.id
|
||||||
|
|
||||||
|
let rangeStartId = end.id
|
||||||
|
let rangeEndId = min(start.id, Int32.max - 1)
|
||||||
|
if rangeStartId <= rangeEndId {
|
||||||
|
minMaxRange = rangeStartId ... rangeEndId
|
||||||
|
} else {
|
||||||
|
minMaxRange = rangeStartId ... rangeStartId
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case let .aroundId(id):
|
||||||
|
offsetId = id.id
|
||||||
|
addOffset = Int32(-selectedLimit / 2)
|
||||||
|
maxId = Int32.max
|
||||||
|
minId = 1
|
||||||
|
minMaxRange = 1 ... Int32.max - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
request = source.request(Api.functions.messages.getHistory(peer: inputPeer, offsetId: offsetId, offsetDate: 0, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId, hash: 0))
|
||||||
|
case let .tag(tag):
|
||||||
|
assert(tag.containsSingleElement)
|
||||||
|
if tag == .unseenPersonalMessage {
|
||||||
let offsetId: Int32
|
let offsetId: Int32
|
||||||
let addOffset: Int32
|
let addOffset: Int32
|
||||||
let selectedLimit = limit
|
let selectedLimit = limit
|
||||||
@ -203,122 +244,72 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH
|
|||||||
addOffset = Int32(-selectedLimit / 2)
|
addOffset = Int32(-selectedLimit / 2)
|
||||||
maxId = Int32.max
|
maxId = Int32.max
|
||||||
minId = 1
|
minId = 1
|
||||||
|
|
||||||
minMaxRange = 1 ... Int32.max - 1
|
minMaxRange = 1 ... Int32.max - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
request = source.request(Api.functions.messages.getHistory(peer: inputPeer, offsetId: offsetId, offsetDate: 0, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId, hash: 0))
|
request = source.request(Api.functions.messages.getUnreadMentions(peer: inputPeer, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId))
|
||||||
case let .tag(tag):
|
} else if tag == .liveLocation {
|
||||||
assert(tag.containsSingleElement)
|
let selectedLimit = limit
|
||||||
if tag == .unseenPersonalMessage {
|
|
||||||
let offsetId: Int32
|
|
||||||
let addOffset: Int32
|
|
||||||
let selectedLimit = limit
|
|
||||||
let maxId: Int32
|
|
||||||
let minId: Int32
|
|
||||||
|
|
||||||
switch direction {
|
switch direction {
|
||||||
case let .range(start, end):
|
case .aroundId, .range:
|
||||||
if start.id <= end.id {
|
implicitelyFillHole = true
|
||||||
offsetId = start.id <= 1 ? 1 : (start.id - 1)
|
}
|
||||||
addOffset = Int32(-selectedLimit)
|
minMaxRange = 1 ... (Int32.max - 1)
|
||||||
maxId = end.id
|
request = source.request(Api.functions.messages.getRecentLocations(peer: inputPeer, limit: Int32(selectedLimit), hash: 0))
|
||||||
minId = start.id - 1
|
} else if let filter = messageFilterForTagMask(tag) {
|
||||||
|
let offsetId: Int32
|
||||||
|
let addOffset: Int32
|
||||||
|
let selectedLimit = limit
|
||||||
|
let maxId: Int32
|
||||||
|
let minId: Int32
|
||||||
|
|
||||||
let rangeStartId = start.id
|
switch direction {
|
||||||
let rangeEndId = min(end.id, Int32.max - 1)
|
case let .range(start, end):
|
||||||
if rangeStartId <= rangeEndId {
|
if start.id <= end.id {
|
||||||
minMaxRange = rangeStartId ... rangeEndId
|
offsetId = start.id <= 1 ? 1 : (start.id - 1)
|
||||||
} else {
|
addOffset = Int32(-selectedLimit)
|
||||||
minMaxRange = rangeStartId ... rangeStartId
|
maxId = end.id
|
||||||
assertionFailure()
|
minId = start.id - 1
|
||||||
}
|
|
||||||
|
let rangeStartId = start.id
|
||||||
|
let rangeEndId = min(end.id, Int32.max - 1)
|
||||||
|
if rangeStartId <= rangeEndId {
|
||||||
|
minMaxRange = rangeStartId ... rangeEndId
|
||||||
} else {
|
} else {
|
||||||
offsetId = start.id == Int32.max ? start.id : (start.id + 1)
|
minMaxRange = rangeStartId ... rangeStartId
|
||||||
addOffset = 0
|
assertionFailure()
|
||||||
maxId = start.id == Int32.max ? start.id : (start.id + 1)
|
|
||||||
minId = end.id
|
|
||||||
|
|
||||||
let rangeStartId = end.id
|
|
||||||
let rangeEndId = min(start.id, Int32.max - 1)
|
|
||||||
if rangeStartId <= rangeEndId {
|
|
||||||
minMaxRange = rangeStartId ... rangeEndId
|
|
||||||
} else {
|
|
||||||
minMaxRange = rangeStartId ... rangeStartId
|
|
||||||
assertionFailure()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case let .aroundId(id):
|
} else {
|
||||||
offsetId = id.id
|
offsetId = start.id == Int32.max ? start.id : (start.id + 1)
|
||||||
addOffset = Int32(-selectedLimit / 2)
|
addOffset = 0
|
||||||
maxId = Int32.max
|
maxId = start.id == Int32.max ? start.id : (start.id + 1)
|
||||||
minId = 1
|
minId = end.id
|
||||||
|
|
||||||
minMaxRange = 1 ... Int32.max - 1
|
let rangeStartId = end.id
|
||||||
}
|
let rangeEndId = min(start.id, Int32.max - 1)
|
||||||
|
if rangeStartId <= rangeEndId {
|
||||||
request = source.request(Api.functions.messages.getUnreadMentions(peer: inputPeer, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId))
|
minMaxRange = rangeStartId ... rangeEndId
|
||||||
} else if tag == .liveLocation {
|
|
||||||
let selectedLimit = limit
|
|
||||||
|
|
||||||
switch direction {
|
|
||||||
case .aroundId, .range:
|
|
||||||
implicitelyFillHole = true
|
|
||||||
}
|
|
||||||
minMaxRange = 1 ... (Int32.max - 1)
|
|
||||||
request = source.request(Api.functions.messages.getRecentLocations(peer: inputPeer, limit: Int32(selectedLimit), hash: 0))
|
|
||||||
} else if let filter = messageFilterForTagMask(tag) {
|
|
||||||
let offsetId: Int32
|
|
||||||
let addOffset: Int32
|
|
||||||
let selectedLimit = limit
|
|
||||||
let maxId: Int32
|
|
||||||
let minId: Int32
|
|
||||||
|
|
||||||
switch direction {
|
|
||||||
case let .range(start, end):
|
|
||||||
if start.id <= end.id {
|
|
||||||
offsetId = start.id <= 1 ? 1 : (start.id - 1)
|
|
||||||
addOffset = Int32(-selectedLimit)
|
|
||||||
maxId = end.id
|
|
||||||
minId = start.id - 1
|
|
||||||
|
|
||||||
let rangeStartId = start.id
|
|
||||||
let rangeEndId = min(end.id, Int32.max - 1)
|
|
||||||
if rangeStartId <= rangeEndId {
|
|
||||||
minMaxRange = rangeStartId ... rangeEndId
|
|
||||||
} else {
|
|
||||||
minMaxRange = rangeStartId ... rangeStartId
|
|
||||||
assertionFailure()
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
offsetId = start.id == Int32.max ? start.id : (start.id + 1)
|
minMaxRange = rangeStartId ... rangeStartId
|
||||||
addOffset = 0
|
assertionFailure()
|
||||||
maxId = start.id == Int32.max ? start.id : (start.id + 1)
|
|
||||||
minId = end.id
|
|
||||||
|
|
||||||
let rangeStartId = end.id
|
|
||||||
let rangeEndId = min(start.id, Int32.max - 1)
|
|
||||||
if rangeStartId <= rangeEndId {
|
|
||||||
minMaxRange = rangeStartId ... rangeEndId
|
|
||||||
} else {
|
|
||||||
minMaxRange = rangeStartId ... rangeStartId
|
|
||||||
assertionFailure()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case let .aroundId(id):
|
}
|
||||||
offsetId = id.id
|
case let .aroundId(id):
|
||||||
addOffset = Int32(-selectedLimit / 2)
|
offsetId = id.id
|
||||||
maxId = Int32.max
|
addOffset = Int32(-selectedLimit / 2)
|
||||||
minId = 1
|
maxId = Int32.max
|
||||||
|
minId = 1
|
||||||
|
|
||||||
minMaxRange = 1 ... (Int32.max - 1)
|
minMaxRange = 1 ... (Int32.max - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
request = source.request(Api.functions.messages.search(flags: 0, peer: inputPeer, q: "", fromId: nil, filter: filter, minDate: 0, maxDate: 0, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId, hash: 0))
|
request = source.request(Api.functions.messages.search(flags: 0, peer: inputPeer, q: "", fromId: nil, filter: filter, minDate: 0, maxDate: 0, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId, hash: 0))
|
||||||
} else {
|
} else {
|
||||||
assertionFailure()
|
assertionFailure()
|
||||||
minMaxRange = 1 ... 1
|
minMaxRange = 1 ... 1
|
||||||
request = .never()
|
request = .never()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -63,7 +63,7 @@ private func withTakenOperation(postbox: Postbox, peerId: PeerId, tagLocalIndex:
|
|||||||
return postbox.transaction { transaction -> Signal<Void, NoError> in
|
return postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||||
var result: PeerMergedOperationLogEntry?
|
var result: PeerMergedOperationLogEntry?
|
||||||
transaction.operationLogUpdateEntry(peerId: peerId, tag: OperationLogTags.SynchronizeEmojiKeywords, tagLocalIndex: tagLocalIndex, { entry in
|
transaction.operationLogUpdateEntry(peerId: peerId, tag: OperationLogTags.SynchronizeEmojiKeywords, tagLocalIndex: tagLocalIndex, { entry in
|
||||||
if let entry = entry, let _ = entry.mergedIndex, entry.contents is SynchronizeEmojiKeywordsOperation {
|
if let entry = entry, let _ = entry.mergedIndex, entry.contents is SynchronizeEmojiKeywordsOperation {
|
||||||
result = entry.mergedEntry!
|
result = entry.mergedEntry!
|
||||||
return PeerOperationLogEntryUpdate(mergedIndex: .none, contents: .none)
|
return PeerOperationLogEntryUpdate(mergedIndex: .none, contents: .none)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -12,6 +12,8 @@ public struct Namespaces {
|
|||||||
public static let SecretIncoming: Int32 = 2
|
public static let SecretIncoming: Int32 = 2
|
||||||
public static let ScheduledCloud: Int32 = 3
|
public static let ScheduledCloud: Int32 = 3
|
||||||
public static let ScheduledLocal: Int32 = 4
|
public static let ScheduledLocal: Int32 = 4
|
||||||
|
|
||||||
|
public static let allScheduled: Set<Int32> = Set([Namespaces.Message.ScheduledCloud, Namespaces.Message.ScheduledLocal])
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct Media {
|
public struct Media {
|
||||||
@ -110,6 +112,7 @@ public extension LocalMessageTags {
|
|||||||
public extension PendingMessageActionType {
|
public extension PendingMessageActionType {
|
||||||
static let consumeUnseenPersonalMessage = PendingMessageActionType(rawValue: 0)
|
static let consumeUnseenPersonalMessage = PendingMessageActionType(rawValue: 0)
|
||||||
static let updateReaction = PendingMessageActionType(rawValue: 1)
|
static let updateReaction = PendingMessageActionType(rawValue: 1)
|
||||||
|
static let sendScheduledMessageImmediately = PendingMessageActionType(rawValue: 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
let peerIdNamespacesWithInitialCloudMessageHoles = [Namespaces.Peer.CloudUser, Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel]
|
let peerIdNamespacesWithInitialCloudMessageHoles = [Namespaces.Peer.CloudUser, Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel]
|
||||||
|
|||||||
@ -9,21 +9,171 @@ import Foundation
|
|||||||
import TelegramApi
|
import TelegramApi
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
public func sendScheduledMessageNow(account: Account, messageId: MessageId) -> Signal<Void, NoError> {
|
final class SendScheduledMessageImmediatelyAction: PendingMessageActionData {
|
||||||
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
|
init() {
|
||||||
if let _ = transaction.getMessage(messageId), let peer = transaction.getPeer(messageId.peerId), let inputPeer = apiInputPeer(peer) {
|
}
|
||||||
return account.network.request(Api.functions.messages.sendScheduledMessages(peer: inputPeer, id: [messageId.id]))
|
|
||||||
|> map(Optional.init)
|
init(decoder: PostboxDecoder) {
|
||||||
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
|
}
|
||||||
return .single(nil)
|
|
||||||
}
|
func encode(_ encoder: PostboxEncoder) {
|
||||||
|> mapToSignal { updates -> Signal<Void, NoError> in
|
}
|
||||||
if let updates = updates {
|
|
||||||
account.stateManager.addUpdates(updates)
|
func isEqual(to: PendingMessageActionData) -> Bool {
|
||||||
|
if let _ = to as? SendScheduledMessageImmediatelyAction {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func sendScheduledMessageNowInteractively(postbox: Postbox, messageId: MessageId) -> Signal<Never, NoError> {
|
||||||
|
return postbox.transaction { transaction -> Void in
|
||||||
|
transaction.setPendingMessageAction(type: .sendScheduledMessageImmediately, id: messageId, action: SendScheduledMessageImmediatelyAction())
|
||||||
|
}
|
||||||
|
|> ignoreValues
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class ManagedApplyPendingScheduledMessagesActionsHelper {
|
||||||
|
var operationDisposables: [MessageId: Disposable] = [:]
|
||||||
|
|
||||||
|
func update(entries: [PendingMessageActionsEntry]) -> (disposeOperations: [Disposable], beginOperations: [(PendingMessageActionsEntry, MetaDisposable)]) {
|
||||||
|
var disposeOperations: [Disposable] = []
|
||||||
|
var beginOperations: [(PendingMessageActionsEntry, MetaDisposable)] = []
|
||||||
|
|
||||||
|
var hasRunningOperationForPeerId = Set<PeerId>()
|
||||||
|
var validIds = Set<MessageId>()
|
||||||
|
for entry in entries {
|
||||||
|
if !hasRunningOperationForPeerId.contains(entry.id.peerId) {
|
||||||
|
hasRunningOperationForPeerId.insert(entry.id.peerId)
|
||||||
|
validIds.insert(entry.id)
|
||||||
|
|
||||||
|
if self.operationDisposables[entry.id] == nil {
|
||||||
|
let disposable = MetaDisposable()
|
||||||
|
beginOperations.append((entry, disposable))
|
||||||
|
self.operationDisposables[entry.id] = disposable
|
||||||
}
|
}
|
||||||
return .complete()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return .complete()
|
|
||||||
} |> switchToLatest
|
var removeMergedIds: [MessageId] = []
|
||||||
|
for (id, disposable) in self.operationDisposables {
|
||||||
|
if !validIds.contains(id) {
|
||||||
|
removeMergedIds.append(id)
|
||||||
|
disposeOperations.append(disposable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for id in removeMergedIds {
|
||||||
|
self.operationDisposables.removeValue(forKey: id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (disposeOperations, beginOperations)
|
||||||
|
}
|
||||||
|
|
||||||
|
func reset() -> [Disposable] {
|
||||||
|
let disposables = Array(self.operationDisposables.values)
|
||||||
|
self.operationDisposables.removeAll()
|
||||||
|
return disposables
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func withTakenAction(postbox: Postbox, type: PendingMessageActionType, id: MessageId, _ f: @escaping (Transaction, PendingMessageActionsEntry?) -> Signal<Never, NoError>) -> Signal<Never, NoError> {
|
||||||
|
return postbox.transaction { transaction -> Signal<Never, NoError> in
|
||||||
|
var result: PendingMessageActionsEntry?
|
||||||
|
|
||||||
|
if let action = transaction.getPendingMessageAction(type: type, id: id) as? SendScheduledMessageImmediatelyAction {
|
||||||
|
result = PendingMessageActionsEntry(id: id, action: action)
|
||||||
|
}
|
||||||
|
|
||||||
|
return f(transaction, result)
|
||||||
|
}
|
||||||
|
|> switchToLatest
|
||||||
|
}
|
||||||
|
|
||||||
|
func managedApplyPendingScheduledMessagesActions(postbox: Postbox, network: Network, stateManager: AccountStateManager) -> Signal<Void, NoError> {
|
||||||
|
return Signal { _ in
|
||||||
|
let helper = Atomic<ManagedApplyPendingScheduledMessagesActionsHelper>(value: ManagedApplyPendingScheduledMessagesActionsHelper())
|
||||||
|
|
||||||
|
let actionsKey = PostboxViewKey.pendingMessageActions(type: .sendScheduledMessageImmediately)
|
||||||
|
let disposable = postbox.combinedView(keys: [actionsKey]).start(next: { view in
|
||||||
|
var entries: [PendingMessageActionsEntry] = []
|
||||||
|
if let v = view.views[actionsKey] as? PendingMessageActionsView {
|
||||||
|
entries = v.entries
|
||||||
|
}
|
||||||
|
|
||||||
|
let (disposeOperations, beginOperations) = helper.with { helper -> (disposeOperations: [Disposable], beginOperations: [(PendingMessageActionsEntry, MetaDisposable)]) in
|
||||||
|
return helper.update(entries: entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
for disposable in disposeOperations {
|
||||||
|
disposable.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
for (entry, disposable) in beginOperations {
|
||||||
|
let signal = withTakenAction(postbox: postbox, type: .sendScheduledMessageImmediately, id: entry.id, { transaction, entry -> Signal<Never, NoError> in
|
||||||
|
if let entry = entry {
|
||||||
|
if let _ = entry.action as? SendScheduledMessageImmediatelyAction {
|
||||||
|
return sendScheduledMessageNow(postbox: postbox, network: network, stateManager: stateManager, messageId: entry.id)
|
||||||
|
|> `catch` { _ -> Signal<Never, NoError> in
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return .complete()
|
||||||
|
})
|
||||||
|
|> then(
|
||||||
|
postbox.transaction { transaction -> Void in
|
||||||
|
transaction.deleteMessages([entry.id])
|
||||||
|
}
|
||||||
|
|> ignoreValues
|
||||||
|
)
|
||||||
|
|
||||||
|
disposable.set(signal.start())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return ActionDisposable {
|
||||||
|
let disposables = helper.with { helper -> [Disposable] in
|
||||||
|
return helper.reset()
|
||||||
|
}
|
||||||
|
for disposable in disposables {
|
||||||
|
disposable.dispose()
|
||||||
|
}
|
||||||
|
disposable.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum SendScheduledMessageNowError {
|
||||||
|
case generic
|
||||||
|
}
|
||||||
|
|
||||||
|
private func sendScheduledMessageNow(postbox: Postbox, network: Network, stateManager: AccountStateManager, messageId: MessageId) -> Signal<Never, SendScheduledMessageNowError> {
|
||||||
|
return postbox.transaction { transaction -> Peer? in
|
||||||
|
guard let peer = transaction.getPeer(messageId.peerId) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return peer
|
||||||
|
}
|
||||||
|
|> introduceError(SendScheduledMessageNowError.self)
|
||||||
|
|> mapToSignal { peer -> Signal<Never, SendScheduledMessageNowError> in
|
||||||
|
guard let peer = peer else {
|
||||||
|
return .fail(.generic)
|
||||||
|
}
|
||||||
|
guard let inputPeer = apiInputPeer(peer) else {
|
||||||
|
return .fail(.generic)
|
||||||
|
}
|
||||||
|
return network.request(Api.functions.messages.sendScheduledMessages(peer: inputPeer, id: [messageId.id]))
|
||||||
|
|> mapError { _ -> SendScheduledMessageNowError in
|
||||||
|
return .generic
|
||||||
|
}
|
||||||
|
|> mapToSignal { updates -> Signal<Never, SendScheduledMessageNowError> in
|
||||||
|
stateManager.addUpdates(updates)
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -97,7 +97,7 @@ public func searchStickers(account: Account, query: String, scope: SearchSticker
|
|||||||
for entry in transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudSavedStickers) {
|
for entry in transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudSavedStickers) {
|
||||||
if let item = entry.contents as? SavedStickerItem {
|
if let item = entry.contents as? SavedStickerItem {
|
||||||
for representation in item.stringRepresentations {
|
for representation in item.stringRepresentations {
|
||||||
if representation == query {
|
if representation.hasPrefix(query) {
|
||||||
result.append(FoundStickerItem(file: item.file, stringRepresentations: item.stringRepresentations))
|
result.append(FoundStickerItem(file: item.file, stringRepresentations: item.stringRepresentations))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -115,7 +115,7 @@ public func searchStickers(account: Account, query: String, scope: SearchSticker
|
|||||||
if let item = entry.contents as? RecentMediaItem, let file = item.media as? TelegramMediaFile {
|
if let item = entry.contents as? RecentMediaItem, let file = item.media as? TelegramMediaFile {
|
||||||
if !currentItems.contains(file.fileId) {
|
if !currentItems.contains(file.fileId) {
|
||||||
for case let .Sticker(sticker) in file.attributes {
|
for case let .Sticker(sticker) in file.attributes {
|
||||||
if sticker.displayText == query {
|
if sticker.displayText.hasPrefix(query) {
|
||||||
matchingRecentItemsIds.insert(file.fileId)
|
matchingRecentItemsIds.insert(file.fileId)
|
||||||
}
|
}
|
||||||
recentItemsIds.insert(file.fileId)
|
recentItemsIds.insert(file.fileId)
|
||||||
@ -130,9 +130,14 @@ public func searchStickers(account: Account, query: String, scope: SearchSticker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var searchQuery: ItemCollectionSearchQuery = .exact(ValueBoxKey(query))
|
||||||
|
if query == "\u{2764}" {
|
||||||
|
searchQuery = .matching([ValueBoxKey("\u{2764}"), ValueBoxKey("\u{2764}\u{fe0f}")])
|
||||||
|
}
|
||||||
|
|
||||||
var installedItems: [FoundStickerItem] = []
|
var installedItems: [FoundStickerItem] = []
|
||||||
var installedAnimatedItems: [FoundStickerItem] = []
|
var installedAnimatedItems: [FoundStickerItem] = []
|
||||||
for item in transaction.searchItemCollection(namespace: Namespaces.ItemCollection.CloudStickerPacks, query: .exact(ValueBoxKey(query))) {
|
for item in transaction.searchItemCollection(namespace: Namespaces.ItemCollection.CloudStickerPacks, query: searchQuery) {
|
||||||
if let item = item as? StickerPackItem {
|
if let item = item as? StickerPackItem {
|
||||||
if !currentItems.contains(item.file.fileId) {
|
if !currentItems.contains(item.file.fileId) {
|
||||||
var stringRepresentations: [String] = []
|
var stringRepresentations: [String] = []
|
||||||
|
|||||||
@ -501,8 +501,8 @@
|
|||||||
D098908022942E3B0053F151 /* ActiveSessionsContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D098907E22942E3B0053F151 /* ActiveSessionsContext.swift */; };
|
D098908022942E3B0053F151 /* ActiveSessionsContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D098907E22942E3B0053F151 /* ActiveSessionsContext.swift */; };
|
||||||
D099D7461EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099D7451EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift */; };
|
D099D7461EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099D7451EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift */; };
|
||||||
D099D7471EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099D7451EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift */; };
|
D099D7471EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099D7451EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift */; };
|
||||||
D099D7491EEF418D00A3128C /* HistoryViewChannelStateValidation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099D7481EEF418D00A3128C /* HistoryViewChannelStateValidation.swift */; };
|
D099D7491EEF418D00A3128C /* HistoryViewStateValidation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099D7481EEF418D00A3128C /* HistoryViewStateValidation.swift */; };
|
||||||
D099D74A1EEF418D00A3128C /* HistoryViewChannelStateValidation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099D7481EEF418D00A3128C /* HistoryViewChannelStateValidation.swift */; };
|
D099D74A1EEF418D00A3128C /* HistoryViewStateValidation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099D7481EEF418D00A3128C /* HistoryViewStateValidation.swift */; };
|
||||||
D099E222229420D600561B75 /* BlockedPeersContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099E221229420D600561B75 /* BlockedPeersContext.swift */; };
|
D099E222229420D600561B75 /* BlockedPeersContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099E221229420D600561B75 /* BlockedPeersContext.swift */; };
|
||||||
D099E223229420D600561B75 /* BlockedPeersContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099E221229420D600561B75 /* BlockedPeersContext.swift */; };
|
D099E223229420D600561B75 /* BlockedPeersContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099E221229420D600561B75 /* BlockedPeersContext.swift */; };
|
||||||
D099EA1C1DE72867001AF5A8 /* PeerCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099EA1B1DE72867001AF5A8 /* PeerCommands.swift */; };
|
D099EA1C1DE72867001AF5A8 /* PeerCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099EA1B1DE72867001AF5A8 /* PeerCommands.swift */; };
|
||||||
@ -1055,7 +1055,7 @@
|
|||||||
D093D805206539D000BC3599 /* SaveSecureIdValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveSecureIdValue.swift; sourceTree = "<group>"; };
|
D093D805206539D000BC3599 /* SaveSecureIdValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveSecureIdValue.swift; sourceTree = "<group>"; };
|
||||||
D098907E22942E3B0053F151 /* ActiveSessionsContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveSessionsContext.swift; sourceTree = "<group>"; };
|
D098907E22942E3B0053F151 /* ActiveSessionsContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveSessionsContext.swift; sourceTree = "<group>"; };
|
||||||
D099D7451EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelMessageStateVersionAttribute.swift; sourceTree = "<group>"; };
|
D099D7451EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelMessageStateVersionAttribute.swift; sourceTree = "<group>"; };
|
||||||
D099D7481EEF418D00A3128C /* HistoryViewChannelStateValidation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HistoryViewChannelStateValidation.swift; sourceTree = "<group>"; };
|
D099D7481EEF418D00A3128C /* HistoryViewStateValidation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HistoryViewStateValidation.swift; sourceTree = "<group>"; };
|
||||||
D099E221229420D600561B75 /* BlockedPeersContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedPeersContext.swift; sourceTree = "<group>"; };
|
D099E221229420D600561B75 /* BlockedPeersContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedPeersContext.swift; sourceTree = "<group>"; };
|
||||||
D099EA1B1DE72867001AF5A8 /* PeerCommands.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerCommands.swift; sourceTree = "<group>"; };
|
D099EA1B1DE72867001AF5A8 /* PeerCommands.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerCommands.swift; sourceTree = "<group>"; };
|
||||||
D09A2FE51D7CD4940018FB72 /* TelegramChannel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramChannel.swift; sourceTree = "<group>"; };
|
D09A2FE51D7CD4940018FB72 /* TelegramChannel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramChannel.swift; sourceTree = "<group>"; };
|
||||||
@ -1552,7 +1552,7 @@
|
|||||||
D0BEAF5C1E54941B00BD963D /* Authorization.swift */,
|
D0BEAF5C1E54941B00BD963D /* Authorization.swift */,
|
||||||
D0BEAF5F1E54ACF900BD963D /* AccountManager.swift */,
|
D0BEAF5F1E54ACF900BD963D /* AccountManager.swift */,
|
||||||
D0528E591E658B3600E2FEF5 /* ManagedLocalInputActivities.swift */,
|
D0528E591E658B3600E2FEF5 /* ManagedLocalInputActivities.swift */,
|
||||||
D099D7481EEF418D00A3128C /* HistoryViewChannelStateValidation.swift */,
|
D099D7481EEF418D00A3128C /* HistoryViewStateValidation.swift */,
|
||||||
D0B1671C1F9EA2C300976B40 /* ChatHistoryPreloadManager.swift */,
|
D0B1671C1F9EA2C300976B40 /* ChatHistoryPreloadManager.swift */,
|
||||||
D06ECFC720B810D300C576C2 /* TermsOfService.swift */,
|
D06ECFC720B810D300C576C2 /* TermsOfService.swift */,
|
||||||
D051DB16215ECC4D00F30F92 /* AppChangelog.swift */,
|
D051DB16215ECC4D00F30F92 /* AppChangelog.swift */,
|
||||||
@ -2235,7 +2235,7 @@
|
|||||||
D08984FB2118816A00918162 /* Reachability.m in Sources */,
|
D08984FB2118816A00918162 /* Reachability.m in Sources */,
|
||||||
D0DA1D321F7043D50034E892 /* ManagedPendingPeerNotificationSettings.swift in Sources */,
|
D0DA1D321F7043D50034E892 /* ManagedPendingPeerNotificationSettings.swift in Sources */,
|
||||||
0925903722F0D02D003D6283 /* ManagedAnimatedEmojiUpdates.swift in Sources */,
|
0925903722F0D02D003D6283 /* ManagedAnimatedEmojiUpdates.swift in Sources */,
|
||||||
D099D7491EEF418D00A3128C /* HistoryViewChannelStateValidation.swift in Sources */,
|
D099D7491EEF418D00A3128C /* HistoryViewStateValidation.swift in Sources */,
|
||||||
C23BC3871E9BE3CA00D79F92 /* ImportContact.swift in Sources */,
|
C23BC3871E9BE3CA00D79F92 /* ImportContact.swift in Sources */,
|
||||||
D0D376E622DCCFD600FA7D7C /* SlowMode.swift in Sources */,
|
D0D376E622DCCFD600FA7D7C /* SlowMode.swift in Sources */,
|
||||||
D00422D321677F4500719B67 /* ManagedAccountPresence.swift in Sources */,
|
D00422D321677F4500719B67 /* ManagedAccountPresence.swift in Sources */,
|
||||||
@ -2649,7 +2649,7 @@
|
|||||||
D0E35A131DE4C69100BC6096 /* OutgoingChatContextResultMessageAttribute.swift in Sources */,
|
D0E35A131DE4C69100BC6096 /* OutgoingChatContextResultMessageAttribute.swift in Sources */,
|
||||||
D0B418961D7E0580004562A4 /* TelegramMediaFile.swift in Sources */,
|
D0B418961D7E0580004562A4 /* TelegramMediaFile.swift in Sources */,
|
||||||
D08CAA881ED81DD40000FDA8 /* LocalizationInfo.swift in Sources */,
|
D08CAA881ED81DD40000FDA8 /* LocalizationInfo.swift in Sources */,
|
||||||
D099D74A1EEF418D00A3128C /* HistoryViewChannelStateValidation.swift in Sources */,
|
D099D74A1EEF418D00A3128C /* HistoryViewStateValidation.swift in Sources */,
|
||||||
D0AF32231FAC95C20097362B /* StandaloneUploadedMedia.swift in Sources */,
|
D0AF32231FAC95C20097362B /* StandaloneUploadedMedia.swift in Sources */,
|
||||||
D04554A721B43440007A6DD9 /* CancelAccountReset.swift in Sources */,
|
D04554A721B43440007A6DD9 /* CancelAccountReset.swift in Sources */,
|
||||||
D001F3EC1E128A1C007A8C60 /* Holes.swift in Sources */,
|
D001F3EC1E128A1C007A8C60 /* Holes.swift in Sources */,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1461,44 +1461,38 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
strongSelf.sendScheduledMessagesNow(messageIds)
|
strongSelf.sendScheduledMessagesNow(messageIds)
|
||||||
}
|
}
|
||||||
}, editScheduledMessagesTime: { [weak self] messageIds in
|
}, editScheduledMessagesTime: { [weak self] messageIds in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self, let messageId = messageIds.first {
|
||||||
let mode: ChatScheduleTimeControllerMode
|
let mode: ChatScheduleTimeControllerMode
|
||||||
if case let .peer(peerId) = strongSelf.presentationInterfaceState.chatLocation, peerId == strongSelf.context.account.peerId {
|
if case let .peer(peerId) = strongSelf.presentationInterfaceState.chatLocation, peerId == strongSelf.context.account.peerId {
|
||||||
mode = .reminders
|
mode = .reminders
|
||||||
} else {
|
} else {
|
||||||
mode = .scheduledMessages
|
mode = .scheduledMessages
|
||||||
}
|
}
|
||||||
let controller = ChatScheduleTimeController(context: strongSelf.context, mode: mode, completion: { [weak self] scheduleTime in
|
|
||||||
if let strongSelf = self, let messageId = messageIds.first {
|
|
||||||
let signal = (strongSelf.context.account.postbox.transaction { transaction -> Message? in
|
|
||||||
return transaction.getMessage(messageId)
|
|
||||||
}
|
|
||||||
|> introduceError(RequestEditMessageError.self)
|
|
||||||
|> mapToSignal({ message -> Signal<RequestEditMessageResult, RequestEditMessageError> in
|
|
||||||
if let message = message {
|
|
||||||
var entities: TextEntitiesMessageAttribute?
|
|
||||||
for attribute in message.attributes {
|
|
||||||
if let attribute = attribute as? TextEntitiesMessageAttribute {
|
|
||||||
entities = attribute
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return requestEditMessage(account: strongSelf.context.account, messageId: messageId, text: message.text, media: .keep
|
|
||||||
, entities: entities, disableUrlPreview: false, scheduleTime: scheduleTime)
|
|
||||||
} else {
|
|
||||||
return .complete()
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
strongSelf.editMessageDisposable.set((signal |> deliverOnMainQueue).start(next: { result in
|
let _ = (strongSelf.context.account.postbox.transaction { transaction -> Message? in
|
||||||
|
return transaction.getMessage(messageId)
|
||||||
}, error: { error in
|
} |> deliverOnMainQueue).start(next: { [weak self] message in
|
||||||
|
guard let strongSelf = self, let message = message else {
|
||||||
}))
|
return
|
||||||
}
|
}
|
||||||
|
let controller = ChatScheduleTimeController(context: strongSelf.context, mode: mode, currentTime: message.timestamp, completion: { [weak self] scheduleTime in
|
||||||
|
if let strongSelf = self {
|
||||||
|
var entities: TextEntitiesMessageAttribute?
|
||||||
|
for attribute in message.attributes {
|
||||||
|
if let attribute = attribute as? TextEntitiesMessageAttribute {
|
||||||
|
entities = attribute
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let signal = requestEditMessage(account: strongSelf.context.account, messageId: messageId, text: message.text, media: .keep, entities: entities, disableUrlPreview: false, scheduleTime: scheduleTime)
|
||||||
|
strongSelf.editMessageDisposable.set((signal |> deliverOnMainQueue).start(next: { result in
|
||||||
|
}, error: { error in
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
strongSelf.chatDisplayNode.dismissInput()
|
||||||
|
strongSelf.present(controller, in: .window(.root))
|
||||||
})
|
})
|
||||||
strongSelf.chatDisplayNode.dismissInput()
|
|
||||||
strongSelf.present(controller, in: .window(.root))
|
|
||||||
}
|
}
|
||||||
}, performTextSelectionAction: { [weak self] _, text, action in
|
}, performTextSelectionAction: { [weak self] _, text, action in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
@ -1638,8 +1632,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
self.reportIrrelvantGeoNoticePromise.set(.single(nil))
|
self.reportIrrelvantGeoNoticePromise.set(.single(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isScheduledMessages {
|
if !isScheduledMessages && peerId.namespace != Namespaces.Peer.SecretChat {
|
||||||
hasScheduledMessages = context.account.viewTracker.aroundMessageHistoryViewForLocation(chatLocation, index: .upperBound, anchorIndex: .upperBound, count: 100, fixedCombinedReadStates: nil, tagMask: nil, excludeNamespaces: [Namespaces.Message.Cloud, Namespaces.Message.Local], orderStatistics: [])
|
hasScheduledMessages = context.account.viewTracker.scheduledMessagesViewForLocation(chatLocation)
|
||||||
|> map { view, _, _ in
|
|> map { view, _, _ in
|
||||||
return !view.entries.isEmpty
|
return !view.entries.isEmpty
|
||||||
}
|
}
|
||||||
@ -5299,7 +5293,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func sendScheduledMessagesNow(_ messageId: [MessageId]) {
|
private func sendScheduledMessagesNow(_ messageId: [MessageId]) {
|
||||||
let _ = sendScheduledMessageNow(account: self.context.account, messageId: messageId.first!).start()
|
let _ = sendScheduledMessageNowInteractively(postbox: self.context.account.postbox, messageId: messageId.first!).start()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func sendMessages(_ messages: [EnqueueMessage], commit: Bool = false) {
|
private func sendMessages(_ messages: [EnqueueMessage], commit: Bool = false) {
|
||||||
|
|||||||
@ -33,7 +33,7 @@ private final class ChatEmptyNodeRegularChatContent: ASDisplayNode, ChatEmptyNod
|
|||||||
self.currentStrings = interfaceState.strings
|
self.currentStrings = interfaceState.strings
|
||||||
|
|
||||||
let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper)
|
let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper)
|
||||||
self.textNode.attributedText = NSAttributedString(string: interfaceState.strings.Conversation_EmptyPlaceholder, font: messageFont, textColor: serviceColor.primaryText)
|
self.textNode.attributedText = NSAttributedString(string: interfaceState.isScheduledMessages ? interfaceState.strings.ScheduledMessages_EmptyPlaceholder : interfaceState.strings.Conversation_EmptyPlaceholder, font: messageFont, textColor: serviceColor.primaryText)
|
||||||
}
|
}
|
||||||
|
|
||||||
let insets = UIEdgeInsets(top: 6.0, left: 10.0, bottom: 6.0, right: 10.0)
|
let insets = UIEdgeInsets(top: 6.0, left: 10.0, bottom: 6.0, right: 10.0)
|
||||||
|
|||||||
@ -30,7 +30,6 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, account: A
|
|||||||
if scheduled {
|
if scheduled {
|
||||||
var preloaded = false
|
var preloaded = false
|
||||||
var fadeIn = false
|
var fadeIn = false
|
||||||
let count = 100
|
|
||||||
return account.viewTracker.scheduledMessagesViewForLocation(chatLocation)
|
return account.viewTracker.scheduledMessagesViewForLocation(chatLocation)
|
||||||
|> map { view, updateType, initialData -> ChatHistoryViewUpdate in
|
|> map { view, updateType, initialData -> ChatHistoryViewUpdate in
|
||||||
let (cachedData, cachedDataMessages, readStateData) = extractAdditionalData(view: view, chatLocation: chatLocation)
|
let (cachedData, cachedDataMessages, readStateData) = extractAdditionalData(view: view, chatLocation: chatLocation)
|
||||||
|
|||||||
@ -382,14 +382,18 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if data.messageActions.options.contains(.sendScheduledNow) {
|
if data.messageActions.options.contains(.sendScheduledNow) {
|
||||||
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.ScheduledMessages_SendNow, icon: { _ in return nil }, action: { _, f in
|
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.ScheduledMessages_SendNow, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.actionSheet.primaryTextColor)
|
||||||
|
}, action: { _, f in
|
||||||
controllerInteraction.sendScheduledMessagesNow(selectAll ? messages.map { $0.id } : [message.id])
|
controllerInteraction.sendScheduledMessagesNow(selectAll ? messages.map { $0.id } : [message.id])
|
||||||
f(.dismissWithoutContent)
|
f(.dismissWithoutContent)
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
if data.messageActions.options.contains(.editScheduledTime) {
|
if data.messageActions.options.contains(.editScheduledTime) {
|
||||||
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.ScheduledMessages_EditTime, icon: { _ in return nil }, action: { _, f in
|
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.ScheduledMessages_EditTime, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Schedule"), color: theme.actionSheet.primaryTextColor)
|
||||||
|
}, action: { _, f in
|
||||||
controllerInteraction.editScheduledMessagesTime(selectAll ? messages.map { $0.id } : [message.id])
|
controllerInteraction.editScheduledMessagesTime(selectAll ? messages.map { $0.id } : [message.id])
|
||||||
f(.dismissWithoutContent)
|
f(.dismissWithoutContent)
|
||||||
})))
|
})))
|
||||||
|
|||||||
@ -329,13 +329,7 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible {
|
|||||||
|
|
||||||
self.effectiveAuthorId = effectiveAuthor?.id
|
self.effectiveAuthorId = effectiveAuthor?.id
|
||||||
|
|
||||||
let timestamp: Int32
|
self.header = ChatMessageDateHeader(timestamp: content.index.timestamp, scheduled: associatedData.isScheduledMessages, presentationData: presentationData, context: context, action: { timestamp in
|
||||||
if let scheduleTime = content.firstMessage.scheduleTime {
|
|
||||||
timestamp = scheduleTime
|
|
||||||
} else {
|
|
||||||
timestamp = content.index.timestamp
|
|
||||||
}
|
|
||||||
self.header = ChatMessageDateHeader(timestamp: timestamp, scheduled: associatedData.isScheduledMessages, presentationData: presentationData, context: context, action: { timestamp in
|
|
||||||
var calendar = NSCalendar.current
|
var calendar = NSCalendar.current
|
||||||
calendar.timeZone = TimeZone(abbreviation: "UTC")!
|
calendar.timeZone = TimeZone(abbreviation: "UTC")!
|
||||||
let date = Date(timeIntervalSince1970: TimeInterval(timestamp))
|
let date = Date(timeIntervalSince1970: TimeInterval(timestamp))
|
||||||
|
|||||||
@ -105,7 +105,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
|
|||||||
self.contentContainerNode.addSubnode(self.doneButton)
|
self.contentContainerNode.addSubnode(self.doneButton)
|
||||||
|
|
||||||
self.pickerView.timeZone = TimeZone.current
|
self.pickerView.timeZone = TimeZone.current
|
||||||
self.pickerView.minuteInterval = 5
|
self.pickerView.minuteInterval = 1
|
||||||
self.pickerView.setValue(self.presentationData.theme.actionSheet.primaryTextColor, forKey: "textColor")
|
self.pickerView.setValue(self.presentationData.theme.actionSheet.primaryTextColor, forKey: "textColor")
|
||||||
|
|
||||||
self.contentContainerNode.view.addSubview(self.pickerView)
|
self.contentContainerNode.view.addSubview(self.pickerView)
|
||||||
@ -265,8 +265,11 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
|
|||||||
insets.top = max(10.0, insets.top)
|
insets.top = max(10.0, insets.top)
|
||||||
|
|
||||||
let bottomInset: CGFloat = 10.0 + cleanInsets.bottom
|
let bottomInset: CGFloat = 10.0 + cleanInsets.bottom
|
||||||
let titleAreaHeight: CGFloat = 54.0
|
let titleHeight: CGFloat = 54.0
|
||||||
let contentHeight = titleAreaHeight + bottomInset + 285.0
|
var contentHeight = titleHeight + bottomInset + 52.0 + 17.0
|
||||||
|
let pickerHeight: CGFloat = min(216.0, layout.size.height - contentHeight)
|
||||||
|
contentHeight = titleHeight + bottomInset + 52.0 + 17.0 + pickerHeight
|
||||||
|
|
||||||
let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: layout.safeInsets.left)
|
let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: layout.safeInsets.left)
|
||||||
|
|
||||||
let sideInset = floor((layout.size.width - width) / 2.0)
|
let sideInset = floor((layout.size.width - width) / 2.0)
|
||||||
@ -281,11 +284,11 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
|
|||||||
transition.updateFrame(node: self.wrappingScrollNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
transition.updateFrame(node: self.wrappingScrollNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||||
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||||
|
|
||||||
let titleSize = self.titleNode.measure(CGSize(width: width, height: titleAreaHeight))
|
let titleSize = self.titleNode.measure(CGSize(width: width, height: titleHeight))
|
||||||
let titleFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - titleSize.width) / 2.0), y: 16.0), size: titleSize)
|
let titleFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - titleSize.width) / 2.0), y: 16.0), size: titleSize)
|
||||||
transition.updateFrame(node: self.titleNode, frame: titleFrame)
|
transition.updateFrame(node: self.titleNode, frame: titleFrame)
|
||||||
|
|
||||||
let cancelSize = self.cancelButton.measure(CGSize(width: width, height: titleAreaHeight))
|
let cancelSize = self.cancelButton.measure(CGSize(width: width, height: titleHeight))
|
||||||
let cancelFrame = CGRect(origin: CGPoint(x: 16.0, y: 16.0), size: cancelSize)
|
let cancelFrame = CGRect(origin: CGPoint(x: 16.0, y: 16.0), size: cancelSize)
|
||||||
transition.updateFrame(node: self.cancelButton, frame: cancelFrame)
|
transition.updateFrame(node: self.cancelButton, frame: cancelFrame)
|
||||||
|
|
||||||
@ -293,9 +296,9 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
|
|||||||
let buttonHeight = self.doneButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition)
|
let buttonHeight = self.doneButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition)
|
||||||
transition.updateFrame(node: self.doneButton, frame: CGRect(x: buttonInset, y: contentHeight - buttonHeight - insets.bottom - 10.0, width: contentFrame.width, height: buttonHeight))
|
transition.updateFrame(node: self.doneButton, frame: CGRect(x: buttonInset, y: contentHeight - buttonHeight - insets.bottom - 10.0, width: contentFrame.width, height: buttonHeight))
|
||||||
|
|
||||||
self.pickerView.frame = CGRect(origin: CGPoint(x: 0.0, y: 54.0), size: CGSize(width: contentFrame.width, height: 216.0))
|
self.pickerView.frame = CGRect(origin: CGPoint(x: 0.0, y: 54.0), size: CGSize(width: contentFrame.width, height: pickerHeight))
|
||||||
|
|
||||||
transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame)
|
transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame)
|
||||||
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: titleAreaHeight), size: CGSize(width: contentContainerFrame.size.width, height: UIScreenPixel)))
|
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: titleHeight), size: CGSize(width: contentContainerFrame.size.width, height: UIScreenPixel)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,15 @@ import MusicAlbumArtResources
|
|||||||
private enum PeerMessagesMediaPlaylistLoadAnchor {
|
private enum PeerMessagesMediaPlaylistLoadAnchor {
|
||||||
case messageId(MessageId)
|
case messageId(MessageId)
|
||||||
case index(MessageIndex)
|
case index(MessageIndex)
|
||||||
|
|
||||||
|
var id: MessageId {
|
||||||
|
switch self {
|
||||||
|
case let .messageId(id):
|
||||||
|
return id
|
||||||
|
case let .index(index):
|
||||||
|
return index.id
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum PeerMessagesMediaPlaylistNavigation {
|
private enum PeerMessagesMediaPlaylistNavigation {
|
||||||
@ -477,6 +486,14 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
|
|||||||
private func loadItem(anchor: PeerMessagesMediaPlaylistLoadAnchor, navigation: PeerMessagesMediaPlaylistNavigation) {
|
private func loadItem(anchor: PeerMessagesMediaPlaylistLoadAnchor, navigation: PeerMessagesMediaPlaylistNavigation) {
|
||||||
self.loadingItem = true
|
self.loadingItem = true
|
||||||
self.updateState()
|
self.updateState()
|
||||||
|
|
||||||
|
let namespaces: HistoryViewNamespaces
|
||||||
|
if Namespaces.Message.allScheduled.contains(anchor.id.namespace) {
|
||||||
|
namespaces = .just(Namespaces.Message.allScheduled)
|
||||||
|
} else {
|
||||||
|
namespaces = .not(Namespaces.Message.allScheduled)
|
||||||
|
}
|
||||||
|
|
||||||
switch anchor {
|
switch anchor {
|
||||||
case let .messageId(messageId):
|
case let .messageId(messageId):
|
||||||
if case let .messages(peerId, tagMask, _) = self.messagesLocation {
|
if case let .messages(peerId, tagMask, _) = self.messagesLocation {
|
||||||
@ -486,7 +503,8 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
|
|||||||
guard let message = message else {
|
guard let message = message else {
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
}
|
}
|
||||||
return self.postbox.aroundMessageHistoryViewForLocation(.peer(peerId), anchor: .index(message.index), count: 10, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, excludeNamespaces: [Namespaces.Message.ScheduledCloud, Namespaces.Message.ScheduledLocal], orderStatistics: [])
|
|
||||||
|
return self.postbox.aroundMessageHistoryViewForLocation(.peer(peerId), anchor: .index(message.index), count: 10, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, namespaces: namespaces, orderStatistics: [])
|
||||||
|> mapToSignal { view -> Signal<(Message, [Message])?, NoError> in
|
|> mapToSignal { view -> Signal<(Message, [Message])?, NoError> in
|
||||||
if let (message, aroundMessages, _) = navigatedMessageFromView(view.0, anchorIndex: message.index, position: .exact) {
|
if let (message, aroundMessages, _) = navigatedMessageFromView(view.0, anchorIndex: message.index, position: .exact) {
|
||||||
return .single((message, aroundMessages))
|
return .single((message, aroundMessages))
|
||||||
@ -560,7 +578,7 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
|
|||||||
}
|
}
|
||||||
let historySignal = inputIndex
|
let historySignal = inputIndex
|
||||||
|> mapToSignal { inputIndex -> Signal<(Message, [Message])?, NoError> in
|
|> mapToSignal { inputIndex -> Signal<(Message, [Message])?, NoError> in
|
||||||
return self.postbox.aroundMessageHistoryViewForLocation(.peer(peerId), anchor: .index(inputIndex), count: 10, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, excludeNamespaces: [Namespaces.Message.ScheduledCloud, Namespaces.Message.ScheduledLocal], orderStatistics: [])
|
return self.postbox.aroundMessageHistoryViewForLocation(.peer(peerId), anchor: .index(inputIndex), count: 10, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, namespaces: namespaces, orderStatistics: [])
|
||||||
|> mapToSignal { view -> Signal<(Message, [Message])?, NoError> in
|
|> mapToSignal { view -> Signal<(Message, [Message])?, NoError> in
|
||||||
let position: NavigatedMessageFromViewPosition
|
let position: NavigatedMessageFromViewPosition
|
||||||
switch navigation {
|
switch navigation {
|
||||||
@ -590,7 +608,7 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
|
|||||||
} else {
|
} else {
|
||||||
viewIndex = .lowerBound
|
viewIndex = .lowerBound
|
||||||
}
|
}
|
||||||
return self.postbox.aroundMessageHistoryViewForLocation(.peer(peerId), anchor: viewIndex, count: 10, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, excludeNamespaces: [Namespaces.Message.ScheduledCloud, Namespaces.Message.ScheduledLocal], orderStatistics: [])
|
return self.postbox.aroundMessageHistoryViewForLocation(.peer(peerId), anchor: viewIndex, count: 10, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, namespaces: namespaces, orderStatistics: [])
|
||||||
|> mapToSignal { view -> Signal<(Message, [Message])?, NoError> in
|
|> mapToSignal { view -> Signal<(Message, [Message])?, NoError> in
|
||||||
let position: NavigatedMessageFromViewPosition
|
let position: NavigatedMessageFromViewPosition
|
||||||
switch navigation {
|
switch navigation {
|
||||||
|
|||||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user