Merge branch 'postbox-mac'

This commit is contained in:
Peter 2019-08-16 23:16:17 +03:00
commit ab26601177
30 changed files with 3436 additions and 3111 deletions

View File

@ -56,8 +56,7 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
override init() {
super.init()
let appBundleIdentifier = Bundle.main.bundleIdentifier!
guard let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else {
guard let appBundleIdentifier = Bundle.main.bundleIdentifier, let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else {
return
}
@ -104,6 +103,7 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
case .upgrading:
return .complete()
case let .authorized(account):
account.shouldKeepOnlinePresence.set(.single(false))
return applicationSettings(accountManager: accountManager)
|> deliverOnMainQueue
|> map { settings -> Account in
@ -175,6 +175,7 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
}
private func resolve(persons: [INPerson]?, with completion: @escaping ([ResolveResult]) -> Void) {
let account = self.accountPromise.get()
guard let initialPersons = persons, !initialPersons.isEmpty else {
completion([.needsValue])
return
@ -231,8 +232,6 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
return nil
})
let account = self.accountPromise.get()
let signal = matchingDeviceContacts(stableIds: stableIds)
|> take(1)
|> mapToSignal { matchedContacts in
@ -276,9 +275,7 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
@available(iOSApplicationExtension 11.0, *)
func resolveRecipients(for intent: INSendMessageIntent, with completion: @escaping ([INSendMessageRecipientResolutionResult]) -> Void) {
if let peerId = intent.conversationIdentifier.flatMap(Int64.init) {
let account = self.accountPromise.get()
let signal = account
let signal = self.accountPromise.get()
|> introduceError(IntentHandlingError.self)
|> mapToSignal { account -> Signal<INPerson?, IntentHandlingError> in
if let account = account {
@ -413,7 +410,13 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|> introduceError(IntentHandlingError.self)
|> take(1)
|> mapToSignal { _ -> Signal<[INMessage], IntentHandlingError> in
return unreadMessages(account: account)
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
|> introduceError(IntentHandlingError.self)
|> afterDisposed {
account.shouldBeServiceTaskMaster.set(.single(.never))
@ -518,6 +521,11 @@ 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) {
self.actionDisposable.set((self.accountPromise.get()
|> take(1)

View File

@ -1,11 +1,33 @@
import Foundation
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
import Contacts
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> {
return account.postbox.tailChatListView(groupId: .root, count: 20, summaryComponents: ChatListEntrySummaryComponents())
|> take(1)
@ -31,7 +53,7 @@ func unreadMessages(account: Account) -> Signal<[INMessage], NoError> {
}
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)
|> map { view -> [INMessage] in
var messages: [INMessage] = []
@ -42,7 +64,7 @@ func unreadMessages(account: Account) -> Signal<[INMessage], NoError> {
}
if !isRead {
if let message = messageWithTelegramMessage(entry.message, account: account) {
if let message = messageWithTelegramMessage(entry.message) {
messages.append(message)
}
}
@ -58,7 +80,7 @@ func unreadMessages(account: Account) -> Signal<[INMessage], NoError> {
} else {
return combineLatest(signals)
|> 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
}
}
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)
}
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 {
return nil
}
@ -181,7 +203,7 @@ private func messageWithTelegramMessage(_ telegramMessage: Message, account: Acc
messageType = .mediaAudio
break loop
} else if file.isVoice {
messageType = .audio
messageType = .mediaAudio
break loop
} else if file.isSticker || file.isAnimatedSticker {
messageType = .sticker
@ -189,6 +211,9 @@ private func messageWithTelegramMessage(_ telegramMessage: Message, account: Acc
} else if file.isAnimated {
messageType = .mediaVideo
break loop
} else if #available(iOSApplicationExtension 12.0, *) {
messageType = .file
break loop
}
} else if media is TelegramMediaMap {
messageType = .mediaLocation

View File

@ -33,7 +33,7 @@
<string>merchant.privatbank.test.telergramios</string>
<string>merchant.privatbank.prod.telergram</string>
</array>
<key>com.apple.developer.carplay-messaging</key><true/>
<key>com.apple.developer.carplay-calling</key><true/>
<key>com.apple.developer.carplay-messaging</key>
<true/>
</dict>
</plist>

View File

@ -4610,6 +4610,7 @@ Any member of this group will be able to see messages in the channel.";
"ScheduledMessages.EditTime" = "Reschedule";
"ScheduledMessages.ClearAll" = "Clear All";
"ScheduledMessages.Delete" = "Delete";
"ScheduledMessages.EmptyPlaceholder" = "No scheduled messages here yet...";
"Conversation.SendMessage.SetReminder" = "Set a Reminder";

View File

@ -368,16 +368,13 @@ public class GalleryController: ViewController {
switch source {
case .peerMessagesAtId:
if let tags = tagsForMessage(message!) {
var excludeNamespaces: [MessageId.Namespace]
if message!.id.namespace == Namespaces.Message.ScheduledCloud {
excludeNamespaces = [Namespaces.Message.Cloud, Namespaces.Message.Local, Namespaces.Message.SecretIncoming]
let namespaces: HistoryViewNamespaces
if Namespaces.Message.allScheduled.contains(message!.id.namespace) {
namespaces = .just(Namespaces.Message.allScheduled)
} else {
excludeNamespaces = [Namespaces.Message.ScheduledCloud, Namespaces.Message.ScheduledLocal]
namespaces = .not(Namespaces.Message.allScheduled)
}
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
return context.account.postbox.aroundMessageHistoryViewForLocation(.peer(message!.id.peerId), anchor: .index(message!.index), count: 50, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tags, namespaces: namespaces, orderStatistics: [.combinedLocation])
|> mapToSignal { (view, _, _) -> Signal<GalleryMessageHistoryView?, NoError> in
let mapped = GalleryMessageHistoryView.view(view)
return .single(mapped)

View File

@ -224,10 +224,27 @@ public enum HistoryViewInputAnchor: Equatable {
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 {
private(set) var peerIds: MessageHistoryViewPeerIds
let tag: MessageTags?
let excludeNamespaces: [MessageId.Namespace]
let namespaces: HistoryViewNamespaces
private let orderStatistics: MessageHistoryViewOrderStatistics
private let anchor: HistoryViewInputAnchor
@ -242,7 +259,7 @@ final class MutableMessageHistoryView {
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.orderStatistics = orderStatistics
@ -250,17 +267,17 @@ final class MutableMessageHistoryView {
self.combinedReadStates = combinedReadStates
self.transientReadStates = transientReadStates
self.tag = tag
self.excludeNamespaces = excludeNamespaces
self.namespaces = namespaces
self.fillCount = count
self.topTaggedMessages = topTaggedMessages
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 {
let sampledState = loadingState.checkAndSample(postbox: postbox)
switch sampledState {
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)
case .loadHole:
break
@ -272,12 +289,12 @@ final class MutableMessageHistoryView {
}
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 {
let sampledState = loadingState.checkAndSample(postbox: postbox)
switch sampledState {
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:
break
}
@ -286,7 +303,7 @@ final class MutableMessageHistoryView {
let sampledState = loadingState.checkAndSample(postbox: postbox)
switch sampledState {
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:
break
}
@ -472,7 +489,7 @@ final class MutableMessageHistoryView {
let sampledState = loadingState.checkAndSample(postbox: postbox)
switch sampledState {
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:
break
}
@ -672,6 +689,7 @@ final class MutableMessageHistoryView {
public final class MessageHistoryView {
public let tagMask: MessageTags?
public let namespaces: HistoryViewNamespaces
public let anchorIndex: MessageHistoryAnchorIndex
public let earlierId: MessageIndex?
public let laterId: MessageIndex?
@ -686,6 +704,7 @@ public final class MessageHistoryView {
init(_ mutableView: MutableMessageHistoryView) {
self.tagMask = mutableView.tag
self.namespaces = mutableView.namespaces
var entries: [MessageHistoryEntry]
switch mutableView.sampledState {
case .loading:
@ -714,7 +733,7 @@ public final class MessageHistoryView {
entries = []
if let transientReadStates = mutableView.transientReadStates, case let .peer(states) = transientReadStates {
for entry in state.entries {
if !mutableView.excludeNamespaces.contains(entry.message.id.namespace) {
if mutableView.namespaces.contains(entry.message.id.namespace) {
let read: Bool
if entry.message.flags.contains(.Incoming) {
read = false
@ -728,7 +747,7 @@ public final class MessageHistoryView {
}
} else {
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))
}
}

View File

@ -748,7 +748,7 @@ struct HistoryViewLoadedSample {
final class HistoryViewLoadedState {
let anchor: HistoryViewAnchor
let tag: MessageTags?
let excludeNamespaces: [MessageId.Namespace]
let namespaces: HistoryViewNamespaces
let statistics: MessageHistoryViewOrderStatistics
let halfLimit: Int
let seedConfiguration: SeedConfiguration
@ -756,11 +756,11 @@ final class HistoryViewLoadedState {
var holes: HistoryViewHoles
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)
self.anchor = anchor
self.tag = tag
self.excludeNamespaces = excludeNamespaces
self.namespaces = namespaces
self.statistics = statistics
self.halfLimit = halfLimit
self.seedConfiguration = postbox.seedConfiguration
@ -781,7 +781,7 @@ final class HistoryViewLoadedState {
var spaces: [PeerIdAndNamespace] = []
for peerId in peerIds {
for namespace in postbox.messageHistoryIndexTable.existingNamespaces(peerId: peerId) {
if !excludeNamespaces.contains(namespace) {
if namespaces.contains(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 peerIds: [PeerId] = []
switch locations {
@ -1193,7 +1193,7 @@ private func fetchHoles(postbox: Postbox, locations: MessageHistoryViewPeerIds,
let holeSpace = tag.flatMap(MessageHistoryHoleSpace.tag) ?? .everywhere
for peerId in peerIds {
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))
if !indices.isEmpty {
let peerIdAndNamespace = PeerIdAndNamespace(peerId: peerId, namespace: namespace)
@ -1217,11 +1217,11 @@ final class HistoryViewLoadingState {
let halfLimit: Int
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.tag = tag
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 {
@ -1261,14 +1261,14 @@ enum HistoryViewState {
case loaded(HistoryViewLoadedState)
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 {
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:
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:
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:
let anchorPeerId: PeerId
switch locations {
@ -1301,26 +1301,26 @@ enum HistoryViewState {
}
}
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)
switch sampledState {
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:
self = .loading(loadingState)
}
} 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 {
preconditionFailure()
}
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)
switch sampledState {
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:
self = .loading(loadingState)
}

View File

@ -52,7 +52,7 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView {
}
}
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()
}
@ -117,7 +117,7 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView {
if 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()
} else if self.wrappedView.replay(postbox: postbox, transaction: transaction) {
return self.updateFromView()

View File

@ -2219,7 +2219,7 @@ public final class Postbox {
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
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
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
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 mainPeerId: PeerId?
switch peerIds {
@ -2371,7 +2371,7 @@ public final class Postbox {
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 {
return Int32(self.messageHistoryTable.getMessageCountInRange(peerId: lowerBound.id.peerId, namespace: lowerBound.id.namespace, tag: tagMask, lowerBound: lowerBound, upperBound: upperBound))
} else {

View File

@ -1243,6 +1243,7 @@ public class Account {
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(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>] = [
managedSynchronizeChatInputStateOperations(postbox: self.postbox, network: self.network) |> map { $0 ? AccountRunningImportantTasks.other : [] },

View File

@ -143,7 +143,6 @@ private var declaredEncodables: Void = {
declareEncodable(EmojiKeywordCollectionInfo.self, f: { EmojiKeywordCollectionInfo(decoder: $0) })
declareEncodable(EmojiKeywordItem.self, f: { EmojiKeywordItem(decoder: $0) })
declareEncodable(SynchronizeEmojiKeywordsOperation.self, f: { SynchronizeEmojiKeywordsOperation(decoder: $0) })
declareEncodable(CloudPhotoSizeMediaResource.self, f: { CloudPhotoSizeMediaResource(decoder: $0) })
declareEncodable(CloudDocumentSizeMediaResource.self, f: { CloudDocumentSizeMediaResource(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(UpdateMessageReactionsAction.self, f: { UpdateMessageReactionsAction(decoder: $0) })
declareEncodable(RestrictedContentMessageAttribute.self, f: { RestrictedContentMessageAttribute(decoder: $0) })
declareEncodable(SendScheduledMessageImmediatelyAction.self, f: { SendScheduledMessageImmediatelyAction(decoder: $0) })
return
}()

View File

@ -189,7 +189,7 @@ private func wrappedHistoryViewAdditionalData(chatLocation: ChatLocation, additi
switch chatLocation {
case let .peer(peerId):
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))
}
}
@ -872,9 +872,7 @@ public final class AccountViewTracker {
strongSelf.updatePolls(viewId: viewId, messageIds: pollMessageIds, messages: pollMessageDict)
if case let .peer(peerId) = chatLocation, peerId.namespace == Namespaces.Peer.CloudChannel {
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
@ -887,8 +885,6 @@ public final class AccountViewTracker {
if peerId.namespace == Namespaces.Peer.CloudChannel {
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> {
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 {
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)
} else {
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 {
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)
} else {
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 {
let inputAnchor: HistoryViewInputAnchor
switch index {
@ -947,7 +969,7 @@ public final class AccountViewTracker {
case let .message(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)
} else {
return .never()

View File

@ -387,14 +387,17 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
}
var messageNamespace = Namespaces.Message.Local
var effectiveTimestamp = timestamp
for attribute in attributes {
if attribute is OutgoingScheduleInfoMessageAttribute {
if let attribute = attribute as? OutgoingScheduleInfoMessageAttribute {
messageNamespace = Namespaces.Message.ScheduledLocal
effectiveTimestamp = attribute.scheduleTime
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):
let sourceMessage = transaction.getMessage(source)
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 entitiesAttribute: TextEntitiesMessageAttribute?
var effectiveTimestamp = timestamp
for attribute in attributes {
if let attribute = attribute as? TextEntitiesMessageAttribute {
entitiesAttribute = attribute
}
if attribute is OutgoingScheduleInfoMessageAttribute {
if let attribute = attribute as? OutgoingScheduleInfoMessageAttribute {
messageNamespace = Namespaces.Message.ScheduledLocal
effectiveTimestamp = attribute.scheduleTime
}
}
@ -543,7 +548,7 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
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))
}
}
}

View File

@ -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
}

View File

@ -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))
})
}
}
}
}
}

View File

@ -151,14 +151,6 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH
var implicitelyFillHole = false
let minMaxRange: ClosedRange<MessageId.Id>
if namespace == Namespaces.Message.ScheduledCloud {
switch direction {
case .aroundId, .range:
implicitelyFillHole = true
}
minMaxRange = 1 ... (Int32.max - 1)
request = source.request(Api.functions.messages.getScheduledHistory(peer: inputPeer, hash: 0))
} else {
switch space {
case .everywhere:
let offsetId: Int32
@ -320,7 +312,6 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH
request = .never()
}
}
}
return request
|> retryRequest

View File

@ -12,6 +12,8 @@ public struct Namespaces {
public static let SecretIncoming: Int32 = 2
public static let ScheduledCloud: Int32 = 3
public static let ScheduledLocal: Int32 = 4
public static let allScheduled: Set<Int32> = Set([Namespaces.Message.ScheduledCloud, Namespaces.Message.ScheduledLocal])
}
public struct Media {
@ -110,6 +112,7 @@ public extension LocalMessageTags {
public extension PendingMessageActionType {
static let consumeUnseenPersonalMessage = PendingMessageActionType(rawValue: 0)
static let updateReaction = PendingMessageActionType(rawValue: 1)
static let sendScheduledMessageImmediately = PendingMessageActionType(rawValue: 2)
}
let peerIdNamespacesWithInitialCloudMessageHoles = [Namespaces.Peer.CloudUser, Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel]

View File

@ -9,21 +9,171 @@ import Foundation
import TelegramApi
#endif
public func sendScheduledMessageNow(account: Account, messageId: MessageId) -> Signal<Void, NoError> {
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
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)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
return .single(nil)
final class SendScheduledMessageImmediatelyAction: PendingMessageActionData {
init() {
}
|> mapToSignal { updates -> Signal<Void, NoError> in
if let updates = updates {
account.stateManager.addUpdates(updates)
init(decoder: PostboxDecoder) {
}
return .complete()
func encode(_ encoder: PostboxEncoder) {
}
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
}
}
}
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()
}
}
return .complete()
} |> switchToLatest
}

View File

@ -97,7 +97,7 @@ public func searchStickers(account: Account, query: String, scope: SearchSticker
for entry in transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudSavedStickers) {
if let item = entry.contents as? SavedStickerItem {
for representation in item.stringRepresentations {
if representation == query {
if representation.hasPrefix(query) {
result.append(FoundStickerItem(file: item.file, stringRepresentations: item.stringRepresentations))
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 !currentItems.contains(file.fileId) {
for case let .Sticker(sticker) in file.attributes {
if sticker.displayText == query {
if sticker.displayText.hasPrefix(query) {
matchingRecentItemsIds.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 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 !currentItems.contains(item.file.fileId) {
var stringRepresentations: [String] = []

View File

@ -501,8 +501,8 @@
D098908022942E3B0053F151 /* ActiveSessionsContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D098907E22942E3B0053F151 /* ActiveSessionsContext.swift */; };
D099D7461EEF0C2700A3128C /* 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 */; };
D099D74A1EEF418D00A3128C /* HistoryViewChannelStateValidation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099D7481EEF418D00A3128C /* HistoryViewChannelStateValidation.swift */; };
D099D7491EEF418D00A3128C /* HistoryViewStateValidation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099D7481EEF418D00A3128C /* HistoryViewStateValidation.swift */; };
D099D74A1EEF418D00A3128C /* HistoryViewStateValidation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099D7481EEF418D00A3128C /* HistoryViewStateValidation.swift */; };
D099E222229420D600561B75 /* 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 */; };
@ -1055,7 +1055,7 @@
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>"; };
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>"; };
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>"; };
@ -1552,7 +1552,7 @@
D0BEAF5C1E54941B00BD963D /* Authorization.swift */,
D0BEAF5F1E54ACF900BD963D /* AccountManager.swift */,
D0528E591E658B3600E2FEF5 /* ManagedLocalInputActivities.swift */,
D099D7481EEF418D00A3128C /* HistoryViewChannelStateValidation.swift */,
D099D7481EEF418D00A3128C /* HistoryViewStateValidation.swift */,
D0B1671C1F9EA2C300976B40 /* ChatHistoryPreloadManager.swift */,
D06ECFC720B810D300C576C2 /* TermsOfService.swift */,
D051DB16215ECC4D00F30F92 /* AppChangelog.swift */,
@ -2235,7 +2235,7 @@
D08984FB2118816A00918162 /* Reachability.m in Sources */,
D0DA1D321F7043D50034E892 /* ManagedPendingPeerNotificationSettings.swift in Sources */,
0925903722F0D02D003D6283 /* ManagedAnimatedEmojiUpdates.swift in Sources */,
D099D7491EEF418D00A3128C /* HistoryViewChannelStateValidation.swift in Sources */,
D099D7491EEF418D00A3128C /* HistoryViewStateValidation.swift in Sources */,
C23BC3871E9BE3CA00D79F92 /* ImportContact.swift in Sources */,
D0D376E622DCCFD600FA7D7C /* SlowMode.swift in Sources */,
D00422D321677F4500719B67 /* ManagedAccountPresence.swift in Sources */,
@ -2649,7 +2649,7 @@
D0E35A131DE4C69100BC6096 /* OutgoingChatContextResultMessageAttribute.swift in Sources */,
D0B418961D7E0580004562A4 /* TelegramMediaFile.swift in Sources */,
D08CAA881ED81DD40000FDA8 /* LocalizationInfo.swift in Sources */,
D099D74A1EEF418D00A3128C /* HistoryViewChannelStateValidation.swift in Sources */,
D099D74A1EEF418D00A3128C /* HistoryViewStateValidation.swift in Sources */,
D0AF32231FAC95C20097362B /* StandaloneUploadedMedia.swift in Sources */,
D04554A721B43440007A6DD9 /* CancelAccountReset.swift in Sources */,
D001F3EC1E128A1C007A8C60 /* Holes.swift in Sources */,

View File

@ -1461,21 +1461,22 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.sendScheduledMessagesNow(messageIds)
}
}, editScheduledMessagesTime: { [weak self] messageIds in
if let strongSelf = self {
if let strongSelf = self, let messageId = messageIds.first {
let mode: ChatScheduleTimeControllerMode
if case let .peer(peerId) = strongSelf.presentationInterfaceState.chatLocation, peerId == strongSelf.context.account.peerId {
mode = .reminders
} else {
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
let _ = (strongSelf.context.account.postbox.transaction { transaction -> Message? in
return transaction.getMessage(messageId)
} |> deliverOnMainQueue).start(next: { [weak self] message in
guard let strongSelf = self, let message = message else {
return
}
|> introduceError(RequestEditMessageError.self)
|> mapToSignal({ message -> Signal<RequestEditMessageResult, RequestEditMessageError> in
if let message = message {
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 {
@ -1483,22 +1484,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
break
}
}
return requestEditMessage(account: strongSelf.context.account, messageId: messageId, text: message.text, media: .keep
, entities: entities, disableUrlPreview: false, scheduleTime: scheduleTime)
} else {
return .complete()
}
}))
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))
})
}
}, performTextSelectionAction: { [weak self] _, text, action in
guard let strongSelf = self else {
@ -1638,8 +1632,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.reportIrrelvantGeoNoticePromise.set(.single(nil))
}
if !isScheduledMessages {
hasScheduledMessages = context.account.viewTracker.aroundMessageHistoryViewForLocation(chatLocation, index: .upperBound, anchorIndex: .upperBound, count: 100, fixedCombinedReadStates: nil, tagMask: nil, excludeNamespaces: [Namespaces.Message.Cloud, Namespaces.Message.Local], orderStatistics: [])
if !isScheduledMessages && peerId.namespace != Namespaces.Peer.SecretChat {
hasScheduledMessages = context.account.viewTracker.scheduledMessagesViewForLocation(chatLocation)
|> map { view, _, _ in
return !view.entries.isEmpty
}
@ -5299,7 +5293,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
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) {

View File

@ -33,7 +33,7 @@ private final class ChatEmptyNodeRegularChatContent: ASDisplayNode, ChatEmptyNod
self.currentStrings = interfaceState.strings
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)

View File

@ -30,7 +30,6 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, account: A
if scheduled {
var preloaded = false
var fadeIn = false
let count = 100
return account.viewTracker.scheduledMessagesViewForLocation(chatLocation)
|> map { view, updateType, initialData -> ChatHistoryViewUpdate in
let (cachedData, cachedDataMessages, readStateData) = extractAdditionalData(view: view, chatLocation: chatLocation)

View File

@ -382,14 +382,18 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
}
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])
f(.dismissWithoutContent)
})))
}
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])
f(.dismissWithoutContent)
})))

View File

@ -329,13 +329,7 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible {
self.effectiveAuthorId = effectiveAuthor?.id
let timestamp: Int32
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
self.header = ChatMessageDateHeader(timestamp: content.index.timestamp, scheduled: associatedData.isScheduledMessages, presentationData: presentationData, context: context, action: { timestamp in
var calendar = NSCalendar.current
calendar.timeZone = TimeZone(abbreviation: "UTC")!
let date = Date(timeIntervalSince1970: TimeInterval(timestamp))

View File

@ -105,7 +105,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
self.contentContainerNode.addSubnode(self.doneButton)
self.pickerView.timeZone = TimeZone.current
self.pickerView.minuteInterval = 5
self.pickerView.minuteInterval = 1
self.pickerView.setValue(self.presentationData.theme.actionSheet.primaryTextColor, forKey: "textColor")
self.contentContainerNode.view.addSubview(self.pickerView)
@ -265,8 +265,11 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
insets.top = max(10.0, insets.top)
let bottomInset: CGFloat = 10.0 + cleanInsets.bottom
let titleAreaHeight: CGFloat = 54.0
let contentHeight = titleAreaHeight + bottomInset + 285.0
let titleHeight: CGFloat = 54.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 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.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)
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)
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)
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.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)))
}
}

View File

@ -10,6 +10,15 @@ import MusicAlbumArtResources
private enum PeerMessagesMediaPlaylistLoadAnchor {
case messageId(MessageId)
case index(MessageIndex)
var id: MessageId {
switch self {
case let .messageId(id):
return id
case let .index(index):
return index.id
}
}
}
private enum PeerMessagesMediaPlaylistNavigation {
@ -477,6 +486,14 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
private func loadItem(anchor: PeerMessagesMediaPlaylistLoadAnchor, navigation: PeerMessagesMediaPlaylistNavigation) {
self.loadingItem = true
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 {
case let .messageId(messageId):
if case let .messages(peerId, tagMask, _) = self.messagesLocation {
@ -486,7 +503,8 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
guard let message = message else {
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
if let (message, aroundMessages, _) = navigatedMessageFromView(view.0, anchorIndex: message.index, position: .exact) {
return .single((message, aroundMessages))
@ -560,7 +578,7 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
}
let historySignal = inputIndex
|> 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
let position: NavigatedMessageFromViewPosition
switch navigation {
@ -590,7 +608,7 @@ final class PeerMessagesMediaPlaylist: SharedMediaPlaylist {
} else {
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
let position: NavigatedMessageFromViewPosition
switch navigation {