Swiftgram/submodules/Postbox/Sources/ChatListView.swift
2023-04-14 18:59:33 +04:00

807 lines
37 KiB
Swift

import Foundation
public struct ChatListEntryMessageTagSummaryKey: Hashable {
public var tag: MessageTags
public var actionType: PendingMessageActionType
public init(tag: MessageTags, actionType: PendingMessageActionType) {
self.tag = tag
self.actionType = actionType
}
}
public struct ChatListEntryMessageTagSummaryComponent: Equatable {
public let namespace: MessageId.Namespace
public init(namespace: MessageId.Namespace) {
self.namespace = namespace
}
}
public struct ChatListEntryPendingMessageActionsSummaryComponent: Equatable {
public let namespace: MessageId.Namespace
public init(namespace: MessageId.Namespace) {
self.namespace = namespace
}
}
public struct ChatListEntrySummaryComponents: Equatable {
public struct Component: Equatable {
public let tagSummary: ChatListEntryMessageTagSummaryComponent?
public let actionsSummary: ChatListEntryPendingMessageActionsSummaryComponent?
public init(tagSummary: ChatListEntryMessageTagSummaryComponent? = nil, actionsSummary: ChatListEntryPendingMessageActionsSummaryComponent? = nil) {
self.tagSummary = tagSummary
self.actionsSummary = actionsSummary
}
}
public var components: [ChatListEntryMessageTagSummaryKey: Component]
public init(components: [ChatListEntryMessageTagSummaryKey: Component] = [:]) {
self.components = components
}
}
public struct ChatListMessageTagSummaryInfo: Equatable {
public let tagSummaryCount: Int32?
public let actionsSummaryCount: Int32?
public init(tagSummaryCount: Int32? = nil, actionsSummaryCount: Int32? = nil) {
self.tagSummaryCount = tagSummaryCount
self.actionsSummaryCount = actionsSummaryCount
}
public static func ==(lhs: ChatListMessageTagSummaryInfo, rhs: ChatListMessageTagSummaryInfo) -> Bool {
return lhs.tagSummaryCount == rhs.tagSummaryCount && lhs.actionsSummaryCount == rhs.actionsSummaryCount
}
}
public final class ChatListGroupReferencePeer: Equatable {
public let peer: RenderedPeer
public let isUnread: Bool
init(peer: RenderedPeer, isUnread: Bool) {
self.peer = peer
self.isUnread = isUnread
}
public static func ==(lhs: ChatListGroupReferencePeer, rhs: ChatListGroupReferencePeer) -> Bool {
if lhs.peer != rhs.peer {
return false
}
if lhs.isUnread != rhs.isUnread {
return false
}
return true
}
}
public struct ChatListGroupReferenceEntry: Equatable {
public let groupId: PeerGroupId
public let message: Message?
public let renderedPeers: [ChatListGroupReferencePeer]
public let unreadState: PeerGroupUnreadCountersCombinedSummary
public static func ==(lhs: ChatListGroupReferenceEntry, rhs: ChatListGroupReferenceEntry) -> Bool {
if lhs.groupId != rhs.groupId {
return false
}
if lhs.unreadState != rhs.unreadState {
return false
}
if lhs.message?.stableVersion != rhs.message?.stableVersion {
return false
}
if lhs.renderedPeers != rhs.renderedPeers {
return false
}
return true
}
}
public struct ChatListForumTopicData: Equatable {
public var id: Int64
public var info: StoredMessageHistoryThreadInfo
public init(id: Int64, info: StoredMessageHistoryThreadInfo) {
self.id = id
self.info = info
}
}
public enum ChatListEntry: Comparable {
case MessageEntry(index: ChatListIndex, messages: [Message], readState: ChatListViewReadState?, isRemovedFromTotalUnreadCount: Bool, embeddedInterfaceState: StoredPeerChatInterfaceState?, renderedPeer: RenderedPeer, presence: PeerPresence?, summaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo], forumTopicData: ChatListForumTopicData?, topForumTopics: [ChatListForumTopicData], hasFailed: Bool, isContact: Bool, autoremoveTimeout: Int32?)
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 static func ==(lhs: ChatListEntry, rhs: ChatListEntry) -> Bool {
switch lhs {
case let .MessageEntry(lhsIndex, lhsMessages, lhsReadState, lhsIsRemovedFromTotalUnreadCount, lhsEmbeddedState, lhsPeer, lhsPresence, lhsInfo, lhsForumTopicData, lhsTopForumTopics, lhsHasFailed, lhsIsContact, lhsAutoremoveTimeout):
switch rhs {
case let .MessageEntry(rhsIndex, rhsMessages, rhsReadState, rhsIsRemovedFromTotalUnreadCount, rhsEmbeddedState, rhsPeer, rhsPresence, rhsInfo, rhsForumTopicData, rhsTopForumTopics, rhsHasFailed, rhsIsContact, rhsAutoremoveTimeout):
if lhsIndex != rhsIndex {
return false
}
if lhsReadState != rhsReadState {
return false
}
if lhsMessages.count != rhsMessages.count {
return false
}
for i in 0 ..< lhsMessages.count {
if lhsMessages[i].stableVersion != rhsMessages[i].stableVersion {
return false
}
}
if lhsIsRemovedFromTotalUnreadCount != rhsIsRemovedFromTotalUnreadCount {
return false
}
if let lhsEmbeddedState = lhsEmbeddedState, let rhsEmbeddedState = rhsEmbeddedState {
if lhsEmbeddedState != rhsEmbeddedState {
return false
}
} else if (lhsEmbeddedState != nil) != (rhsEmbeddedState != nil) {
return false
}
if lhsPeer != rhsPeer {
return false
}
if let lhsPresence = lhsPresence, let rhsPresence = rhsPresence {
if !lhsPresence.isEqual(to: rhsPresence) {
return false
}
} else if (lhsPresence != nil) != (rhsPresence != nil) {
return false
}
if lhsInfo != rhsInfo {
return false
}
if lhsForumTopicData != rhsForumTopicData {
return false
}
if lhsTopForumTopics != rhsTopForumTopics {
return false
}
if lhsHasFailed != rhsHasFailed {
return false
}
if lhsIsContact != rhsIsContact {
return false
}
if lhsAutoremoveTimeout != rhsAutoremoveTimeout {
return false
}
return true
default:
return false
}
case let .HoleEntry(hole):
if case .HoleEntry(hole) = rhs {
return true
} else {
return false
}
}
}
public static func <(lhs: ChatListEntry, rhs: ChatListEntry) -> Bool {
return lhs.index < rhs.index
}
}
enum MutableChatListEntry: Equatable {
case IntermediateMessageEntry(index: ChatListIndex, messageIndex: MessageIndex?)
case MessageEntry(index: ChatListIndex, messages: [Message], readState: ChatListViewReadState?, notificationSettings: PeerNotificationSettings?, isRemovedFromTotalUnreadCount: Bool, embeddedInterfaceState: StoredPeerChatInterfaceState?, renderedPeer: RenderedPeer, presence: PeerPresence?, tagSummaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo], forumTopicData: ChatListForumTopicData?, topForumTopics: [ChatListForumTopicData], hasFailedMessages: Bool, isContact: Bool, autoremoveTimeout: Int32?)
case HoleEntry(ChatListHole)
init(_ intermediateEntry: ChatListIntermediateEntry, cachedDataTable: CachedPeerDataTable, readStateTable: MessageHistoryReadStateTable, messageHistoryTable: MessageHistoryTable) {
switch intermediateEntry {
case let .message(index, messageIndex):
self = .IntermediateMessageEntry(index: index, messageIndex: messageIndex)
case let .hole(hole):
self = .HoleEntry(hole)
}
}
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)
}
}
static 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 enum ChatListEntryType {
case message
case hole
case groupReference
}
public struct ChatListFilterPredicate {
public var includePeerIds: Set<PeerId>
public var excludePeerIds: Set<PeerId>
public var pinnedPeerIds: [PeerId]
public var messageTagSummary: ChatListMessageTagSummaryResultCalculation?
public var includeAdditionalPeerGroupIds: [PeerGroupId]
public var include: (Peer, Bool, Bool, Bool, Bool?) -> Bool
public init(includePeerIds: Set<PeerId>, excludePeerIds: Set<PeerId>, pinnedPeerIds: [PeerId], messageTagSummary: ChatListMessageTagSummaryResultCalculation?, includeAdditionalPeerGroupIds: [PeerGroupId], include: @escaping (Peer, Bool, Bool, Bool, Bool?) -> Bool) {
self.includePeerIds = includePeerIds
self.excludePeerIds = excludePeerIds
self.pinnedPeerIds = pinnedPeerIds
self.messageTagSummary = messageTagSummary
self.includeAdditionalPeerGroupIds = includeAdditionalPeerGroupIds
self.include = include
}
public func includes(peer: Peer, groupId: PeerGroupId, isRemovedFromTotalUnreadCount: Bool, isUnread: Bool, isContact: Bool, messageTagSummaryResult: Bool?) -> Bool {
if self.pinnedPeerIds.contains(peer.id) {
return false
}
let includePeerId = peer.associatedPeerId ?? peer.id
if self.excludePeerIds.contains(includePeerId) {
return false
}
if self.includePeerIds.contains(includePeerId) {
return true
}
if groupId != .root {
if !self.includeAdditionalPeerGroupIds.contains(groupId) {
return false
}
}
return self.include(peer, isRemovedFromTotalUnreadCount, isUnread, isContact, messageTagSummaryResult)
}
}
struct MutableChatListAdditionalItemEntry: Equatable {
public var entry: MutableChatListEntry
public var info: AdditionalChatListItem
static func ==(lhs: MutableChatListAdditionalItemEntry, rhs: MutableChatListAdditionalItemEntry) -> Bool {
if lhs.entry != rhs.entry {
return false
}
if !lhs.info.isEqual(to: rhs.info) {
return false
}
return true
}
}
public struct ChatListAdditionalItemEntry: Equatable {
public let entry: ChatListEntry
public let info: AdditionalChatListItem
public static func ==(lhs: ChatListAdditionalItemEntry, rhs: ChatListAdditionalItemEntry) -> Bool {
if lhs.entry != rhs.entry {
return false
}
if !lhs.info.isEqual(to: rhs.info) {
return false
}
return true
}
}
func renderAssociatedMediaForPeers(postbox: PostboxImpl, peers: SimpleDictionary<PeerId, Peer>) -> [MediaId: Media] {
var result: [MediaId: Media] = [:]
for (_, peer) in peers {
if let associatedMediaIds = peer.associatedMediaIds {
for id in associatedMediaIds {
if result[id] == nil {
if let media = postbox.messageHistoryTable.getMedia(id) {
result[id] = media
}
}
}
}
}
return result
}
func renderAssociatedMediaForPeers(postbox: PostboxImpl, peers: [Peer]) -> [MediaId: Media] {
var result: [MediaId: Media] = [:]
for peer in peers {
if let associatedMediaIds = peer.associatedMediaIds {
for id in associatedMediaIds {
if result[id] == nil {
if let media = postbox.messageHistoryTable.getMedia(id) {
result[id] = media
}
}
}
}
}
return result
}
func renderAssociatedMediaForPeers(postbox: PostboxImpl, peers: [PeerId: Peer]) -> [MediaId: Media] {
var result: [MediaId: Media] = [:]
for (_, peer) in peers {
if let associatedMediaIds = peer.associatedMediaIds {
for id in associatedMediaIds {
if result[id] == nil {
if let media = postbox.messageHistoryTable.getMedia(id) {
result[id] = media
}
}
}
}
}
return result
}
public struct ChatListViewReadState: Equatable {
public var state: CombinedPeerReadState
public var isMuted: Bool
public init(state: CombinedPeerReadState, isMuted: Bool) {
self.state = state
self.isMuted = isMuted
}
}
final class MutableChatListView {
let groupId: PeerGroupId
let filterPredicate: ChatListFilterPredicate?
private let aroundIndex: ChatListIndex
private let summaryComponents: ChatListEntrySummaryComponents
fileprivate var groupEntries: [ChatListGroupReferenceEntry]
private var count: Int
private let spaces: [ChatListViewSpace]
fileprivate var state: ChatListViewState
fileprivate var sampledState: ChatListViewSample
private var additionalItemIds = Set<PeerId>()
private var additionalItems: [AdditionalChatListItem] = []
fileprivate var additionalItemEntries: [MutableChatListAdditionalItemEntry] = []
private var currentHiddenPeerIds = Set<PeerId>()
init(postbox: PostboxImpl, currentTransaction: Transaction, groupId: PeerGroupId, filterPredicate: ChatListFilterPredicate?, aroundIndex: ChatListIndex, count: Int, summaryComponents: ChatListEntrySummaryComponents) {
self.groupId = groupId
self.filterPredicate = filterPredicate
self.aroundIndex = aroundIndex
self.summaryComponents = summaryComponents
self.currentHiddenPeerIds = postbox.hiddenChatIds
var spaces: [ChatListViewSpace] = [
.group(groupId: self.groupId, pinned: .notPinned, predicate: filterPredicate)
]
if let filterPredicate = self.filterPredicate {
spaces.append(.group(groupId: self.groupId, pinned: .includePinnedAsUnpinned, predicate: filterPredicate))
for additionalGroupId in filterPredicate.includeAdditionalPeerGroupIds {
spaces.append(.group(groupId: additionalGroupId, pinned: .notPinned, predicate: filterPredicate))
spaces.append(.group(groupId: additionalGroupId, pinned: .includePinnedAsUnpinned, predicate: filterPredicate))
}
if !filterPredicate.pinnedPeerIds.isEmpty {
spaces.append(.peers(peerIds: filterPredicate.pinnedPeerIds, asPinned: true))
}
} else {
spaces.append(.group(groupId: self.groupId, pinned: .includePinned, predicate: filterPredicate))
}
self.spaces = spaces
self.state = ChatListViewState(postbox: postbox, currentTransaction: currentTransaction, spaces: self.spaces, anchorIndex: aroundIndex, summaryComponents: self.summaryComponents, halfLimit: count)
self.sampledState = self.state.sample(postbox: postbox, currentTransaction: currentTransaction)
self.count = count
if case .root = groupId, self.filterPredicate == nil {
let items = postbox.additionalChatListItemsTable.get()
self.additionalItems = items
self.additionalItemIds = Set(items.map { $0.peerId })
for item in items {
if let entry = postbox.chatListTable.getStandalone(peerId: item.peerId, messageHistoryTable: postbox.messageHistoryTable, includeIfNoHistory: item.includeIfNoHistory) {
self.additionalItemEntries.append(MutableChatListAdditionalItemEntry(
entry: MutableChatListEntry(entry, cachedDataTable: postbox.cachedPeerDataTable, readStateTable: postbox.readStateTable, messageHistoryTable: postbox.messageHistoryTable),
info: item
))
}
}
self.groupEntries = []
self.reloadGroups(postbox: postbox)
} else {
self.groupEntries = []
}
}
private func reloadGroups(postbox: PostboxImpl) {
self.groupEntries.removeAll()
if case .root = self.groupId, self.filterPredicate == nil {
for groupId in postbox.chatListTable.existingGroups() {
var foundIndices: [(ChatListIndex, MessageIndex)] = []
var unpinnedCount = 0
let maxCount = 8
var upperBound: (ChatListIndex, Bool)?
inner: while true {
if let entry = postbox.chatListTable.earlierEntryInfos(groupId: groupId, index: upperBound, messageHistoryTable: postbox.messageHistoryTable, peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable, count: 1).first {
switch entry {
case let .message(index, messageIndex):
if let messageIndex = messageIndex, !postbox.isChatHidden(peerId: messageIndex.id.peerId) {
foundIndices.append((index, messageIndex))
if index.pinningIndex == nil {
unpinnedCount += 1
}
if unpinnedCount >= maxCount {
break inner
}
upperBound = (entry.index, true)
} else {
upperBound = (entry.index.predecessor, true)
}
case .hole:
upperBound = (entry.index, false)
}
} else {
break inner
}
}
foundIndices.sort(by: { $0.1 > $1.1 })
if foundIndices.count > maxCount {
foundIndices.removeSubrange(maxCount...)
}
if !foundIndices.isEmpty {
var message: Message?
var renderedPeers: [ChatListGroupReferencePeer] = []
for (index, messageIndex) in foundIndices {
if let peer = postbox.peerTable.get(index.messageIndex.id.peerId) {
var peers = SimpleDictionary<PeerId, Peer>()
peers[peer.id] = peer
if let associatedPeerId = peer.associatedPeerId {
if let associatedPeer = postbox.peerTable.get(associatedPeerId) {
peers[associatedPeer.id] = associatedPeer
}
}
let renderedPeer = RenderedPeer(peerId: peer.id, peers: peers, associatedMedia: renderAssociatedMediaForPeers(postbox: postbox, peers: peers))
let isUnread: Bool
if postbox.seedConfiguration.peerSummaryIsThreadBased(peer) {
let hasUnmutedUnread = postbox.peerThreadsSummaryTable.get(peerId: peer.id)?.hasUnmutedUnread ?? false
isUnread = hasUnmutedUnread
} else {
isUnread = postbox.readStateTable.getCombinedState(peer.id)?.isUnread ?? false
}
renderedPeers.append(ChatListGroupReferencePeer(peer: renderedPeer, isUnread: isUnread))
if foundIndices.count == 1 && message == nil {
message = postbox.messageHistoryTable.getMessage(messageIndex).flatMap({ postbox.messageHistoryTable.renderMessage($0, peerTable: postbox.peerTable, threadIndexTable: postbox.messageHistoryThreadIndexTable) })
}
}
}
self.groupEntries.append(ChatListGroupReferenceEntry(groupId: groupId, message: message, renderedPeers: renderedPeers, unreadState: postbox.groupMessageStatsTable.get(groupId: groupId)))
}
}
}
}
func refreshDueToExternalTransaction(postbox: PostboxImpl, currentTransaction: Transaction) -> Bool {
var updated = false
self.state = ChatListViewState(postbox: postbox, currentTransaction: currentTransaction, spaces: self.spaces, anchorIndex: self.aroundIndex, summaryComponents: self.summaryComponents, halfLimit: self.count)
self.sampledState = self.state.sample(postbox: postbox, currentTransaction: currentTransaction)
updated = true
let currentGroupEntries = self.groupEntries
self.reloadGroups(postbox: postbox)
if self.groupEntries != currentGroupEntries {
updated = true
}
return updated
}
func replay(postbox: PostboxImpl, currentTransaction: Transaction, operations: [PeerGroupId: [ChatListOperation]], updatedPeerNotificationSettings: [PeerId: (PeerNotificationSettings?, PeerNotificationSettings)], updatedPeers: [PeerId: Peer], updatedPeerPresences: [PeerId: PeerPresence], transaction: PostboxTransaction, context: MutableChatListViewReplayContext) -> Bool {
var hasChanges = false
let hiddenChatIds = postbox.hiddenChatIds
var hasFilterChanges = false
if hiddenChatIds != self.currentHiddenPeerIds {
self.currentHiddenPeerIds = hiddenChatIds
hasFilterChanges = true
}
if transaction.updatedGlobalNotificationSettings && self.filterPredicate != nil {
self.state = ChatListViewState(postbox: postbox, currentTransaction: currentTransaction, spaces: self.spaces, anchorIndex: self.aroundIndex, summaryComponents: self.summaryComponents, halfLimit: self.count)
self.sampledState = self.state.sample(postbox: postbox, currentTransaction: currentTransaction)
hasChanges = true
} else if hasFilterChanges {
self.state = ChatListViewState(postbox: postbox, currentTransaction: currentTransaction, spaces: self.spaces, anchorIndex: self.aroundIndex, summaryComponents: self.summaryComponents, halfLimit: self.count)
self.sampledState = self.state.sample(postbox: postbox, currentTransaction: currentTransaction)
hasChanges = true
} else {
if self.state.replay(postbox: postbox, currentTransaction: currentTransaction, transaction: transaction) {
self.sampledState = self.state.sample(postbox: postbox, currentTransaction: currentTransaction)
hasChanges = true
}
}
if case .root = self.groupId, self.filterPredicate == nil {
var invalidatedGroups = false
for (groupId, groupOperations) in operations {
if case .group = groupId, !groupOperations.isEmpty {
invalidatedGroups = true
}
}
if hasFilterChanges {
invalidatedGroups = true
}
if invalidatedGroups {
self.reloadGroups(postbox: postbox)
hasChanges = true
} else {
for i in 0 ..< self.groupEntries.count {
if let updatedState = transaction.currentUpdatedTotalUnreadSummaries[self.groupEntries[i].groupId] {
self.groupEntries[i] = ChatListGroupReferenceEntry(groupId: self.groupEntries[i].groupId, message: self.groupEntries[i].message, renderedPeers: self.groupEntries[i].renderedPeers, unreadState: updatedState)
hasChanges = true
}
}
if !transaction.alteredInitialPeerCombinedReadStates.isEmpty {
for i in 0 ..< self.groupEntries.count {
for j in 0 ..< groupEntries[i].renderedPeers.count {
if transaction.alteredInitialPeerCombinedReadStates[groupEntries[i].renderedPeers[j].peer.peerId] != nil {
let isUnread: Bool
if let peer = groupEntries[i].renderedPeers[j].peer.peer, postbox.seedConfiguration.peerSummaryIsThreadBased(peer) {
isUnread = postbox.peerThreadsSummaryTable.get(peerId: peer.id)?.hasUnmutedUnread ?? false
} else {
isUnread = postbox.readStateTable.getCombinedState(groupEntries[i].renderedPeers[j].peer.peerId)?.isUnread ?? false
}
if isUnread != groupEntries[i].renderedPeers[j].isUnread {
var renderedPeers = self.groupEntries[i].renderedPeers
renderedPeers[j] = ChatListGroupReferencePeer(peer: groupEntries[i].renderedPeers[j].peer, isUnread: isUnread)
self.groupEntries[i] = ChatListGroupReferenceEntry(groupId: self.groupEntries[i].groupId, message: self.groupEntries[i].message, renderedPeers: renderedPeers, unreadState: self.groupEntries[i].unreadState)
}
}
}
}
}
}
}
var updateAdditionalItems = false
if case .root = self.groupId, self.filterPredicate == nil, let items = transaction.replacedAdditionalChatListItems {
self.additionalItems = items
self.additionalItemIds = Set(items.map { $0.peerId })
updateAdditionalItems = true
}
for peerId in self.additionalItemIds {
if transaction.currentOperationsByPeerId[peerId] != nil {
updateAdditionalItems = true
}
if transaction.currentUpdatedPeers[peerId] != nil {
updateAdditionalItems = true
}
if transaction.currentUpdatedChatListInclusions[peerId] != nil {
updateAdditionalItems = true
}
}
if updateAdditionalItems {
self.additionalItemEntries.removeAll()
for item in self.additionalItems {
if let entry = postbox.chatListTable.getStandalone(peerId: item.peerId, messageHistoryTable: postbox.messageHistoryTable, includeIfNoHistory: item.includeIfNoHistory) {
self.additionalItemEntries.append(MutableChatListAdditionalItemEntry(
entry: MutableChatListEntry(entry, cachedDataTable: postbox.cachedPeerDataTable, readStateTable: postbox.readStateTable, messageHistoryTable: postbox.messageHistoryTable),
info: item
))
}
}
hasChanges = true
}
return hasChanges
}
func complete(postbox: PostboxImpl, context: MutableChatListViewReplayContext) {
}
func firstHole() -> (PeerGroupId, ChatListHole)? {
return self.sampledState.hole
}
private func renderEntry(_ entry: MutableChatListEntry, postbox: PostboxImpl) -> MutableChatListEntry? {
switch entry {
case let .IntermediateMessageEntry(index, messageIndex):
var renderedMessages: [Message] = []
if let messageIndex = messageIndex {
if let messageGroup = postbox.messageHistoryTable.getMessageGroup(at: messageIndex, limit: 10) {
renderedMessages.append(contentsOf: messageGroup.compactMap(postbox.renderIntermediateMessage))
}
}
var peers = SimpleDictionary<PeerId, Peer>()
var notificationSettings: PeerNotificationSettings?
var presence: PeerPresence?
var isContact: Bool = false
if let peer = postbox.peerTable.get(index.messageIndex.id.peerId) {
peers[peer.id] = peer
if let associatedPeerId = peer.associatedPeerId {
if let associatedPeer = postbox.peerTable.get(associatedPeerId) {
peers[associatedPeer.id] = associatedPeer
}
notificationSettings = postbox.peerNotificationSettingsTable.getEffective(associatedPeerId)
presence = postbox.peerPresenceTable.get(associatedPeerId)
isContact = postbox.contactsTable.isContact(peerId: associatedPeerId)
} else {
notificationSettings = postbox.peerNotificationSettingsTable.getEffective(index.messageIndex.id.peerId)
presence = postbox.peerPresenceTable.get(index.messageIndex.id.peerId)
isContact = postbox.contactsTable.isContact(peerId: peer.id)
}
}
let renderedPeer = RenderedPeer(peerId: index.messageIndex.id.peerId, peers: peers, associatedMedia: renderAssociatedMediaForPeers(postbox: postbox, peers: peers))
var forumTopicData: ChatListForumTopicData?
if let message = renderedMessages.first, let threadId = message.threadId {
if let info = postbox.messageHistoryThreadIndexTable.get(peerId: message.id.peerId, threadId: threadId) {
forumTopicData = ChatListForumTopicData(id: threadId, info: info)
}
}
var topForumTopics: [ChatListForumTopicData] = []
if let peer = renderedPeer.peer, postbox.seedConfiguration.peerSummaryIsThreadBased(peer) {
for item in postbox.messageHistoryThreadIndexTable.fetch(peerId: peer.id, namespace: 0, start: .upperBound, end: .lowerBound, limit: 5) {
topForumTopics.append(ChatListForumTopicData(id: item.threadId, info: item.info))
}
}
let readState: ChatListViewReadState?
if let peer = postbox.peerTable.get(index.messageIndex.id.peerId), postbox.seedConfiguration.peerSummaryIsThreadBased(peer) {
let summary = postbox.peerThreadsSummaryTable.get(peerId: index.messageIndex.id.peerId)
var count: Int32 = 0
var isMuted: Bool = false
if let summary = summary {
count = summary.totalUnreadCount
if count > 0 {
isMuted = !summary.hasUnmutedUnread
}
}
readState = ChatListViewReadState(state: CombinedPeerReadState(states: [(0, .idBased(maxIncomingReadId: 1, maxOutgoingReadId: 0, maxKnownId: 0, count: count, markedUnread: false))]), isMuted: isMuted)
} else {
readState = postbox.readStateTable.getCombinedState(index.messageIndex.id.peerId).flatMap { state -> ChatListViewReadState in
return ChatListViewReadState(state: state, isMuted: false)
}
}
var autoremoveTimeout: Int32?
if let cachedData = postbox.cachedPeerDataTable.get(index.messageIndex.id.peerId) {
autoremoveTimeout = postbox.seedConfiguration.decodeAutoremoveTimeout(cachedData)
}
return .MessageEntry(index: index, messages: renderedMessages, readState: readState, notificationSettings: notificationSettings, isRemovedFromTotalUnreadCount: false, embeddedInterfaceState: postbox.peerChatInterfaceStateTable.get(index.messageIndex.id.peerId), renderedPeer: renderedPeer, presence: presence, tagSummaryInfo: [:], forumTopicData: forumTopicData, topForumTopics: topForumTopics, hasFailedMessages: postbox.messageHistoryFailedTable.contains(peerId: index.messageIndex.id.peerId), isContact: isContact, autoremoveTimeout: autoremoveTimeout)
default:
return nil
}
}
func render(postbox: PostboxImpl) {
for i in 0 ..< self.additionalItemEntries.count {
if let updatedEntry = self.renderEntry(self.additionalItemEntries[i].entry, postbox: postbox) {
self.additionalItemEntries[i].entry = updatedEntry
}
}
}
}
public final class ChatListView {
public let groupId: PeerGroupId
public let additionalItemEntries: [ChatListAdditionalItemEntry]
public let entries: [ChatListEntry]
public let groupEntries: [ChatListGroupReferenceEntry]
public let earlierIndex: ChatListIndex?
public let laterIndex: ChatListIndex?
init(_ mutableView: MutableChatListView) {
self.groupId = mutableView.groupId
var entries: [ChatListEntry] = []
for entry in mutableView.sampledState.entries {
switch entry {
case let .MessageEntry(index, messages, combinedReadState, _, isRemovedFromTotalUnreadCount, embeddedState, peer, peerPresence, summaryInfo, forumTopicData, topForumTopics, hasFailed, isContact, autoremoveTimeout):
entries.append(.MessageEntry(index: index, messages: messages, readState: combinedReadState, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: embeddedState, renderedPeer: peer, presence: peerPresence, summaryInfo: summaryInfo, forumTopicData: forumTopicData, topForumTopics: topForumTopics, hasFailed: hasFailed, isContact: isContact, autoremoveTimeout: autoremoveTimeout))
case let .HoleEntry(hole):
entries.append(.HoleEntry(hole))
case .IntermediateMessageEntry:
assertionFailure()
}
}
self.entries = entries
self.earlierIndex = mutableView.sampledState.lower?.index
self.laterIndex = mutableView.sampledState.upper?.index
self.groupEntries = mutableView.groupEntries
var additionalItemEntries: [ChatListAdditionalItemEntry] = []
for entry in mutableView.additionalItemEntries {
switch entry.entry {
case let .MessageEntry(index, messages, combinedReadState, _, isExcludedFromUnreadCount, embeddedState, peer, peerPresence, summaryInfo, forumTopicData, topForumTopics, hasFailed, isContact, autoremoveTimeout):
additionalItemEntries.append(ChatListAdditionalItemEntry(
entry: .MessageEntry(index: index, messages: messages, readState: combinedReadState, isRemovedFromTotalUnreadCount: isExcludedFromUnreadCount, embeddedInterfaceState: embeddedState, renderedPeer: peer, presence: peerPresence, summaryInfo: summaryInfo, forumTopicData: forumTopicData, topForumTopics: topForumTopics, hasFailed: hasFailed, isContact: isContact, autoremoveTimeout: autoremoveTimeout),
info: entry.info
))
case .HoleEntry:
assertionFailure()
case .IntermediateMessageEntry:
assertionFailure()
}
}
self.additionalItemEntries = additionalItemEntries
}
}