import Foundation public enum ChatListEntry: Comparable { case MessageEntry(ChatListIndex, Message?, CombinedPeerReadState?, PeerNotificationSettings?, PeerChatListEmbeddedInterfaceState?, RenderedPeer) case HoleEntry(ChatListHole) public var index: ChatListIndex { switch self { case let .MessageEntry(index, _, _, _, _, _): return index case let .HoleEntry(hole): return ChatListIndex(pinningIndex: nil, messageIndex: hole.index) } } } public func ==(lhs: ChatListEntry, rhs: ChatListEntry) -> Bool { switch lhs { case let .MessageEntry(lhsIndex, lhsMessage, lhsReadState, lhsSettings, lhsEmbeddedState, lhsPeer): switch rhs { case let .MessageEntry(rhsIndex, rhsMessage, rhsReadState, rhsSettings, rhsEmbeddedState, rhsPeer): if lhsIndex != rhsIndex { return false } if lhsReadState != rhsReadState { return false } if lhsMessage?.stableVersion != rhsMessage?.stableVersion { return false } if let lhsSettings = lhsSettings, let rhsSettings = rhsSettings { if !lhsSettings.isEqual(to: rhsSettings) { return false } } else if (lhsSettings != nil) != (rhsSettings != nil) { return false } if let lhsEmbeddedState = lhsEmbeddedState, let rhsEmbeddedState = rhsEmbeddedState { if !lhsEmbeddedState.isEqual(to: rhsEmbeddedState) { return false } } else if (lhsEmbeddedState != nil) != (rhsEmbeddedState != nil) { return false } if lhsPeer != rhsPeer { return false } return true default: return false } case let .HoleEntry(hole): if case .HoleEntry(hole) = rhs { return true } else { return false } } } public func <(lhs: ChatListEntry, rhs: ChatListEntry) -> Bool { return lhs.index < rhs.index } enum MutableChatListEntry: Equatable { case IntermediateMessageEntry(ChatListIndex, IntermediateMessage?, CombinedPeerReadState?, PeerChatListEmbeddedInterfaceState?) case MessageEntry(ChatListIndex, Message?, CombinedPeerReadState?, PeerNotificationSettings?, PeerChatListEmbeddedInterfaceState?, RenderedPeer) case HoleEntry(ChatListHole) var index: ChatListIndex { switch self { case let .IntermediateMessageEntry(index, _, _, _): return index case let .MessageEntry(index, _, _, _, _, _): return index case let .HoleEntry(hole): return ChatListIndex(pinningIndex: nil, messageIndex: hole.index) } } } func ==(lhs: MutableChatListEntry, rhs: MutableChatListEntry) -> Bool { if lhs.index != rhs.index { return false } switch lhs { case .IntermediateMessageEntry: switch rhs { case .IntermediateMessageEntry: return true default: return false } case .MessageEntry: switch rhs { case .MessageEntry: return true default: return false } case .HoleEntry: switch rhs { case .HoleEntry: return true default: return false } } } final class MutableChatListViewReplayContext { var invalidEarlier: Bool = false var invalidLater: Bool = false var removedEntries: Bool = false func empty() -> Bool { return !self.removedEntries && !invalidEarlier && !invalidLater } } private func updateMessagePeers(_ message: Message, updatedPeers: [PeerId: Peer]) -> Message? { var updated = false for (peerId, currentPeer) in message.peers { if let updatedPeer = updatedPeers[peerId], !arePeersEqual(currentPeer, updatedPeer) { updated = true break } } if updated { var peers = SimpleDictionary() for (peerId, currentPeer) in message.peers { if let updatedPeer = updatedPeers[peerId] { peers[peerId] = updatedPeer } else { peers[peerId] = currentPeer } } return Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: message.media, peers: peers, associatedMessages: message.associatedMessages, associatedMessageIds: message.associatedMessageIds) } return nil } private func updatedRenderedPeer(_ renderedPeer: RenderedPeer, updatedPeers: [PeerId: Peer]) -> RenderedPeer? { var updated = false for (peerId, currentPeer) in renderedPeer.peers { if let updatedPeer = updatedPeers[peerId], !arePeersEqual(currentPeer, updatedPeer) { updated = true break } } if updated { var peers = SimpleDictionary() for (peerId, currentPeer) in renderedPeer.peers { if let updatedPeer = updatedPeers[peerId] { peers[peerId] = updatedPeer } else { peers[peerId] = currentPeer } } return RenderedPeer(peerId: renderedPeer.peerId, peers: peers) } return nil } final class MutableChatListView { fileprivate var earlier: MutableChatListEntry? fileprivate var later: MutableChatListEntry? fileprivate var entries: [MutableChatListEntry] private var count: Int init(earlier: MutableChatListEntry?, entries: [MutableChatListEntry], later: MutableChatListEntry?, count: Int) { self.earlier = earlier self.entries = entries self.later = later self.count = count } func refreshDueToExternalTransaction(fetchAroundChatEntries: (_ index: ChatListIndex, _ count: Int) -> (entries: [MutableChatListEntry], earlier: MutableChatListEntry?, later: MutableChatListEntry?)) -> Bool { var index = ChatListIndex.absoluteUpperBound if !self.entries.isEmpty && self.later != nil { index = self.entries[self.entries.count / 2].index } let (entries, earlier, later) = fetchAroundChatEntries(index, self.entries.count) if entries != self.entries || earlier != self.earlier || later != self.later { self.entries = entries self.earlier = earlier self.later = later return true } else { return false } } func replay(_ operations: [ChatListOperation], updatedPeerNotificationSettings: [PeerId: PeerNotificationSettings], updatedPeers: [PeerId: Peer], context: MutableChatListViewReplayContext) -> Bool { var hasChanges = false for operation in operations { switch operation { case let .InsertEntry(index, message, combinedReadState, embeddedState): if self.add(.IntermediateMessageEntry(index, message, combinedReadState, embeddedState)) { hasChanges = true } case let .InsertHole(index): if self.add(.HoleEntry(index)) { hasChanges = true } case let .RemoveEntry(indices): if self.remove(Set(indices), holes: false, context: context) { hasChanges = true } case let .RemoveHoles(indices): if self.remove(Set(indices), holes: true, context: context) { hasChanges = true } } } if !updatedPeerNotificationSettings.isEmpty { for i in 0 ..< self.entries.count { switch self.entries[i] { case let .MessageEntry(index, message, readState, _, embeddedState, peer): var notificationSettingsPeerId = peer.peerId if let peer = peer.peers[peer.peerId], let associatedPeerId = peer.associatedPeerId { notificationSettingsPeerId = associatedPeerId } if let settings = updatedPeerNotificationSettings[notificationSettingsPeerId] { self.entries[i] = .MessageEntry(index, message, readState, settings, embeddedState, peer) hasChanges = true } default: continue } } } if !updatedPeers.isEmpty { for i in 0 ..< self.entries.count { switch self.entries[i] { case let .MessageEntry(index, message, readState, settings, embeddedState, peer): var updatedMessage: Message? if let message = message { updatedMessage = updateMessagePeers(message, updatedPeers: updatedPeers) } let updatedPeer = updatedRenderedPeer(peer, updatedPeers: updatedPeers) if updatedMessage != nil || updatedPeer != nil { self.entries[i] = .MessageEntry(index, updatedMessage ?? message, readState, settings, embeddedState, updatedPeer ?? peer) hasChanges = true } default: continue } } } return hasChanges } func add(_ entry: MutableChatListEntry) -> Bool { if self.entries.count == 0 { self.entries.append(entry) return true } else { let first = self.entries[self.entries.count - 1] let last = self.entries[0] let next = self.later if entry.index < last.index { if self.earlier == nil || self.earlier!.index < entry.index { if self.entries.count < self.count { self.entries.insert(entry, at: 0) } else { self.earlier = entry } return true } else { return false } } else if entry.index > first.index { if next != nil && entry.index > next!.index { if self.later == nil || self.later!.index > entry.index { if self.entries.count < self.count { self.entries.append(entry) } else { self.later = entry } return true } else { return false } } else { self.entries.append(entry) if self.entries.count > self.count { self.earlier = self.entries[0] self.entries.remove(at: 0) } return true } } else if entry != last && entry != first { var i = self.entries.count while i >= 1 { if self.entries[i - 1].index < entry.index { break } i -= 1 } self.entries.insert(entry, at: i) if self.entries.count > self.count { self.earlier = self.entries[0] self.entries.remove(at: 0) } return true } else { return false } } } func remove(_ indices: Set, holes: Bool, context: MutableChatListViewReplayContext) -> Bool { var hasChanges = false if let earlier = self.earlier , indices.contains(earlier.index) { var match = false switch earlier { case .HoleEntry: match = holes case .IntermediateMessageEntry, .MessageEntry: match = !holes } if match { context.invalidEarlier = true hasChanges = true } } if let later = self.later , indices.contains(later.index) { var match = false switch later { case .HoleEntry: match = holes case .IntermediateMessageEntry, .MessageEntry: match = !holes } if match { context.invalidLater = true hasChanges = true } } if self.entries.count != 0 { var i = self.entries.count - 1 while i >= 0 { if indices.contains(self.entries[i].index) { var match = false switch self.entries[i] { case .HoleEntry: match = holes case .IntermediateMessageEntry, .MessageEntry: match = !holes } if match { self.entries.remove(at: i) context.removedEntries = true hasChanges = true } } i -= 1 } } return hasChanges } func complete(context: MutableChatListViewReplayContext, fetchEarlier: (ChatListIndex?, Int) -> [MutableChatListEntry], fetchLater: (ChatListIndex?, Int) -> [MutableChatListEntry]) { if context.removedEntries { var addedEntries: [MutableChatListEntry] = [] var latestAnchor: ChatListIndex? if let last = self.entries.last { latestAnchor = last.index } if latestAnchor == nil { if let later = self.later { latestAnchor = later.index } } if let later = self.later { addedEntries += fetchLater(later.index.predecessor, self.count) } if let earlier = self.earlier { addedEntries += fetchEarlier(earlier.index.successor, self.count) } addedEntries += self.entries addedEntries.sort(by: { $0.index < $1.index }) var i = addedEntries.count - 1 while i >= 1 { if addedEntries[i].index.messageIndex.id == addedEntries[i - 1].index.messageIndex.id { addedEntries.remove(at: i) } i -= 1 } self.entries = [] var anchorIndex = addedEntries.count - 1 if let latestAnchor = latestAnchor { var i = addedEntries.count - 1 while i >= 0 { if addedEntries[i].index <= latestAnchor { anchorIndex = i break } i -= 1 } } self.later = nil if anchorIndex + 1 < addedEntries.count { self.later = addedEntries[anchorIndex + 1] } i = anchorIndex while i >= 0 && i > anchorIndex - self.count { self.entries.insert(addedEntries[i], at: 0) i -= 1 } self.earlier = nil if anchorIndex - self.count >= 0 { self.earlier = addedEntries[anchorIndex - self.count] } } else { if context.invalidEarlier { var earlyId: ChatListIndex? let i = 0 if i < self.entries.count { earlyId = self.entries[i].index } let earlierEntries = fetchEarlier(earlyId, 1) self.earlier = earlierEntries.first } if context.invalidLater { var laterId: ChatListIndex? let i = self.entries.count - 1 if i >= 0 { laterId = self.entries[i].index } let laterEntries = fetchLater(laterId, 1) self.later = laterEntries.first } } } func firstHole() -> ChatListHole? { for entry in self.entries { if case let .HoleEntry(hole) = entry { return hole } } return nil } func render(_ renderMessage: (IntermediateMessage) -> Message, getPeer: (PeerId) -> Peer?, getPeerNotificationSettings: (PeerId) -> PeerNotificationSettings?) { for i in 0 ..< self.entries.count { switch self.entries[i] { case let .IntermediateMessageEntry(index, message, combinedReadState, embeddedState): let renderedMessage: Message? if let message = message { renderedMessage = renderMessage(message) } else { renderedMessage = nil } var peers = SimpleDictionary() var notificationSettings: PeerNotificationSettings? if let peer = getPeer(index.messageIndex.id.peerId) { peers[peer.id] = peer if let associatedPeerId = peer.associatedPeerId { if let associatedPeer = getPeer(associatedPeerId) { peers[associatedPeer.id] = associatedPeer } notificationSettings = getPeerNotificationSettings(associatedPeerId) } else { notificationSettings = getPeerNotificationSettings(index.messageIndex.id.peerId) } } self.entries[i] = .MessageEntry(index, renderedMessage, combinedReadState, notificationSettings, embeddedState, RenderedPeer(peerId: index.messageIndex.id.peerId, peers: peers)) default: break } } } } public final class ChatListView { public let entries: [ChatListEntry] public let earlierIndex: ChatListIndex? public let laterIndex: ChatListIndex? init(_ mutableView: MutableChatListView) { var entries: [ChatListEntry] = [] for entry in mutableView.entries { switch entry { case let .MessageEntry(index, message, combinedReadState, notificationSettings, embeddedState, peer): entries.append(.MessageEntry(index, message, combinedReadState, notificationSettings, embeddedState, peer)) case let .HoleEntry(hole): entries.append(.HoleEntry(hole)) case .IntermediateMessageEntry: assertionFailure() } } self.entries = entries self.earlierIndex = mutableView.earlier?.index self.laterIndex = mutableView.later?.index } }