mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 06:35:51 +00:00
refactor and cleanup [skip ci]
This commit is contained in:
847
submodules/Postbox/Sources/MessageHistoryView.swift
Normal file
847
submodules/Postbox/Sources/MessageHistoryView.swift
Normal file
@@ -0,0 +1,847 @@
|
||||
import Foundation
|
||||
|
||||
public struct MessageHistoryViewPeerHole: Equatable, Hashable {
|
||||
public let peerId: PeerId
|
||||
public let namespace: MessageId.Namespace
|
||||
}
|
||||
|
||||
public enum MessageHistoryViewHole: Equatable, Hashable {
|
||||
case peer(MessageHistoryViewPeerHole)
|
||||
}
|
||||
|
||||
public struct MessageHistoryMessageEntry {
|
||||
let message: Message
|
||||
let location: MessageHistoryEntryLocation?
|
||||
let monthLocation: MessageHistoryEntryMonthLocation?
|
||||
let attributes: MutableMessageHistoryEntryAttributes
|
||||
}
|
||||
|
||||
enum MutableMessageHistoryEntry {
|
||||
case IntermediateMessageEntry(IntermediateMessage, MessageHistoryEntryLocation?, MessageHistoryEntryMonthLocation?)
|
||||
case MessageEntry(MessageHistoryMessageEntry)
|
||||
|
||||
var index: MessageIndex {
|
||||
switch self {
|
||||
case let .IntermediateMessageEntry(message, _, _):
|
||||
return message.index
|
||||
case let .MessageEntry(message):
|
||||
return message.message.index
|
||||
}
|
||||
}
|
||||
|
||||
var tags: MessageTags {
|
||||
switch self {
|
||||
case let .IntermediateMessageEntry(message, _, _):
|
||||
return message.tags
|
||||
case let .MessageEntry(message):
|
||||
return message.message.tags
|
||||
}
|
||||
}
|
||||
|
||||
func updatedLocation(_ location: MessageHistoryEntryLocation?) -> MutableMessageHistoryEntry {
|
||||
switch self {
|
||||
case let .IntermediateMessageEntry(message, _, monthLocation):
|
||||
return .IntermediateMessageEntry(message, location, monthLocation)
|
||||
case let .MessageEntry(message):
|
||||
return .MessageEntry(MessageHistoryMessageEntry(message: message.message, location: location, monthLocation: message.monthLocation, attributes: message.attributes))
|
||||
}
|
||||
}
|
||||
|
||||
func updatedMonthLocation(_ monthLocation: MessageHistoryEntryMonthLocation?) -> MutableMessageHistoryEntry {
|
||||
switch self {
|
||||
case let .IntermediateMessageEntry(message, location, _):
|
||||
return .IntermediateMessageEntry(message, location, monthLocation)
|
||||
case let .MessageEntry(message):
|
||||
return .MessageEntry(MessageHistoryMessageEntry(message: message.message, location: message.location, monthLocation: monthLocation, attributes: message.attributes))
|
||||
}
|
||||
}
|
||||
|
||||
func offsetLocationForInsertedIndex(_ index: MessageIndex) -> MutableMessageHistoryEntry {
|
||||
switch self {
|
||||
case let .IntermediateMessageEntry(message, location, monthLocation):
|
||||
if let location = location {
|
||||
if MessageIndex(id: message.id, timestamp: message.timestamp) > index {
|
||||
return .IntermediateMessageEntry(message, MessageHistoryEntryLocation(index: location.index + 1, count: location.count + 1), monthLocation)
|
||||
} else {
|
||||
return .IntermediateMessageEntry(message, MessageHistoryEntryLocation(index: location.index, count: location.count - 1), monthLocation)
|
||||
}
|
||||
} else {
|
||||
return self
|
||||
}
|
||||
case let .MessageEntry(message):
|
||||
if let location = message.location {
|
||||
if message.message.index > index {
|
||||
return .MessageEntry(MessageHistoryMessageEntry(message: message.message, location: MessageHistoryEntryLocation(index: location.index + 1, count: location.count + 1), monthLocation: message.monthLocation, attributes: message.attributes))
|
||||
} else {
|
||||
return .MessageEntry(MessageHistoryMessageEntry(message: message.message, location: MessageHistoryEntryLocation(index: location.index, count: location.count + 1), monthLocation: message.monthLocation, attributes: message.attributes))
|
||||
}
|
||||
} else {
|
||||
return self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func offsetLocationForRemovedIndex(_ index: MessageIndex) -> MutableMessageHistoryEntry {
|
||||
switch self {
|
||||
case let .IntermediateMessageEntry(message, location, monthLocation):
|
||||
if let location = location {
|
||||
if MessageIndex(id: message.id, timestamp: message.timestamp) > index {
|
||||
//assert(location.index > 0)
|
||||
//assert(location.count != 0)
|
||||
return .IntermediateMessageEntry(message, MessageHistoryEntryLocation(index: location.index - 1, count: location.count - 1), monthLocation)
|
||||
} else {
|
||||
//assert(location.count != 0)
|
||||
return .IntermediateMessageEntry(message, MessageHistoryEntryLocation(index: location.index, count: location.count - 1), monthLocation)
|
||||
}
|
||||
} else {
|
||||
return self
|
||||
}
|
||||
case let .MessageEntry(message):
|
||||
if let location = message.location {
|
||||
if message.message.index > index {
|
||||
//assert(location.index > 0)
|
||||
//assert(location.count != 0)
|
||||
return .MessageEntry(MessageHistoryMessageEntry(message: message.message, location: MessageHistoryEntryLocation(index: location.index - 1, count: location.count - 1), monthLocation: message.monthLocation, attributes: message.attributes))
|
||||
} else {
|
||||
//assert(location.count != 0)
|
||||
return .MessageEntry(MessageHistoryMessageEntry(message: message.message, location: MessageHistoryEntryLocation(index: location.index, count: location.count - 1), monthLocation: message.monthLocation, attributes: message.attributes))
|
||||
}
|
||||
} else {
|
||||
return self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updatedTimestamp(_ timestamp: Int32) -> MutableMessageHistoryEntry {
|
||||
switch self {
|
||||
case let .IntermediateMessageEntry(message, location, monthLocation):
|
||||
let updatedMessage = IntermediateMessage(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributesData: message.attributesData, embeddedMediaData: message.embeddedMediaData, referencedMedia: message.referencedMedia)
|
||||
return .IntermediateMessageEntry(updatedMessage, location, monthLocation)
|
||||
case let .MessageEntry(value):
|
||||
let message = value.message
|
||||
let updatedMessage = Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: message.media, peers: message.peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds)
|
||||
return .MessageEntry(MessageHistoryMessageEntry(message: updatedMessage, location: value.location, monthLocation: value.monthLocation, attributes: value.attributes))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct MessageHistoryEntryLocation: Equatable {
|
||||
public let index: Int
|
||||
public let count: Int
|
||||
|
||||
var predecessor: MessageHistoryEntryLocation? {
|
||||
if index == 0 {
|
||||
return nil
|
||||
} else {
|
||||
return MessageHistoryEntryLocation(index: index - 1, count: count)
|
||||
}
|
||||
}
|
||||
|
||||
var successor: MessageHistoryEntryLocation {
|
||||
return MessageHistoryEntryLocation(index: index + 1, count: count)
|
||||
}
|
||||
}
|
||||
|
||||
public struct MessageHistoryEntryMonthLocation: Equatable {
|
||||
public let indexInMonth: Int32
|
||||
}
|
||||
|
||||
public struct MessageHistoryEntry: Comparable {
|
||||
public let message: Message
|
||||
public let isRead: Bool
|
||||
public let location: MessageHistoryEntryLocation?
|
||||
public let monthLocation: MessageHistoryEntryMonthLocation?
|
||||
public let attributes: MutableMessageHistoryEntryAttributes
|
||||
|
||||
public var index: MessageIndex {
|
||||
return MessageIndex(id: self.message.id, timestamp: self.message.timestamp)
|
||||
}
|
||||
|
||||
public init(message: Message, isRead: Bool, location: MessageHistoryEntryLocation?, monthLocation: MessageHistoryEntryMonthLocation?, attributes: MutableMessageHistoryEntryAttributes) {
|
||||
self.message = message
|
||||
self.isRead = isRead
|
||||
self.location = location
|
||||
self.monthLocation = monthLocation
|
||||
self.attributes = attributes
|
||||
}
|
||||
|
||||
public static func ==(lhs: MessageHistoryEntry, rhs: MessageHistoryEntry) -> Bool {
|
||||
if lhs.message.index == rhs.message.index && lhs.message.flags == rhs.message.flags && lhs.location == rhs.location && lhs.isRead == rhs.isRead && lhs.monthLocation == rhs.monthLocation && lhs.attributes == rhs.attributes {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public static func <(lhs: MessageHistoryEntry, rhs: MessageHistoryEntry) -> Bool {
|
||||
return lhs.index < rhs.index
|
||||
}
|
||||
}
|
||||
|
||||
enum MessageHistoryTopTaggedMessage {
|
||||
case message(Message)
|
||||
case intermediate(IntermediateMessage)
|
||||
|
||||
var id: MessageId {
|
||||
switch self {
|
||||
case let .message(message):
|
||||
return message.id
|
||||
case let .intermediate(message):
|
||||
return message.id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum MessageHistoryViewRelativeHoleDirection: Equatable, Hashable {
|
||||
case range(start: MessageId, end: MessageId)
|
||||
case aroundId(MessageId)
|
||||
}
|
||||
|
||||
public struct MessageHistoryViewOrderStatistics: OptionSet {
|
||||
public var rawValue: Int32
|
||||
|
||||
public init(rawValue: Int32) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
public static let combinedLocation = MessageHistoryViewOrderStatistics(rawValue: 1 << 0)
|
||||
public static let locationWithinMonth = MessageHistoryViewOrderStatistics(rawValue: 1 << 1)
|
||||
}
|
||||
|
||||
public enum MessageHistoryViewPeerIds: Equatable {
|
||||
case single(PeerId)
|
||||
case associated(PeerId, MessageId?)
|
||||
}
|
||||
|
||||
public enum MessageHistoryViewReadState {
|
||||
case peer([PeerId: CombinedPeerReadState])
|
||||
}
|
||||
|
||||
public enum HistoryViewInputAnchor: Equatable {
|
||||
case lowerBound
|
||||
case upperBound
|
||||
case message(MessageId)
|
||||
case index(MessageIndex)
|
||||
case unread
|
||||
}
|
||||
|
||||
final class MutableMessageHistoryView {
|
||||
private(set) var peerIds: MessageHistoryViewPeerIds
|
||||
let tag: MessageTags?
|
||||
let namespaces: MessageIdNamespaces
|
||||
private let orderStatistics: MessageHistoryViewOrderStatistics
|
||||
private let anchor: HistoryViewInputAnchor
|
||||
|
||||
fileprivate var combinedReadStates: MessageHistoryViewReadState?
|
||||
fileprivate var transientReadStates: MessageHistoryViewReadState?
|
||||
|
||||
fileprivate let fillCount: Int
|
||||
fileprivate var state: HistoryViewState
|
||||
|
||||
fileprivate var topTaggedMessages: [MessageId.Namespace: MessageHistoryTopTaggedMessage?]
|
||||
fileprivate var additionalDatas: [AdditionalMessageHistoryViewDataEntry]
|
||||
|
||||
fileprivate(set) var sampledState: HistoryViewSample
|
||||
|
||||
init(postbox: Postbox, orderStatistics: MessageHistoryViewOrderStatistics, peerIds: MessageHistoryViewPeerIds, anchor inputAnchor: HistoryViewInputAnchor, combinedReadStates: MessageHistoryViewReadState?, transientReadStates: MessageHistoryViewReadState?, tag: MessageTags?, namespaces: MessageIdNamespaces, count: Int, topTaggedMessages: [MessageId.Namespace: MessageHistoryTopTaggedMessage?], additionalDatas: [AdditionalMessageHistoryViewDataEntry], getMessageCountInRange: (MessageIndex, MessageIndex) -> Int32) {
|
||||
self.anchor = inputAnchor
|
||||
|
||||
self.orderStatistics = orderStatistics
|
||||
self.peerIds = peerIds
|
||||
self.combinedReadStates = combinedReadStates
|
||||
self.transientReadStates = transientReadStates
|
||||
self.tag = tag
|
||||
self.namespaces = namespaces
|
||||
self.fillCount = count
|
||||
self.topTaggedMessages = topTaggedMessages
|
||||
self.additionalDatas = additionalDatas
|
||||
|
||||
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, namespaces: namespaces, statistics: self.orderStatistics, halfLimit: count + 1, locations: peerIds, postbox: postbox, holes: holes))
|
||||
self.sampledState = self.state.sample(postbox: postbox)
|
||||
case .loadHole:
|
||||
break
|
||||
}
|
||||
}
|
||||
self.sampledState = self.state.sample(postbox: postbox)
|
||||
|
||||
self.render(postbox: postbox)
|
||||
}
|
||||
|
||||
private func reset(postbox: Postbox) {
|
||||
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, namespaces: self.namespaces, statistics: self.orderStatistics, halfLimit: self.fillCount + 1, locations: self.peerIds, postbox: postbox, holes: holes))
|
||||
case .loadHole:
|
||||
break
|
||||
}
|
||||
}
|
||||
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, namespaces: self.namespaces, statistics: self.orderStatistics, halfLimit: self.fillCount + 1, locations: self.peerIds, postbox: postbox, holes: holes))
|
||||
case .loadHole:
|
||||
break
|
||||
}
|
||||
}
|
||||
self.sampledState = self.state.sample(postbox: postbox)
|
||||
}
|
||||
|
||||
func refreshDueToExternalTransaction(postbox: Postbox) -> Bool {
|
||||
self.reset(postbox: postbox)
|
||||
return true
|
||||
}
|
||||
|
||||
func updatePeerIds(transaction: PostboxTransaction) {
|
||||
switch self.peerIds {
|
||||
case let .single(peerId):
|
||||
if let updatedData = transaction.currentUpdatedCachedPeerData[peerId] {
|
||||
if updatedData.associatedHistoryMessageId != nil {
|
||||
self.peerIds = .associated(peerId, updatedData.associatedHistoryMessageId)
|
||||
}
|
||||
}
|
||||
case let .associated(peerId, associatedId):
|
||||
if let updatedData = transaction.currentUpdatedCachedPeerData[peerId] {
|
||||
if updatedData.associatedHistoryMessageId != associatedId {
|
||||
self.peerIds = .associated(peerId, updatedData.associatedHistoryMessageId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func replay(postbox: Postbox, transaction: PostboxTransaction) -> Bool {
|
||||
var operations: [[MessageHistoryOperation]] = []
|
||||
var peerIdsSet = Set<PeerId>()
|
||||
|
||||
switch self.peerIds {
|
||||
case let .single(peerId):
|
||||
peerIdsSet.insert(peerId)
|
||||
if let value = transaction.currentOperationsByPeerId[peerId] {
|
||||
operations.append(value)
|
||||
}
|
||||
case .associated:
|
||||
switch self.peerIds {
|
||||
case .single:
|
||||
assertionFailure()
|
||||
case let .associated(mainPeerId, associatedPeerId):
|
||||
peerIdsSet.insert(mainPeerId)
|
||||
if let associatedPeerId = associatedPeerId {
|
||||
peerIdsSet.insert(associatedPeerId.peerId)
|
||||
}
|
||||
}
|
||||
|
||||
for (peerId, value) in transaction.currentOperationsByPeerId {
|
||||
if peerIdsSet.contains(peerId) {
|
||||
operations.append(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var hasChanges = false
|
||||
|
||||
let unwrappedTag: MessageTags = self.tag ?? []
|
||||
|
||||
switch self.state {
|
||||
case let .loading(loadingState):
|
||||
for (key, holeOperations) in transaction.currentPeerHoleOperations {
|
||||
var matchesSpace = false
|
||||
switch key.space {
|
||||
case .everywhere:
|
||||
matchesSpace = unwrappedTag.isEmpty
|
||||
case let .tag(tag):
|
||||
if let currentTag = self.tag, currentTag == tag {
|
||||
matchesSpace = true
|
||||
}
|
||||
}
|
||||
if matchesSpace {
|
||||
if peerIdsSet.contains(key.peerId) {
|
||||
for operation in holeOperations {
|
||||
switch operation {
|
||||
case let .insert(range):
|
||||
if loadingState.insertHole(space: PeerIdAndNamespace(peerId: key.peerId, namespace: key.namespace), range: range) {
|
||||
hasChanges = true
|
||||
}
|
||||
case let .remove(range):
|
||||
if loadingState.removeHole(space: PeerIdAndNamespace(peerId: key.peerId, namespace: key.namespace), range: range) {
|
||||
hasChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case let .loaded(loadedState):
|
||||
for operationSet in operations {
|
||||
var addCount = 0
|
||||
var removeCount = 0
|
||||
for operation in operationSet {
|
||||
switch operation {
|
||||
case .InsertMessage:
|
||||
addCount += 1
|
||||
case .Remove:
|
||||
removeCount += 1
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
if addCount == 2 && removeCount == 2 {
|
||||
assert(true)
|
||||
}
|
||||
for operation in operationSet {
|
||||
switch operation {
|
||||
case let .InsertMessage(message):
|
||||
if unwrappedTag.isEmpty || message.tags.contains(unwrappedTag) {
|
||||
if loadedState.add(entry: .IntermediateMessageEntry(message, nil, nil)) {
|
||||
hasChanges = true
|
||||
}
|
||||
}
|
||||
case let .Remove(indicesAndTags):
|
||||
for (index, _) in indicesAndTags {
|
||||
if loadedState.remove(index: index) {
|
||||
hasChanges = true
|
||||
}
|
||||
}
|
||||
case let .UpdateEmbeddedMedia(index, buffer):
|
||||
if loadedState.updateEmbeddedMedia(index: index, buffer: buffer) {
|
||||
hasChanges = true
|
||||
}
|
||||
case let .UpdateGroupInfos(groupInfos):
|
||||
if loadedState.updateGroupInfo(mapping: groupInfos) {
|
||||
hasChanges = true
|
||||
}
|
||||
case let .UpdateReadState(peerId, combinedReadState):
|
||||
hasChanges = true
|
||||
if let transientReadStates = self.transientReadStates {
|
||||
switch transientReadStates {
|
||||
case let .peer(states):
|
||||
var updatedStates = states
|
||||
updatedStates[peerId] = combinedReadState
|
||||
self.transientReadStates = .peer(updatedStates)
|
||||
}
|
||||
}
|
||||
case let .UpdateTimestamp(index, timestamp):
|
||||
if loadedState.updateTimestamp(postbox: postbox, index: index, timestamp: timestamp) {
|
||||
hasChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (key, holeOperations) in transaction.currentPeerHoleOperations {
|
||||
var matchesSpace = false
|
||||
switch key.space {
|
||||
case .everywhere:
|
||||
matchesSpace = unwrappedTag.isEmpty
|
||||
case let .tag(tag):
|
||||
if let currentTag = self.tag, currentTag == tag {
|
||||
matchesSpace = true
|
||||
}
|
||||
}
|
||||
if matchesSpace {
|
||||
if peerIdsSet.contains(key.peerId) {
|
||||
for operation in holeOperations {
|
||||
switch operation {
|
||||
case let .insert(range):
|
||||
if loadedState.insertHole(space: PeerIdAndNamespace(peerId: key.peerId, namespace: key.namespace), range: range) {
|
||||
hasChanges = true
|
||||
}
|
||||
case let .remove(range):
|
||||
if loadedState.removeHole(space: PeerIdAndNamespace(peerId: key.peerId, namespace: key.namespace), range: range) {
|
||||
hasChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !transaction.updatedMedia.isEmpty {
|
||||
if loadedState.updateMedia(updatedMedia: transaction.updatedMedia) {
|
||||
hasChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if hasChanges {
|
||||
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, namespaces: self.namespaces, statistics: self.orderStatistics, halfLimit: self.fillCount + 1, locations: self.peerIds, postbox: postbox, holes: holes))
|
||||
case .loadHole:
|
||||
break
|
||||
}
|
||||
}
|
||||
self.sampledState = self.state.sample(postbox: postbox)
|
||||
}
|
||||
|
||||
for operationSet in operations {
|
||||
for operation in operationSet {
|
||||
switch operation {
|
||||
case let .InsertMessage(message):
|
||||
if message.flags.contains(.TopIndexable) {
|
||||
if let currentTopMessage = self.topTaggedMessages[message.id.namespace] {
|
||||
if currentTopMessage == nil || currentTopMessage!.id < message.id {
|
||||
self.topTaggedMessages[message.id.namespace] = MessageHistoryTopTaggedMessage.intermediate(message)
|
||||
hasChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
case let .Remove(indices):
|
||||
if !self.topTaggedMessages.isEmpty {
|
||||
for (index, _) in indices {
|
||||
if let maybeCurrentTopMessage = self.topTaggedMessages[index.id.namespace], let currentTopMessage = maybeCurrentTopMessage, index.id == currentTopMessage.id {
|
||||
let item: MessageHistoryTopTaggedMessage? = nil
|
||||
self.topTaggedMessages[index.id.namespace] = item
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var updatedCachedPeerDataMessages = false
|
||||
var currentCachedPeerData: CachedPeerData?
|
||||
for i in 0 ..< self.additionalDatas.count {
|
||||
switch self.additionalDatas[i] {
|
||||
case let .cachedPeerData(peerId, currentData):
|
||||
currentCachedPeerData = currentData
|
||||
if let updatedData = transaction.currentUpdatedCachedPeerData[peerId] {
|
||||
if currentData?.messageIds != updatedData.messageIds {
|
||||
updatedCachedPeerDataMessages = true
|
||||
}
|
||||
currentCachedPeerData = updatedData
|
||||
self.additionalDatas[i] = .cachedPeerData(peerId, updatedData)
|
||||
hasChanges = true
|
||||
}
|
||||
case .cachedPeerDataMessages:
|
||||
break
|
||||
case let .peerChatState(peerId, _):
|
||||
if transaction.currentUpdatedPeerChatStates.contains(peerId) {
|
||||
self.additionalDatas[i] = .peerChatState(peerId, postbox.peerChatStateTable.get(peerId) as? PeerChatState)
|
||||
hasChanges = true
|
||||
}
|
||||
case .totalUnreadState:
|
||||
break
|
||||
case .peerNotificationSettings:
|
||||
break
|
||||
case let .cacheEntry(entryId, _):
|
||||
if transaction.updatedCacheEntryKeys.contains(entryId) {
|
||||
self.additionalDatas[i] = .cacheEntry(entryId, postbox.retrieveItemCacheEntry(id: entryId))
|
||||
hasChanges = true
|
||||
}
|
||||
case .preferencesEntry:
|
||||
break
|
||||
case let .peerIsContact(peerId, value):
|
||||
if let replacedPeerIds = transaction.replaceContactPeerIds {
|
||||
let updatedValue: Bool
|
||||
if let contactPeer = postbox.peerTable.get(peerId), let associatedPeerId = contactPeer.associatedPeerId {
|
||||
updatedValue = replacedPeerIds.contains(associatedPeerId)
|
||||
} else {
|
||||
updatedValue = replacedPeerIds.contains(peerId)
|
||||
}
|
||||
|
||||
if value != updatedValue {
|
||||
self.additionalDatas[i] = .peerIsContact(peerId, value)
|
||||
hasChanges = true
|
||||
}
|
||||
}
|
||||
case let .peer(peerId, _):
|
||||
if let peer = transaction.currentUpdatedPeers[peerId] {
|
||||
self.additionalDatas[i] = .peer(peerId, peer)
|
||||
hasChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if let cachedData = currentCachedPeerData, !cachedData.messageIds.isEmpty {
|
||||
for i in 0 ..< self.additionalDatas.count {
|
||||
switch self.additionalDatas[i] {
|
||||
case .cachedPeerDataMessages(_, _):
|
||||
outer: for operationSet in operations {
|
||||
for operation in operationSet {
|
||||
switch operation {
|
||||
case let .InsertMessage(message):
|
||||
if cachedData.messageIds.contains(message.id) {
|
||||
updatedCachedPeerDataMessages = true
|
||||
break outer
|
||||
}
|
||||
case let .Remove(indicesWithTags):
|
||||
for (index, _) in indicesWithTags {
|
||||
if cachedData.messageIds.contains(index.id) {
|
||||
updatedCachedPeerDataMessages = true
|
||||
break outer
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if updatedCachedPeerDataMessages {
|
||||
hasChanges = true
|
||||
for i in 0 ..< self.additionalDatas.count {
|
||||
switch self.additionalDatas[i] {
|
||||
case let .cachedPeerDataMessages(peerId, _):
|
||||
var messages: [MessageId: Message] = [:]
|
||||
if let cachedData = currentCachedPeerData {
|
||||
for id in cachedData.messageIds {
|
||||
if let message = postbox.getMessage(id) {
|
||||
messages[id] = message
|
||||
}
|
||||
}
|
||||
}
|
||||
self.additionalDatas[i] = .cachedPeerDataMessages(peerId, messages)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !transaction.currentPeerHoleOperations.isEmpty {
|
||||
var peerIdsSet: [PeerId] = []
|
||||
switch peerIds {
|
||||
case let .single(peerId):
|
||||
peerIdsSet.append(peerId)
|
||||
case let .associated(peerId, associatedId):
|
||||
peerIdsSet.append(peerId)
|
||||
if let associatedId = associatedId {
|
||||
peerIdsSet.append(associatedId.peerId)
|
||||
}
|
||||
}
|
||||
let space: MessageHistoryHoleSpace = self.tag.flatMap(MessageHistoryHoleSpace.tag) ?? .everywhere
|
||||
for key in transaction.currentPeerHoleOperations.keys {
|
||||
if peerIdsSet.contains(key.peerId) && key.space == space {
|
||||
hasChanges = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if hasChanges {
|
||||
self.render(postbox: postbox)
|
||||
}
|
||||
|
||||
return hasChanges
|
||||
}
|
||||
|
||||
private func render(postbox: Postbox) {
|
||||
for namespace in self.topTaggedMessages.keys {
|
||||
if let entry = self.topTaggedMessages[namespace]!, case let .intermediate(message) = entry {
|
||||
let item: MessageHistoryTopTaggedMessage? = .message(postbox.messageHistoryTable.renderMessage(message, peerTable: postbox.peerTable))
|
||||
self.topTaggedMessages[namespace] = item
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func firstHole() -> (MessageHistoryViewHole, MessageHistoryViewRelativeHoleDirection)? {
|
||||
switch self.sampledState {
|
||||
case let .loading(loadingSample):
|
||||
switch loadingSample {
|
||||
case .ready:
|
||||
return nil
|
||||
case let .loadHole(peerId, namespace, _, id):
|
||||
return (.peer(MessageHistoryViewPeerHole(peerId: peerId, namespace: namespace)), .aroundId(MessageId(peerId: peerId, namespace: namespace, id: id)))
|
||||
}
|
||||
case let .loaded(loadedSample):
|
||||
if let hole = loadedSample.hole {
|
||||
let direction: MessageHistoryViewRelativeHoleDirection
|
||||
if let endId = hole.endId {
|
||||
direction = .range(start: MessageId(peerId: hole.peerId, namespace: hole.namespace, id: hole.startId), end: MessageId(peerId: hole.peerId, namespace: hole.namespace, id: endId))
|
||||
} else {
|
||||
direction = .aroundId(MessageId(peerId: hole.peerId, namespace: hole.namespace, id: hole.startId))
|
||||
}
|
||||
return (.peer(MessageHistoryViewPeerHole(peerId: hole.peerId, namespace: hole.namespace)), direction)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class MessageHistoryView {
|
||||
public let tagMask: MessageTags?
|
||||
public let namespaces: MessageIdNamespaces
|
||||
public let anchorIndex: MessageHistoryAnchorIndex
|
||||
public let earlierId: MessageIndex?
|
||||
public let laterId: MessageIndex?
|
||||
public let holeEarlier: Bool
|
||||
public let holeLater: Bool
|
||||
public let entries: [MessageHistoryEntry]
|
||||
public let maxReadIndex: MessageIndex?
|
||||
public let fixedReadStates: MessageHistoryViewReadState?
|
||||
public let topTaggedMessages: [Message]
|
||||
public let additionalData: [AdditionalMessageHistoryViewDataEntry]
|
||||
public let isLoading: Bool
|
||||
|
||||
init(_ mutableView: MutableMessageHistoryView) {
|
||||
self.tagMask = mutableView.tag
|
||||
self.namespaces = mutableView.namespaces
|
||||
var entries: [MessageHistoryEntry]
|
||||
switch mutableView.sampledState {
|
||||
case .loading:
|
||||
self.isLoading = true
|
||||
self.anchorIndex = .upperBound
|
||||
entries = []
|
||||
self.holeEarlier = true
|
||||
self.holeLater = true
|
||||
self.earlierId = nil
|
||||
self.laterId = nil
|
||||
case let .loaded(state):
|
||||
var isLoading = false
|
||||
switch state.anchor {
|
||||
case .lowerBound:
|
||||
self.anchorIndex = .lowerBound
|
||||
case .upperBound:
|
||||
self.anchorIndex = .upperBound
|
||||
case let .index(index):
|
||||
self.anchorIndex = .message(index)
|
||||
}
|
||||
self.holeEarlier = state.holesToLower
|
||||
self.holeLater = state.holesToHigher
|
||||
if state.entries.isEmpty && state.hole != nil {
|
||||
isLoading = true
|
||||
}
|
||||
entries = []
|
||||
if let transientReadStates = mutableView.transientReadStates, case let .peer(states) = transientReadStates {
|
||||
for entry in state.entries {
|
||||
if mutableView.namespaces.contains(entry.message.id.namespace) {
|
||||
let read: Bool
|
||||
if entry.message.flags.contains(.Incoming) {
|
||||
read = false
|
||||
} else if let readState = states[entry.message.id.peerId] {
|
||||
read = readState.isOutgoingMessageIndexRead(entry.message.index)
|
||||
} else {
|
||||
read = false
|
||||
}
|
||||
entries.append(MessageHistoryEntry(message: entry.message, isRead: read, location: entry.location, monthLocation: entry.monthLocation, attributes: entry.attributes))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for entry in state.entries {
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(Set(entries.map({ $0.message.stableId })).count == entries.count)
|
||||
if !entries.isEmpty {
|
||||
let anchorIndex = binaryIndexOrLower(entries, state.anchor)
|
||||
let lowerOrEqualThanAnchorCount = anchorIndex + 1
|
||||
let higherThanAnchorCount = entries.count - anchorIndex - 1
|
||||
|
||||
if higherThanAnchorCount > mutableView.fillCount {
|
||||
self.laterId = entries[entries.count - 1].index
|
||||
entries.removeLast()
|
||||
} else {
|
||||
self.laterId = nil
|
||||
}
|
||||
|
||||
if lowerOrEqualThanAnchorCount > mutableView.fillCount {
|
||||
self.earlierId = entries[0].index
|
||||
entries.removeFirst()
|
||||
} else {
|
||||
self.earlierId = nil
|
||||
}
|
||||
} else {
|
||||
self.earlierId = nil
|
||||
self.laterId = nil
|
||||
if state.holesToLower || state.holesToHigher {
|
||||
isLoading = true
|
||||
}
|
||||
}
|
||||
self.isLoading = isLoading
|
||||
}
|
||||
|
||||
var topTaggedMessages: [Message] = []
|
||||
for (_, message) in mutableView.topTaggedMessages {
|
||||
if let message = message {
|
||||
switch message {
|
||||
case let .message(message):
|
||||
topTaggedMessages.append(message)
|
||||
default:
|
||||
assertionFailure("unexpected intermediate tagged message entry in MessageHistoryView.init()")
|
||||
}
|
||||
}
|
||||
}
|
||||
self.topTaggedMessages = topTaggedMessages
|
||||
self.additionalData = mutableView.additionalDatas
|
||||
|
||||
self.fixedReadStates = mutableView.combinedReadStates
|
||||
|
||||
if let combinedReadStates = mutableView.combinedReadStates {
|
||||
switch combinedReadStates {
|
||||
case let .peer(states):
|
||||
var hasUnread = false
|
||||
for (_, readState) in states {
|
||||
if readState.count > 0 {
|
||||
hasUnread = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var maxIndex: MessageIndex?
|
||||
|
||||
if hasUnread {
|
||||
var peerIds = Set<PeerId>()
|
||||
for entry in entries {
|
||||
peerIds.insert(entry.index.id.peerId)
|
||||
}
|
||||
for peerId in peerIds {
|
||||
if let combinedReadState = states[peerId] {
|
||||
for (namespace, state) in combinedReadState.states {
|
||||
var maxNamespaceIndex: MessageIndex?
|
||||
var index = entries.count - 1
|
||||
for entry in entries.reversed() {
|
||||
if entry.index.id.peerId == peerId && entry.index.id.namespace == namespace && state.isIncomingMessageIndexRead(entry.index) {
|
||||
maxNamespaceIndex = entry.index
|
||||
break
|
||||
}
|
||||
index -= 1
|
||||
}
|
||||
if maxNamespaceIndex == nil && index == -1 && entries.count != 0 {
|
||||
index = 0
|
||||
for entry in entries {
|
||||
if entry.index.id.peerId == peerId && entry.index.id.namespace == namespace {
|
||||
maxNamespaceIndex = entry.index.predecessor()
|
||||
break
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
if let _ = maxNamespaceIndex , index + 1 < entries.count {
|
||||
for i in index + 1 ..< entries.count {
|
||||
if entries[i].message.flags.intersection(.IsIncomingMask).isEmpty {
|
||||
maxNamespaceIndex = entries[i].message.index
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if let maxNamespaceIndex = maxNamespaceIndex , maxIndex == nil || maxIndex! < maxNamespaceIndex {
|
||||
maxIndex = maxNamespaceIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.maxReadIndex = maxIndex
|
||||
}
|
||||
} else {
|
||||
self.maxReadIndex = nil
|
||||
}
|
||||
|
||||
self.entries = entries
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user