diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 43fa5514cd..d0ea668e9c 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1330,6 +1330,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } strongSelf.deletePeerThread(peerId: peerId, threadId: threadId) } + self.chatListDisplayNode.containerNode.setPeerThreadStopped = { [weak self] peerId, threadId, isStopped in + guard let strongSelf = self else { + return + } + strongSelf.setPeerThreadStopped(peerId: peerId, threadId: threadId, isStopped: isStopped) + } self.chatListDisplayNode.containerNode.peerSelected = { [weak self] peer, threadId, animated, activateInput, promoInfo in if let strongSelf = self { @@ -3675,6 +3681,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController }) } + private func setPeerThreadStopped(peerId: EnginePeer.Id, threadId: Int64, isStopped: Bool) { + + } + public func maybeAskForPeerChatRemoval(peer: EngineRenderedPeer, joined: Bool = false, deleteGloballyIfPossible: Bool = false, completion: @escaping (Bool) -> Void, removed: @escaping () -> Void) { guard let chatPeer = peer.peers[peer.peerId], let mainPeer = peer.chatMainPeer else { completion(false) diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index f048e72e81..fd0ccb087b 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -183,7 +183,7 @@ private final class ChatListShimmerNode: ASDisplayNode { let timestamp1: Int32 = 100000 let peers: [EnginePeer.Id: EnginePeer] = [:] let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in - }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in }, activateChatPreview: { _, _, gesture, _ in + }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in }, activateChatPreview: { _, _, gesture, _ in gesture?.cancel() }, present: { _ in }) @@ -213,7 +213,7 @@ private final class ChatListShimmerNode: ASDisplayNode { ) let readState = EnginePeerReadCounters() - return ChatListItem(presentationData: chatListPresentationData, context: context, chatListLocation: .chatList(groupId: .root), filterData: nil, index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: 0, messageIndex: EngineMessage.Index(id: EngineMessage.Id(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1))), content: .peer(messages: [message], peer: EngineRenderedPeer(peer: peer1), threadInfo: nil, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false, forumThreadTitle: nil), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) + return ChatListItem(presentationData: chatListPresentationData, context: context, chatListLocation: .chatList(groupId: .root), filterData: nil, index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: 0, messageIndex: EngineMessage.Index(id: EngineMessage.Id(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1))), content: .peer(messages: [message], peer: EngineRenderedPeer(peer: peer1), threadInfo: nil, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false, forumTopicData: nil), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) } var itemNodes: [ChatListItemNode] = [] @@ -505,6 +505,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { previousItemNode.listNode.hidePsa = nil previousItemNode.listNode.deletePeerChat = nil previousItemNode.listNode.deletePeerThread = nil + previousItemNode.listNode.setPeerThreadStopped = nil previousItemNode.listNode.peerSelected = nil previousItemNode.listNode.groupSelected = nil previousItemNode.listNode.updatePeerGrouping = nil @@ -543,6 +544,9 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { itemNode.listNode.deletePeerThread = { [weak self] peerId, threadId in self?.deletePeerThread?(peerId, threadId) } + itemNode.listNode.setPeerThreadStopped = { [weak self] peerId, threadId, isStopped in + self?.setPeerThreadStopped?(peerId, threadId, isStopped) + } itemNode.listNode.peerSelected = { [weak self] peerId, threadId, animated, activateInput, promoInfo in self?.peerSelected?(peerId, threadId, animated, activateInput, promoInfo) } @@ -602,6 +606,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { var hidePsa: ((EnginePeer.Id) -> Void)? var deletePeerChat: ((EnginePeer.Id, Bool) -> Void)? var deletePeerThread: ((EnginePeer.Id, Int64) -> Void)? + var setPeerThreadStopped: ((EnginePeer.Id, Int64, Bool) -> Void)? var peerSelected: ((EnginePeer, Int64?, Bool, Bool, ChatListNodeEntryPromoInfo?) -> Void)? var groupSelected: ((EngineChatList.Group) -> Void)? var updatePeerGrouping: ((EnginePeer.Id, Bool) -> Void)? diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 910609959e..80bf8a8c72 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -694,7 +694,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { if isMedia { return ListMessageItem(presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: .builtin(WallpaperSettings())), fontSize: presentationData.fontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: true, largeEmoji: false, chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0.0, auxiliaryRadius: 0.0, mergeBubbleCorners: false)), context: context, chatLocation: .peer(id: peer.peerId), interaction: listInteraction, message: message._asMessage(), selection: selection, displayHeader: enableHeaders && !displayCustomHeader, customHeader: key == .downloads ? header : nil, hintIsLink: tagMask == .webPage, isGlobalSearchResult: key != .downloads, isDownloadList: key == .downloads) } else { - return ChatListItem(presentationData: presentationData, context: context, chatListLocation: .chatList(groupId: .root), filterData: nil, index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: message.index)), content: .peer(messages: [message], peer: peer, threadInfo: nil, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: true, displayAsMessage: false, hasFailedMessages: false, forumThreadTitle: nil), editing: false, hasActiveRevealControls: false, selected: false, header: tagMask == nil ? header : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) + return ChatListItem(presentationData: presentationData, context: context, chatListLocation: .chatList(groupId: .root), filterData: nil, index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: message.index)), content: .peer(messages: [message], peer: peer, threadInfo: nil, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: true, displayAsMessage: false, hasFailedMessages: false, forumTopicData: nil), editing: false, hasActiveRevealControls: false, selected: false, header: tagMask == nil ? header : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) } case let .addContact(phoneNumber, theme, strings): return ContactsAddItem(theme: theme, strings: strings, phoneNumber: phoneNumber, header: ChatListSearchItemHeader(type: .phoneNumber, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { @@ -1744,6 +1744,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in + }, setPeerThreadStopped: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in }, toggleArchivedFolderHiddenByDefault: { @@ -2953,7 +2954,7 @@ private final class ChatListSearchShimmerNode: ASDisplayNode { var peers: [EnginePeer.Id: EnginePeer] = [:] peers[peer1.id] = peer1 let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in - }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in }, activateChatPreview: { _, _, gesture, _ in + }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in }, activateChatPreview: { _, _, gesture, _ in gesture?.cancel() }, present: { _ in }) @@ -2984,7 +2985,7 @@ private final class ChatListSearchShimmerNode: ASDisplayNode { associatedMedia: [:] ) let readState = EnginePeerReadCounters() - return ChatListItem(presentationData: chatListPresentationData, context: context, chatListLocation: .chatList(groupId: .root), filterData: nil, index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: 0, messageIndex: EngineMessage.Index(id: EngineMessage.Id(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1))), content: .peer(messages: [message], peer: EngineRenderedPeer(peer: peer1), threadInfo: nil, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false, forumThreadTitle: nil), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) + return ChatListItem(presentationData: chatListPresentationData, context: context, chatListLocation: .chatList(groupId: .root), filterData: nil, index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: 0, messageIndex: EngineMessage.Index(id: EngineMessage.Id(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1))), content: .peer(messages: [message], peer: EngineRenderedPeer(peer: peer1), threadInfo: nil, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false, forumTopicData: nil), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) case .media: return nil case .links: diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 485a8bafb8..efb1daa852 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -59,7 +59,7 @@ public enum ChatListItemContent { } } - case peer(messages: [EngineMessage], peer: EngineRenderedPeer, threadInfo: ThreadInfo?, combinedReadState: EnginePeerReadCounters?, isRemovedFromTotalUnreadCount: Bool, presence: EnginePeer.Presence?, hasUnseenMentions: Bool, hasUnseenReactions: Bool, draftState: DraftState?, inputActivities: [(EnginePeer, PeerInputActivity)]?, promoInfo: ChatListNodeEntryPromoInfo?, ignoreUnreadBadge: Bool, displayAsMessage: Bool, hasFailedMessages: Bool, forumThreadTitle: String?) + case peer(messages: [EngineMessage], peer: EngineRenderedPeer, threadInfo: ThreadInfo?, combinedReadState: EnginePeerReadCounters?, isRemovedFromTotalUnreadCount: Bool, presence: EnginePeer.Presence?, hasUnseenMentions: Bool, hasUnseenReactions: Bool, draftState: DraftState?, inputActivities: [(EnginePeer, PeerInputActivity)]?, promoInfo: ChatListNodeEntryPromoInfo?, ignoreUnreadBadge: Bool, displayAsMessage: Bool, hasFailedMessages: Bool, forumTopicData: EngineChatList.ForumTopicData?) case groupReference(groupId: EngineChatList.Group, peers: [EngineChatList.GroupItem.Item], message: EngineMessage?, unreadCount: Int, hiddenByDefault: Bool) public var chatLocation: ChatLocation? { @@ -1045,12 +1045,12 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let displayAsMessage: Bool let hasFailedMessages: Bool var threadInfo: ChatListItemContent.ThreadInfo? - var forumThreadTitle: String? + var forumTopicData: EngineChatList.ForumTopicData? var groupHiddenByDefault = false switch item.content { - case let .peer(messagesValue, peerValue, threadInfoValue, combinedReadStateValue, isRemovedFromTotalUnreadCountValue, peerPresenceValue, hasUnseenMentionsValue, hasUnseenReactionsValue, draftStateValue, inputActivitiesValue, promoInfoValue, ignoreUnreadBadge, displayAsMessageValue, _, forumThreadTitleValue): + case let .peer(messagesValue, peerValue, threadInfoValue, combinedReadStateValue, isRemovedFromTotalUnreadCountValue, peerPresenceValue, hasUnseenMentionsValue, hasUnseenReactionsValue, draftStateValue, inputActivitiesValue, promoInfoValue, ignoreUnreadBadge, displayAsMessageValue, _, forumTopicDataValue): messages = messagesValue contentPeer = .chat(peerValue) combinedReadState = combinedReadStateValue @@ -1071,7 +1071,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { threadInfo = threadInfoValue hasUnseenMentions = hasUnseenMentionsValue hasUnseenReactions = hasUnseenReactionsValue - forumThreadTitle = forumThreadTitleValue + forumTopicData = forumTopicDataValue switch peerValue.peer { case .user, .secretChat: @@ -1272,8 +1272,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } if let peerTextValue = peerText, case let .channel(channel) = itemPeer.chatMainPeer, channel.flags.contains(.isForum), threadInfo == nil { - if let forumThreadTitle = forumThreadTitle { - peerText = "\(peerTextValue) → \(forumThreadTitle)" + if let forumTopicData = forumTopicData { + peerText = "\(peerTextValue) → \(forumTopicData.title)" } else { //TODO:localize peerText = "\(peerTextValue) → General" @@ -1547,6 +1547,12 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } else { statusState = .delivered(item.presentationData.theme.chatList.checkmarkColor) } + } else if case .forum = item.chatListLocation { + if let forumTopicData = forumTopicData, message.id.namespace == forumTopicData.maxOutgoingReadMessageId.namespace, message.id.id >= forumTopicData.maxOutgoingReadMessageId.id { + statusState = .read(item.presentationData.theme.chatList.checkmarkColor) + } else { + statusState = .delivered(item.presentationData.theme.chatList.checkmarkColor) + } } } } @@ -2690,6 +2696,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { case RevealOptionKey.unmute.rawValue: item.interaction.setPeerThreadMuted(peerId, threadId, false) close = false + case RevealOptionKey.close.rawValue: + item.interaction.setPeerThreadStopped(peerId, threadId, true) + case RevealOptionKey.open.rawValue: + item.interaction.setPeerThreadStopped(peerId, threadId, false) default: break } diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 6f7eda22f4..f6374a7034 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -70,6 +70,7 @@ public final class ChatListNodeInteraction { let setPeerThreadMuted: (EnginePeer.Id, Int64?, Bool) -> Void let deletePeer: (EnginePeer.Id, Bool) -> Void let deletePeerThread: (EnginePeer.Id, Int64) -> Void + let setPeerThreadStopped: (EnginePeer.Id, Int64, Bool) -> Void let updatePeerGrouping: (EnginePeer.Id, Bool) -> Void let togglePeerMarkedUnread: (EnginePeer.Id, Bool) -> Void let toggleArchivedFolderHiddenByDefault: () -> Void @@ -102,6 +103,7 @@ public final class ChatListNodeInteraction { setPeerThreadMuted: @escaping (EnginePeer.Id, Int64?, Bool) -> Void, deletePeer: @escaping (EnginePeer.Id, Bool) -> Void, deletePeerThread: @escaping (EnginePeer.Id, Int64) -> Void, + setPeerThreadStopped: @escaping (EnginePeer.Id, Int64, Bool) -> Void, updatePeerGrouping: @escaping (EnginePeer.Id, Bool) -> Void, togglePeerMarkedUnread: @escaping (EnginePeer.Id, Bool) -> Void, toggleArchivedFolderHiddenByDefault: @escaping () -> Void, @@ -124,6 +126,7 @@ public final class ChatListNodeInteraction { self.setPeerThreadMuted = setPeerThreadMuted self.deletePeer = deletePeer self.deletePeerThread = deletePeerThread + self.setPeerThreadStopped = setPeerThreadStopped self.updatePeerGrouping = updatePeerGrouping self.togglePeerMarkedUnread = togglePeerMarkedUnread self.toggleArchivedFolderHiddenByDefault = toggleArchivedFolderHiddenByDefault @@ -248,7 +251,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL nodeInteraction.additionalCategorySelected(id) } ), directionHint: entry.directionHint) - case let .PeerEntry(index, presentationData, messages, combinedReadState, isRemovedFromTotalUnreadCount, draftState, peer, threadInfo, presence, hasUnseenMentions, hasUnseenReactions, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact, forumThreadTitle): + case let .PeerEntry(index, presentationData, messages, combinedReadState, isRemovedFromTotalUnreadCount, draftState, peer, threadInfo, presence, hasUnseenMentions, hasUnseenReactions, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact, forumTopicData): switch mode { case .chatList: return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem( @@ -272,7 +275,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: hasFailedMessages, - forumThreadTitle: forumThreadTitle + forumTopicData: forumTopicData ), editing: editing, hasActiveRevealControls: hasActiveRevealControls, @@ -439,7 +442,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, filterData: ChatListItemFilterData?, mode: ChatListNodeMode, entries: [ChatListNodeViewTransitionUpdateEntry]) -> [ListViewUpdateItem] { return entries.map { entry -> ListViewUpdateItem in switch entry.entry { - case let .PeerEntry(index, presentationData, messages, combinedReadState, isRemovedFromTotalUnreadCount, draftState, peer, threadInfo, presence, hasUnseenMentions, hasUnseenReactions, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact, forumThreadTitle): + case let .PeerEntry(index, presentationData, messages, combinedReadState, isRemovedFromTotalUnreadCount, draftState, peer, threadInfo, presence, hasUnseenMentions, hasUnseenReactions, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact, forumTopicData): switch mode { case .chatList: return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem( @@ -463,7 +466,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: hasFailedMessages, - forumThreadTitle: forumThreadTitle + forumTopicData: forumTopicData ), editing: editing, hasActiveRevealControls: hasActiveRevealControls, @@ -662,6 +665,7 @@ public final class ChatListNode: ListView { public var activateSearch: (() -> Void)? public var deletePeerChat: ((EnginePeer.Id, Bool) -> Void)? public var deletePeerThread: ((EnginePeer.Id, Int64) -> Void)? + public var setPeerThreadStopped: ((EnginePeer.Id, Int64, Bool) -> Void)? public var updatePeerGrouping: ((EnginePeer.Id, Bool) -> Void)? public var presentAlert: ((String) -> Void)? public var present: ((ViewController) -> Void)? @@ -973,6 +977,8 @@ public final class ChatListNode: ListView { self?.deletePeerChat?(peerId, joined) }, deletePeerThread: { [weak self] peerId, threadId in self?.deletePeerThread?(peerId, threadId) + }, setPeerThreadStopped: { [weak self] peerId, threadId, isStopped in + self?.setPeerThreadStopped?(peerId, threadId, isStopped) }, updatePeerGrouping: { [weak self] peerId, group in self?.updatePeerGrouping?(peerId, group) }, togglePeerMarkedUnread: { [weak self, weak context] peerId, animated in diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift index 1b9c9cfad1..288e7b4dc4 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift @@ -66,7 +66,7 @@ enum ChatListNodeEntry: Comparable, Identifiable { promoInfo: ChatListNodeEntryPromoInfo?, hasFailedMessages: Bool, isContact: Bool, - forumThreadTitle: String? + forumTopicData: EngineChatList.ForumTopicData? ) case HoleEntry(EngineMessage.Index, theme: PresentationTheme) case GroupReferenceEntry(index: EngineChatList.Item.Index, presentationData: ChatListPresentationData, groupId: EngineChatList.Group, peers: [EngineChatList.GroupItem.Item], message: EngineMessage?, editing: Bool, unreadCount: Int, revealed: Bool, hiddenByDefault: Bool) @@ -386,7 +386,7 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState break } - result.append(.PeerEntry(index: offsetPinnedIndex(entry.index, offset: pinnedIndexOffset), presentationData: state.presentationData, messages: updatedMessages, readState: updatedCombinedReadState, isRemovedFromTotalUnreadCount: entry.isMuted, draftState: draftState, peer: entry.renderedPeer, threadInfo: entry.threadInfo.flatMap { ChatListItemContent.ThreadInfo(id: threadId, info: $0) }, presence: entry.presence, hasUnseenMentions: entry.hasUnseenMentions, hasUnseenReactions: entry.hasUnseenReactions, editing: state.editing, hasActiveRevealControls: hasActiveRevealControls, selected: isSelected, inputActivities: inputActivities, promoInfo: nil, hasFailedMessages: entry.hasFailed, isContact: entry.isContact, forumThreadTitle: entry.forumTopicTitle)) + result.append(.PeerEntry(index: offsetPinnedIndex(entry.index, offset: pinnedIndexOffset), presentationData: state.presentationData, messages: updatedMessages, readState: updatedCombinedReadState, isRemovedFromTotalUnreadCount: entry.isMuted, draftState: draftState, peer: entry.renderedPeer, threadInfo: entry.threadInfo.flatMap { ChatListItemContent.ThreadInfo(id: threadId, info: $0) }, presence: entry.presence, hasUnseenMentions: entry.hasUnseenMentions, hasUnseenReactions: entry.hasUnseenReactions, editing: state.editing, hasActiveRevealControls: hasActiveRevealControls, selected: isSelected, inputActivities: inputActivities, promoInfo: nil, hasFailedMessages: entry.hasFailed, isContact: entry.isContact, forumTopicData: entry.forumTopicData)) } if !view.hasLater { var pinningIndex: UInt16 = UInt16(pinnedIndexOffset == 0 ? 0 : (pinnedIndexOffset - 1)) @@ -420,7 +420,7 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState promoInfo: nil, hasFailedMessages: false, isContact: false, - forumThreadTitle: nil + forumTopicData: nil )) if foundPinningIndex != 0 { foundPinningIndex -= 1 @@ -428,7 +428,7 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState } } - result.append(.PeerEntry(index: .chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.predecessor), presentationData: state.presentationData, messages: [], readState: nil, isRemovedFromTotalUnreadCount: false, draftState: nil, peer: EngineRenderedPeer(peerId: savedMessagesPeer.id, peers: [savedMessagesPeer.id: savedMessagesPeer], associatedMedia: [:]), threadInfo: nil, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, editing: state.editing, hasActiveRevealControls: false, selected: state.selectedPeerIds.contains(savedMessagesPeer.id), inputActivities: nil, promoInfo: nil, hasFailedMessages: false, isContact: false, forumThreadTitle: nil)) + result.append(.PeerEntry(index: .chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.predecessor), presentationData: state.presentationData, messages: [], readState: nil, isRemovedFromTotalUnreadCount: false, draftState: nil, peer: EngineRenderedPeer(peerId: savedMessagesPeer.id, peers: [savedMessagesPeer.id: savedMessagesPeer], associatedMedia: [:]), threadInfo: nil, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, editing: state.editing, hasActiveRevealControls: false, selected: state.selectedPeerIds.contains(savedMessagesPeer.id), inputActivities: nil, promoInfo: nil, hasFailedMessages: false, isContact: false, forumTopicData: nil)) } else { if !filteredAdditionalItemEntries.isEmpty { for item in filteredAdditionalItemEntries.reversed() { @@ -475,7 +475,7 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState promoInfo: promoInfo, hasFailedMessages: item.item.hasFailed, isContact: item.item.isContact, - forumThreadTitle: item.item.forumTopicTitle + forumTopicData: item.item.forumTopicData )) if pinningIndex != 0 { pinningIndex -= 1 diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift index ded7bc2ae2..49556fad93 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift @@ -237,7 +237,7 @@ func chatListViewForLocation(chatListLocation: ChatListControllerLocation, locat presence: nil, hasUnseenMentions: hasUnseenMentions, hasUnseenReactions: hasUnseenReactions, - forumTopicTitle: nil, + forumTopicData: nil, hasFailed: false, isContact: false )) diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift index b8d5b9d26c..1a174f12f8 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift @@ -79,6 +79,7 @@ public final class HashtagSearchController: TelegramBaseController { }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in + }, setPeerThreadStopped: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in }, toggleArchivedFolderHiddenByDefault: { diff --git a/submodules/Postbox/Sources/ChatListIndexTable.swift b/submodules/Postbox/Sources/ChatListIndexTable.swift index 066c4bca0f..a09eff075b 100644 --- a/submodules/Postbox/Sources/ChatListIndexTable.swift +++ b/submodules/Postbox/Sources/ChatListIndexTable.swift @@ -170,8 +170,9 @@ final class ChatListIndexTable: Table { assert(self.updatedPreviousPeerCachedIndices.isEmpty) } - func commitWithTransaction(postbox: PostboxImpl, currentTransaction: Transaction, alteredInitialPeerCombinedReadStates: [PeerId: CombinedPeerReadState], updatedPeers: [((Peer, Bool)?, (Peer, Bool))], transactionParticipationInTotalUnreadCountUpdates: (added: Set, removed: Set), updatedTotalUnreadStates: inout [PeerGroupId: ChatListTotalUnreadState], updatedGroupTotalUnreadSummaries: inout [PeerGroupId: PeerGroupUnreadCountersCombinedSummary], currentUpdatedGroupSummarySynchronizeOperations: inout [PeerGroupAndNamespace: Bool]) { + func commitWithTransaction(postbox: PostboxImpl, currentTransaction: Transaction, alteredInitialPeerCombinedReadStates: [PeerId: CombinedPeerReadState], updatedPeers: [((Peer, Bool)?, (Peer, Bool))], transactionParticipationInTotalUnreadCountUpdates: (added: Set, removed: Set), alteredInitialPeerThreadsSummaries: [PeerId: StoredPeerThreadsSummary], updatedTotalUnreadStates: inout [PeerGroupId: ChatListTotalUnreadState], updatedGroupTotalUnreadSummaries: inout [PeerGroupId: PeerGroupUnreadCountersCombinedSummary], currentUpdatedGroupSummarySynchronizeOperations: inout [PeerGroupAndNamespace: Bool]) { var updatedPeerTags: [PeerId: (previous: PeerSummaryCounterTags, updated: PeerSummaryCounterTags)] = [:] + var updatedIsThreadBased: [PeerId: Bool] = [:] for (previous, updated) in updatedPeers { let previousTags: PeerSummaryCounterTags if let (previous, previousIsContact) = previous { @@ -183,9 +184,16 @@ final class ChatListIndexTable: Table { if previousTags != updatedTags { updatedPeerTags[updated.0.id] = (previousTags, updatedTags) } + + if let previous = previous { + let isThreadBased = postbox.seedConfiguration.peerSummaryIsThreadBased(updated.0) + if postbox.seedConfiguration.peerSummaryIsThreadBased(previous.0) != isThreadBased { + updatedIsThreadBased[updated.0.id] = isThreadBased + } + } } - if !self.updatedPreviousPeerCachedIndices.isEmpty || !alteredInitialPeerCombinedReadStates.isEmpty || !updatedPeerTags.isEmpty || !transactionParticipationInTotalUnreadCountUpdates.added.isEmpty || !transactionParticipationInTotalUnreadCountUpdates.removed.isEmpty { + if !self.updatedPreviousPeerCachedIndices.isEmpty || !alteredInitialPeerCombinedReadStates.isEmpty || !updatedPeerTags.isEmpty || !transactionParticipationInTotalUnreadCountUpdates.added.isEmpty || !transactionParticipationInTotalUnreadCountUpdates.removed.isEmpty || !alteredInitialPeerThreadsSummaries.isEmpty { var addedToGroupPeerIds: [PeerId: PeerGroupId] = [:] var removedFromGroupPeerIds: [PeerId: PeerGroupId] = [:] var addedToIndexPeerIds = Set() @@ -272,6 +280,10 @@ final class ChatListIndexTable: Table { for (peerId, _) in alteredInitialPeerCombinedReadStates { alteredPeerIds.insert(peerId) } + for (peerId, _) in alteredInitialPeerThreadsSummaries { + alteredPeerIds.insert(peerId) + } + alteredPeerIds.formUnion(addedToGroupPeerIds.keys) alteredPeerIds.formUnion(removedFromGroupPeerIds.keys) alteredPeerIds.formUnion(transactionParticipationInTotalUnreadCountUpdates.added) @@ -281,6 +293,10 @@ final class ChatListIndexTable: Table { alteredPeerIds.insert(peerId) } + for peerId in updatedIsThreadBased.keys { + alteredPeerIds.insert(peerId) + } + var additionalAlteredPeerIds = Set() for peerId in alteredPeerIds { guard let peer = postbox.peerTable.get(peerId) else { @@ -347,8 +363,42 @@ final class ChatListIndexTable: Table { } let isContact = postbox.contactsTable.isContact(peerId: peerId) let notificationPeerId: PeerId = peer.associatedPeerId ?? peerId - let initialReadState = alteredInitialPeerCombinedReadStates[peerId] ?? postbox.readStateTable.getCombinedState(peerId) - let currentReadState = postbox.readStateTable.getCombinedState(peerId) + + let initialReadState: CombinedPeerReadState? + if let updated = updatedIsThreadBased[peerId] { + if updated { + // was not thread-based, use peer read state + initialReadState = alteredInitialPeerCombinedReadStates[peerId] ?? postbox.readStateTable.getCombinedState(peerId) + } else { + let previousCount: Int32 + if let previousSummary = alteredInitialPeerThreadsSummaries[peerId] { + previousCount = previousSummary.unreadCount + } else { + previousCount = postbox.peerThreadsSummaryTable.get(peerId: peerId)?.unreadCount ?? 0 + } + initialReadState = CombinedPeerReadState(states: [(0, .idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: previousCount, markedUnread: false))]) + } + } else { + if let peer = postbox.peerTable.get(peerId), postbox.seedConfiguration.peerSummaryIsThreadBased(peer) { + let previousCount: Int32 + if let previousSummary = alteredInitialPeerThreadsSummaries[peerId] { + previousCount = previousSummary.unreadCount + } else { + previousCount = postbox.peerThreadsSummaryTable.get(peerId: peerId)?.unreadCount ?? 0 + } + initialReadState = CombinedPeerReadState(states: [(0, .idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: previousCount, markedUnread: false))]) + } else { + initialReadState = alteredInitialPeerCombinedReadStates[peerId] ?? postbox.readStateTable.getCombinedState(peerId) + } + } + + let currentReadState: CombinedPeerReadState? + if let peer = postbox.peerTable.get(peerId), postbox.seedConfiguration.peerSummaryIsThreadBased(peer) { + let count = postbox.peerThreadsSummaryTable.get(peerId: peerId)?.unreadCount ?? 0 + currentReadState = CombinedPeerReadState(states: [(0, .idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: count, markedUnread: false))]) + } else { + currentReadState = postbox.readStateTable.getCombinedState(peerId) + } var groupIds: [PeerGroupId] = [] if let (groupId, _) = self.get(peerId: peerId).includedIndex(peerId: peerId) { @@ -574,9 +624,19 @@ final class ChatListIndexTable: Table { guard let peer = postbox.peerTable.get(peerId) else { continue } - guard let combinedState = postbox.readStateTable.getCombinedState(peerId) else { + + let combinedState: CombinedPeerReadState? + if postbox.seedConfiguration.peerSummaryIsThreadBased(peer) { + let count: Int32 = postbox.peerThreadsSummaryTable.get(peerId: peerId)?.unreadCount ?? 0 + combinedState = CombinedPeerReadState(states: [(0, .idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: count, markedUnread: false))]) + } else { + combinedState = postbox.readStateTable.getCombinedState(peerId) + } + + guard let combinedState = combinedState else { continue } + let isContact = postbox.contactsTable.isContact(peerId: peerId) let notificationPeerId: PeerId = peer.associatedPeerId ?? peerId let inclusion = self.get(peerId: peerId) @@ -653,7 +713,19 @@ final class ChatListIndexTable: Table { if peerId.namespace == .max { return } - guard let combinedState = postbox.readStateTable.getCombinedState(peerId) else { + guard let peer = postbox.peerTable.get(peerId) else { + return + } + + let combinedState: CombinedPeerReadState? + if postbox.seedConfiguration.peerSummaryIsThreadBased(peer) { + let count: Int32 = postbox.peerThreadsSummaryTable.get(peerId: peerId)?.unreadCount ?? 0 + combinedState = CombinedPeerReadState(states: [(0, .idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: count, markedUnread: false))]) + } else { + combinedState = postbox.readStateTable.getCombinedState(peerId) + } + + guard let combinedState = combinedState else { return } diff --git a/submodules/Postbox/Sources/ChatListView.swift b/submodules/Postbox/Sources/ChatListView.swift index 8718c9b02f..1374b9354b 100644 --- a/submodules/Postbox/Sources/ChatListView.swift +++ b/submodules/Postbox/Sources/ChatListView.swift @@ -101,7 +101,7 @@ public struct ChatListGroupReferenceEntry: Equatable { } public enum ChatListEntry: Comparable { - case MessageEntry(index: ChatListIndex, messages: [Message], readState: CombinedPeerReadState?, isRemovedFromTotalUnreadCount: Bool, embeddedInterfaceState: StoredPeerChatInterfaceState?, renderedPeer: RenderedPeer, presence: PeerPresence?, summaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo], forumTopicData: CodableEntry?, hasFailed: Bool, isContact: Bool) + case MessageEntry(index: ChatListIndex, messages: [Message], readState: CombinedPeerReadState?, isRemovedFromTotalUnreadCount: Bool, embeddedInterfaceState: StoredPeerChatInterfaceState?, renderedPeer: RenderedPeer, presence: PeerPresence?, summaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo], forumTopicData: StoredMessageHistoryThreadInfo?, hasFailed: Bool, isContact: Bool) case HoleEntry(ChatListHole) public var index: ChatListIndex { @@ -184,7 +184,7 @@ public enum ChatListEntry: Comparable { enum MutableChatListEntry: Equatable { case IntermediateMessageEntry(index: ChatListIndex, messageIndex: MessageIndex?) - case MessageEntry(index: ChatListIndex, messages: [Message], readState: CombinedPeerReadState?, notificationSettings: PeerNotificationSettings?, isRemovedFromTotalUnreadCount: Bool, embeddedInterfaceState: StoredPeerChatInterfaceState?, renderedPeer: RenderedPeer, presence: PeerPresence?, tagSummaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo], forumTopicData: CodableEntry?, hasFailedMessages: Bool, isContact: Bool) + case MessageEntry(index: ChatListIndex, messages: [Message], readState: CombinedPeerReadState?, notificationSettings: PeerNotificationSettings?, isRemovedFromTotalUnreadCount: Bool, embeddedInterfaceState: StoredPeerChatInterfaceState?, renderedPeer: RenderedPeer, presence: PeerPresence?, tagSummaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo], forumTopicData: StoredMessageHistoryThreadInfo?, hasFailedMessages: Bool, isContact: Bool) case HoleEntry(ChatListHole) init(_ intermediateEntry: ChatListIntermediateEntry, cachedDataTable: CachedPeerDataTable, readStateTable: MessageHistoryReadStateTable, messageHistoryTable: MessageHistoryTable) { @@ -489,7 +489,15 @@ final class MutableChatListView { } let renderedPeer = RenderedPeer(peerId: peer.id, peers: peers, associatedMedia: renderAssociatedMediaForPeers(postbox: postbox, peers: peers)) - let isUnread = postbox.readStateTable.getCombinedState(peer.id)?.isUnread ?? false + + let isUnread: Bool + if postbox.seedConfiguration.peerSummaryIsThreadBased(peer) { + let count = postbox.peerThreadsSummaryTable.get(peerId: peer.id)?.unreadCount ?? 0 + isUnread = count > 0 + } else { + isUnread = postbox.readStateTable.getCombinedState(peer.id)?.isUnread ?? false + } + renderedPeers.append(ChatListGroupReferencePeer(peer: renderedPeer, isUnread: isUnread)) if foundIndices.count == 1 && message == nil { @@ -559,7 +567,15 @@ final class MutableChatListView { 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 = postbox.readStateTable.getCombinedState(groupEntries[i].renderedPeers[j].peer.peerId)?.isUnread ?? false + + let isUnread: Bool + if let peer = groupEntries[i].renderedPeers[j].peer.peer, postbox.seedConfiguration.peerSummaryIsThreadBased(peer) { + let count = postbox.peerThreadsSummaryTable.get(peerId: peer.id)?.unreadCount ?? 0 + isUnread = count > 0 + } 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) @@ -641,12 +657,20 @@ final class MutableChatListView { } } - var forumTopicData: CodableEntry? + var forumTopicData: StoredMessageHistoryThreadInfo? if let message = renderedMessages.first, let threadId = message.threadId { forumTopicData = postbox.messageHistoryThreadIndexTable.get(peerId: message.id.peerId, threadId: threadId) } - return .MessageEntry(index: index, messages: renderedMessages, readState: postbox.readStateTable.getCombinedState(index.messageIndex.id.peerId), notificationSettings: notificationSettings, isRemovedFromTotalUnreadCount: false, embeddedInterfaceState: postbox.peerChatInterfaceStateTable.get(index.messageIndex.id.peerId), renderedPeer: RenderedPeer(peerId: index.messageIndex.id.peerId, peers: peers, associatedMedia: renderAssociatedMediaForPeers(postbox: postbox, peers: peers)), presence: presence, tagSummaryInfo: [:], forumTopicData: forumTopicData, hasFailedMessages: postbox.messageHistoryFailedTable.contains(peerId: index.messageIndex.id.peerId), isContact: isContact) + let readState: CombinedPeerReadState? + if let peer = postbox.peerTable.get(index.messageIndex.id.peerId), postbox.seedConfiguration.peerSummaryIsThreadBased(peer) { + let count = postbox.peerThreadsSummaryTable.get(peerId: index.messageIndex.id.peerId)?.unreadCount ?? 0 + readState = CombinedPeerReadState(states: [(0, .idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: count, markedUnread: false))]) + } else { + readState = postbox.readStateTable.getCombinedState(index.messageIndex.id.peerId) + } + + return .MessageEntry(index: index, messages: renderedMessages, readState: readState, notificationSettings: notificationSettings, isRemovedFromTotalUnreadCount: false, embeddedInterfaceState: postbox.peerChatInterfaceStateTable.get(index.messageIndex.id.peerId), renderedPeer: RenderedPeer(peerId: index.messageIndex.id.peerId, peers: peers, associatedMedia: renderAssociatedMediaForPeers(postbox: postbox, peers: peers)), presence: presence, tagSummaryInfo: [:], forumTopicData: forumTopicData, hasFailedMessages: postbox.messageHistoryFailedTable.contains(peerId: index.messageIndex.id.peerId), isContact: isContact) default: return nil } diff --git a/submodules/Postbox/Sources/ChatListViewState.swift b/submodules/Postbox/Sources/ChatListViewState.swift index 588f35b9e0..deb8d2b5bc 100644 --- a/submodules/Postbox/Sources/ChatListViewState.swift +++ b/submodules/Postbox/Sources/ChatListViewState.swift @@ -59,7 +59,13 @@ private func mappedChatListFilterPredicate(postbox: PostboxImpl, currentTransact switch entry { case let .message(index, _): if let peer = postbox.peerTable.get(index.messageIndex.id.peerId) { - let isUnread = postbox.readStateTable.getCombinedState(index.messageIndex.id.peerId)?.isUnread ?? false + var isUnread: Bool + if postbox.seedConfiguration.peerSummaryIsThreadBased(peer) { + isUnread = (postbox.peerThreadsSummaryTable.get(peerId: peer.id)?.unreadCount ?? 0) > 0 + } else { + isUnread = postbox.readStateTable.getCombinedState(index.messageIndex.id.peerId)?.isUnread ?? false + } + let notificationsPeerId = peer.notificationSettingsPeerId ?? peer.id let isContact = postbox.contactsTable.isContact(peerId: notificationsPeerId) let isRemovedFromTotalUnreadCount = resolvedIsRemovedFromTotalUnreadCount(globalSettings: globalNotificationSettings, peer: peer, peerSettings: postbox.peerNotificationSettingsTable.getEffective(notificationsPeerId)) @@ -404,7 +410,14 @@ private final class ChatListViewSpaceState { let messageTagSummaryResult = resolveChatListMessageTagSummaryResultCalculation(postbox: postbox, peerId: peer.id, threadId: nil, calculation: filterPredicate.messageTagSummary) - if !filterPredicate.includes(peer: peer, groupId: groupId, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, isUnread: postbox.readStateTable.getCombinedState(peer.id)?.isUnread ?? false, isContact: postbox.contactsTable.isContact(peerId: notificationsPeerId), messageTagSummaryResult: messageTagSummaryResult) { + var isUnread: Bool + if postbox.seedConfiguration.peerSummaryIsThreadBased(peer) { + isUnread = (postbox.peerThreadsSummaryTable.get(peerId: peer.id)?.unreadCount ?? 0) > 0 + } else { + isUnread = postbox.readStateTable.getCombinedState(index.messageIndex.id.peerId)?.isUnread ?? false + } + + if !filterPredicate.includes(peer: peer, groupId: groupId, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, isUnread: isUnread, isContact: postbox.contactsTable.isContact(peerId: notificationsPeerId), messageTagSummaryResult: messageTagSummaryResult) { continue inner } } else { @@ -496,7 +509,7 @@ private final class ChatListViewSpaceState { } } - if !transaction.currentUpdatedPeerNotificationSettings.isEmpty, case let .group(groupId, pinned, maybeFilterPredicate) = self.space, let filterPredicate = maybeFilterPredicate { + if (!transaction.currentUpdatedPeerNotificationSettings.isEmpty || !transaction.updatedPeerThreadsSummaries.isEmpty), case let .group(groupId, pinned, maybeFilterPredicate) = self.space, let filterPredicate = maybeFilterPredicate { var removeEntryIndices: [MutableChatListEntryIndex] = [] let _ = self.orderedEntries.mutableScan { entry -> MutableChatListEntry? in let entryPeer: Peer @@ -519,8 +532,15 @@ private final class ChatListViewSpaceState { case .HoleEntry: return nil } - if let settingsChange = transaction.currentUpdatedPeerNotificationSettings[entryNotificationsPeerId] { - let isUnread = postbox.readStateTable.getCombinedState(entryPeer.id)?.isUnread ?? false + + let settingsChange = transaction.currentUpdatedPeerNotificationSettings[entryNotificationsPeerId] + if settingsChange != nil || transaction.updatedPeerThreadsSummaries.contains(entryNotificationsPeerId) { + var isUnread: Bool + if postbox.seedConfiguration.peerSummaryIsThreadBased(entryPeer) { + isUnread = (postbox.peerThreadsSummaryTable.get(peerId: entryPeer.id)?.unreadCount ?? 0) > 0 + } else { + isUnread = postbox.readStateTable.getCombinedState(entryPeer.id)?.isUnread ?? false + } let globalNotificationSettingsValue: PostboxGlobalNotificationSettings if let current = globalNotificationSettings { @@ -530,7 +550,7 @@ private final class ChatListViewSpaceState { globalNotificationSettings = globalNotificationSettingsValue } - let nowRemovedFromTotalUnreadCount = resolvedIsRemovedFromTotalUnreadCount(globalSettings: globalNotificationSettingsValue, peer: entryPeer, peerSettings: settingsChange.1) + let nowRemovedFromTotalUnreadCount = resolvedIsRemovedFromTotalUnreadCount(globalSettings: globalNotificationSettingsValue, peer: entryPeer, peerSettings: settingsChange?.1 ?? postbox.peerNotificationSettingsTable.getEffective(entryNotificationsPeerId)) let messageTagSummaryResult = resolveChatListMessageTagSummaryResultCalculation(postbox: postbox, peerId: entryPeer.id, threadId: nil, calculation: filterPredicate.messageTagSummary) @@ -548,7 +568,8 @@ private final class ChatListViewSpaceState { let _ = self.orderedEntries.remove(index: index) } } - for (peerId, settingsChange) in transaction.currentUpdatedPeerNotificationSettings { + + for peerId in transaction.updatedPeerThreadsSummaries.union(transaction.currentUpdatedPeerNotificationSettings.keys) { if let mainPeer = postbox.peerTable.get(peerId) { var peers: [Peer] = [mainPeer] for associatedId in postbox.reverseAssociatedPeerTable.get(peerId: mainPeer.id) { @@ -558,7 +579,12 @@ private final class ChatListViewSpaceState { } assert(Set(peers.map { $0.id }).count == peers.count) - let isUnread = postbox.readStateTable.getCombinedState(peerId)?.isUnread ?? false + var isUnread: Bool + if postbox.seedConfiguration.peerSummaryIsThreadBased(mainPeer) { + isUnread = (postbox.peerThreadsSummaryTable.get(peerId: peerId)?.unreadCount ?? 0) > 0 + } else { + isUnread = postbox.readStateTable.getCombinedState(peerId)?.isUnread ?? false + } let globalNotificationSettingsValue: PostboxGlobalNotificationSettings if let current = globalNotificationSettings { @@ -568,7 +594,7 @@ private final class ChatListViewSpaceState { globalNotificationSettings = globalNotificationSettingsValue } - let nowRemovedFromTotalUnreadCount = resolvedIsRemovedFromTotalUnreadCount(globalSettings: globalNotificationSettingsValue, peer: mainPeer, peerSettings: settingsChange.1) + let nowRemovedFromTotalUnreadCount = resolvedIsRemovedFromTotalUnreadCount(globalSettings: globalNotificationSettingsValue, peer: mainPeer, peerSettings: transaction.currentUpdatedPeerNotificationSettings[mainPeer.id]?.1 ?? postbox.peerNotificationSettingsTable.getEffective(mainPeer.id)) let messageTagSummaryResult = resolveChatListMessageTagSummaryResultCalculation(postbox: postbox, peerId: peerId, threadId: nil, calculation: filterPredicate.messageTagSummary) @@ -756,7 +782,12 @@ private final class ChatListViewSpaceState { let updatedActionsSummary = transaction.currentUpdatedMessageActionsSummaries[PendingMessageActionsSummaryKey(type: filterMessageTagSummary.subtractCount.type, peerId: entryPeer.id, namespace: filterMessageTagSummary.subtractCount.namespace)] if updatedMessageSummary != nil || updatedActionsSummary != nil { - let isUnread = postbox.readStateTable.getCombinedState(entryPeer.id)?.isUnread ?? false + var isUnread: Bool + if postbox.seedConfiguration.peerSummaryIsThreadBased(entryPeer) { + isUnread = (postbox.peerThreadsSummaryTable.get(peerId: entryPeer.id)?.unreadCount ?? 0) > 0 + } else { + isUnread = postbox.readStateTable.getCombinedState(entryPeer.id)?.isUnread ?? false + } let globalNotificationSettingsValue: PostboxGlobalNotificationSettings if let current = globalNotificationSettings { @@ -801,7 +832,12 @@ private final class ChatListViewSpaceState { } assert(Set(peers.map { $0.id }).count == peers.count) - let isUnread = postbox.readStateTable.getCombinedState(peerId)?.isUnread ?? false + var isUnread: Bool + if postbox.seedConfiguration.peerSummaryIsThreadBased(mainPeer) { + isUnread = (postbox.peerThreadsSummaryTable.get(peerId: peerId)?.unreadCount ?? 0) > 0 + } else { + isUnread = postbox.readStateTable.getCombinedState(peerId)?.isUnread ?? false + } let globalNotificationSettingsValue: PostboxGlobalNotificationSettings if let current = globalNotificationSettings { @@ -842,7 +878,7 @@ private final class ChatListViewSpaceState { } } - if !transaction.currentUpdatedMessageTagSummaries.isEmpty || !transaction.currentUpdatedMessageActionsSummaries.isEmpty { + if !transaction.currentUpdatedMessageTagSummaries.isEmpty || !transaction.currentUpdatedMessageActionsSummaries.isEmpty || !transaction.updatedPeerThreadsSummaries.isEmpty { if self.orderedEntries.mutableScan({ entry in switch entry { case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, entryRenderedPeer, presence, tagSummaryInfo, forumTopicData, hasFailedMessages, isContact): @@ -875,11 +911,23 @@ private final class ChatListViewSpaceState { } } + var updatedReadState = readState + if let peer = postbox.peerTable.get(index.messageIndex.id.peerId), postbox.seedConfiguration.peerSummaryIsThreadBased(peer) { + let count = postbox.peerThreadsSummaryTable.get(peerId: peer.id)?.unreadCount ?? 0 + updatedReadState = CombinedPeerReadState(states: [(0, .idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: count, markedUnread: false))]) + } else { + updatedReadState = postbox.readStateTable.getCombinedState(index.messageIndex.id.peerId) + } + + if updatedReadState != readState { + didUpdateSummaryInfo = true + } + if didUpdateSummaryInfo { return .MessageEntry( index: index, messages: messages, - readState: readState, + readState: updatedReadState, notificationSettings: notificationSettings, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: embeddedInterfaceState, @@ -1461,12 +1509,20 @@ struct ChatListViewState { } } - var forumTopicData: CodableEntry? + var forumTopicData: StoredMessageHistoryThreadInfo? if let message = renderedMessages.first, let threadId = message.threadId { forumTopicData = postbox.messageHistoryThreadIndexTable.get(peerId: message.id.peerId, threadId: threadId) } - let updatedEntry: MutableChatListEntry = .MessageEntry(index: index, messages: renderedMessages, readState: postbox.readStateTable.getCombinedState(index.messageIndex.id.peerId), notificationSettings: notificationSettings, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: postbox.peerChatInterfaceStateTable.get(index.messageIndex.id.peerId), renderedPeer: renderedPeer, presence: presence, tagSummaryInfo: tagSummaryInfo, forumTopicData: forumTopicData, hasFailedMessages: false, isContact: postbox.contactsTable.isContact(peerId: index.messageIndex.id.peerId)) + let readState: CombinedPeerReadState? + if let peer = postbox.peerTable.get(index.messageIndex.id.peerId), postbox.seedConfiguration.peerSummaryIsThreadBased(peer) { + let count = postbox.peerThreadsSummaryTable.get(peerId: peer.id)?.unreadCount ?? 0 + readState = CombinedPeerReadState(states: [(0, .idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: count, markedUnread: false))]) + } else { + readState = postbox.readStateTable.getCombinedState(index.messageIndex.id.peerId) + } + + let updatedEntry: MutableChatListEntry = .MessageEntry(index: index, messages: renderedMessages, readState: readState, notificationSettings: notificationSettings, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: postbox.peerChatInterfaceStateTable.get(index.messageIndex.id.peerId), renderedPeer: renderedPeer, presence: presence, tagSummaryInfo: tagSummaryInfo, forumTopicData: forumTopicData, hasFailedMessages: false, isContact: postbox.contactsTable.isContact(peerId: index.messageIndex.id.peerId)) if directionIndex == 0 { self.stateBySpace[space]!.orderedEntries.setLowerOrAtAnchorAtArrayIndex(listIndex, to: updatedEntry) } else { diff --git a/submodules/Postbox/Sources/MessageHistoryThreadIndexView.swift b/submodules/Postbox/Sources/MessageHistoryThreadIndexView.swift index 18f5284742..ec2a3a6cec 100644 --- a/submodules/Postbox/Sources/MessageHistoryThreadIndexView.swift +++ b/submodules/Postbox/Sources/MessageHistoryThreadIndexView.swift @@ -67,7 +67,7 @@ final class MutableMessageHistoryThreadIndexView: MutablePostboxView { self.items.append(Item( id: item.threadId, index: item.index, - info: item.info, + info: item.info.data, tagSummaryInfo: tagSummaryInfo, topMessage: postbox.getMessage(item.index.id) )) @@ -170,7 +170,7 @@ final class MutableMessageHistoryThreadInfoView: MutablePostboxView { private let peerId: PeerId private let threadId: Int64 - fileprivate var info: CodableEntry? + fileprivate var info: StoredMessageHistoryThreadInfo? init(postbox: PostboxImpl, peerId: PeerId, threadId: Int64) { self.peerId = peerId @@ -207,7 +207,7 @@ final class MutableMessageHistoryThreadInfoView: MutablePostboxView { } public final class MessageHistoryThreadInfoView: PostboxView { - public let info: CodableEntry? + public let info: StoredMessageHistoryThreadInfo? init(_ view: MutableMessageHistoryThreadInfoView) { self.info = view.info diff --git a/submodules/Postbox/Sources/MessageThreadIndexTable.swift b/submodules/Postbox/Sources/MessageThreadIndexTable.swift index bbc9f159d3..fb350067ae 100644 --- a/submodules/Postbox/Sources/MessageThreadIndexTable.swift +++ b/submodules/Postbox/Sources/MessageThreadIndexTable.swift @@ -1,5 +1,49 @@ import Foundation +public struct StoredMessageHistoryThreadInfo: Equatable, PostboxCoding { + public struct Summary: Equatable, PostboxCoding { + public var totalUnreadCount: Int32 + public var mutedUntil: Int32? + + public init(totalUnreadCount: Int32, mutedUntil: Int32?) { + self.totalUnreadCount = totalUnreadCount + self.mutedUntil = mutedUntil + } + + public init(decoder: PostboxDecoder) { + self.totalUnreadCount = decoder.decodeInt32ForKey("u", orElse: 0) + self.mutedUntil = decoder.decodeOptionalInt32ForKey("m") + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt32(self.totalUnreadCount, forKey: "u") + if let mutedUntil = self.mutedUntil { + encoder.encodeInt32(mutedUntil, forKey: "m") + } else { + encoder.encodeNil(forKey: "m") + } + } + } + + public var data: CodableEntry + public var summary: Summary + + public init(data: CodableEntry, summary: Summary) { + self.data = data + self.summary = summary + } + + public init(decoder: PostboxDecoder) { + self.data = CodableEntry(data: decoder.decodeDataForKey("d")!) + self.summary = decoder.decodeObjectForKey("s", decoder: { return Summary(decoder: $0) }) as! Summary + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeData(self.data.data, forKey: "d") + encoder.encodeObject(self.summary, forKey: "s") + } +} + private func extractKey(_ key: ValueBoxKey) -> MessageIndex { return MessageIndex(id: MessageId(peerId: PeerId(key.getInt64(0)), namespace: key.getInt32(8 + 8), id: key.getInt32(8 + 8 + 4 + 4)), timestamp: key.getInt32(8 + 8 + 4)) } @@ -65,7 +109,13 @@ class MessageHistoryThreadIndexTable: Table { } private struct UpdatedEntry { - var value: CodableEntry? + var value: StoredMessageHistoryThreadInfo? + } + + enum IndexBoundary { + case lowerBound + case upperBound + case index(StoredPeerThreadCombinedState.Index) } private let reverseIndexTable: MessageHistoryThreadReverseIndexTable @@ -114,14 +164,16 @@ class MessageHistoryThreadIndexTable: Table { return self.lowerBound(peerId: peerId).successor } - func get(peerId: PeerId, threadId: Int64) -> CodableEntry? { + func get(peerId: PeerId, threadId: Int64) -> StoredMessageHistoryThreadInfo? { if let updated = self.updatedInfoItems[MessageHistoryThreadsTable.ItemId(peerId: peerId, threadId: threadId)] { return updated.value } else { if let itemIndex = self.reverseIndexTable.get(peerId: peerId, threadId: threadId) { if let value = self.valueBox.get(self.table, key: self.key(peerId: itemIndex.id.peerId, timestamp: itemIndex.timestamp, threadId: threadId, namespace: itemIndex.id.namespace, id: itemIndex.id.id, key: self.sharedKey)) { if value.length != 0 { - return CodableEntry(data: value.makeData()) + let decoder = PostboxDecoder(buffer: value) + let state = StoredMessageHistoryThreadInfo(decoder: decoder) + return state } else { return nil } @@ -134,7 +186,7 @@ class MessageHistoryThreadIndexTable: Table { } } - func set(peerId: PeerId, threadId: Int64, info: CodableEntry?) { + func set(peerId: PeerId, threadId: Int64, info: StoredMessageHistoryThreadInfo?) { self.updatedInfoItems[MessageHistoryThreadsTable.ItemId(peerId: peerId, threadId: threadId)] = UpdatedEntry(value: info) } @@ -160,7 +212,9 @@ class MessageHistoryThreadIndexTable: Table { } if let updatedInfo = self.updatedInfoItems[itemId] { if let value = updatedInfo.value { - info = ReadBuffer(data: value.data) + let encoder = PostboxEncoder() + value.encode(encoder) + info = encoder.makeReadBufferAndReset() } else { info = nil } @@ -185,15 +239,52 @@ class MessageHistoryThreadIndexTable: Table { return peerIds } - func getAll(peerId: PeerId) -> [(threadId: Int64, index: MessageIndex, info: CodableEntry)] { - var result: [(threadId: Int64, index: MessageIndex, info: CodableEntry)] = [] + func fetch(peerId: PeerId, namespace: MessageId.Namespace, start: IndexBoundary, end: IndexBoundary, limit: Int) -> [(threadId: Int64, index: MessageIndex, info: StoredMessageHistoryThreadInfo)] { + let startKey: ValueBoxKey + switch start { + case let .index(index): + startKey = self.key(peerId: peerId, timestamp: index.timestamp, threadId: index.threadId, namespace: namespace, id: index.messageId, key: ValueBoxKey(length: self.sharedKey.length)) + case .lowerBound: + startKey = self.lowerBound(peerId: peerId) + case .upperBound: + startKey = self.upperBound(peerId: peerId) + } + + let endKey: ValueBoxKey + switch end { + case let .index(index): + endKey = self.key(peerId: peerId, timestamp: index.timestamp, threadId: index.threadId, namespace: namespace, id: index.messageId, key: ValueBoxKey(length: self.sharedKey.length)) + case .lowerBound: + endKey = self.lowerBound(peerId: peerId) + case .upperBound: + endKey = self.upperBound(peerId: peerId) + } + + var result: [(threadId: Int64, index: MessageIndex, info: StoredMessageHistoryThreadInfo)] = [] + self.valueBox.range(self.table, start: startKey, end: endKey, values: { key, value in + let keyData = MessageHistoryThreadIndexTable.extract(key: key) + if value.length == 0 { + return true + } + let decoder = PostboxDecoder(buffer: value) + let state = StoredMessageHistoryThreadInfo(decoder: decoder) + result.append((keyData.threadId, keyData.index, state)) + return true + }, limit: limit) + + return result + } + + func getAll(peerId: PeerId) -> [(threadId: Int64, index: MessageIndex, info: StoredMessageHistoryThreadInfo)] { + var result: [(threadId: Int64, index: MessageIndex, info: StoredMessageHistoryThreadInfo)] = [] self.valueBox.range(self.table, start: self.upperBound(peerId: peerId), end: self.lowerBound(peerId: peerId), values: { key, value in let keyData = MessageHistoryThreadIndexTable.extract(key: key) if value.length == 0 { return true } - let info = CodableEntry(data: value.makeData()) - result.append((keyData.threadId, keyData.index, info)) + let decoder = PostboxDecoder(buffer: value) + let state = StoredMessageHistoryThreadInfo(decoder: decoder) + result.append((keyData.threadId, keyData.index, state)) return true }, limit: 100000) diff --git a/submodules/Postbox/Sources/Peer.swift b/submodules/Postbox/Sources/Peer.swift index 59bf6a6ee9..8f339d2b31 100644 --- a/submodules/Postbox/Sources/Peer.swift +++ b/submodules/Postbox/Sources/Peer.swift @@ -357,6 +357,16 @@ public func arePeerDictionariesEqual(_ lhs: [PeerId: Peer], _ rhs: [PeerId: Peer return true } +public struct PeerSummaryCounterData: Equatable { + public var tags: PeerSummaryCounterTags + public var usesThreadSummary: Bool + + public init(tags: PeerSummaryCounterTags, usesThreadSummary: Bool) { + self.tags = tags + self.usesThreadSummary = usesThreadSummary + } +} + public struct PeerSummaryCounterTags: OptionSet, Sequence, Hashable { public var rawValue: Int32 diff --git a/submodules/Postbox/Sources/PeerThreadCombinedStateTable.swift b/submodules/Postbox/Sources/PeerThreadCombinedStateTable.swift new file mode 100644 index 0000000000..98f7f269b5 --- /dev/null +++ b/submodules/Postbox/Sources/PeerThreadCombinedStateTable.swift @@ -0,0 +1,193 @@ +import Foundation + +public struct StoredPeerThreadCombinedState: Equatable, Codable { + public struct Index: Equatable, Comparable, Codable { + private enum CodingKeys: String, CodingKey { + case timestamp = "t" + case threadId = "i" + case messageId = "m" + } + + public var timestamp: Int32 + public var threadId: Int64 + public var messageId: Int32 + + public init(timestamp: Int32, threadId: Int64, messageId: Int32) { + self.timestamp = timestamp + self.threadId = threadId + self.messageId = messageId + } + + public static func <(lhs: Index, rhs: Index) -> Bool { + if lhs.timestamp != rhs.timestamp { + return lhs.timestamp < rhs.timestamp + } + if lhs.threadId != rhs.threadId { + return lhs.threadId < rhs.threadId + } + return lhs.messageId < rhs.messageId + } + } + + private enum CodingKeys: String, CodingKey { + case data = "d" + case validIndexBoundary = "r" + } + + var data: CodableEntry + var validIndexBoundary: Index? + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.data = CodableEntry(data: try container.decode(Data.self, forKey: .data)) + self.validIndexBoundary = try container.decodeIfPresent(Index.self, forKey: .validIndexBoundary) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(self.data.data, forKey: .data) + try container.encodeIfPresent(self.validIndexBoundary, forKey: .validIndexBoundary) + } +} + +class PeerThreadCombinedStateTable: Table { + static func tableSpec(_ id: Int32) -> ValueBoxTable { + return ValueBoxTable(id: id, keyType: .binary, compactValuesOnCreation: true) + } + + private let sharedKey = ValueBoxKey(length: 8) + + private(set) var updatedIds = Set() + + override init(valueBox: ValueBox, table: ValueBoxTable, useCaches: Bool) { + super.init(valueBox: valueBox, table: table, useCaches: useCaches) + } + + private func key(peerId: PeerId) -> ValueBoxKey { + self.sharedKey.setInt64(0, value: peerId.toInt64()) + return self.sharedKey + } + + func set(peerId: PeerId, state: StoredPeerThreadCombinedState?) { + if let state = state { + do { + let data = try AdaptedPostboxEncoder().encode(state) + self.valueBox.set(self.table, key: self.key(peerId: peerId), value: MemoryBuffer(data: data)) + } catch { + assertionFailure() + } + } else { + self.valueBox.remove(self.table, key: self.key(peerId: peerId), secure: false) + } + } + + func get(peerId: PeerId) -> StoredPeerThreadCombinedState? { + do { + guard let value = self.valueBox.get(self.table, key: self.key(peerId: peerId)) else { + return nil + } + let state = try withExtendedLifetime(value, { + return try AdaptedPostboxDecoder().decode(StoredPeerThreadCombinedState.self, from: value.dataNoCopy()) + }) + return state + } catch { + return nil + } + } + + override func beforeCommit() { + super.beforeCommit() + } +} + +struct StoredPeerThreadsSummary: Equatable, Codable { + private enum CodingKeys: String, CodingKey { + case unreadCount = "u" + } + + var unreadCount: Int32 + + init(unreadCount: Int32) { + self.unreadCount = unreadCount + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.unreadCount = try container.decode(Int32.self, forKey: .unreadCount) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(self.unreadCount, forKey: .unreadCount) + } +} + +class PeerThreadsSummaryTable: Table { + static func tableSpec(_ id: Int32) -> ValueBoxTable { + return ValueBoxTable(id: id, keyType: .binary, compactValuesOnCreation: true) + } + + private let sharedKey = ValueBoxKey(length: 8) + + private(set) var updatedIds = Set() + + override init(valueBox: ValueBox, table: ValueBoxTable, useCaches: Bool) { + super.init(valueBox: valueBox, table: table, useCaches: useCaches) + } + + private func key(peerId: PeerId) -> ValueBoxKey { + self.sharedKey.setInt64(0, value: peerId.toInt64()) + return self.sharedKey + } + + private func set(peerId: PeerId, state: StoredPeerThreadsSummary) { + do { + let data = try AdaptedPostboxEncoder().encode(state) + self.valueBox.set(self.table, key: self.key(peerId: peerId), value: MemoryBuffer(data: data)) + } catch { + assertionFailure() + } + } + + func get(peerId: PeerId) -> StoredPeerThreadsSummary? { + do { + guard let value = self.valueBox.get(self.table, key: self.key(peerId: peerId)) else { + return nil + } + let state = try withExtendedLifetime(value, { + return try AdaptedPostboxDecoder().decode(StoredPeerThreadsSummary.self, from: value.dataNoCopy()) + }) + return state + } catch { + return nil + } + } + + func update(peerIds: Set, indexTable: MessageHistoryThreadIndexTable, combinedStateTable: PeerThreadCombinedStateTable) -> [PeerId: StoredPeerThreadsSummary] { + var updatedInitialSummaries: [PeerId: StoredPeerThreadsSummary] = [:] + + for peerId in peerIds { + var unreadCount: Int32 = 0 + for item in indexTable.fetch(peerId: peerId, namespace: 0, start: .upperBound, end: .lowerBound, limit: 20) { + if item.info.summary.mutedUntil == nil { + unreadCount += item.info.summary.totalUnreadCount + } + } + let current = self.get(peerId: peerId) + if current?.unreadCount != unreadCount { + updatedInitialSummaries[peerId] = current ?? StoredPeerThreadsSummary(unreadCount: 0) + self.set(peerId: peerId, state: StoredPeerThreadsSummary(unreadCount: unreadCount)) + } + } + + return updatedInitialSummaries + } + + override func beforeCommit() { + super.beforeCommit() + } +} diff --git a/submodules/Postbox/Sources/Postbox.swift b/submodules/Postbox/Sources/Postbox.swift index 1e6e315e5a..4b4f449eff 100644 --- a/submodules/Postbox/Sources/Postbox.swift +++ b/submodules/Postbox/Sources/Postbox.swift @@ -1153,20 +1153,30 @@ public final class Transaction { self.postbox?.removePeerTimeoutAttributeEntry(peerId: peerId, timestamp: timestamp) } - public func getMessageHistoryThreadIndex(peerId: PeerId) -> [(threadId: Int64, index: MessageIndex, info: CodableEntry)] { + public func getMessageHistoryThreadIndex(peerId: PeerId) -> [(threadId: Int64, index: MessageIndex, info: StoredMessageHistoryThreadInfo)] { assert(!self.disposed) return self.postbox!.messageHistoryThreadIndexTable.getAll(peerId: peerId) } - public func getMessageHistoryThreadInfo(peerId: PeerId, threadId: Int64) -> CodableEntry? { + public func getMessageHistoryThreadInfo(peerId: PeerId, threadId: Int64) -> StoredMessageHistoryThreadInfo? { assert(!self.disposed) return self.postbox!.messageHistoryThreadIndexTable.get(peerId: peerId, threadId: threadId) } - public func setMessageHistoryThreadInfo(peerId: PeerId, threadId: Int64, info: CodableEntry?) { + public func setMessageHistoryThreadInfo(peerId: PeerId, threadId: Int64, info: StoredMessageHistoryThreadInfo?) { assert(!self.disposed) self.postbox!.messageHistoryThreadIndexTable.set(peerId: peerId, threadId: threadId, info: info) } + + public func getPeerThreadCombinedState(peerId: PeerId) -> StoredPeerThreadCombinedState? { + assert(!self.disposed) + return self.postbox!.peerThreadCombinedStateTable.get(peerId: peerId) + } + + public func setPeerThreadCombinedState(peerId: PeerId, state: StoredPeerThreadCombinedState?) { + assert(!self.disposed) + return self.postbox!.setPeerThreadCombinedState(peerId: peerId, state: state) + } } public enum PostboxResult { @@ -1398,6 +1408,8 @@ final class PostboxImpl { private var currentUpdatedNoticeEntryKeys = Set() private var currentUpdatedCacheEntryKeys = Set() + private var currentUpdatedPeerThreadCombinedStates = Set() + private var currentNeedsReindexUnreadCounters: Bool = false private let statePipe: ValuePipe = ValuePipe() @@ -1441,6 +1453,8 @@ final class PostboxImpl { let messageHistoryFailedTable: MessageHistoryFailedTable let messageHistoryTagsTable: MessageHistoryTagsTable let messageHistoryThreadsTable: MessageHistoryThreadsTable + let peerThreadCombinedStateTable: PeerThreadCombinedStateTable + let peerThreadsSummaryTable: PeerThreadsSummaryTable let messageHistoryThreadTagsTable: MessageHistoryThreadTagsTable let messageHistoryThreadHoleIndexTable: MessageHistoryThreadHoleIndexTable let messageHistoryThreadReverseIndexTable: MessageHistoryThreadReverseIndexTable @@ -1519,10 +1533,12 @@ final class PostboxImpl { self.pendingMessageActionsTable = PendingMessageActionsTable(valueBox: self.valueBox, table: PendingMessageActionsTable.tableSpec(46), useCaches: useCaches, metadataTable: self.pendingMessageActionsMetadataTable) self.messageHistoryTagsTable = MessageHistoryTagsTable(valueBox: self.valueBox, table: MessageHistoryTagsTable.tableSpec(12), useCaches: useCaches, seedConfiguration: self.seedConfiguration, summaryTable: self.messageHistoryTagsSummaryTable) self.messageHistoryThreadsTable = MessageHistoryThreadsTable(valueBox: self.valueBox, table: MessageHistoryThreadsTable.tableSpec(62), useCaches: useCaches) + self.peerThreadCombinedStateTable = PeerThreadCombinedStateTable(valueBox: self.valueBox, table: PeerThreadCombinedStateTable.tableSpec(74), useCaches: useCaches) + self.peerThreadsSummaryTable = PeerThreadsSummaryTable(valueBox: self.valueBox, table: PeerThreadsSummaryTable.tableSpec(75), useCaches: useCaches) self.messageHistoryThreadTagsTable = MessageHistoryThreadTagsTable(valueBox: self.valueBox, table: MessageHistoryThreadTagsTable.tableSpec(71), useCaches: useCaches, seedConfiguration: self.seedConfiguration, summaryTable: self.messageHistoryTagsSummaryTable) self.messageHistoryThreadHoleIndexTable = MessageHistoryThreadHoleIndexTable(valueBox: self.valueBox, table: MessageHistoryThreadHoleIndexTable.tableSpec(63), useCaches: useCaches, metadataTable: self.messageHistoryMetadataTable, seedConfiguration: self.seedConfiguration) - self.messageHistoryThreadReverseIndexTable = MessageHistoryThreadReverseIndexTable(valueBox: self.valueBox, table: MessageHistoryThreadReverseIndexTable.tableSpec(69), useCaches: useCaches) - self.messageHistoryThreadIndexTable = MessageHistoryThreadIndexTable(valueBox: self.valueBox, table: MessageHistoryThreadIndexTable.tableSpec(70), reverseIndexTable: self.messageHistoryThreadReverseIndexTable, useCaches: useCaches) + self.messageHistoryThreadReverseIndexTable = MessageHistoryThreadReverseIndexTable(valueBox: self.valueBox, table: MessageHistoryThreadReverseIndexTable.tableSpec(72), useCaches: useCaches) + self.messageHistoryThreadIndexTable = MessageHistoryThreadIndexTable(valueBox: self.valueBox, table: MessageHistoryThreadIndexTable.tableSpec(73), reverseIndexTable: self.messageHistoryThreadReverseIndexTable, useCaches: useCaches) self.globalMessageHistoryTagsTable = GlobalMessageHistoryTagsTable(valueBox: self.valueBox, table: GlobalMessageHistoryTagsTable.tableSpec(39), useCaches: useCaches) self.localMessageHistoryTagsTable = LocalMessageHistoryTagsTable(valueBox: self.valueBox, table: GlobalMessageHistoryTagsTable.tableSpec(52), useCaches: useCaches) self.messageHistoryIndexTable = MessageHistoryIndexTable(valueBox: self.valueBox, table: MessageHistoryIndexTable.tableSpec(4), useCaches: useCaches, messageHistoryHoleIndexTable: self.messageHistoryHoleIndexTable, globalMessageIdsTable: self.globalMessageIdsTable, metadataTable: self.messageHistoryMetadataTable, seedConfiguration: self.seedConfiguration) @@ -1578,6 +1594,8 @@ final class PostboxImpl { tables.append(self.messageHistoryFailedTable) tables.append(self.messageHistoryTagsTable) tables.append(self.messageHistoryThreadsTable) + tables.append(self.peerThreadCombinedStateTable) + tables.append(self.peerThreadsSummaryTable) tables.append(self.messageHistoryThreadTagsTable) tables.append(self.messageHistoryThreadHoleIndexTable) tables.append(self.messageHistoryThreadReverseIndexTable) @@ -2016,11 +2034,13 @@ final class PostboxImpl { } } let transactionParticipationInTotalUnreadCountUpdates = self.peerNotificationSettingsTable.transactionParticipationInTotalUnreadCountUpdates(postbox: self, transaction: currentTransaction) - self.chatListIndexTable.commitWithTransaction(postbox: self, currentTransaction: currentTransaction, alteredInitialPeerCombinedReadStates: alteredInitialPeerCombinedReadStates, updatedPeers: updatedPeers, transactionParticipationInTotalUnreadCountUpdates: transactionParticipationInTotalUnreadCountUpdates, updatedTotalUnreadStates: &self.currentUpdatedTotalUnreadStates, updatedGroupTotalUnreadSummaries: &self.currentUpdatedGroupTotalUnreadSummaries, currentUpdatedGroupSummarySynchronizeOperations: &self.currentUpdatedGroupSummarySynchronizeOperations) - - self.peerTable.commitDependentTables() let updatedMessageThreadPeerIds = self.messageHistoryThreadIndexTable.replay(threadsTable: self.messageHistoryThreadsTable, namespaces: self.seedConfiguration.chatMessagesNamespaces, updatedIds: self.messageHistoryThreadsTable.updatedIds) + let alteredInitialPeerThreadsSummaries = self.peerThreadsSummaryTable.update(peerIds: updatedMessageThreadPeerIds.union(self.currentUpdatedPeerThreadCombinedStates), indexTable: self.messageHistoryThreadIndexTable, combinedStateTable: self.peerThreadCombinedStateTable) + + self.chatListIndexTable.commitWithTransaction(postbox: self, currentTransaction: currentTransaction, alteredInitialPeerCombinedReadStates: alteredInitialPeerCombinedReadStates, updatedPeers: updatedPeers, transactionParticipationInTotalUnreadCountUpdates: transactionParticipationInTotalUnreadCountUpdates, alteredInitialPeerThreadsSummaries: alteredInitialPeerThreadsSummaries, updatedTotalUnreadStates: &self.currentUpdatedTotalUnreadStates, updatedGroupTotalUnreadSummaries: &self.currentUpdatedGroupTotalUnreadSummaries, currentUpdatedGroupSummarySynchronizeOperations: &self.currentUpdatedGroupSummarySynchronizeOperations) + + self.peerTable.commitDependentTables() if self.currentNeedsReindexUnreadCounters { self.reindexUnreadCounters(currentTransaction: currentTransaction) @@ -2028,7 +2048,7 @@ final class PostboxImpl { let updatedPeerTimeoutAttributes = self.peerTimeoutPropertiesTable.hasUpdates - let transaction = PostboxTransaction(currentUpdatedState: self.currentUpdatedState, currentPeerHoleOperations: self.currentPeerHoleOperations, currentOperationsByPeerId: self.currentOperationsByPeerId, chatListOperations: self.currentChatListOperations, currentUpdatedChatListInclusions: self.currentUpdatedChatListInclusions, currentUpdatedPeers: self.currentUpdatedPeers, currentUpdatedPeerNotificationSettings: self.currentUpdatedPeerNotificationSettings, currentUpdatedPeerNotificationBehaviorTimestamps: self.currentUpdatedPeerNotificationBehaviorTimestamps, currentUpdatedCachedPeerData: self.currentUpdatedCachedPeerData, currentUpdatedPeerPresences: currentUpdatedPeerPresences, currentUpdatedPeerChatListEmbeddedStates: self.currentUpdatedPeerChatListEmbeddedStates, currentUpdatedTotalUnreadStates: self.currentUpdatedTotalUnreadStates, currentUpdatedTotalUnreadSummaries: self.currentUpdatedGroupTotalUnreadSummaries, alteredInitialPeerCombinedReadStates: alteredInitialPeerCombinedReadStates, currentPeerMergedOperationLogOperations: self.currentPeerMergedOperationLogOperations, currentTimestampBasedMessageAttributesOperations: self.currentTimestampBasedMessageAttributesOperations, unsentMessageOperations: self.currentUnsentOperations, updatedSynchronizePeerReadStateOperations: self.currentUpdatedSynchronizeReadStateOperations, currentUpdatedGroupSummarySynchronizeOperations: self.currentUpdatedGroupSummarySynchronizeOperations, currentPreferencesOperations: self.currentPreferencesOperations, currentOrderedItemListOperations: self.currentOrderedItemListOperations, currentItemCollectionItemsOperations: self.currentItemCollectionItemsOperations, currentItemCollectionInfosOperations: self.currentItemCollectionInfosOperations, currentUpdatedPeerChatStates: self.currentUpdatedPeerChatStates, currentGlobalTagsOperations: self.currentGlobalTagsOperations, currentLocalTagsOperations: self.currentLocalTagsOperations, updatedMedia: self.currentUpdatedMedia, replaceRemoteContactCount: self.currentReplaceRemoteContactCount, replaceContactPeerIds: self.currentReplacedContactPeerIds, currentPendingMessageActionsOperations: self.currentPendingMessageActionsOperations, currentUpdatedMessageActionsSummaries: self.currentUpdatedMessageActionsSummaries, currentUpdatedMessageTagSummaries: self.currentUpdatedMessageTagSummaries, currentInvalidateMessageTagSummaries: self.currentInvalidateMessageTagSummaries, currentUpdatedPendingPeerNotificationSettings: self.currentUpdatedPendingPeerNotificationSettings, replacedAdditionalChatListItems: self.currentReplacedAdditionalChatListItems, updatedNoticeEntryKeys: self.currentUpdatedNoticeEntryKeys, updatedCacheEntryKeys: self.currentUpdatedCacheEntryKeys, currentUpdatedMasterClientId: currentUpdatedMasterClientId, updatedFailedMessagePeerIds: self.messageHistoryFailedTable.updatedPeerIds, updatedFailedMessageIds: self.messageHistoryFailedTable.updatedMessageIds, updatedGlobalNotificationSettings: self.currentNeedsReindexUnreadCounters, updatedPeerTimeoutAttributes: updatedPeerTimeoutAttributes, updatedMessageThreadPeerIds: updatedMessageThreadPeerIds) + let transaction = PostboxTransaction(currentUpdatedState: self.currentUpdatedState, currentPeerHoleOperations: self.currentPeerHoleOperations, currentOperationsByPeerId: self.currentOperationsByPeerId, chatListOperations: self.currentChatListOperations, currentUpdatedChatListInclusions: self.currentUpdatedChatListInclusions, currentUpdatedPeers: self.currentUpdatedPeers, currentUpdatedPeerNotificationSettings: self.currentUpdatedPeerNotificationSettings, currentUpdatedPeerNotificationBehaviorTimestamps: self.currentUpdatedPeerNotificationBehaviorTimestamps, currentUpdatedCachedPeerData: self.currentUpdatedCachedPeerData, currentUpdatedPeerPresences: currentUpdatedPeerPresences, currentUpdatedPeerChatListEmbeddedStates: self.currentUpdatedPeerChatListEmbeddedStates, currentUpdatedTotalUnreadStates: self.currentUpdatedTotalUnreadStates, currentUpdatedTotalUnreadSummaries: self.currentUpdatedGroupTotalUnreadSummaries, alteredInitialPeerCombinedReadStates: alteredInitialPeerCombinedReadStates, currentPeerMergedOperationLogOperations: self.currentPeerMergedOperationLogOperations, currentTimestampBasedMessageAttributesOperations: self.currentTimestampBasedMessageAttributesOperations, unsentMessageOperations: self.currentUnsentOperations, updatedSynchronizePeerReadStateOperations: self.currentUpdatedSynchronizeReadStateOperations, currentUpdatedGroupSummarySynchronizeOperations: self.currentUpdatedGroupSummarySynchronizeOperations, currentPreferencesOperations: self.currentPreferencesOperations, currentOrderedItemListOperations: self.currentOrderedItemListOperations, currentItemCollectionItemsOperations: self.currentItemCollectionItemsOperations, currentItemCollectionInfosOperations: self.currentItemCollectionInfosOperations, currentUpdatedPeerChatStates: self.currentUpdatedPeerChatStates, currentGlobalTagsOperations: self.currentGlobalTagsOperations, currentLocalTagsOperations: self.currentLocalTagsOperations, updatedMedia: self.currentUpdatedMedia, replaceRemoteContactCount: self.currentReplaceRemoteContactCount, replaceContactPeerIds: self.currentReplacedContactPeerIds, currentPendingMessageActionsOperations: self.currentPendingMessageActionsOperations, currentUpdatedMessageActionsSummaries: self.currentUpdatedMessageActionsSummaries, currentUpdatedMessageTagSummaries: self.currentUpdatedMessageTagSummaries, currentInvalidateMessageTagSummaries: self.currentInvalidateMessageTagSummaries, currentUpdatedPendingPeerNotificationSettings: self.currentUpdatedPendingPeerNotificationSettings, replacedAdditionalChatListItems: self.currentReplacedAdditionalChatListItems, updatedNoticeEntryKeys: self.currentUpdatedNoticeEntryKeys, updatedCacheEntryKeys: self.currentUpdatedCacheEntryKeys, currentUpdatedMasterClientId: currentUpdatedMasterClientId, updatedFailedMessagePeerIds: self.messageHistoryFailedTable.updatedPeerIds, updatedFailedMessageIds: self.messageHistoryFailedTable.updatedMessageIds, updatedGlobalNotificationSettings: self.currentNeedsReindexUnreadCounters, updatedPeerTimeoutAttributes: updatedPeerTimeoutAttributes, updatedMessageThreadPeerIds: updatedMessageThreadPeerIds, updatedPeerThreadCombinedStates: self.currentUpdatedPeerThreadCombinedStates, updatedPeerThreadsSummaries: Set(alteredInitialPeerThreadsSummaries.keys)) var updatedTransactionState: Int64? var updatedMasterClientId: Int64? if !transaction.isEmpty { @@ -2059,6 +2079,7 @@ final class PostboxImpl { self.currentReplacedAdditionalChatListItems = nil self.currentUpdatedNoticeEntryKeys.removeAll() self.currentUpdatedCacheEntryKeys.removeAll() + self.currentUpdatedPeerThreadCombinedStates.removeAll() self.currentUpdatedMasterClientId = nil self.currentUpdatedPeerNotificationSettings.removeAll() self.currentUpdatedPeerNotificationBehaviorTimestamps.removeAll() @@ -3685,6 +3706,11 @@ final class PostboxImpl { self.peerTimeoutPropertiesTable.remove(peerId: peerId, timestamp: timestamp) } + fileprivate func setPeerThreadCombinedState(peerId: PeerId, state: StoredPeerThreadCombinedState?) { + self.currentUpdatedPeerThreadCombinedStates.insert(peerId) + self.peerThreadCombinedStateTable.set(peerId: peerId, state: state) + } + fileprivate func reindexUnreadCounters(currentTransaction: Transaction) { self.groupMessageStatsTable.removeAll() let _ = CFAbsoluteTimeGetCurrent() diff --git a/submodules/Postbox/Sources/PostboxTransaction.swift b/submodules/Postbox/Sources/PostboxTransaction.swift index 66d90c4aea..8449e0aec3 100644 --- a/submodules/Postbox/Sources/PostboxTransaction.swift +++ b/submodules/Postbox/Sources/PostboxTransaction.swift @@ -45,6 +45,8 @@ final class PostboxTransaction { let updatedGlobalNotificationSettings: Bool let updatedPeerTimeoutAttributes: Bool let updatedMessageThreadPeerIds: Set + let updatedPeerThreadCombinedStates: Set + let updatedPeerThreadsSummaries: Set var isEmpty: Bool { if currentUpdatedState != nil { @@ -179,10 +181,16 @@ final class PostboxTransaction { if !self.updatedMessageThreadPeerIds.isEmpty { return false } + if !self.updatedPeerThreadCombinedStates.isEmpty { + return false + } + if !self.updatedPeerThreadsSummaries.isEmpty { + return false + } return true } - init(currentUpdatedState: PostboxCoding?, currentPeerHoleOperations: [MessageHistoryIndexHoleOperationKey: [MessageHistoryIndexHoleOperation]] = [:], currentOperationsByPeerId: [PeerId: [MessageHistoryOperation]], chatListOperations: [PeerGroupId: [ChatListOperation]], currentUpdatedChatListInclusions: [PeerId: PeerChatListInclusion], currentUpdatedPeers: [PeerId: Peer], currentUpdatedPeerNotificationSettings: [PeerId: (PeerNotificationSettings?, PeerNotificationSettings)], currentUpdatedPeerNotificationBehaviorTimestamps: [PeerId: PeerNotificationSettingsBehaviorTimestamp], currentUpdatedCachedPeerData: [PeerId: CachedPeerData], currentUpdatedPeerPresences: [PeerId: PeerPresence], currentUpdatedPeerChatListEmbeddedStates: Set, currentUpdatedTotalUnreadStates: [PeerGroupId: ChatListTotalUnreadState], currentUpdatedTotalUnreadSummaries: [PeerGroupId: PeerGroupUnreadCountersCombinedSummary], alteredInitialPeerCombinedReadStates: [PeerId: CombinedPeerReadState], currentPeerMergedOperationLogOperations: [PeerMergedOperationLogOperation], currentTimestampBasedMessageAttributesOperations: [TimestampBasedMessageAttributesOperation], unsentMessageOperations: [IntermediateMessageHistoryUnsentOperation], updatedSynchronizePeerReadStateOperations: [PeerId: PeerReadStateSynchronizationOperation?], currentUpdatedGroupSummarySynchronizeOperations: [PeerGroupAndNamespace: Bool], currentPreferencesOperations: [PreferencesOperation], currentOrderedItemListOperations: [Int32: [OrderedItemListOperation]], currentItemCollectionItemsOperations: [ItemCollectionId: [ItemCollectionItemsOperation]], currentItemCollectionInfosOperations: [ItemCollectionInfosOperation], currentUpdatedPeerChatStates: Set, currentGlobalTagsOperations: [GlobalMessageHistoryTagsOperation], currentLocalTagsOperations: [IntermediateMessageHistoryLocalTagsOperation], updatedMedia: [MediaId: Media?], replaceRemoteContactCount: Int32?, replaceContactPeerIds: Set?, currentPendingMessageActionsOperations: [PendingMessageActionsOperation], currentUpdatedMessageActionsSummaries: [PendingMessageActionsSummaryKey: Int32], currentUpdatedMessageTagSummaries: [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], currentInvalidateMessageTagSummaries: [InvalidatedMessageHistoryTagsSummaryEntryOperation], currentUpdatedPendingPeerNotificationSettings: Set, replacedAdditionalChatListItems: [AdditionalChatListItem]?, updatedNoticeEntryKeys: Set, updatedCacheEntryKeys: Set, currentUpdatedMasterClientId: Int64?, updatedFailedMessagePeerIds: Set, updatedFailedMessageIds: Set, updatedGlobalNotificationSettings: Bool, updatedPeerTimeoutAttributes: Bool, updatedMessageThreadPeerIds: Set) { + init(currentUpdatedState: PostboxCoding?, currentPeerHoleOperations: [MessageHistoryIndexHoleOperationKey: [MessageHistoryIndexHoleOperation]] = [:], currentOperationsByPeerId: [PeerId: [MessageHistoryOperation]], chatListOperations: [PeerGroupId: [ChatListOperation]], currentUpdatedChatListInclusions: [PeerId: PeerChatListInclusion], currentUpdatedPeers: [PeerId: Peer], currentUpdatedPeerNotificationSettings: [PeerId: (PeerNotificationSettings?, PeerNotificationSettings)], currentUpdatedPeerNotificationBehaviorTimestamps: [PeerId: PeerNotificationSettingsBehaviorTimestamp], currentUpdatedCachedPeerData: [PeerId: CachedPeerData], currentUpdatedPeerPresences: [PeerId: PeerPresence], currentUpdatedPeerChatListEmbeddedStates: Set, currentUpdatedTotalUnreadStates: [PeerGroupId: ChatListTotalUnreadState], currentUpdatedTotalUnreadSummaries: [PeerGroupId: PeerGroupUnreadCountersCombinedSummary], alteredInitialPeerCombinedReadStates: [PeerId: CombinedPeerReadState], currentPeerMergedOperationLogOperations: [PeerMergedOperationLogOperation], currentTimestampBasedMessageAttributesOperations: [TimestampBasedMessageAttributesOperation], unsentMessageOperations: [IntermediateMessageHistoryUnsentOperation], updatedSynchronizePeerReadStateOperations: [PeerId: PeerReadStateSynchronizationOperation?], currentUpdatedGroupSummarySynchronizeOperations: [PeerGroupAndNamespace: Bool], currentPreferencesOperations: [PreferencesOperation], currentOrderedItemListOperations: [Int32: [OrderedItemListOperation]], currentItemCollectionItemsOperations: [ItemCollectionId: [ItemCollectionItemsOperation]], currentItemCollectionInfosOperations: [ItemCollectionInfosOperation], currentUpdatedPeerChatStates: Set, currentGlobalTagsOperations: [GlobalMessageHistoryTagsOperation], currentLocalTagsOperations: [IntermediateMessageHistoryLocalTagsOperation], updatedMedia: [MediaId: Media?], replaceRemoteContactCount: Int32?, replaceContactPeerIds: Set?, currentPendingMessageActionsOperations: [PendingMessageActionsOperation], currentUpdatedMessageActionsSummaries: [PendingMessageActionsSummaryKey: Int32], currentUpdatedMessageTagSummaries: [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], currentInvalidateMessageTagSummaries: [InvalidatedMessageHistoryTagsSummaryEntryOperation], currentUpdatedPendingPeerNotificationSettings: Set, replacedAdditionalChatListItems: [AdditionalChatListItem]?, updatedNoticeEntryKeys: Set, updatedCacheEntryKeys: Set, currentUpdatedMasterClientId: Int64?, updatedFailedMessagePeerIds: Set, updatedFailedMessageIds: Set, updatedGlobalNotificationSettings: Bool, updatedPeerTimeoutAttributes: Bool, updatedMessageThreadPeerIds: Set, updatedPeerThreadCombinedStates: Set, updatedPeerThreadsSummaries: Set) { self.currentUpdatedState = currentUpdatedState self.currentPeerHoleOperations = currentPeerHoleOperations self.currentOperationsByPeerId = currentOperationsByPeerId @@ -226,5 +234,7 @@ final class PostboxTransaction { self.updatedGlobalNotificationSettings = updatedGlobalNotificationSettings self.updatedPeerTimeoutAttributes = updatedPeerTimeoutAttributes self.updatedMessageThreadPeerIds = updatedMessageThreadPeerIds + self.updatedPeerThreadCombinedStates = updatedPeerThreadCombinedStates + self.updatedPeerThreadsSummaries = updatedPeerThreadsSummaries } } diff --git a/submodules/Postbox/Sources/SeedConfiguration.swift b/submodules/Postbox/Sources/SeedConfiguration.swift index 53561a25ab..3cadb5dfa5 100644 --- a/submodules/Postbox/Sources/SeedConfiguration.swift +++ b/submodules/Postbox/Sources/SeedConfiguration.swift @@ -65,6 +65,7 @@ public final class SeedConfiguration { public let existingGlobalMessageTags: GlobalMessageTags public let peerNamespacesRequiringMessageTextIndex: [PeerId.Namespace] public let peerSummaryCounterTags: (Peer, Bool) -> PeerSummaryCounterTags + public let peerSummaryIsThreadBased: (Peer) -> Bool public let additionalChatListIndexNamespace: MessageId.Namespace? public let messageNamespacesRequiringGroupStatsValidation: Set public let defaultMessageNamespaceReadStates: [MessageId.Namespace: PeerReadState] @@ -87,6 +88,7 @@ public final class SeedConfiguration { existingGlobalMessageTags: GlobalMessageTags, peerNamespacesRequiringMessageTextIndex: [PeerId.Namespace], peerSummaryCounterTags: @escaping (Peer, Bool) -> PeerSummaryCounterTags, + peerSummaryIsThreadBased: @escaping (Peer) -> Bool, additionalChatListIndexNamespace: MessageId.Namespace?, messageNamespacesRequiringGroupStatsValidation: Set, defaultMessageNamespaceReadStates: [MessageId.Namespace: PeerReadState], @@ -104,6 +106,7 @@ public final class SeedConfiguration { self.existingGlobalMessageTags = existingGlobalMessageTags self.peerNamespacesRequiringMessageTextIndex = peerNamespacesRequiringMessageTextIndex self.peerSummaryCounterTags = peerSummaryCounterTags + self.peerSummaryIsThreadBased = peerSummaryIsThreadBased self.additionalChatListIndexNamespace = additionalChatListIndexNamespace self.messageNamespacesRequiringGroupStatsValidation = messageNamespacesRequiringGroupStatsValidation self.defaultMessageNamespaceReadStates = defaultMessageNamespaceReadStates diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index 64cec008cb..f82801fc34 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -219,7 +219,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView var items: [ChatListItem] = [] let interaction = ChatListNodeInteraction(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in - }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in + }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in }, activateChatPreview: { _, _, gesture, _ in gesture?.cancel() }, present: { _ in }) @@ -283,7 +283,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false, - forumThreadTitle: nil + forumTopicData: nil ), editing: false, hasActiveRevealControls: false, diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift index 18f75dbecc..56040d328d 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift @@ -839,7 +839,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate var items: [ChatListItem] = [] let interaction = ChatListNodeInteraction(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in - }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in + }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in }, activateChatPreview: { _, _, gesture, _ in gesture?.cancel() }, present: { _ in @@ -903,7 +903,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false, - forumThreadTitle: nil + forumTopicData: nil ), editing: false, hasActiveRevealControls: false, diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index 8c48f5debe..528efaf3bd 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -363,7 +363,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { var items: [ChatListItem] = [] let interaction = ChatListNodeInteraction(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in - }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in + }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in }, activateChatPreview: { _, _, gesture, _ in gesture?.cancel() }, present: { _ in @@ -426,7 +426,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false, - forumThreadTitle: nil + forumTopicData: nil ), editing: false, hasActiveRevealControls: false, diff --git a/submodules/TelegramCore/Sources/ForumChannels.swift b/submodules/TelegramCore/Sources/ForumChannels.swift index 97704a58e6..32c31d88c0 100644 --- a/submodules/TelegramCore/Sources/ForumChannels.swift +++ b/submodules/TelegramCore/Sources/ForumChannels.swift @@ -55,14 +55,102 @@ public extension EngineMessageHistoryThread { } public struct MessageHistoryThreadData: Codable, Equatable { + private enum CodingKeys: CodingKey { + case creationDate + case isOwnedByMe + case author + case info + case incomingUnreadCount + case maxIncomingReadId + case maxKnownMessageId + case maxOutgoingReadId + case isClosed + case notificationSettings + } + public var creationDate: Int32 + public var isOwnedByMe: Bool public var author: PeerId public var info: EngineMessageHistoryThread.Info public var incomingUnreadCount: Int32 public var maxIncomingReadId: Int32 public var maxKnownMessageId: Int32 public var maxOutgoingReadId: Int32 + public var isClosed: Bool public var notificationSettings: TelegramPeerNotificationSettings + + public init( + creationDate: Int32, + isOwnedByMe: Bool, + author: PeerId, + info: EngineMessageHistoryThread.Info, + incomingUnreadCount: Int32, + maxIncomingReadId: Int32, + maxKnownMessageId: Int32, + maxOutgoingReadId: Int32, + isClosed: Bool, + notificationSettings: TelegramPeerNotificationSettings + ) { + self.creationDate = creationDate + self.isOwnedByMe = isOwnedByMe + self.author = author + self.info = info + self.incomingUnreadCount = incomingUnreadCount + self.maxIncomingReadId = maxIncomingReadId + self.maxKnownMessageId = maxKnownMessageId + self.maxOutgoingReadId = maxOutgoingReadId + self.isClosed = isClosed + self.notificationSettings = notificationSettings + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.creationDate = try container.decode(Int32.self, forKey: .creationDate) + self.isOwnedByMe = try container.decodeIfPresent(Bool.self, forKey: .isOwnedByMe) ?? false + self.author = try container.decode(PeerId.self, forKey: .author) + self.info = try container.decode(EngineMessageHistoryThread.Info.self, forKey: .info) + self.incomingUnreadCount = try container.decode(Int32.self, forKey: .incomingUnreadCount) + self.maxIncomingReadId = try container.decode(Int32.self, forKey: .maxIncomingReadId) + self.maxKnownMessageId = try container.decode(Int32.self, forKey: .maxKnownMessageId) + self.maxOutgoingReadId = try container.decode(Int32.self, forKey: .maxOutgoingReadId) + self.isClosed = try container.decodeIfPresent(Bool.self, forKey: .isClosed) ?? false + self.notificationSettings = try container.decode(TelegramPeerNotificationSettings.self, forKey: .notificationSettings) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(self.creationDate, forKey: .creationDate) + try container.encode(self.isOwnedByMe, forKey: .isOwnedByMe) + try container.encode(self.author, forKey: .author) + try container.encode(self.info, forKey: .info) + try container.encode(self.incomingUnreadCount, forKey: .incomingUnreadCount) + try container.encode(self.maxIncomingReadId, forKey: .maxIncomingReadId) + try container.encode(self.maxKnownMessageId, forKey: .maxKnownMessageId) + try container.encode(self.maxOutgoingReadId, forKey: .maxOutgoingReadId) + try container.encode(self.isClosed, forKey: .isClosed) + try container.encode(self.notificationSettings, forKey: .notificationSettings) + } +} + +extension StoredMessageHistoryThreadInfo { + init?(_ data: MessageHistoryThreadData) { + guard let entry = CodableEntry(data) else { + return nil + } + var mutedUntil: Int32? + switch data.notificationSettings.muteState { + case let .muted(until): + mutedUntil = until + case .unmuted, .default: + break + } + self.init(data: entry, summary: Summary( + totalUnreadCount: data.incomingUnreadCount, + mutedUntil: mutedUntil + )) + } } struct StoreMessageHistoryThreadData { @@ -165,13 +253,13 @@ func _internal_editForumChannelTopic(account: Account, peerId: PeerId, threadId: account.stateManager.addUpdates(result) return account.postbox.transaction { transaction -> Void in - if let initialData = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.get(MessageHistoryThreadData.self) { + if let initialData = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self) { var data = initialData data.info = EngineMessageHistoryThread.Info(title: title, icon: iconFileId, iconColor: data.info.iconColor) if data != initialData { - if let entry = CodableEntry(data) { + if let entry = StoredMessageHistoryThreadInfo(data) { transaction.setMessageHistoryThreadInfo(peerId: peerId, threadId: threadId, info: entry) } } @@ -268,9 +356,10 @@ func _internal_loadMessageHistoryThreads(account: Account, peerId: PeerId) -> Si for topic in topics { switch topic { - case let .forumTopic(_, id, date, title, iconColor, iconEmojiId, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, fromId, notifySettings): + case let .forumTopic(flags, id, date, title, iconColor, iconEmojiId, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, fromId, notifySettings): let data = MessageHistoryThreadData( creationDate: date, + isOwnedByMe: (flags & (1 << 1)) != 0, author: fromId.peerId, info: EngineMessageHistoryThread.Info( title: title, @@ -281,9 +370,10 @@ func _internal_loadMessageHistoryThreads(account: Account, peerId: PeerId) -> Si maxIncomingReadId: readInboxMaxId, maxKnownMessageId: topMessage, maxOutgoingReadId: readOutboxMaxId, + isClosed: (flags & (1 << 2)) != 0, notificationSettings: TelegramPeerNotificationSettings(apiSettings: notifySettings) ) - guard let info = CodableEntry(data) else { + guard let info = StoredMessageHistoryThreadInfo(data) else { continue } transaction.setMessageHistoryThreadInfo(peerId: peerId, threadId: Int64(id), info: info) diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index 77d49ca317..eede6b1d05 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -1694,12 +1694,13 @@ func resolveForumThreads(postbox: Postbox, network: Network, state: AccountMutab for topic in topics { switch topic { - case let .forumTopic(_, id, date, title, iconColor, iconEmojiId, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, fromId, notifySettings): + case let .forumTopic(flags, id, date, title, iconColor, iconEmojiId, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, fromId, notifySettings): state.operations.append(.ResetForumTopic( topicId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: id), data: StoreMessageHistoryThreadData( data: MessageHistoryThreadData( creationDate: date, + isOwnedByMe: (flags & (1 << 1)) != 0, author: fromId.peerId, info: EngineMessageHistoryThread.Info( title: title, @@ -1710,6 +1711,7 @@ func resolveForumThreads(postbox: Postbox, network: Network, state: AccountMutab maxIncomingReadId: readInboxMaxId, maxKnownMessageId: topMessage, maxOutgoingReadId: readOutboxMaxId, + isClosed: (flags & (1 << 2)) != 0, notificationSettings: TelegramPeerNotificationSettings(apiSettings: notifySettings) ), topMessageId: topMessage, @@ -1790,9 +1792,10 @@ func resolveForumThreads(postbox: Postbox, network: Network, ids: [MessageId]) - for topic in topics { switch topic { - case let .forumTopic(_, id, date, title, iconColor, iconEmojiId, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, fromId, notifySettings): + case let .forumTopic(flags, id, date, title, iconColor, iconEmojiId, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, fromId, notifySettings): let data = MessageHistoryThreadData( creationDate: date, + isOwnedByMe: (flags & (1 << 1)) != 0, author: fromId.peerId, info: EngineMessageHistoryThread.Info( title: title, @@ -1803,9 +1806,10 @@ func resolveForumThreads(postbox: Postbox, network: Network, ids: [MessageId]) - maxIncomingReadId: readInboxMaxId, maxKnownMessageId: topMessage, maxOutgoingReadId: readOutboxMaxId, + isClosed: (flags & (1 << 2)) != 0, notificationSettings: TelegramPeerNotificationSettings(apiSettings: notifySettings) ) - if let entry = CodableEntry(data) { + if let entry = StoredMessageHistoryThreadInfo(data) { transaction.setMessageHistoryThreadInfo(peerId: peerId, threadId: Int64(id), info: entry) } @@ -1906,10 +1910,11 @@ func resolveForumThreads(postbox: Postbox, network: Network, fetchedChatList: Fe for topic in topics { switch topic { - case let .forumTopic(_, id, date, title, iconColor, iconEmojiId, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, fromId, notifySettings): + case let .forumTopic(flags, id, date, title, iconColor, iconEmojiId, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, fromId, notifySettings): fetchedChatList.threadInfos[MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: id)] = StoreMessageHistoryThreadData( data: MessageHistoryThreadData( creationDate: date, + isOwnedByMe: (flags & (1 << 1)) != 0, author: fromId.peerId, info: EngineMessageHistoryThread.Info( title: title, @@ -1920,6 +1925,7 @@ func resolveForumThreads(postbox: Postbox, network: Network, fetchedChatList: Fe maxIncomingReadId: readInboxMaxId, maxKnownMessageId: topMessage, maxOutgoingReadId: readOutboxMaxId, + isClosed: (flags & (1 << 2)) != 0, notificationSettings: TelegramPeerNotificationSettings(apiSettings: notifySettings) ), topMessageId: topMessage, @@ -2994,7 +3000,7 @@ func replayFinalState( if let action = media as? TelegramMediaAction { switch action.action { case let .topicEdited(components): - if let initialData = transaction.getMessageHistoryThreadInfo(peerId: id.peerId, threadId: threadId)?.get(MessageHistoryThreadData.self) { + if let initialData = transaction.getMessageHistoryThreadInfo(peerId: id.peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self) { var data = initialData for component in components { @@ -3009,7 +3015,7 @@ func replayFinalState( } if data != initialData { - if let entry = CodableEntry(data) { + if let entry = StoredMessageHistoryThreadInfo(data) { transaction.setMessageHistoryThreadInfo(peerId: id.peerId, threadId: threadId, info: entry) } } @@ -3028,12 +3034,12 @@ func replayFinalState( } if message.flags.contains(.Incoming) { - if var data = transaction.getMessageHistoryThreadInfo(peerId: id.peerId, threadId: threadId)?.get(MessageHistoryThreadData.self) { + if var data = transaction.getMessageHistoryThreadInfo(peerId: id.peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self) { if id.id >= data.maxKnownMessageId { data.maxKnownMessageId = id.id data.incomingUnreadCount += 1 - if let entry = CodableEntry(data) { + if let entry = StoredMessageHistoryThreadInfo(data) { transaction.setMessageHistoryThreadInfo(peerId: id.peerId, threadId: threadId, info: entry) } } @@ -3297,7 +3303,7 @@ func replayFinalState( updatedIncomingThreadReadStates[threadMessageId] = readMaxId } if let channel = transaction.getPeer(threadMessageId.peerId) as? TelegramChannel, case .group = channel.info, channel.flags.contains(.isForum) { - if var data = transaction.getMessageHistoryThreadInfo(peerId: threadMessageId.peerId, threadId: Int64(threadMessageId.id))?.get(MessageHistoryThreadData.self) { + if var data = transaction.getMessageHistoryThreadInfo(peerId: threadMessageId.peerId, threadId: Int64(threadMessageId.id))?.data.get(MessageHistoryThreadData.self) { if readMaxId > data.maxIncomingReadId { if let toIndex = transaction.getMessage(MessageId(peerId: threadMessageId.peerId, namespace: threadMessageId.namespace, id: readMaxId))?.index { if let count = transaction.getThreadMessageCount(peerId: threadMessageId.peerId, threadId: Int64(threadMessageId.id), namespace: threadMessageId.namespace, fromIdExclusive: data.maxIncomingReadId, toIndex: toIndex) { @@ -3307,7 +3313,7 @@ func replayFinalState( data.maxKnownMessageId = max(data.maxKnownMessageId, readMaxId) - if let entry = CodableEntry(data) { + if let entry = StoredMessageHistoryThreadInfo(data) { transaction.setMessageHistoryThreadInfo(peerId: threadMessageId.peerId, threadId: Int64(threadMessageId.id), info: entry) } } @@ -3338,11 +3344,11 @@ func replayFinalState( updatedOutgoingThreadReadStates[threadMessageId] = readMaxId } if let channel = transaction.getPeer(threadMessageId.peerId) as? TelegramChannel, case .group = channel.info, channel.flags.contains(.isForum) { - if var data = transaction.getMessageHistoryThreadInfo(peerId: threadMessageId.peerId, threadId: Int64(threadMessageId.id))?.get(MessageHistoryThreadData.self) { + if var data = transaction.getMessageHistoryThreadInfo(peerId: threadMessageId.peerId, threadId: Int64(threadMessageId.id))?.data.get(MessageHistoryThreadData.self) { if readMaxId >= data.maxOutgoingReadId { data.maxOutgoingReadId = readMaxId - if let entry = CodableEntry(data) { + if let entry = StoredMessageHistoryThreadInfo(data) { transaction.setMessageHistoryThreadInfo(peerId: threadMessageId.peerId, threadId: Int64(threadMessageId.id), info: entry) } } @@ -3954,7 +3960,7 @@ func replayFinalState( case let .ResetForumTopic(topicId, data, pts): if finalState.state.resetForumTopicLists[topicId.peerId] == nil { let _ = pts - if let entry = CodableEntry(data.data) { + if let entry = StoredMessageHistoryThreadInfo(data.data) { transaction.setMessageHistoryThreadInfo(peerId: topicId.peerId, threadId: Int64(topicId.id), info: entry) } else { assertionFailure() diff --git a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift index faa73cc986..11dd59fef6 100644 --- a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift +++ b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift @@ -1520,14 +1520,14 @@ public final class AccountViewTracker { signal = combineLatest(signal, postbox.combinedView(keys: [viewKey])) |> map { view, additionalViews -> (MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?) in var view = view - if let threadInfo = additionalViews.views[viewKey] as? MessageHistoryThreadInfoView, let data = threadInfo.info?.get(MessageHistoryThreadData.self) { + if let threadInfo = additionalViews.views[viewKey] as? MessageHistoryThreadInfoView, let data = threadInfo.info?.data.get(MessageHistoryThreadData.self) { let readState = CombinedPeerReadState(states: [(Namespaces.Message.Cloud, .idBased(maxIncomingReadId: data.maxIncomingReadId, maxOutgoingReadId: data.maxOutgoingReadId, maxKnownId: data.maxKnownMessageId, count: data.incomingUnreadCount, markedUnread: false))]) let fixed = fixedReadStates.modify { current in if let current = current { return current } else { - return view.0.fixedReadStates ?? .peer([peerId: readState]) + return /*view.0.fixedReadStates ?? */.peer([peerId: readState]) } } @@ -1701,8 +1701,8 @@ public final class AccountViewTracker { if let account = self.account { let signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> if let peerId = chatLocation.peerId, let threadId = chatLocation.threadId, tagMask == nil { - return account.postbox.transaction { transaction -> MessageHistoryThreadData? in - return transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.get(MessageHistoryThreadData.self) + signal = account.postbox.transaction { transaction -> MessageHistoryThreadData? in + return transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self) } |> mapToSignal { threadInfo -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> in if let threadInfo = threadInfo { diff --git a/submodules/TelegramCore/Sources/State/Holes.swift b/submodules/TelegramCore/Sources/State/Holes.swift index 39c5f1229d..fee47cf0b0 100644 --- a/submodules/TelegramCore/Sources/State/Holes.swift +++ b/submodules/TelegramCore/Sources/State/Holes.swift @@ -751,7 +751,7 @@ func fetchChatListHole(postbox: Postbox, network: Network, accountPeerId: PeerId }) for (threadMessageId, data) in fetchedChats.threadInfos { - if let entry = CodableEntry(data.data) { + if let entry = StoredMessageHistoryThreadInfo(data.data) { transaction.setMessageHistoryThreadInfo(peerId: threadMessageId.peerId, threadId: Int64(threadMessageId.id), info: entry) } transaction.replaceMessageTagSummary(peerId: threadMessageId.peerId, threadId: Int64(threadMessageId.id), tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud, count: data.unreadMentionCount, maxId: data.topMessageId) diff --git a/submodules/TelegramCore/Sources/State/ManagedPendingPeerNotificationSettings.swift b/submodules/TelegramCore/Sources/State/ManagedPendingPeerNotificationSettings.swift index 2ada61fd82..2f817a367c 100644 --- a/submodules/TelegramCore/Sources/State/ManagedPendingPeerNotificationSettings.swift +++ b/submodules/TelegramCore/Sources/State/ManagedPendingPeerNotificationSettings.swift @@ -98,7 +98,7 @@ func pushPeerNotificationSettings(postbox: Postbox, network: Network, peerId: Pe } if let threadId = threadId { - if let data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.get(MessageHistoryThreadData.self) { + if let data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self) { let settings = data.notificationSettings let showPreviews: Api.Bool? diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift index f9d808e19a..11ca8c9954 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift @@ -66,15 +66,27 @@ public let telegramPostboxSeedConfiguration: SeedConfiguration = { return .channel case .group: if channel.flags.contains(.isForum) { - return [] + return .group + } else { + return .group } - return .group } } else { assertionFailure() return .nonContact } }, + peerSummaryIsThreadBased: { peer in + if let channel = peer as? TelegramChannel { + if channel.flags.contains(.isForum) { + return true + } else { + return false + } + } else { + return false + } + }, additionalChatListIndexNamespace: Namespaces.Message.Cloud, messageNamespacesRequiringGroupStatsValidation: [Namespaces.Message.Cloud], defaultMessageNamespaceReadStates: [Namespaces.Message.Local: .idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false)], diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift index c0cbdc19f4..605365f4a4 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift @@ -278,7 +278,7 @@ public extension TelegramEngine.EngineData.Item { guard let view = view as? MessageHistoryThreadInfoView else { preconditionFailure() } - guard let data = view.info?.get(MessageHistoryThreadData.self) else { + guard let data = view.info?.data.get(MessageHistoryThreadData.self) else { return EnginePeer.NotificationSettings(TelegramPeerNotificationSettings.defaultSettings) } return EnginePeer.NotificationSettings(data.notificationSettings) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift index 6560a519bf..8e73e8b8b1 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift @@ -26,6 +26,16 @@ public final class EngineChatList: Equatable { self.entities = entities } } + + public struct ForumTopicData: Equatable { + public var title: String + public var maxOutgoingReadMessageId: EngineMessage.Id + + public init(title: String, maxOutgoingReadMessageId: EngineMessage.Id) { + self.title = title + self.maxOutgoingReadMessageId = maxOutgoingReadMessageId + } + } public final class Item: Equatable { public enum Id: Hashable { @@ -77,7 +87,7 @@ public final class EngineChatList: Equatable { public let presence: EnginePeer.Presence? public let hasUnseenMentions: Bool public let hasUnseenReactions: Bool - public let forumTopicTitle: String? + public let forumTopicData: ForumTopicData? public let hasFailed: Bool public let isContact: Bool @@ -93,7 +103,7 @@ public final class EngineChatList: Equatable { presence: EnginePeer.Presence?, hasUnseenMentions: Bool, hasUnseenReactions: Bool, - forumTopicTitle: String?, + forumTopicData: ForumTopicData?, hasFailed: Bool, isContact: Bool ) { @@ -108,7 +118,7 @@ public final class EngineChatList: Equatable { self.presence = presence self.hasUnseenMentions = hasUnseenMentions self.hasUnseenReactions = hasUnseenReactions - self.forumTopicTitle = forumTopicTitle + self.forumTopicData = forumTopicData self.hasFailed = hasFailed self.isContact = isContact } @@ -147,7 +157,7 @@ public final class EngineChatList: Equatable { if lhs.hasUnseenReactions != rhs.hasUnseenReactions { return false } - if lhs.forumTopicTitle != rhs.forumTopicTitle { + if lhs.forumTopicData != rhs.forumTopicData { return false } if lhs.hasFailed != rhs.hasFailed { @@ -383,16 +393,12 @@ extension EngineChatList.Item { hasUnseenReactions = (info.tagSummaryCount ?? 0) != 0// > (info.actionsSummaryCount ?? 0) } - var forumTopicTitle: String? - if let forumTopicData = forumTopicData?.get(MessageHistoryThreadData.self) { - forumTopicTitle = forumTopicData.info.title + var forumTopicDataValue: EngineChatList.ForumTopicData? + if let forumTopicData = forumTopicData?.data.get(MessageHistoryThreadData.self) { + forumTopicDataValue = EngineChatList.ForumTopicData(title: forumTopicData.info.title, maxOutgoingReadMessageId: MessageId(peerId: index.messageIndex.id.peerId, namespace: Namespaces.Message.Cloud, id: forumTopicData.maxOutgoingReadId)) } - var readCounters = readState.flatMap(EnginePeerReadCounters.init) - - if let channel = renderedPeer.peer as? TelegramChannel, channel.flags.contains(.isForum) { - readCounters = nil - } + let readCounters = readState.flatMap(EnginePeerReadCounters.init) self.init( id: .chatList(index.messageIndex.id.peerId), @@ -406,7 +412,7 @@ extension EngineChatList.Item { presence: presence.flatMap(EnginePeer.Presence.init), hasUnseenMentions: hasUnseenMentions, hasUnseenReactions: hasUnseenReactions, - forumTopicTitle: forumTopicTitle, + forumTopicData: forumTopicDataValue, hasFailed: hasFailed, isContact: isContact ) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift index cbeabea069..13dbfb37b9 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift @@ -337,7 +337,7 @@ private class ReplyThreadHistoryContextImpl { let account = self.account let _ = (self.account.postbox.transaction { transaction -> (Api.InputPeer?, MessageId?, Int?) in - if var data = transaction.getMessageHistoryThreadInfo(peerId: messageId.peerId, threadId: Int64(messageId.id))?.get(MessageHistoryThreadData.self) { + if var data = transaction.getMessageHistoryThreadInfo(peerId: messageId.peerId, threadId: Int64(messageId.id))?.data.get(MessageHistoryThreadData.self) { if messageIndex.id.id >= data.maxIncomingReadId { if let count = transaction.getThreadMessageCount(peerId: messageId.peerId, threadId: Int64(messageId.id), namespace: messageId.namespace, fromIdExclusive: data.maxIncomingReadId, toIndex: messageIndex) { data.incomingUnreadCount = max(0, data.incomingUnreadCount - Int32(count)) @@ -346,7 +346,7 @@ private class ReplyThreadHistoryContextImpl { data.maxKnownMessageId = max(data.maxKnownMessageId, messageIndex.id.id) - if let entry = CodableEntry(data) { + if let entry = StoredMessageHistoryThreadInfo(data) { transaction.setMessageHistoryThreadInfo(peerId: messageId.peerId, threadId: Int64(messageId.id), info: entry) } } @@ -598,7 +598,7 @@ func _internal_fetchChannelReplyThreadMessage(account: Account, messageId: Messa let replyInfo = Promise() replyInfo.set(account.postbox.transaction { transaction -> MessageHistoryThreadData? in - return transaction.getMessageHistoryThreadInfo(peerId: messageId.peerId, threadId: Int64(messageId.id))?.get(MessageHistoryThreadData.self) + return transaction.getMessageHistoryThreadInfo(peerId: messageId.peerId, threadId: Int64(messageId.id))?.data.get(MessageHistoryThreadData.self) }) let remoteDiscussionMessageSignal: Signal = account.network.request(Api.functions.messages.getDiscussionMessage(peer: inputPeer, msgId: messageId.id)) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChangePeerNotificationSettings.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChangePeerNotificationSettings.swift index 1e5b574517..69fb6cfd56 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChangePeerNotificationSettings.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChangePeerNotificationSettings.swift @@ -15,7 +15,7 @@ func _internal_togglePeerMuted(account: Account, peerId: PeerId, threadId: Int64 } if let threadId = threadId { - if var data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.get(MessageHistoryThreadData.self) { + if var data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self) { var updatedSettings: TelegramPeerNotificationSettings switch data.notificationSettings.muteState { case .default: @@ -27,7 +27,7 @@ func _internal_togglePeerMuted(account: Account, peerId: PeerId, threadId: Int64 } data.notificationSettings = updatedSettings - if let entry = CodableEntry(data) { + if let entry = StoredMessageHistoryThreadInfo(data) { transaction.setMessageHistoryThreadInfo(peerId: peerId, threadId: threadId, info: entry) //TODO:loc @@ -71,7 +71,7 @@ func _internal_updatePeerMuteSetting(account: Account, peerId: PeerId, threadId: func _internal_updatePeerMuteSetting(account: Account, transaction: Transaction, peerId: PeerId, threadId: Int64?, muteInterval: Int32?) { if let peer = transaction.getPeer(peerId) { if let threadId = threadId { - if var data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.get(MessageHistoryThreadData.self) { + if var data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self) { let previousSettings: TelegramPeerNotificationSettings = data.notificationSettings let muteState: PeerMuteState @@ -93,7 +93,7 @@ func _internal_updatePeerMuteSetting(account: Account, transaction: Transaction, data.notificationSettings = previousSettings.withUpdatedMuteState(muteState) - if let entry = CodableEntry(data) { + if let entry = StoredMessageHistoryThreadInfo(data) { transaction.setMessageHistoryThreadInfo(peerId: peerId, threadId: threadId, info: entry) } @@ -146,12 +146,12 @@ func _internal_updatePeerDisplayPreviewsSetting(account: Account, peerId: PeerId func _internal_updatePeerDisplayPreviewsSetting(account: Account, transaction: Transaction, peerId: PeerId, threadId: Int64?, displayPreviews: PeerNotificationDisplayPreviews) { if let peer = transaction.getPeer(peerId) { if let threadId = threadId { - if var data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.get(MessageHistoryThreadData.self) { + if var data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self) { let previousSettings: TelegramPeerNotificationSettings = data.notificationSettings data.notificationSettings = previousSettings.withUpdatedDisplayPreviews(displayPreviews) - if let entry = CodableEntry(data) { + if let entry = StoredMessageHistoryThreadInfo(data) { transaction.setMessageHistoryThreadInfo(peerId: peerId, threadId: threadId, info: entry) } @@ -187,12 +187,12 @@ func _internal_updatePeerNotificationSoundInteractive(account: Account, peerId: func _internal_updatePeerNotificationSoundInteractive(account: Account, transaction: Transaction, peerId: PeerId, threadId: Int64?, sound: PeerMessageSound) { if let peer = transaction.getPeer(peerId) { if let threadId = threadId { - if var data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.get(MessageHistoryThreadData.self) { + if var data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self) { let previousSettings: TelegramPeerNotificationSettings = data.notificationSettings data.notificationSettings = previousSettings.withUpdatedMessageSound(sound) - if let entry = CodableEntry(data) { + if let entry = StoredMessageHistoryThreadInfo(data) { transaction.setMessageHistoryThreadInfo(peerId: peerId, threadId: threadId, info: entry) } diff --git a/submodules/TelegramUI/Sources/AccountContext.swift b/submodules/TelegramUI/Sources/AccountContext.swift index c12ef414e8..76eed4af3d 100644 --- a/submodules/TelegramUI/Sources/AccountContext.swift +++ b/submodules/TelegramUI/Sources/AccountContext.swift @@ -365,7 +365,7 @@ public final class AccountContextImpl: AccountContext { let viewKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: data.messageId.peerId, threadId: Int64(data.messageId.id)) return self.account.postbox.combinedView(keys: [viewKey]) |> map { views -> MessageId? in - if let threadInfo = views.views[viewKey] as? MessageHistoryThreadInfoView, let data = threadInfo.info?.get(MessageHistoryThreadData.self) { + if let threadInfo = views.views[viewKey] as? MessageHistoryThreadInfoView, let data = threadInfo.info?.data.get(MessageHistoryThreadData.self) { return MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: data.maxOutgoingReadId) } else { return nil @@ -402,7 +402,7 @@ public final class AccountContextImpl: AccountContext { let viewKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: data.messageId.peerId, threadId: Int64(data.messageId.id)) return self.account.postbox.combinedView(keys: [viewKey]) |> map { views -> Int in - if let threadInfo = views.views[viewKey] as? MessageHistoryThreadInfoView, let data = threadInfo.info?.get(MessageHistoryThreadData.self) { + if let threadInfo = views.views[viewKey] as? MessageHistoryThreadInfoView, let data = threadInfo.info?.data.get(MessageHistoryThreadData.self) { return Int(data.incomingUnreadCount) } else { return 0 diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 9b88b87970..81384f75a8 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -4753,7 +4753,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let view = views.views[viewKey] as? MessageHistoryThreadInfoView else { return (message, nil) } - return (message, view.info?.get(MessageHistoryThreadData.self)) + return (message, view.info?.data.get(MessageHistoryThreadData.self)) } } diff --git a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift index d377a652ac..49cb44b1b6 100644 --- a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift @@ -100,7 +100,7 @@ private enum ChatListSearchEntry: Comparable, Identifiable { ignoreUnreadBadge: true, displayAsMessage: true, hasFailedMessages: false, - forumThreadTitle: nil + forumTopicData: nil ), editing: false, hasActiveRevealControls: false, @@ -232,6 +232,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in + }, setPeerThreadStopped: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in }, toggleArchivedFolderHiddenByDefault: { diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift index 28c6916643..a169440598 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift @@ -824,7 +824,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen guard let view = views.views[viewKey] as? MessageHistoryThreadInfoView else { return nil } - return view.info?.get(MessageHistoryThreadData.self) + return view.info?.data.get(MessageHistoryThreadData.self) } } else { threadData = .single(nil) diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index 642feed5ab..21b1823900 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -495,7 +495,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { self.containerNode.isGestureEnabled = !isSettings - if let video = videoRepresentations.last, let peerReference = PeerReference(peer) { + if threadInfo == nil, let video = videoRepresentations.last, let peerReference = PeerReference(peer) { let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])])) let videoContent = NativeVideoContent(id: .profileVideo(videoId, nil), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.representation.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.representation.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, captureProtected: peer.isCopyProtectionEnabled) if videoContent.id != self.videoContent?.id { @@ -799,7 +799,7 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode { } } - if let video = videoRepresentations.last, let peerReference = PeerReference(peer) { + if threadData == nil, let video = videoRepresentations.last, let peerReference = PeerReference(peer) { let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])])) let videoContent = NativeVideoContent(id: .profileVideo(id, nil), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.representation.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.representation.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, captureProtected: peer.isCopyProtectionEnabled) if videoContent.id != self.videoContent?.id {