From 0bdb130775afad89bb80fa01aa243f15e7fe66fe Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 21 Jan 2022 20:30:43 +0400 Subject: [PATCH] no message --- .../Sources/ChatListController.swift | 6 +- .../Sources/ChatListControllerNode.swift | 2 +- .../Sources/ChatListSearchListPaneNode.swift | 8 +- .../Sources/Node/ChatListItem.swift | 29 ++-- .../Sources/Node/ChatListNode.swift | 32 ++-- .../Sources/Node/ChatListNodeEntries.swift | 21 ++- .../ContextUI/Sources/ContextController.swift | 10 +- ...tControllerExtractedPresentationNode.swift | 4 +- submodules/Postbox/Sources/ChatListView.swift | 44 +++-- .../Postbox/Sources/ChatListViewState.swift | 80 ++++++---- .../Postbox/Sources/MessageHistoryTable.swift | 5 +- .../MessageHistoryTagsSummaryTable.swift | 4 +- .../Sources/MessageHistoryTagsTable.swift | 4 +- .../Sources/ReactionContextNode.swift | 16 +- .../TextSizeSelectionController.swift | 1 + .../ThemeAccentColorControllerNode.swift | 1 + .../Themes/ThemePreviewControllerNode.swift | 1 + submodules/TelegramApi/Sources/Api0.swift | 14 +- submodules/TelegramApi/Sources/Api1.swift | 84 ++++++++-- submodules/TelegramApi/Sources/Api2.swift | 94 ++++++----- submodules/TelegramApi/Sources/Api4.swift | 52 ++++++ .../Sources/Account/Account.swift | 1 + .../Account/AccountIntermediateState.swift | 6 +- .../Sources/Account/AccountManager.swift | 1 + .../ApiUtils/ReactionsMessageAttribute.swift | 14 +- .../ApiUtils/StoreMessage_Telegram.swift | 6 + .../State/AccountStateManagementUtils.swift | 52 +++--- .../Sources/State/AccountViewTracker.swift | 126 +++++++++++++-- .../Sources/State/FetchChatList.swift | 14 +- .../TelegramCore/Sources/State/Holes.swift | 51 ++++++ ...anagedConsumePersonalMessagesActions.swift | 151 +++++++++++++++++- ...kAllUnseenPersonalMessagesOperations.swift | 28 ++++ ...agedSynchronizePinnedChatsOperations.swift | 2 +- .../Sources/State/MessageReactions.swift | 34 ++-- .../Sources/State/Serialization.swift | 2 +- .../State/SynchronizePeerReadState.swift | 2 +- .../Sources/State/UpdatesApiUtils.swift | 2 +- ...yncCore_ConsumePersonalMessageAction.swift | 19 +++ .../SyncCore/SyncCore_Namespaces.swift | 4 +- .../SyncCore_ReactionsMessageAttribute.swift | 43 ++++- ...yncCore_StandaloneAccountTransaction.swift | 2 +- .../TelegramEngine/Messages/ChatList.swift | 25 ++- ...EarliestUnseenPersonalMentionMessage.swift | 64 ++++++++ ...InstallInteractiveReadMessagesAction.swift | 34 +++- ...essageContentAsConsumedInteractively.swift | 34 +++- .../Messages/TelegramEngineMessages.swift | 4 + .../Peers/ChatListFiltering.swift | 3 +- .../Resources/PresentationResourceKey.swift | 1 + .../Resources/PresentationResourcesChat.swift | 17 ++ .../TelegramUI/Sources/ChatController.swift | 30 +++- .../Sources/ChatHistoryListNode.swift | 73 +++++++-- .../ChatHistoryNavigationButtonNode.swift | 5 + .../ChatHistoryNavigationButtons.swift | 71 +++++++- ...hatMessageThrottledProcessingManager.swift | 2 +- .../ChatSearchResultsContollerNode.swift | 3 +- 55 files changed, 1161 insertions(+), 277 deletions(-) diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 25daec2156..814bf5a941 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -829,7 +829,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } var joined = false - if case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _) = item.content, let message = messages.first { + if case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _) = item.content, let message = messages.first { for media in message.media { if let action = media as? TelegramMediaAction, action.action == .peerJoined { joined = true @@ -843,7 +843,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController chatListController.navigationPresentation = .master let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatListController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: archiveContextMenuItems(context: strongSelf.context, groupId: groupId._asGroup(), chatListController: strongSelf) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) strongSelf.presentInGlobalOverlay(contextController) - case let .peer(_, peer, _, _, _, _, _, _, promoInfo, _, _, _): + case let .peer(_, peer, _, _, _, _, _, _, _, promoInfo, _, _, _): let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(peer.peerId), subject: nil, botStart: nil, mode: .standard(previewing: true)) chatController.canReadHistory.set(false) let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: chatContextMenuItems(context: strongSelf.context, peerId: peer.peerId, promoInfo: promoInfo, source: .chatList(filter: strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter), chatListController: strongSelf, joined: joined) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) @@ -1783,7 +1783,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController public private(set) var isSearchActive: Bool = false public func activateSearch(filter: ChatListSearchFilter = .chats, query: String? = nil) { if self.displayNavigationBar { - let _ = (combineLatest(self.chatListDisplayNode.containerNode.currentItemNode.contentsReady |> take(1), self.context.account.postbox.tailChatListView(groupId: .root, count: 16, summaryComponents: ChatListEntrySummaryComponents(tagSummary: nil, actionsSummary: nil)) |> take(1)) + let _ = (combineLatest(self.chatListDisplayNode.containerNode.currentItemNode.contentsReady |> take(1), self.context.account.postbox.tailChatListView(groupId: .root, count: 16, summaryComponents: ChatListEntrySummaryComponents(components: [:])) |> take(1)) |> deliverOnMainQueue).start(next: { [weak self] _, chatListView in guard let strongSelf = self else { return diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index ebe749e643..32410e70a6 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -210,7 +210,7 @@ private final class ChatListShimmerNode: ASDisplayNode { ) let readState = EnginePeerReadCounters() - return ChatListItem(presentationData: chatListPresentationData, context: context, peerGroupId: .root, filterData: nil, index: EngineChatList.Item.Index(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), combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) + return ChatListItem(presentationData: chatListPresentationData, context: context, peerGroupId: .root, filterData: nil, index: EngineChatList.Item.Index(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), combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) } var itemNodes: [ChatListItemNode] = [] diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 245220e46c..63f14b6aaa 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -492,7 +492,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { if let tagMask = tagMask, tagMask != .photoOrVideo { 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(peer.peerId), interaction: listInteraction, message: message._asMessage(), selection: selection, displayHeader: enableHeaders && !displayCustomHeader, customHeader: nil, hintIsLink: tagMask == .webPage, isGlobalSearchResult: true) } else { - return ChatListItem(presentationData: presentationData, context: context, peerGroupId: .root, filterData: nil, index: EngineChatList.Item.Index(pinningIndex: nil, messageIndex: message.index), content: .peer(messages: [message], peer: peer, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: true, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: tagMask == nil ? header : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) + return ChatListItem(presentationData: presentationData, context: context, peerGroupId: .root, filterData: nil, index: EngineChatList.Item.Index(pinningIndex: nil, messageIndex: message.index), content: .peer(messages: [message], peer: peer, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: true, displayAsMessage: false, hasFailedMessages: false), 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: { @@ -1237,7 +1237,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { return } switch item.content { - case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _): + case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _, _): if let peer = peer.peer, let message = messages.first { peerContextAction(peer, .search(message.id), node, gesture) } @@ -2171,7 +2171,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { bounds = selectedItemNode.bounds } switch item.content { - case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _): + case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _, _): return (selectedItemNode.view, bounds, messages.last?.id ?? peer.peerId) case let .groupReference(groupId, _, _, _, _): return (selectedItemNode.view, bounds, groupId) @@ -2358,7 +2358,7 @@ private final class ChatListSearchShimmerNode: ASDisplayNode { associatedMessageIds: [] ) let readState = EnginePeerReadCounters() - return ChatListItem(presentationData: chatListPresentationData, context: context, peerGroupId: .root, filterData: nil, index: EngineChatList.Item.Index(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), combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) + return ChatListItem(presentationData: chatListPresentationData, context: context, peerGroupId: .root, filterData: nil, index: EngineChatList.Item.Index(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), combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), 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 bbf9f243f3..bce85e8aea 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -36,12 +36,12 @@ public enum ChatListItemContent { } } - case peer(messages: [EngineMessage], peer: EngineRenderedPeer, combinedReadState: EnginePeerReadCounters?, isRemovedFromTotalUnreadCount: Bool, presence: EnginePeer.Presence?, hasUnseenMentions: Bool, draftState: DraftState?, inputActivities: [(EnginePeer, PeerInputActivity)]?, promoInfo: ChatListNodeEntryPromoInfo?, ignoreUnreadBadge: Bool, displayAsMessage: Bool, hasFailedMessages: Bool) + case peer(messages: [EngineMessage], peer: EngineRenderedPeer, combinedReadState: EnginePeerReadCounters?, isRemovedFromTotalUnreadCount: Bool, presence: EnginePeer.Presence?, hasUnseenMentions: Bool, hasUnseenReactions: Bool, draftState: DraftState?, inputActivities: [(EnginePeer, PeerInputActivity)]?, promoInfo: ChatListNodeEntryPromoInfo?, ignoreUnreadBadge: Bool, displayAsMessage: Bool, hasFailedMessages: Bool) case groupReference(groupId: EngineChatList.Group, peers: [EngineChatList.GroupItem.Item], message: EngineMessage?, unreadCount: Int, hiddenByDefault: Bool) public var chatLocation: ChatLocation? { switch self { - case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _): + case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _): return .peer(peer.peerId) case .groupReference: return nil @@ -140,7 +140,7 @@ public class ChatListItem: ListViewItem, ChatListSearchItemNeighbour { public func selected(listView: ListView) { switch self.content { - case let .peer(messages, peer, _, _, _, _, _, _, promoInfo, _, _, _): + case let .peer(messages, peer, _, _, _, _, _, _, _, promoInfo, _, _, _): if let message = messages.last, let peer = peer.peer { self.interaction.messageSelected(peer, message, promoInfo) } else if let peer = peer.peer { @@ -501,7 +501,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { result += "\n\(item.presentationData.strings.VoiceOver_Chat_UnreadMessages(Int32(allCount)))" } return result - case let .peer(_, peer, combinedReadState, _, _, _, _, _, _, _, _, _): + case let .peer(_, peer, combinedReadState, _, _, _, _, _, _, _, _, _, _): guard let chatMainPeer = peer.chatMainPeer else { return nil } @@ -561,7 +561,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } else { return item.presentationData.strings.VoiceOver_ChatList_MessageEmpty } - case let .peer(messages, peer, combinedReadState, _, _, _, _, _, _, _, _, _): + case let .peer(messages, peer, combinedReadState, _, _, _, _, _, _, _, _, _, _): if let message = messages.last { var result = "" if message.flags.contains(.Incoming) { @@ -683,7 +683,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { var displayAsMessage = false var enablePreview = true switch item.content { - case let .peer(messages, peerValue, _, _, _, _, _, _, _, _, displayAsMessageValue, _): + case let .peer(messages, peerValue, _, _, _, _, _, _, _, _, _, displayAsMessageValue, _): displayAsMessage = displayAsMessageValue if displayAsMessage, case let .user(author) = messages.last?.author { peer = .user(author) @@ -790,7 +790,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { guard let item = self.item, item.editing else { return } - if case let .peer(_, peer, _, _, _, _, _, _, promoInfo, _, _, _) = item.content { + if case let .peer(_, peer, _, _, _, _, _, _, _, promoInfo, _, _, _) = item.content { if promoInfo == nil, let mainPeer = peer.peer { item.interaction.togglePeerSelected(mainPeer) } @@ -833,6 +833,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let peerPresence: EnginePeer.Presence? let draftState: ChatListItemContent.DraftState? let hasUnseenMentions: Bool + let hasUnseenReactions: Bool let inputActivities: [(EnginePeer, PeerInputActivity)]? let isPeerGroup: Bool let promoInfo: ChatListNodeEntryPromoInfo? @@ -842,7 +843,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { var groupHiddenByDefault = false switch item.content { - case let .peer(messagesValue, peerValue, combinedReadStateValue, isRemovedFromTotalUnreadCountValue, peerPresenceValue, hasUnseenMentionsValue, draftStateValue, inputActivitiesValue, promoInfoValue, ignoreUnreadBadge, displayAsMessageValue, _): + case let .peer(messagesValue, peerValue, combinedReadStateValue, isRemovedFromTotalUnreadCountValue, peerPresenceValue, hasUnseenMentionsValue, hasUnseenReactionsValue, draftStateValue, inputActivitiesValue, promoInfoValue, ignoreUnreadBadge, displayAsMessageValue, _): messages = messagesValue contentPeer = .chat(peerValue) combinedReadState = combinedReadStateValue @@ -861,6 +862,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } draftState = draftStateValue hasUnseenMentions = hasUnseenMentionsValue + hasUnseenReactions = hasUnseenReactionsValue switch peerValue.peer { case .user, .secretChat: @@ -892,6 +894,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { isRemovedFromTotalUnreadCount = false draftState = nil hasUnseenMentions = false + hasUnseenReactions = false inputActivities = nil isPeerGroup = true groupHiddenByDefault = hiddenByDefault @@ -1304,7 +1307,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } if !isPeerGroup { - if hasUnseenMentions { + if hasUnseenMentions || hasUnseenReactions { if case .archive = item.peerGroupId { currentMentionBadgeImage = PresentationResourcesChatList.badgeBackgroundInactiveMention(item.presentationData.theme, diameter: badgeDiameter) } else { @@ -1344,7 +1347,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { if !isPeerGroup { if displayAsMessage { switch item.content { - case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _): + case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _): if let peer = messages.last?.author { if peer.isScam { currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular) @@ -1448,7 +1451,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let peerRevealOptions: [ItemListRevealOption] let peerLeftRevealOptions: [ItemListRevealOption] switch item.content { - case let .peer(_, renderedPeer, _, _, presence, _ ,_ ,_, _, _, displayAsMessage, _): + case let .peer(_, renderedPeer, _, _, presence, _, _, _, _, _, _, displayAsMessage, _): if !displayAsMessage { if case let .user(peer) = renderedPeer.chatMainPeer, let presence = presence, !isServicePeer(peer) && !peer.flags.contains(.isSupport) && peer.id != item.context.account.peerId { let updatedPresence = EnginePeer.Presence(status: presence.status, lastActivity: 0) @@ -2143,7 +2146,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { close = false case RevealOptionKey.delete.rawValue: var joined = false - if case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _) = item.content, let message = messages.first { + if case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _) = item.content, let message = messages.first { for media in message.media { if let action = media as? TelegramMediaAction, action.action == .peerJoined { joined = true @@ -2179,7 +2182,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { item.interaction.toggleArchivedFolderHiddenByDefault() close = false case RevealOptionKey.hidePsa.rawValue: - if let item = self.item, case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _) = item.content { + if let item = self.item, case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _) = item.content { item.interaction.hidePsa(peer.peerId) } close = false diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index f1b94746df..9f77b07889 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -210,7 +210,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL nodeInteraction.additionalCategorySelected(id) } ), directionHint: entry.directionHint) - case let .PeerEntry(index, presentationData, messages, combinedReadState, isRemovedFromTotalUnreadCount, draftState, peer, presence, hasUnseenMentions, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact): + case let .PeerEntry(index, presentationData, messages, combinedReadState, isRemovedFromTotalUnreadCount, draftState, peer, presence, hasUnseenMentions, hasUnseenReactions, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact): switch mode { case .chatList: return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem( @@ -226,6 +226,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, presence: presence, hasUnseenMentions: hasUnseenMentions, + hasUnseenReactions: hasUnseenReactions, draftState: draftState, inputActivities: inputActivities, promoInfo: promoInfo, @@ -396,7 +397,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, peerGroupId: EngineChatList.Group, 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, presence, hasUnseenMentions, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact): + case let .PeerEntry(index, presentationData, messages, combinedReadState, isRemovedFromTotalUnreadCount, draftState, peer, presence, hasUnseenMentions, hasUnseenReactions, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact): switch mode { case .chatList: return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem( @@ -412,6 +413,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, presence: presence, hasUnseenMentions: hasUnseenMentions, + hasUnseenReactions: hasUnseenReactions, draftState: draftState, inputActivities: inputActivities, promoInfo: promoInfo, @@ -967,7 +969,7 @@ public final class ChatListNode: ListView { let (rawEntries, isLoading) = chatListNodeEntriesForView(EngineChatList(update.view), state: state, savedMessagesPeer: savedMessagesPeer, foundPeers: state.foundPeers, hideArchivedFolderByDefault: hideArchivedFolderByDefault, displayArchiveIntro: displayArchiveIntro, mode: mode) let entries = rawEntries.filter { entry in switch entry { - case let .PeerEntry(_, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _): + case let .PeerEntry(_, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _): switch mode { case .chatList: return true @@ -1078,7 +1080,7 @@ public final class ChatListNode: ListView { var didIncludeHiddenByDefaultArchive = false if let previous = previousView { for entry in previous.filteredEntries { - if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = entry { + if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = entry { if index.pinningIndex != nil { previousPinnedChats.append(index.messageIndex.id.peerId) } @@ -1094,7 +1096,7 @@ public final class ChatListNode: ListView { var doesIncludeArchive = false var doesIncludeHiddenByDefaultArchive = false for entry in processedView.filteredEntries { - if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = entry { + if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = entry { if index.pinningIndex != nil { updatedPinnedChats.append(index.messageIndex.id.peerId) } @@ -1325,7 +1327,7 @@ public final class ChatListNode: ListView { var referenceId: EngineChatList.PinnedItem.Id? var beforeAll = false switch toEntry { - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _): + case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _): if promoInfo != nil { beforeAll = true } else { @@ -1350,7 +1352,7 @@ public final class ChatListNode: ListView { var itemId: EngineChatList.PinnedItem.Id? switch fromEntry { - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): itemId = .peer(index.messageIndex.id.peerId) default: break @@ -1599,7 +1601,7 @@ public final class ChatListNode: ListView { if transition.chatListView.originalView.laterIndex == nil { for entry in filteredEntries.reversed() { switch entry { - case let .PeerEntry(index, _, _, combinedReadState, isMuted, _, _, _, _, _, _, _, _, promoInfo, _, _): + case let .PeerEntry(index, _, _, combinedReadState, isMuted, _, _, _, _, _, _, _, _, _, promoInfo, _, _): if promoInfo == nil { var hasUnread = false if let combinedReadState = combinedReadState { @@ -1720,7 +1722,7 @@ public final class ChatListNode: ListView { for item in transition.insertItems { if let item = item.item as? ChatListItem { switch item.content { - case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _): + case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _): insertedPeerIds.append(peer.peerId) case .groupReference: break @@ -1935,7 +1937,7 @@ public final class ChatListNode: ListView { continue } switch chatListView.filteredEntries[entryCount - i - 1] { - case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _): + case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _): if interaction.highlightedChatLocation?.location == ChatLocation.peer(peer.peerId) { current = (index, peer.peer!, entryCount - i - 1) break outer @@ -1981,10 +1983,10 @@ public final class ChatListNode: ListView { case .previous(unread: false), .next(unread: false): var target: (EngineChatList.Item.Index, EnginePeer)? = nil if let current = current, entryCount > 1 { - if current.2 > 0, case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[current.2 - 1] { + if current.2 > 0, case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[current.2 - 1] { next = (index, peer.peer!) } - if current.2 <= entryCount - 2, case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[current.2 + 1] { + if current.2 <= entryCount - 2, case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[current.2 + 1] { previous = (index, peer.peer!) } if case .previous = option { @@ -1993,7 +1995,7 @@ public final class ChatListNode: ListView { target = next } } else if entryCount > 0 { - if case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[entryCount - 1] { + if case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[entryCount - 1] { target = (index, peer.peer!) } } @@ -2069,7 +2071,7 @@ public final class ChatListNode: ListView { continue } switch chatListView.filteredEntries[entryCount - i - 1] { - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return index default: break @@ -2085,7 +2087,7 @@ public final class ChatListNode: ListView { if resultPeer == nil, let itemNode = itemNode as? ListViewItemNode, itemNode.frame.contains(point) { if let itemNode = itemNode as? ChatListItemNode, let item = itemNode.item { switch item.content { - case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _): + case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _): resultPeer = peer.peer default: break diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift index e4149d77f4..ef11865c02 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift @@ -45,7 +45,7 @@ public enum ChatListNodeEntryPromoInfo: Equatable { enum ChatListNodeEntry: Comparable, Identifiable { case HeaderEntry - case PeerEntry(index: EngineChatList.Item.Index, presentationData: ChatListPresentationData, messages: [EngineMessage], readState: EnginePeerReadCounters?, isRemovedFromTotalUnreadCount: Bool, draftState: ChatListItemContent.DraftState?, peer: EngineRenderedPeer, presence: EnginePeer.Presence?, hasUnseenMentions: Bool, editing: Bool, hasActiveRevealControls: Bool, selected: Bool, inputActivities: [(EnginePeer, PeerInputActivity)]?, promoInfo: ChatListNodeEntryPromoInfo?, hasFailedMessages: Bool, isContact: Bool) + case PeerEntry(index: EngineChatList.Item.Index, presentationData: ChatListPresentationData, messages: [EngineMessage], readState: EnginePeerReadCounters?, isRemovedFromTotalUnreadCount: Bool, draftState: ChatListItemContent.DraftState?, peer: EngineRenderedPeer, presence: EnginePeer.Presence?, hasUnseenMentions: Bool, hasUnseenReactions: Bool, editing: Bool, hasActiveRevealControls: Bool, selected: Bool, inputActivities: [(EnginePeer, PeerInputActivity)]?, promoInfo: ChatListNodeEntryPromoInfo?, hasFailedMessages: Bool, isContact: Bool) 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) case ArchiveIntro(presentationData: ChatListPresentationData) @@ -55,7 +55,7 @@ enum ChatListNodeEntry: Comparable, Identifiable { switch self { case .HeaderEntry: return .index(EngineChatList.Item.Index.absoluteUpperBound) - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return .index(index) case let .HoleEntry(holeIndex, _): return .index(EngineChatList.Item.Index(pinningIndex: nil, messageIndex: holeIndex)) @@ -72,7 +72,7 @@ enum ChatListNodeEntry: Comparable, Identifiable { switch self { case .HeaderEntry: return .Header - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return .PeerId(index.messageIndex.id.peerId.toInt64()) case let .HoleEntry(holeIndex, _): return .Hole(Int64(holeIndex.id.id)) @@ -97,9 +97,9 @@ enum ChatListNodeEntry: Comparable, Identifiable { } else { return false } - case let .PeerEntry(lhsIndex, lhsPresentationData, lhsMessages, lhsUnreadCount, lhsIsRemovedFromTotalUnreadCount, lhsEmbeddedState, lhsPeer, lhsPresence, lhsSummaryInfo, lhsEditing, lhsHasRevealControls, lhsSelected, lhsInputActivities, lhsAd, lhsHasFailedMessages, lhsIsContact): + case let .PeerEntry(lhsIndex, lhsPresentationData, lhsMessages, lhsUnreadCount, lhsIsRemovedFromTotalUnreadCount, lhsEmbeddedState, lhsPeer, lhsPresence, lhsHasUnseenMentions, lhsHasUnseenReactions, lhsEditing, lhsHasRevealControls, lhsSelected, lhsInputActivities, lhsAd, lhsHasFailedMessages, lhsIsContact): switch rhs { - case let .PeerEntry(rhsIndex, rhsPresentationData, rhsMessages, rhsUnreadCount, rhsIsRemovedFromTotalUnreadCount, rhsEmbeddedState, rhsPeer, rhsPresence, rhsSummaryInfo, rhsEditing, rhsHasRevealControls, rhsSelected, rhsInputActivities, rhsAd, rhsHasFailedMessages, rhsIsContact): + case let .PeerEntry(rhsIndex, rhsPresentationData, rhsMessages, rhsUnreadCount, rhsIsRemovedFromTotalUnreadCount, rhsEmbeddedState, rhsPeer, rhsPresence, rhsHasUnseenMentions, rhsHasUnseenReactions, rhsEditing, rhsHasRevealControls, rhsSelected, rhsInputActivities, rhsAd, rhsHasFailedMessages, rhsIsContact): if lhsIndex != rhsIndex { return false } @@ -161,7 +161,10 @@ enum ChatListNodeEntry: Comparable, Identifiable { if lhsPeer != rhsPeer { return false } - if lhsSummaryInfo != rhsSummaryInfo { + if lhsHasUnseenMentions != rhsHasUnseenMentions { + return false + } + if lhsHasUnseenReactions != rhsHasUnseenReactions { return false } if let lhsInputActivities = lhsInputActivities, let rhsInputActivities = rhsInputActivities { @@ -325,7 +328,7 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState draftState = ChatListItemContent.DraftState(text: draftText) } - result.append(.PeerEntry(index: offsetPinnedIndex(entry.index, offset: pinnedIndexOffset), presentationData: state.presentationData, messages: updatedMessages, readState: updatedCombinedReadState, isRemovedFromTotalUnreadCount: entry.isMuted, draftState: draftState, peer: entry.renderedPeer, presence: entry.presence, hasUnseenMentions: entry.hasUnseenMentions, editing: state.editing, hasActiveRevealControls: entry.index.messageIndex.id.peerId == state.peerIdWithRevealedOptions, selected: state.selectedPeerIds.contains(entry.index.messageIndex.id.peerId), inputActivities: state.peerInputActivities?.activities[entry.index.messageIndex.id.peerId], promoInfo: nil, hasFailedMessages: entry.hasFailed, isContact: entry.isContact)) + result.append(.PeerEntry(index: offsetPinnedIndex(entry.index, offset: pinnedIndexOffset), presentationData: state.presentationData, messages: updatedMessages, readState: updatedCombinedReadState, isRemovedFromTotalUnreadCount: entry.isMuted, draftState: draftState, peer: entry.renderedPeer, presence: entry.presence, hasUnseenMentions: entry.hasUnseenMentions, hasUnseenReactions: entry.hasUnseenReactions, editing: state.editing, hasActiveRevealControls: entry.index.messageIndex.id.peerId == state.peerIdWithRevealedOptions, selected: state.selectedPeerIds.contains(entry.index.messageIndex.id.peerId), inputActivities: state.peerInputActivities?.activities[entry.index.messageIndex.id.peerId], promoInfo: nil, hasFailedMessages: entry.hasFailed, isContact: entry.isContact)) } if !view.hasLater { var pinningIndex: UInt16 = UInt16(pinnedIndexOffset == 0 ? 0 : (pinnedIndexOffset - 1)) @@ -350,6 +353,7 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState peer: EngineRenderedPeer(peerId: peer.0.id, peers: peers), presence: nil, hasUnseenMentions: false, + hasUnseenReactions: false, editing: state.editing, hasActiveRevealControls: false, selected: state.selectedPeerIds.contains(peer.0.id), @@ -364,7 +368,7 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState } } - result.append(.PeerEntry(index: EngineChatList.Item.Index.absoluteUpperBound.predecessor, presentationData: state.presentationData, messages: [], readState: nil, isRemovedFromTotalUnreadCount: false, draftState: nil, peer: EngineRenderedPeer(peerId: savedMessagesPeer.id, peers: [savedMessagesPeer.id: savedMessagesPeer]), presence: nil, hasUnseenMentions: false, editing: state.editing, hasActiveRevealControls: false, selected: state.selectedPeerIds.contains(savedMessagesPeer.id), inputActivities: nil, promoInfo: nil, hasFailedMessages: false, isContact: false)) + result.append(.PeerEntry(index: EngineChatList.Item.Index.absoluteUpperBound.predecessor, presentationData: state.presentationData, messages: [], readState: nil, isRemovedFromTotalUnreadCount: false, draftState: nil, peer: EngineRenderedPeer(peerId: savedMessagesPeer.id, peers: [savedMessagesPeer.id: savedMessagesPeer]), 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)) } else { if !filteredAdditionalItemEntries.isEmpty { for item in filteredAdditionalItemEntries.reversed() { @@ -386,6 +390,7 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState peer: item.item.renderedPeer, presence: item.item.presence, hasUnseenMentions: item.item.hasUnseenMentions, + hasUnseenReactions: item.item.hasUnseenReactions, editing: state.editing, hasActiveRevealControls: item.item.index.messageIndex.id.peerId == state.peerIdWithRevealedOptions, selected: state.selectedPeerIds.contains(item.item.index.messageIndex.id.peerId), diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index 5afc682cb8..b6f1227e3c 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -402,7 +402,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi highlightedActionNode.performAction() } if let highlightedReaction = strongSelf.highlightedReaction { - strongSelf.reactionContextNode?.performReactionSelection(reaction: highlightedReaction) + strongSelf.reactionContextNode?.performReactionSelection(reaction: highlightedReaction, isLarge: false) } } else { if let highlightedActionNode = strongSelf.highlightedActionNode { @@ -481,7 +481,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi } if let highlightedReaction = strongSelf.highlightedReaction { - strongSelf.reactionContextNode?.performReactionSelection(reaction: highlightedReaction) + strongSelf.reactionContextNode?.performReactionSelection(reaction: highlightedReaction, isLarge: false) } } else { if let highlightedActionNode = strongSelf.highlightedActionNode { @@ -1340,11 +1340,11 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi self.reactionContextNode = reactionContextNode self.addSubnode(reactionContextNode) - reactionContextNode.reactionSelected = { [weak self] reaction in + reactionContextNode.reactionSelected = { [weak self] reaction, isLarge in guard let strongSelf = self, let controller = strongSelf.getController() as? ContextController else { return } - controller.reactionSelected?(reaction) + controller.reactionSelected?(reaction, isLarge) } } @@ -2226,7 +2226,7 @@ public final class ContextController: ViewController, StandalonePresentableContr private var shouldBeDismissedDisposable: Disposable? - public var reactionSelected: ((ReactionContextItem) -> Void)? + public var reactionSelected: ((ReactionContextItem, Bool) -> Void)? public init(account: Account, presentationData: PresentationData, source: ContextContentSource, items: Signal, recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil, gesture: ContextGesture? = nil) { self.account = account diff --git a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift index 8dab51ddf1..c7972df81a 100644 --- a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift @@ -285,11 +285,11 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo animateReactionsIn = true } - reactionContextNode.reactionSelected = { [weak self] reaction in + reactionContextNode.reactionSelected = { [weak self] reaction, isLarge in guard let strongSelf = self, let controller = strongSelf.getController() as? ContextController else { return } - controller.reactionSelected?(reaction) + controller.reactionSelected?(reaction, isLarge) } } contentTopInset += 70.0 diff --git a/submodules/Postbox/Sources/ChatListView.swift b/submodules/Postbox/Sources/ChatListView.swift index 97b8729d15..31bfbf3416 100644 --- a/submodules/Postbox/Sources/ChatListView.swift +++ b/submodules/Postbox/Sources/ChatListView.swift @@ -1,32 +1,45 @@ import Foundation +public struct ChatListEntryMessageTagSummaryKey: Hashable { + public var tag: MessageTags + public var actionType: PendingMessageActionType + + public init(tag: MessageTags, actionType: PendingMessageActionType) { + self.tag = tag + self.actionType = actionType + } +} + public struct ChatListEntryMessageTagSummaryComponent { - public let tag: MessageTags public let namespace: MessageId.Namespace - public init(tag: MessageTags, namespace: MessageId.Namespace) { - self.tag = tag + public init(namespace: MessageId.Namespace) { self.namespace = namespace } } public struct ChatListEntryPendingMessageActionsSummaryComponent { - public let type: PendingMessageActionType public let namespace: MessageId.Namespace - public init(type: PendingMessageActionType, namespace: MessageId.Namespace) { - self.type = type + public init(namespace: MessageId.Namespace) { self.namespace = namespace } } public struct ChatListEntrySummaryComponents { - public let tagSummary: ChatListEntryMessageTagSummaryComponent? - public let actionsSummary: ChatListEntryPendingMessageActionsSummaryComponent? + public struct Component { + public let tagSummary: ChatListEntryMessageTagSummaryComponent? + public let actionsSummary: ChatListEntryPendingMessageActionsSummaryComponent? + + public init(tagSummary: ChatListEntryMessageTagSummaryComponent? = nil, actionsSummary: ChatListEntryPendingMessageActionsSummaryComponent? = nil) { + self.tagSummary = tagSummary + self.actionsSummary = actionsSummary + } + } + public var components: [ChatListEntryMessageTagSummaryKey: Component] - public init(tagSummary: ChatListEntryMessageTagSummaryComponent? = nil, actionsSummary: ChatListEntryPendingMessageActionsSummaryComponent? = nil) { - self.tagSummary = tagSummary - self.actionsSummary = actionsSummary + public init(components: [ChatListEntryMessageTagSummaryKey: Component] = [:]) { + self.components = components } } @@ -88,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: ChatListMessageTagSummaryInfo, hasFailed: Bool, isContact: Bool) + case MessageEntry(index: ChatListIndex, messages: [Message], readState: CombinedPeerReadState?, isRemovedFromTotalUnreadCount: Bool, embeddedInterfaceState: StoredPeerChatInterfaceState?, renderedPeer: RenderedPeer, presence: PeerPresence?, summaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo], hasFailed: Bool, isContact: Bool) case HoleEntry(ChatListHole) public var index: ChatListIndex { @@ -185,7 +198,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: ChatListMessageTagSummaryInfo, 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], hasFailedMessages: Bool, isContact: Bool) case HoleEntry(ChatListHole) init(_ intermediateEntry: ChatListIntermediateEntry, cachedDataTable: CachedPeerDataTable, readStateTable: MessageHistoryReadStateTable, messageHistoryTable: MessageHistoryTable) { @@ -588,10 +601,7 @@ final class MutableChatListView { } } - let tagSummaryCount: Int32? = nil - let actionsSummaryCount: Int32? = nil - - 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), presence: presence, tagSummaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: tagSummaryCount, actionsSummaryCount: actionsSummaryCount), hasFailedMessages: postbox.messageHistoryFailedTable.contains(peerId: index.messageIndex.id.peerId), isContact: isContact) + 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), presence: presence, tagSummaryInfo: [:], 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 e718a6ab4a..100ea0fb22 100644 --- a/submodules/Postbox/Sources/ChatListViewState.swift +++ b/submodules/Postbox/Sources/ChatListViewState.swift @@ -843,26 +843,36 @@ private final class ChatListViewSpaceState { if self.orderedEntries.mutableScan({ entry in switch entry { case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, entryRenderedPeer, presence, tagSummaryInfo, hasFailedMessages, isContact): - var updatedTagSummaryCount: Int32? - var updatedActionsSummaryCount: Int32? + var updatedChatListMessageTagSummaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo] = tagSummaryInfo + var didUpdateSummaryInfo = false - if let tagSummary = self.summaryComponents.tagSummary { - let key = MessageHistoryTagsSummaryKey(tag: tagSummary.tag, peerId: index.messageIndex.id.peerId, namespace: tagSummary.namespace) - if let summary = transaction.currentUpdatedMessageTagSummaries[key] { - updatedTagSummaryCount = summary.count + for (key, component) in self.summaryComponents.components { + var updatedTagSummaryCount: Int32? + if let tagSummary = component.tagSummary { + let key = MessageHistoryTagsSummaryKey(tag: key.tag, peerId: index.messageIndex.id.peerId, namespace: tagSummary.namespace) + if let summary = transaction.currentUpdatedMessageTagSummaries[key] { + updatedTagSummaryCount = summary.count + } } - } - - if let actionsSummary = self.summaryComponents.actionsSummary { - let key = PendingMessageActionsSummaryKey(type: actionsSummary.type, peerId: index.messageIndex.id.peerId, namespace: actionsSummary.namespace) - if let count = transaction.currentUpdatedMessageActionsSummaries[key] { - updatedActionsSummaryCount = count - } - } - - if updatedTagSummaryCount != nil || updatedActionsSummaryCount != nil { - let summaryInfo = ChatListMessageTagSummaryInfo(tagSummaryCount: updatedTagSummaryCount ?? tagSummaryInfo.tagSummaryCount, actionsSummaryCount: updatedActionsSummaryCount ?? tagSummaryInfo.actionsSummaryCount) + var updatedActionsSummaryCount: Int32? + if let actionsSummary = component.actionsSummary { + let key = PendingMessageActionsSummaryKey(type: key.actionType, peerId: index.messageIndex.id.peerId, namespace: actionsSummary.namespace) + if let count = transaction.currentUpdatedMessageActionsSummaries[key] { + updatedActionsSummaryCount = count + } + } + + if updatedTagSummaryCount != nil || updatedActionsSummaryCount != nil { + updatedChatListMessageTagSummaryInfo[key] = ChatListMessageTagSummaryInfo( + tagSummaryCount: updatedTagSummaryCount ?? updatedChatListMessageTagSummaryInfo[key]?.tagSummaryCount, + actionsSummaryCount: updatedActionsSummaryCount ?? updatedChatListMessageTagSummaryInfo[key]?.actionsSummaryCount + ) + didUpdateSummaryInfo = true + } + } + + if didUpdateSummaryInfo { return .MessageEntry( index: index, messages: messages, @@ -872,7 +882,7 @@ private final class ChatListViewSpaceState { embeddedInterfaceState: embeddedInterfaceState, renderedPeer: entryRenderedPeer, presence: presence, - tagSummaryInfo: summaryInfo, + tagSummaryInfo: updatedChatListMessageTagSummaryInfo, hasFailedMessages: hasFailedMessages, isContact: isContact ) @@ -1408,23 +1418,29 @@ struct ChatListViewState { } let renderedPeer = RenderedPeer(peerId: index.messageIndex.id.peerId, peers: peers) - var tagSummaryCount: Int32? - var actionsSummaryCount: Int32? - - if let tagSummary = self.summaryComponents.tagSummary { - let key = MessageHistoryTagsSummaryKey(tag: tagSummary.tag, peerId: index.messageIndex.id.peerId, namespace: tagSummary.namespace) - if let summary = postbox.messageHistoryTagsSummaryTable.get(key) { - tagSummaryCount = summary.count + var tagSummaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo] = [:] + for (key, component) in self.summaryComponents.components { + var tagSummaryCount: Int32? + var actionsSummaryCount: Int32? + + if let tagSummary = component.tagSummary { + let key = MessageHistoryTagsSummaryKey(tag: key.tag, peerId: index.messageIndex.id.peerId, namespace: tagSummary.namespace) + if let summary = postbox.messageHistoryTagsSummaryTable.get(key) { + tagSummaryCount = summary.count + } } + + if let actionsSummary = component.actionsSummary { + let key = PendingMessageActionsSummaryKey(type: key.actionType, peerId: index.messageIndex.id.peerId, namespace: actionsSummary.namespace) + actionsSummaryCount = postbox.pendingMessageActionsMetadataTable.getCount(.peerNamespaceAction(key.peerId, key.namespace, key.type)) + } + + tagSummaryInfo[key] = ChatListMessageTagSummaryInfo( + tagSummaryCount: tagSummaryCount, + actionsSummaryCount: actionsSummaryCount + ) } - if let actionsSummary = self.summaryComponents.actionsSummary { - let key = PendingMessageActionsSummaryKey(type: actionsSummary.type, peerId: index.messageIndex.id.peerId, namespace: actionsSummary.namespace) - actionsSummaryCount = postbox.pendingMessageActionsMetadataTable.getCount(.peerNamespaceAction(key.peerId, key.namespace, key.type)) - } - - let tagSummaryInfo = ChatListMessageTagSummaryInfo(tagSummaryCount: tagSummaryCount, actionsSummaryCount: actionsSummaryCount) - let notificationSettings = postbox.peerNotificationSettingsTable.getEffective(notificationsPeerId) let isRemovedFromTotalUnreadCount: Bool diff --git a/submodules/Postbox/Sources/MessageHistoryTable.swift b/submodules/Postbox/Sources/MessageHistoryTable.swift index 58af91bd5b..b32c9f695f 100644 --- a/submodules/Postbox/Sources/MessageHistoryTable.swift +++ b/submodules/Postbox/Sources/MessageHistoryTable.swift @@ -277,7 +277,7 @@ final class MessageHistoryTable: Table { if (currentTags & 1) != 0 { let tag = MessageTags(rawValue: 1 << UInt32(i)) - self.tagsTable.add(tags: tag, index: message.index, updatedSummaries: &updatedMessageTagSummaries, invalidateSummaries: &invalidateMessageTagSummaries) + self.tagsTable.add(tags: tag, index: message.index, isNewlyAdded: true, updatedSummaries: &updatedMessageTagSummaries, invalidateSummaries: &invalidateMessageTagSummaries) } } } @@ -1481,11 +1481,12 @@ final class MessageHistoryTable: Table { let updatedGroupInfo = self.updateMovingGroupInfoInNamespace(index: updatedIndex, updatedIndex: updatedIndex, groupingKey: message.groupingKey, previousInfo: previousMessage.groupInfo, updatedGroupInfos: &updatedGroupInfos) if previousMessage.tags != message.tags || index != updatedIndex { + let isNewlyAdded = previousMessage.tags.isEmpty if !previousMessage.tags.isEmpty { self.tagsTable.remove(tags: previousMessage.tags, index: index, updatedSummaries: &updatedMessageTagSummaries, invalidateSummaries: &invalidateMessageTagSummaries) } if !message.tags.isEmpty { - self.tagsTable.add(tags: message.tags, index: message.index, updatedSummaries: &updatedMessageTagSummaries, invalidateSummaries: &invalidateMessageTagSummaries) + self.tagsTable.add(tags: message.tags, index: message.index, isNewlyAdded: isNewlyAdded, updatedSummaries: &updatedMessageTagSummaries, invalidateSummaries: &invalidateMessageTagSummaries) } } if previousMessage.threadId != message.threadId || index != message.index { diff --git a/submodules/Postbox/Sources/MessageHistoryTagsSummaryTable.swift b/submodules/Postbox/Sources/MessageHistoryTagsSummaryTable.swift index 71bc63c674..3aedbe2057 100644 --- a/submodules/Postbox/Sources/MessageHistoryTagsSummaryTable.swift +++ b/submodules/Postbox/Sources/MessageHistoryTagsSummaryTable.swift @@ -116,9 +116,9 @@ class MessageHistoryTagsSummaryTable: Table { } } - func addMessage(key: MessageHistoryTagsSummaryKey, id: MessageId.Id, updatedSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation]) { + func addMessage(key: MessageHistoryTagsSummaryKey, id: MessageId.Id, isNewlyAdded: Bool, updatedSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation]) { if let current = self.get(key) { - if !current.range.contains(id) { + if !isNewlyAdded || !current.range.contains(id) { self.set(key, summary: current.withAddedCount(1), updatedSummaries: &updatedSummaries) if current.range.maxId == 0 { self.invalidateTable.insert(InvalidatedMessageHistoryTagsSummaryKey(peerId: key.peerId, namespace: key.namespace, tagMask: key.tag), operations: &invalidateSummaries) diff --git a/submodules/Postbox/Sources/MessageHistoryTagsTable.swift b/submodules/Postbox/Sources/MessageHistoryTagsTable.swift index 42d0a8f09c..9bf21b3a3d 100644 --- a/submodules/Postbox/Sources/MessageHistoryTagsTable.swift +++ b/submodules/Postbox/Sources/MessageHistoryTagsTable.swift @@ -42,11 +42,11 @@ class MessageHistoryTagsTable: Table { return self.lowerBound(tag: tag, peerId: peerId, namespace: namespace).successor } - func add(tags: MessageTags, index: MessageIndex, updatedSummaries: inout[MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation]) { + func add(tags: MessageTags, index: MessageIndex, isNewlyAdded: Bool, updatedSummaries: inout[MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation]) { for tag in tags { self.valueBox.set(self.table, key: self.key(tag: tag, index: index, key: self.sharedKey), value: MemoryBuffer()) if self.summaryTags.contains(tag) { - self.summaryTable.addMessage(key: MessageHistoryTagsSummaryKey(tag: tag, peerId: index.id.peerId, namespace: index.id.namespace), id: index.id.id, updatedSummaries: &updatedSummaries, invalidateSummaries: &invalidateSummaries) + self.summaryTable.addMessage(key: MessageHistoryTagsSummaryKey(tag: tag, peerId: index.id.peerId, namespace: index.id.namespace), id: index.id.id, isNewlyAdded: isNewlyAdded, updatedSummaries: &updatedSummaries, invalidateSummaries: &invalidateSummaries) } } } diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift index a92477c809..ccbde6eab7 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift @@ -70,7 +70,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { private var validLayout: (CGSize, UIEdgeInsets, CGRect)? private var isLeftAligned: Bool = true - public var reactionSelected: ((ReactionContextItem) -> Void)? + public var reactionSelected: ((ReactionContextItem, Bool) -> Void)? private var hapticFeedback: HapticFeedback? private var standaloneReactionAnimation: StandaloneReactionAnimation? @@ -721,7 +721,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { self.longPressTimer?.invalidate() self.continuousHaptic = nil self.didTriggerExpandedReaction = true - self.highlightGestureFinished(performAction: true) + self.highlightGestureFinished(performAction: true, isLarge: true) default: break } @@ -732,7 +732,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { case .ended: let point = recognizer.location(in: self.view) if let reaction = self.reaction(at: point) { - self.reactionSelected?(reaction) + self.reactionSelected?(reaction, false) } default: break @@ -755,10 +755,14 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { } public func highlightGestureFinished(performAction: Bool) { + self.highlightGestureFinished(performAction: performAction, isLarge: false) + } + + private func highlightGestureFinished(performAction: Bool, isLarge: Bool) { if let highlightedReaction = self.highlightedReaction { self.highlightedReaction = nil if performAction { - self.performReactionSelection(reaction: highlightedReaction) + self.performReactionSelection(reaction: highlightedReaction, isLarge: isLarge) } else { if let (size, insets, anchorRect) = self.validLayout { self.updateLayout(size: size, insets: insets, anchorRect: anchorRect, transition: .animated(duration: 0.18, curve: .easeInOut), animateInFromAnchorRect: nil, animateOutToAnchorRect: nil, animateReactionHighlight: true) @@ -819,10 +823,10 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { return self.reactionItemNode(at: point)?.item } - public func performReactionSelection(reaction: ReactionContextItem.Reaction) { + public func performReactionSelection(reaction: ReactionContextItem.Reaction, isLarge: Bool) { for (_, itemNode) in self.visibleItemNodes { if itemNode.item.reaction == reaction { - self.reactionSelected?(itemNode.item) + self.reactionSelected?(itemNode.item, isLarge) break } } diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index 6a03bca4b4..0fae46c287 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -266,6 +266,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView EnginePeer.Presence(status: .present(until: presenceTimestamp + 1000), lastActivity: presenceTimestamp) }, hasUnseenMentions: false, + hasUnseenReactions: false, draftState: nil, inputActivities: hasInputActivity ? [(author, .typingText)] : [], promoInfo: nil, diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift index baf947afc2..c81f297f4f 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift @@ -886,6 +886,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate EnginePeer.Presence(status: .present(until: presenceTimestamp + 1000), lastActivity: presenceTimestamp) }, hasUnseenMentions: false, + hasUnseenReactions: false, draftState: nil, inputActivities: hasInputActivity ? [(author, .typingText)] : [], promoInfo: nil, diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index 1f33534bed..1345575f73 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -409,6 +409,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { EnginePeer.Presence(status: .present(until: presenceTimestamp + 1000), lastActivity: presenceTimestamp) }, hasUnseenMentions: false, + hasUnseenReactions: false, draftState: nil, inputActivities: hasInputActivity ? [(author, .typingText)] : [], promoInfo: nil, diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 97e8b98da8..250feb01f5 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -166,7 +166,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[2011940674] = { return Api.UserStatus.parse_userStatusLastMonth($0) } dict[-911191137] = { return Api.SearchResultsCalendarPeriod.parse_searchResultsCalendarPeriod($0) } dict[-11252123] = { return Api.Folder.parse_folder($0) } - dict[739712882] = { return Api.Dialog.parse_dialog($0) } + dict[-1460809483] = { return Api.Dialog.parse_dialog($0) } dict[1908216652] = { return Api.Dialog.parse_dialogFolder($0) } dict[381645902] = { return Api.SendMessageAction.parse_sendMessageTypingAction($0) } dict[-44119819] = { return Api.SendMessageAction.parse_sendMessageCancelAction($0) } @@ -342,7 +342,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-842824308] = { return Api.account.WallPapers.parse_wallPapers($0) } dict[1012306921] = { return Api.InputTheme.parse_inputTheme($0) } dict[-175567375] = { return Api.InputTheme.parse_inputThemeSlug($0) } - dict[142306870] = { return Api.MessageReactions.parse_messageReactions($0) } + dict[1328256121] = { return Api.MessageReactions.parse_messageReactions($0) } dict[-2032041631] = { return Api.Poll.parse_poll($0) } dict[-1195615476] = { return Api.InputNotifyPeer.parse_inputNotifyPeer($0) } dict[423314455] = { return Api.InputNotifyPeer.parse_inputNotifyUsers($0) } @@ -518,7 +518,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-524237339] = { return Api.PageTableRow.parse_pageTableRow($0) } dict[453805082] = { return Api.DraftMessage.parse_draftMessageEmpty($0) } dict[-40996577] = { return Api.DraftMessage.parse_draftMessage($0) } - dict[-1553558980] = { return Api.messages.MessageReactionsList.parse_messageReactionsList($0) } + dict[834488621] = { return Api.messages.MessageReactionsList.parse_messageReactionsList($0) } dict[-1014526429] = { return Api.help.Country.parse_country($0) } dict[-1660637285] = { return Api.StatsGroupTopPoster.parse_statsGroupTopPoster($0) } dict[-2128640689] = { return Api.account.SentEmailCode.parse_sentEmailCode($0) } @@ -793,8 +793,10 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-290921362] = { return Api.upload.CdnFile.parse_cdnFileReuploadNeeded($0) } dict[-1449145777] = { return Api.upload.CdnFile.parse_cdnFile($0) } dict[-1065882623] = { return Api.AvailableReaction.parse_availableReaction($0) } + dict[1741309751] = { return Api.messages.TranslatedText.parse_translateNoResult($0) } + dict[-1575684144] = { return Api.messages.TranslatedText.parse_translateResultText($0) } dict[415997816] = { return Api.help.InviteText.parse_inviteText($0) } - dict[-1826077446] = { return Api.MessageUserReaction.parse_messageUserReaction($0) } + dict[1370914559] = { return Api.MessagePeerReaction.parse_messagePeerReaction($0) } dict[1984755728] = { return Api.BotInlineMessage.parse_botInlineMessageMediaAuto($0) } dict[-1937807902] = { return Api.BotInlineMessage.parse_botInlineMessageText($0) } dict[85477117] = { return Api.BotInlineMessage.parse_botInlineMessageMediaGeo($0) } @@ -1574,9 +1576,11 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.AvailableReaction: _1.serialize(buffer, boxed) + case let _1 as Api.messages.TranslatedText: + _1.serialize(buffer, boxed) case let _1 as Api.help.InviteText: _1.serialize(buffer, boxed) - case let _1 as Api.MessageUserReaction: + case let _1 as Api.MessagePeerReaction: _1.serialize(buffer, boxed) case let _1 as Api.BotInlineMessage: _1.serialize(buffer, boxed) diff --git a/submodules/TelegramApi/Sources/Api1.swift b/submodules/TelegramApi/Sources/Api1.swift index 732b6d3f7a..0773d24fad 100644 --- a/submodules/TelegramApi/Sources/Api1.swift +++ b/submodules/TelegramApi/Sources/Api1.swift @@ -1219,13 +1219,13 @@ public struct messages { } public enum MessageReactionsList: TypeConstructorDescription { - case messageReactionsList(flags: Int32, count: Int32, reactions: [Api.MessageUserReaction], users: [Api.User], nextOffset: String?) + case messageReactionsList(flags: Int32, count: Int32, reactions: [Api.MessagePeerReaction], chats: [Api.Chat], users: [Api.User], nextOffset: String?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .messageReactionsList(let flags, let count, let reactions, let users, let nextOffset): + case .messageReactionsList(let flags, let count, let reactions, let chats, let users, let nextOffset): if boxed { - buffer.appendInt32(-1553558980) + buffer.appendInt32(834488621) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(count, buffer: buffer, boxed: false) @@ -1235,6 +1235,11 @@ public struct messages { item.serialize(buffer, true) } buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) buffer.appendInt32(Int32(users.count)) for item in users { item.serialize(buffer, true) @@ -1246,8 +1251,8 @@ public struct messages { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .messageReactionsList(let flags, let count, let reactions, let users, let nextOffset): - return ("messageReactionsList", [("flags", flags), ("count", count), ("reactions", reactions), ("users", users), ("nextOffset", nextOffset)]) + case .messageReactionsList(let flags, let count, let reactions, let chats, let users, let nextOffset): + return ("messageReactionsList", [("flags", flags), ("count", count), ("reactions", reactions), ("chats", chats), ("users", users), ("nextOffset", nextOffset)]) } } @@ -1256,23 +1261,28 @@ public struct messages { _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() - var _3: [Api.MessageUserReaction]? + var _3: [Api.MessagePeerReaction]? if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageUserReaction.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessagePeerReaction.self) } - var _4: [Api.User]? + var _4: [Api.Chat]? if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) } - var _5: String? - if Int(_1!) & Int(1 << 0) != 0 {_5 = parseString(reader) } + var _5: [Api.User]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + var _6: String? + if Int(_1!) & Int(1 << 0) != 0 {_6 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.messages.MessageReactionsList.messageReactionsList(flags: _1!, count: _2!, reactions: _3!, users: _4!, nextOffset: _5) + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.messages.MessageReactionsList.messageReactionsList(flags: _1!, count: _2!, reactions: _3!, chats: _4!, users: _5!, nextOffset: _6) } else { return nil @@ -2473,6 +2483,52 @@ public struct messages { } } + } + public enum TranslatedText: TypeConstructorDescription { + case translateNoResult + case translateResultText(text: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .translateNoResult: + if boxed { + buffer.appendInt32(1741309751) + } + + break + case .translateResultText(let text): + if boxed { + buffer.appendInt32(-1575684144) + } + serializeString(text, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .translateNoResult: + return ("translateNoResult", []) + case .translateResultText(let text): + return ("translateResultText", [("text", text)]) + } + } + + public static func parse_translateNoResult(_ reader: BufferReader) -> TranslatedText? { + return Api.messages.TranslatedText.translateNoResult + } + public static func parse_translateResultText(_ reader: BufferReader) -> TranslatedText? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.messages.TranslatedText.translateResultText(text: _1!) + } + else { + return nil + } + } + } public enum FavedStickers: TypeConstructorDescription { case favedStickersNotModified diff --git a/submodules/TelegramApi/Sources/Api2.swift b/submodules/TelegramApi/Sources/Api2.swift index 1d4cb88c84..786fb00bd7 100644 --- a/submodules/TelegramApi/Sources/Api2.swift +++ b/submodules/TelegramApi/Sources/Api2.swift @@ -4236,14 +4236,14 @@ public extension Api { } public enum Dialog: TypeConstructorDescription { - case dialog(flags: Int32, peer: Api.Peer, topMessage: Int32, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, unreadMentionsCount: Int32, notifySettings: Api.PeerNotifySettings, pts: Int32?, draft: Api.DraftMessage?, folderId: Int32?) + case dialog(flags: Int32, peer: Api.Peer, topMessage: Int32, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, unreadMentionsCount: Int32, unreadReactionsCount: Int32, notifySettings: Api.PeerNotifySettings, pts: Int32?, draft: Api.DraftMessage?, folderId: Int32?) case dialogFolder(flags: Int32, folder: Api.Folder, peer: Api.Peer, topMessage: Int32, unreadMutedPeersCount: Int32, unreadUnmutedPeersCount: Int32, unreadMutedMessagesCount: Int32, unreadUnmutedMessagesCount: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .dialog(let flags, let peer, let topMessage, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let unreadMentionsCount, let notifySettings, let pts, let draft, let folderId): + case .dialog(let flags, let peer, let topMessage, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let unreadMentionsCount, let unreadReactionsCount, let notifySettings, let pts, let draft, let folderId): if boxed { - buffer.appendInt32(739712882) + buffer.appendInt32(-1460809483) } serializeInt32(flags, buffer: buffer, boxed: false) peer.serialize(buffer, true) @@ -4252,6 +4252,7 @@ public extension Api { serializeInt32(readOutboxMaxId, buffer: buffer, boxed: false) serializeInt32(unreadCount, buffer: buffer, boxed: false) serializeInt32(unreadMentionsCount, buffer: buffer, boxed: false) + serializeInt32(unreadReactionsCount, buffer: buffer, boxed: false) notifySettings.serialize(buffer, true) if Int(flags) & Int(1 << 0) != 0 {serializeInt32(pts!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 1) != 0 {draft!.serialize(buffer, true)} @@ -4275,8 +4276,8 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .dialog(let flags, let peer, let topMessage, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let unreadMentionsCount, let notifySettings, let pts, let draft, let folderId): - return ("dialog", [("flags", flags), ("peer", peer), ("topMessage", topMessage), ("readInboxMaxId", readInboxMaxId), ("readOutboxMaxId", readOutboxMaxId), ("unreadCount", unreadCount), ("unreadMentionsCount", unreadMentionsCount), ("notifySettings", notifySettings), ("pts", pts), ("draft", draft), ("folderId", folderId)]) + case .dialog(let flags, let peer, let topMessage, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let unreadMentionsCount, let unreadReactionsCount, let notifySettings, let pts, let draft, let folderId): + return ("dialog", [("flags", flags), ("peer", peer), ("topMessage", topMessage), ("readInboxMaxId", readInboxMaxId), ("readOutboxMaxId", readOutboxMaxId), ("unreadCount", unreadCount), ("unreadMentionsCount", unreadMentionsCount), ("unreadReactionsCount", unreadReactionsCount), ("notifySettings", notifySettings), ("pts", pts), ("draft", draft), ("folderId", folderId)]) case .dialogFolder(let flags, let folder, let peer, let topMessage, let unreadMutedPeersCount, let unreadUnmutedPeersCount, let unreadMutedMessagesCount, let unreadUnmutedMessagesCount): return ("dialogFolder", [("flags", flags), ("folder", folder), ("peer", peer), ("topMessage", topMessage), ("unreadMutedPeersCount", unreadMutedPeersCount), ("unreadUnmutedPeersCount", unreadUnmutedPeersCount), ("unreadMutedMessagesCount", unreadMutedMessagesCount), ("unreadUnmutedMessagesCount", unreadUnmutedMessagesCount)]) } @@ -4299,18 +4300,20 @@ public extension Api { _6 = reader.readInt32() var _7: Int32? _7 = reader.readInt32() - var _8: Api.PeerNotifySettings? + var _8: Int32? + _8 = reader.readInt32() + var _9: Api.PeerNotifySettings? if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings + _9 = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings } - var _9: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_9 = reader.readInt32() } - var _10: Api.DraftMessage? + var _10: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_10 = reader.readInt32() } + var _11: Api.DraftMessage? if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { - _10 = Api.parse(reader, signature: signature) as? Api.DraftMessage + _11 = Api.parse(reader, signature: signature) as? Api.DraftMessage } } - var _11: Int32? - if Int(_1!) & Int(1 << 4) != 0 {_11 = reader.readInt32() } + var _12: Int32? + if Int(_1!) & Int(1 << 4) != 0 {_12 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -4319,11 +4322,12 @@ public extension Api { let _c6 = _6 != nil let _c7 = _7 != nil let _c8 = _8 != nil - let _c9 = (Int(_1!) & Int(1 << 0) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 1) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 4) == 0) || _11 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { - return Api.Dialog.dialog(flags: _1!, peer: _2!, topMessage: _3!, readInboxMaxId: _4!, readOutboxMaxId: _5!, unreadCount: _6!, unreadMentionsCount: _7!, notifySettings: _8!, pts: _9, draft: _10, folderId: _11) + let _c9 = _9 != nil + let _c10 = (Int(_1!) & Int(1 << 0) == 0) || _10 != nil + let _c11 = (Int(_1!) & Int(1 << 1) == 0) || _11 != nil + let _c12 = (Int(_1!) & Int(1 << 4) == 0) || _12 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { + return Api.Dialog.dialog(flags: _1!, peer: _2!, topMessage: _3!, readInboxMaxId: _4!, readOutboxMaxId: _5!, unreadCount: _6!, unreadMentionsCount: _7!, unreadReactionsCount: _8!, notifySettings: _9!, pts: _10, draft: _11, folderId: _12) } else { return nil @@ -8922,13 +8926,13 @@ public extension Api { } public enum MessageReactions: TypeConstructorDescription { - case messageReactions(flags: Int32, results: [Api.ReactionCount], recentReactons: [Api.MessageUserReaction]?) + case messageReactions(flags: Int32, results: [Api.ReactionCount], recentReactions: [Api.MessagePeerReaction]?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .messageReactions(let flags, let results, let recentReactons): + case .messageReactions(let flags, let results, let recentReactions): if boxed { - buffer.appendInt32(142306870) + buffer.appendInt32(1328256121) } serializeInt32(flags, buffer: buffer, boxed: false) buffer.appendInt32(481674261) @@ -8937,8 +8941,8 @@ public extension Api { item.serialize(buffer, true) } if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(recentReactons!.count)) - for item in recentReactons! { + buffer.appendInt32(Int32(recentReactions!.count)) + for item in recentReactions! { item.serialize(buffer, true) }} break @@ -8947,8 +8951,8 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .messageReactions(let flags, let results, let recentReactons): - return ("messageReactions", [("flags", flags), ("results", results), ("recentReactons", recentReactons)]) + case .messageReactions(let flags, let results, let recentReactions): + return ("messageReactions", [("flags", flags), ("results", results), ("recentReactions", recentReactions)]) } } @@ -8959,15 +8963,15 @@ public extension Api { if let _ = reader.readInt32() { _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ReactionCount.self) } - var _3: [Api.MessageUserReaction]? + var _3: [Api.MessagePeerReaction]? if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageUserReaction.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessagePeerReaction.self) } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil if _c1 && _c2 && _c3 { - return Api.MessageReactions.messageReactions(flags: _1!, results: _2!, recentReactons: _3) + return Api.MessageReactions.messageReactions(flags: _1!, results: _2!, recentReactions: _3) } else { return nil @@ -20319,16 +20323,17 @@ public extension Api { } } - public enum MessageUserReaction: TypeConstructorDescription { - case messageUserReaction(userId: Int64, reaction: String) + public enum MessagePeerReaction: TypeConstructorDescription { + case messagePeerReaction(flags: Int32, peerId: Api.Peer, reaction: String) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .messageUserReaction(let userId, let reaction): + case .messagePeerReaction(let flags, let peerId, let reaction): if boxed { - buffer.appendInt32(-1826077446) + buffer.appendInt32(1370914559) } - serializeInt64(userId, buffer: buffer, boxed: false) + serializeInt32(flags, buffer: buffer, boxed: false) + peerId.serialize(buffer, true) serializeString(reaction, buffer: buffer, boxed: false) break } @@ -20336,20 +20341,25 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .messageUserReaction(let userId, let reaction): - return ("messageUserReaction", [("userId", userId), ("reaction", reaction)]) + case .messagePeerReaction(let flags, let peerId, let reaction): + return ("messagePeerReaction", [("flags", flags), ("peerId", peerId), ("reaction", reaction)]) } } - public static func parse_messageUserReaction(_ reader: BufferReader) -> MessageUserReaction? { - var _1: Int64? - _1 = reader.readInt64() - var _2: String? - _2 = parseString(reader) + public static func parse_messagePeerReaction(_ reader: BufferReader) -> MessagePeerReaction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: String? + _3 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageUserReaction.messageUserReaction(userId: _1!, reaction: _2!) + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessagePeerReaction.messagePeerReaction(flags: _1!, peerId: _2!, reaction: _3!) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api4.swift b/submodules/TelegramApi/Sources/Api4.swift index 8c014fef20..a5e27a13fd 100644 --- a/submodules/TelegramApi/Sources/Api4.swift +++ b/submodules/TelegramApi/Sources/Api4.swift @@ -4605,6 +4605,58 @@ public extension Api { return result }) } + + public static func translateText(flags: Int32, peer: Api.InputPeer?, msgId: Int32?, text: String?, fromLang: String?, toLang: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(617508334) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {peer!.serialize(buffer, true)} + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(msgId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeString(text!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {serializeString(fromLang!, buffer: buffer, boxed: false)} + serializeString(toLang, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.translateText", parameters: [("flags", flags), ("peer", peer), ("msgId", msgId), ("text", text), ("fromLang", fromLang), ("toLang", toLang)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.TranslatedText? in + let reader = BufferReader(buffer) + var result: Api.messages.TranslatedText? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.TranslatedText + } + return result + }) + } + + public static func getUnreadReactions(peer: Api.InputPeer, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-396644838) + peer.serialize(buffer, true) + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(addOffset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt32(maxId, buffer: buffer, boxed: false) + serializeInt32(minId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getUnreadReactions", parameters: [("peer", peer), ("offsetId", offsetId), ("addOffset", addOffset), ("limit", limit), ("maxId", maxId), ("minId", minId)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } + + public static func readReactions(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2099097129) + peer.serialize(buffer, true) + return (FunctionDescription(name: "messages.readReactions", parameters: [("peer", peer)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in + let reader = BufferReader(buffer) + var result: Api.messages.AffectedHistory? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AffectedHistory + } + return result + }) + } } public struct channels { public static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { diff --git a/submodules/TelegramCore/Sources/Account/Account.swift b/submodules/TelegramCore/Sources/Account/Account.swift index 3280d9669c..53a1f1c04a 100644 --- a/submodules/TelegramCore/Sources/Account/Account.swift +++ b/submodules/TelegramCore/Sources/Account/Account.swift @@ -1084,6 +1084,7 @@ public class Account { self.managedOperationsDisposable.add(managedLocalTypingActivities(activities: self.localInputActivityManager.allActivities(), postbox: self.postbox, network: self.network, accountPeerId: self.peerId).start()) self.managedOperationsDisposable.add(managedSynchronizeConsumeMessageContentOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start()) self.managedOperationsDisposable.add(managedConsumePersonalMessagesActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start()) + self.managedOperationsDisposable.add(managedReadReactionActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start()) self.managedOperationsDisposable.add(managedSynchronizeMarkAllUnseenPersonalMessagesOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start()) self.managedOperationsDisposable.add(managedApplyPendingMessageReactionsActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start()) self.managedOperationsDisposable.add(managedSynchronizeEmojiKeywordsOperations(postbox: self.postbox, network: self.network).start()) diff --git a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift index c27c3e73e0..e1ef1284e6 100644 --- a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift +++ b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift @@ -72,7 +72,7 @@ enum AccountStateMutationOperation { case ResetReadState(peerId: PeerId, namespace: MessageId.Namespace, maxIncomingReadId: MessageId.Id, maxOutgoingReadId: MessageId.Id, maxKnownId: MessageId.Id, count: Int32, markedUnread: Bool?) case ResetIncomingReadState(groupId: PeerGroupId, peerId: PeerId, namespace: MessageId.Namespace, maxIncomingReadId: MessageId.Id, count: Int32, pts: Int32) case UpdatePeerChatUnreadMark(PeerId, MessageId.Namespace, Bool) - case ResetMessageTagSummary(PeerId, MessageId.Namespace, Int32, MessageHistoryTagNamespaceCountValidityRange) + case ResetMessageTagSummary(PeerId, MessageTags, MessageId.Namespace, Int32, MessageHistoryTagNamespaceCountValidityRange) case ReadGroupFeedInbox(PeerGroupId, MessageIndex) case UpdateState(AuthorizedAccountState.State) case UpdateChannelState(PeerId, Int32) @@ -306,8 +306,8 @@ struct AccountMutableState { self.addOperation(.UpdatePeerChatUnreadMark(peerId, namespace, value)) } - mutating func resetMessageTagSummary(_ peerId: PeerId, namespace: MessageId.Namespace, count: Int32, range: MessageHistoryTagNamespaceCountValidityRange) { - self.addOperation(.ResetMessageTagSummary(peerId, namespace, count, range)) + mutating func resetMessageTagSummary(_ peerId: PeerId, tag: MessageTags, namespace: MessageId.Namespace, count: Int32, range: MessageHistoryTagNamespaceCountValidityRange) { + self.addOperation(.ResetMessageTagSummary(peerId, tag, namespace, count, range)) } mutating func updateState(_ state: AuthorizedAccountState.State) { diff --git a/submodules/TelegramCore/Sources/Account/AccountManager.swift b/submodules/TelegramCore/Sources/Account/AccountManager.swift index 70981a45b6..b4b6e2d91f 100644 --- a/submodules/TelegramCore/Sources/Account/AccountManager.swift +++ b/submodules/TelegramCore/Sources/Account/AccountManager.swift @@ -152,6 +152,7 @@ private var declaredEncodables: Void = { declareEncodable(TelegramMediaExpiredContent.self, f: { TelegramMediaExpiredContent(decoder: $0) }) declareEncodable(ConsumablePersonalMentionMessageAttribute.self, f: { ConsumablePersonalMentionMessageAttribute(decoder: $0) }) declareEncodable(ConsumePersonalMessageAction.self, f: { ConsumePersonalMessageAction(decoder: $0) }) + declareEncodable(ReadReactionAction.self, f: { ReadReactionAction(decoder: $0) }) declareEncodable(SynchronizeGroupedPeersOperation.self, f: { SynchronizeGroupedPeersOperation(decoder: $0) }) declareEncodable(TelegramDeviceContactImportedData.self, f: { TelegramDeviceContactImportedData(decoder: $0) }) declareEncodable(SecureFileMediaResource.self, f: { SecureFileMediaResource(decoder: $0) }) diff --git a/submodules/TelegramCore/Sources/ApiUtils/ReactionsMessageAttribute.swift b/submodules/TelegramCore/Sources/ApiUtils/ReactionsMessageAttribute.swift index 359fc61bc3..32c3f32a9e 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/ReactionsMessageAttribute.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/ReactionsMessageAttribute.swift @@ -18,8 +18,10 @@ extension ReactionsMessageAttribute { if let recentReactions = recentReactions { parsedRecentReactions = recentReactions.map { recentReaction -> ReactionsMessageAttribute.RecentPeer in switch recentReaction { - case let .messageUserReaction(userId, reaction): - return ReactionsMessageAttribute.RecentPeer(value: reaction, peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))) + case let .messagePeerReaction(flags, peerId, reaction): + let isLarge = (flags & (1 << 0)) != 0 + let isUnseen = (flags & (1 << 1)) != 0 + return ReactionsMessageAttribute.RecentPeer(value: reaction, isLarge: isLarge, isUnseen: isUnseen, peerId: peerId.peerId) } } } else { @@ -103,7 +105,7 @@ public func mergedMessageReactions(attributes: [MessageAttribute]) -> ReactionsM } } if let value = pending.value { - recentPeers.append(ReactionsMessageAttribute.RecentPeer(value: value, peerId: accountPeerId)) + recentPeers.append(ReactionsMessageAttribute.RecentPeer(value: value, isLarge: false, isUnseen: false, peerId: accountPeerId)) } } for i in (0 ..< reactions.count).reversed() { @@ -137,8 +139,10 @@ extension ReactionsMessageAttribute { if let recentReactions = recentReactions { parsedRecentReactions = recentReactions.map { recentReaction -> ReactionsMessageAttribute.RecentPeer in switch recentReaction { - case let .messageUserReaction(userId, reaction): - return ReactionsMessageAttribute.RecentPeer(value: reaction, peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))) + case let .messagePeerReaction(flags, peerId, reaction): + let isLarge = (flags & (1 << 0)) != 0 + let isUnseen = (flags & (1 << 1)) != 0 + return ReactionsMessageAttribute.RecentPeer(value: reaction, isLarge: isLarge, isUnseen: isUnseen, peerId: peerId.peerId) } } } else { diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index 20d48266fb..a99c979800 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -6,6 +6,7 @@ import TelegramApi public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute], media: [Media], textEntities: [MessageTextEntity]?, isPinned: Bool) -> (MessageTags, GlobalMessageTags) { var isSecret = false var isUnconsumedPersonalMention = false + var hasUnseenReactions = false for attribute in attributes { if let timerAttribute = attribute as? AutoclearTimeoutMessageAttribute { if timerAttribute.timeout > 0 && timerAttribute.timeout <= 60 { @@ -19,6 +20,8 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute], if !mentionAttribute.consumed { isUnconsumedPersonalMention = true } + } else if let attribute = attribute as? ReactionsMessageAttribute, attribute.hasUnseen { + hasUnseenReactions = true } } @@ -28,6 +31,9 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute], if isUnconsumedPersonalMention { tags.insert(.unseenPersonalMessage) } + if hasUnseenReactions { + tags.insert(.unseenReaction) + } if isPinned { tags.insert(.pinned) diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index 4efc5b7060..29c7106317 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -1646,7 +1646,7 @@ private func resolveMissingPeerChatInfos(network: Network, state: AccountMutable for dialog in dialogs { switch dialog { - case let .dialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, notifySettings, pts, _, folderId): + case let .dialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, notifySettings, pts, _, folderId): let peerId = peer.peerId updatedState.setNeedsHoleFromPreviousState(peerId: peerId, namespace: Namespaces.Message.Cloud, validateChannelPts: pts) @@ -1691,7 +1691,8 @@ private func resolveMissingPeerChatInfos(network: Network, state: AccountMutable updatedState.updateNotificationSettings(.peer(peer.peerId), notificationSettings: notificationSettings) updatedState.resetReadState(peer.peerId, namespace: Namespaces.Message.Cloud, maxIncomingReadId: readInboxMaxId, maxOutgoingReadId: readOutboxMaxId, maxKnownId: topMessage, count: unreadCount, markedUnread: nil) - updatedState.resetMessageTagSummary(peer.peerId, namespace: Namespaces.Message.Cloud, count: unreadMentionsCount, range: MessageHistoryTagNamespaceCountValidityRange(maxId: topMessage)) + updatedState.resetMessageTagSummary(peer.peerId, tag: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud, count: unreadMentionsCount, range: MessageHistoryTagNamespaceCountValidityRange(maxId: topMessage)) + updatedState.resetMessageTagSummary(peer.peerId, tag: .unseenReaction, namespace: Namespaces.Message.Cloud, count: unreadReactionsCount, range: MessageHistoryTagNamespaceCountValidityRange(maxId: topMessage)) updatedState.peerChatInfos[peer.peerId] = PeerChatInfo(notificationSettings: notificationSettings) if let pts = pts { channelStates[peer.peerId] = ChannelState(pts: pts, invalidatedPts: pts, synchronizedUntilMessageId: nil) @@ -1860,7 +1861,7 @@ private func resetChannels(network: Network, peers: [Peer], state: AccountMutabl var storeMessages: [StoreMessage] = [] var readStates: [PeerId: [MessageId.Namespace: PeerReadState]] = [:] - var mentionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary] = [:] + var mentionTagSummaries: [PeerId: (tag: MessageTags, summary: MessageHistoryTagNamespaceSummary)] = [:] var channelStates: [PeerId: AccountStateChannelState] = [:] var invalidateChannelStates: [PeerId: Int32] = [:] var channelSynchronizedUntilMessage: [PeerId: MessageId.Id] = [:] @@ -1879,12 +1880,13 @@ private func resetChannels(network: Network, peers: [Peer], state: AccountMutabl let apiTopMessage: Int32 let apiUnreadCount: Int32 let apiUnreadMentionsCount: Int32 + let apiUnreadReactionsCount: Int32 var apiChannelPts: Int32? let apiNotificationSettings: Api.PeerNotifySettings let apiMarkedUnread: Bool let groupId: PeerGroupId switch dialog { - case let .dialog(flags, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, peerNotificationSettings, pts, _, folderId): + case let .dialog(flags, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, peerNotificationSettings, pts, _, folderId): apiPeer = peer apiTopMessage = topMessage apiReadInboxMaxId = readInboxMaxId @@ -1892,6 +1894,7 @@ private func resetChannels(network: Network, peers: [Peer], state: AccountMutabl apiUnreadCount = unreadCount apiMarkedUnread = (flags & (1 << 3)) != 0 apiUnreadMentionsCount = unreadMentionsCount + apiUnreadReactionsCount = unreadReactionsCount apiNotificationSettings = peerNotificationSettings apiChannelPts = pts groupId = PeerGroupId(rawValue: folderId ?? 0) @@ -1908,7 +1911,8 @@ private func resetChannels(network: Network, peers: [Peer], state: AccountMutabl readStates[peerId]![Namespaces.Message.Cloud] = .idBased(maxIncomingReadId: apiReadInboxMaxId, maxOutgoingReadId: apiReadOutboxMaxId, maxKnownId: apiTopMessage, count: apiUnreadCount, markedUnread: apiMarkedUnread) if apiTopMessage != 0 { - mentionTagSummaries[peerId] = MessageHistoryTagNamespaceSummary(version: 1, count: apiUnreadMentionsCount, range: MessageHistoryTagNamespaceCountValidityRange(maxId: apiTopMessage)) + mentionTagSummaries[peerId] = (MessageTags.unseenPersonalMessage, MessageHistoryTagNamespaceSummary(version: 1, count: apiUnreadMentionsCount, range: MessageHistoryTagNamespaceCountValidityRange(maxId: apiTopMessage))) + mentionTagSummaries[peerId] = (MessageTags.unseenReaction, MessageHistoryTagNamespaceSummary(version: 1, count: apiUnreadReactionsCount, range: MessageHistoryTagNamespaceCountValidityRange(maxId: apiTopMessage))) } if let apiChannelPts = apiChannelPts { @@ -1966,7 +1970,7 @@ private func resetChannels(network: Network, peers: [Peer], state: AccountMutabl } for (peerId, tagSummary) in mentionTagSummaries { - updatedState.resetMessageTagSummary(peerId, namespace: Namespaces.Message.Cloud, count: tagSummary.count, range: tagSummary.range) + updatedState.resetMessageTagSummary(peerId, tag: tagSummary.tag, namespace: Namespaces.Message.Cloud, count: tagSummary.summary.count, range: tagSummary.summary.range) } for (peerId, channelState) in channelStates { @@ -2116,18 +2120,18 @@ private func pollChannel(network: Network, peer: Peer, state: AccountMutableStat case let .channelDifferenceTooLong(_, timeout, dialog, messages, chats, users): apiTimeout = timeout - var parameters: (peer: Api.Peer, pts: Int32, topMessage: Int32, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, unreadMentionsCount: Int32)? + var parameters: (peer: Api.Peer, pts: Int32, topMessage: Int32, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, unreadMentionsCount: Int32, unreadReactionsCount: Int32)? switch dialog { - case let .dialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, _, pts, _, _): + case let .dialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, _, pts, _, _): if let pts = pts { - parameters = (peer, pts, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount) + parameters = (peer, pts, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount) } case .dialogFolder: break } - if let (peer, pts, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount) = parameters { + if let (peer, pts, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount) = parameters { updatedState.updateChannelState(peer.peerId, pts: pts) updatedState.updateChannelInvalidationPts(peer.peerId, invalidationPts: pts) @@ -2161,7 +2165,8 @@ private func pollChannel(network: Network, peer: Peer, state: AccountMutableStat updatedState.resetReadState(peer.peerId, namespace: Namespaces.Message.Cloud, maxIncomingReadId: readInboxMaxId, maxOutgoingReadId: readOutboxMaxId, maxKnownId: topMessage, count: unreadCount, markedUnread: nil) - updatedState.resetMessageTagSummary(peer.peerId, namespace: Namespaces.Message.Cloud, count: unreadMentionsCount, range: MessageHistoryTagNamespaceCountValidityRange(maxId: topMessage)) + updatedState.resetMessageTagSummary(peer.peerId, tag: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud, count: unreadMentionsCount, range: MessageHistoryTagNamespaceCountValidityRange(maxId: topMessage)) + updatedState.resetMessageTagSummary(peer.peerId, tag: .unseenReaction, namespace: Namespaces.Message.Cloud, count: unreadReactionsCount, range: MessageHistoryTagNamespaceCountValidityRange(maxId: topMessage)) } else { assertionFailure() } @@ -2845,14 +2850,16 @@ func replayFinalState( } case let .UpdatePeerChatUnreadMark(peerId, namespace, value): transaction.applyMarkUnread(peerId: peerId, namespace: namespace, value: value, interactive: false) - case let .ResetMessageTagSummary(peerId, namespace, count, range): - transaction.replaceMessageTagSummary(peerId: peerId, tagMask: .unseenPersonalMessage, namespace: namespace, count: count, maxId: range.maxId) + case let .ResetMessageTagSummary(peerId, tag, namespace, count, range): + transaction.replaceMessageTagSummary(peerId: peerId, tagMask: tag, namespace: namespace, count: count, maxId: range.maxId) if count == 0 { - transaction.removeHole(peerId: peerId, namespace: namespace, space: .tag(.unseenPersonalMessage), range: 1 ... (Int32.max - 1)) - let ids = transaction.getMessageIndicesWithTag(peerId: peerId, namespace: namespace, tag: .unseenPersonalMessage).map({ $0.id }) - Logger.shared.log("State", "will call markUnseenPersonalMessage for \(ids.count) messages") - for id in ids { - markUnseenPersonalMessage(transaction: transaction, id: id, addSynchronizeAction: false) + transaction.removeHole(peerId: peerId, namespace: namespace, space: .tag(tag), range: 1 ... (Int32.max - 1)) + if tag == .unseenPersonalMessage { + let ids = transaction.getMessageIndicesWithTag(peerId: peerId, namespace: namespace, tag: tag).map({ $0.id }) + Logger.shared.log("State", "will call markUnseenPersonalMessage for \(ids.count) messages") + for id in ids { + markUnseenPersonalMessage(transaction: transaction, id: id, addSynchronizeAction: false) + } } } case let .UpdateState(innerState): @@ -3288,7 +3295,14 @@ func replayFinalState( } } - return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + var tags = currentMessage.tags + if updatedReactions.hasUnseen { + tags.insert(.unseenReaction) + } else { + tags.remove(.unseenReaction) + } + + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) }) if let generatedEvent = generatedEvent { addedReactionEvents.append(generatedEvent) diff --git a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift index 1c3435e00a..81877b0763 100644 --- a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift +++ b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift @@ -307,6 +307,7 @@ public final class AccountViewTracker { private var updatedUnsupportedMediaDisposables = DisposableDict() private var updatedSeenPersonalMessageIds = Set() + private var updatedReactionsSeenForMessageIds = Set() private var cachedDataContexts: [PeerId: PeerCachedDataContext] = [:] private var cachedChannelParticipantsContexts: [PeerId: CachedChannelParticipantsContext] = [:] @@ -1227,6 +1228,36 @@ public final class AccountViewTracker { } } + public func updateMarkReactionsSeenForMessageIds(messageIds: Set) { + self.queue.async { + let addedMessageIds: [MessageId] = Array(messageIds) + if !addedMessageIds.isEmpty { + if let account = self.account { + let _ = (account.postbox.transaction { transaction -> Void in + for id in addedMessageIds { + if let _ = transaction.getMessage(id) { + /*transaction.updateMessage(id, update: { currentMessage in + var attributes = currentMessage.attributes + loop: for j in 0 ..< attributes.count { + if let attribute = attributes[j] as? ConsumablePersonalMentionMessageAttribute { + attributes[j] = ConsumablePersonalMentionMessageAttribute(consumed: attribute.consumed, pending: true) + break loop + } + } + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + })*/ + + if transaction.getPendingMessageAction(type: .readReaction, id: id) == nil { + transaction.setPendingMessageAction(type: .readReaction, id: id, action: ReadReactionAction()) + } + } + } + }).start() + } + } + } + } + public func forceUpdateCachedPeerData(peerId: PeerId) { self.queue.async { let context: PeerCachedDataContext @@ -1756,23 +1787,45 @@ public final class AccountViewTracker { } } - public func unseenPersonalMessagesCount(peerId: PeerId) -> Signal { + public func unseenPersonalMessagesAndReactionCount(peerId: PeerId) -> Signal<(mentionCount: Int32, reactionCount: Int32), NoError> { if let account = self.account { - let pendingKey: PostboxViewKey = .pendingMessageActionsSummary(type: .consumeUnseenPersonalMessage, peerId: peerId, namespace: Namespaces.Message.Cloud) - let summaryKey: PostboxViewKey = .historyTagSummaryView(tag: .unseenPersonalMessage, peerId: peerId, namespace: Namespaces.Message.Cloud) - return account.postbox.combinedView(keys: [pendingKey, summaryKey]) - |> map { views -> Int32 in - var count: Int32 = 0 - if let view = views.views[pendingKey] as? PendingMessageActionsSummaryView { - count -= view.count + let pendingMentionsKey: PostboxViewKey = .pendingMessageActionsSummary(type: .consumeUnseenPersonalMessage, peerId: peerId, namespace: Namespaces.Message.Cloud) + let summaryMentionsKey: PostboxViewKey = .historyTagSummaryView(tag: .unseenPersonalMessage, peerId: peerId, namespace: Namespaces.Message.Cloud) + + let pendingReactionsKey: PostboxViewKey = .pendingMessageActionsSummary(type: .readReaction, peerId: peerId, namespace: Namespaces.Message.Cloud) + let summaryReactionsKey: PostboxViewKey = .historyTagSummaryView(tag: .unseenReaction, peerId: peerId, namespace: Namespaces.Message.Cloud) + + return account.postbox.combinedView(keys: [pendingMentionsKey, summaryMentionsKey, pendingReactionsKey, summaryReactionsKey]) + |> map { views -> (mentionCount: Int32, reactionCount: Int32) in + var mentionCount: Int32 = 0 + if let view = views.views[pendingMentionsKey] as? PendingMessageActionsSummaryView { + mentionCount -= view.count } - if let view = views.views[summaryKey] as? MessageHistoryTagSummaryView { + if let view = views.views[summaryMentionsKey] as? MessageHistoryTagSummaryView { if let unseenCount = view.count { - count += unseenCount + mentionCount += unseenCount } } - return max(0, count) - } |> distinctUntilChanged + var reactionCount: Int32 = 0 + if let view = views.views[pendingReactionsKey] as? PendingMessageActionsSummaryView { + reactionCount -= view.count + } + if let view = views.views[summaryReactionsKey] as? MessageHistoryTagSummaryView { + if let unseenCount = view.count { + reactionCount += unseenCount + } + } + return (max(0, mentionCount), max(0, reactionCount)) + } + |> distinctUntilChanged(isEqual: { lhs, rhs in + if lhs.mentionCount != rhs.mentionCount { + return false + } + if lhs.reactionCount != rhs.reactionCount { + return false + } + return true + }) } else { return .never() } @@ -1802,7 +1855,29 @@ public final class AccountViewTracker { public func tailChatListView(groupId: PeerGroupId, filterPredicate: ChatListFilterPredicate? = nil, count: Int) -> Signal<(ChatListView, ViewUpdateType), NoError> { if let account = self.account { - return self.wrappedChatListView(signal: account.postbox.tailChatListView(groupId: groupId, filterPredicate: filterPredicate, count: count, summaryComponents: ChatListEntrySummaryComponents(tagSummary: ChatListEntryMessageTagSummaryComponent(tag: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud), actionsSummary: ChatListEntryPendingMessageActionsSummaryComponent(type: PendingMessageActionType.consumeUnseenPersonalMessage, namespace: Namespaces.Message.Cloud)))) + return self.wrappedChatListView(signal: account.postbox.tailChatListView( + groupId: groupId, + filterPredicate: filterPredicate, + count: count, + summaryComponents: ChatListEntrySummaryComponents( + components: [ + ChatListEntryMessageTagSummaryKey( + tag: .unseenPersonalMessage, + actionType: PendingMessageActionType.consumeUnseenPersonalMessage + ): ChatListEntrySummaryComponents.Component( + tagSummary: ChatListEntryMessageTagSummaryComponent(namespace: Namespaces.Message.Cloud), + actionsSummary: ChatListEntryPendingMessageActionsSummaryComponent(namespace: Namespaces.Message.Cloud) + ), + ChatListEntryMessageTagSummaryKey( + tag: .unseenReaction, + actionType: PendingMessageActionType.readReaction + ): ChatListEntrySummaryComponents.Component( + tagSummary: ChatListEntryMessageTagSummaryComponent(namespace: Namespaces.Message.Cloud), + actionsSummary: ChatListEntryPendingMessageActionsSummaryComponent(namespace: Namespaces.Message.Cloud) + ) + ] + ) + )) } else { return .never() } @@ -1810,7 +1885,30 @@ public final class AccountViewTracker { public func aroundChatListView(groupId: PeerGroupId, filterPredicate: ChatListFilterPredicate? = nil, index: ChatListIndex, count: Int) -> Signal<(ChatListView, ViewUpdateType), NoError> { if let account = self.account { - return self.wrappedChatListView(signal: account.postbox.aroundChatListView(groupId: groupId, filterPredicate: filterPredicate, index: index, count: count, summaryComponents: ChatListEntrySummaryComponents(tagSummary: ChatListEntryMessageTagSummaryComponent(tag: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud), actionsSummary: ChatListEntryPendingMessageActionsSummaryComponent(type: PendingMessageActionType.consumeUnseenPersonalMessage, namespace: Namespaces.Message.Cloud)))) + return self.wrappedChatListView(signal: account.postbox.aroundChatListView( + groupId: groupId, + filterPredicate: filterPredicate, + index: index, + count: count, + summaryComponents: ChatListEntrySummaryComponents( + components: [ + ChatListEntryMessageTagSummaryKey( + tag: .unseenPersonalMessage, + actionType: PendingMessageActionType.consumeUnseenPersonalMessage + ): ChatListEntrySummaryComponents.Component( + tagSummary: ChatListEntryMessageTagSummaryComponent(namespace: Namespaces.Message.Cloud), + actionsSummary: ChatListEntryPendingMessageActionsSummaryComponent(namespace: Namespaces.Message.Cloud) + ), + ChatListEntryMessageTagSummaryKey( + tag: .unseenReaction, + actionType: PendingMessageActionType.readReaction + ): ChatListEntrySummaryComponents.Component( + tagSummary: ChatListEntryMessageTagSummaryComponent(namespace: Namespaces.Message.Cloud), + actionsSummary: ChatListEntryPendingMessageActionsSummaryComponent(namespace: Namespaces.Message.Cloud) + ) + ] + ) + )) } else { return .never() } diff --git a/submodules/TelegramCore/Sources/State/FetchChatList.swift b/submodules/TelegramCore/Sources/State/FetchChatList.swift index addfcbae94..bcf6be922b 100644 --- a/submodules/TelegramCore/Sources/State/FetchChatList.swift +++ b/submodules/TelegramCore/Sources/State/FetchChatList.swift @@ -18,6 +18,7 @@ struct ParsedDialogs { let notificationSettings: [PeerId: PeerNotificationSettings] let readStates: [PeerId: [MessageId.Namespace: PeerReadState]] let mentionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary] + let reactionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary] let channelStates: [PeerId: Int32] let topMessageIds: [PeerId: MessageId] let storeMessages: [StoreMessage] @@ -49,6 +50,7 @@ private func parseDialogs(apiDialogs: [Api.Dialog], apiMessages: [Api.Message], var notificationSettings: [PeerId: PeerNotificationSettings] = [:] var readStates: [PeerId: [MessageId.Namespace: PeerReadState]] = [:] var mentionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary] = [:] + var reactionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary] = [:] var channelStates: [PeerId: Int32] = [:] var topMessageIds: [PeerId: MessageId] = [:] @@ -81,10 +83,11 @@ private func parseDialogs(apiDialogs: [Api.Dialog], apiMessages: [Api.Message], let apiUnreadCount: Int32 let apiMarkedUnread: Bool let apiUnreadMentionsCount: Int32 + let apiUnreadReactionsCount: Int32 var apiChannelPts: Int32? let apiNotificationSettings: Api.PeerNotifySettings switch dialog { - case let .dialog(flags, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, peerNotificationSettings, pts, _, _): + case let .dialog(flags, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, peerNotificationSettings, pts, _, _): if let peer = peers[peer.peerId] { var isExluded = false if let group = peer as? TelegramGroup { @@ -103,6 +106,7 @@ private func parseDialogs(apiDialogs: [Api.Dialog], apiMessages: [Api.Message], apiUnreadCount = unreadCount apiMarkedUnread = (flags & (1 << 3)) != 0 apiUnreadMentionsCount = unreadMentionsCount + apiUnreadReactionsCount = unreadReactionsCount apiNotificationSettings = peerNotificationSettings apiChannelPts = pts let isPinned = (flags & (1 << 2)) != 0 @@ -126,6 +130,7 @@ private func parseDialogs(apiDialogs: [Api.Dialog], apiMessages: [Api.Message], if apiTopMessage != 0 { mentionTagSummaries[peerId] = MessageHistoryTagNamespaceSummary(version: 1, count: apiUnreadMentionsCount, range: MessageHistoryTagNamespaceCountValidityRange(maxId: apiTopMessage)) + reactionTagSummaries[peerId] = MessageHistoryTagNamespaceSummary(version: 1, count: apiUnreadReactionsCount, range: MessageHistoryTagNamespaceCountValidityRange(maxId: apiTopMessage)) topMessageIds[peerId] = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: apiTopMessage) } @@ -173,6 +178,7 @@ private func parseDialogs(apiDialogs: [Api.Dialog], apiMessages: [Api.Message], notificationSettings: notificationSettings, readStates: readStates, mentionTagSummaries: mentionTagSummaries, + reactionTagSummaries: reactionTagSummaries, channelStates: channelStates, topMessageIds: topMessageIds, storeMessages: storeMessages, @@ -189,6 +195,7 @@ struct FetchedChatList { let notificationSettings: [PeerId: PeerNotificationSettings] let readStates: [PeerId: [MessageId.Namespace: PeerReadState]] let mentionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary] + let reactionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary] let channelStates: [PeerId: Int32] let storeMessages: [StoreMessage] let topMessageIds: [PeerId: MessageId] @@ -294,6 +301,7 @@ func fetchChatList(postbox: Postbox, network: Network, location: FetchChatListLo var notificationSettings: [PeerId: PeerNotificationSettings] = [:] var readStates: [PeerId: [MessageId.Namespace: PeerReadState]] = [:] var mentionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary] = [:] + var reactionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary] = [:] var channelStates: [PeerId: Int32] = [:] var storeMessages: [StoreMessage] = [] var topMessageIds: [PeerId: MessageId] = [:] @@ -303,6 +311,7 @@ func fetchChatList(postbox: Postbox, network: Network, location: FetchChatListLo notificationSettings.merge(parsedRemoteChats.notificationSettings, uniquingKeysWith: { _, updated in updated }) readStates.merge(parsedRemoteChats.readStates, uniquingKeysWith: { _, updated in updated }) mentionTagSummaries.merge(parsedRemoteChats.mentionTagSummaries, uniquingKeysWith: { _, updated in updated }) + reactionTagSummaries.merge(parsedRemoteChats.reactionTagSummaries, uniquingKeysWith: { _, updated in updated }) channelStates.merge(parsedRemoteChats.channelStates, uniquingKeysWith: { _, updated in updated }) storeMessages.append(contentsOf: parsedRemoteChats.storeMessages) topMessageIds.merge(parsedRemoteChats.topMessageIds, uniquingKeysWith: { _, updated in updated }) @@ -313,6 +322,7 @@ func fetchChatList(postbox: Postbox, network: Network, location: FetchChatListLo notificationSettings.merge(parsedPinnedChats.notificationSettings, uniquingKeysWith: { _, updated in updated }) readStates.merge(parsedPinnedChats.readStates, uniquingKeysWith: { _, updated in updated }) mentionTagSummaries.merge(parsedPinnedChats.mentionTagSummaries, uniquingKeysWith: { _, updated in updated }) + reactionTagSummaries.merge(parsedPinnedChats.reactionTagSummaries, uniquingKeysWith: { _, updated in updated }) channelStates.merge(parsedPinnedChats.channelStates, uniquingKeysWith: { _, updated in updated }) storeMessages.append(contentsOf: parsedPinnedChats.storeMessages) topMessageIds.merge(parsedPinnedChats.topMessageIds, uniquingKeysWith: { _, updated in updated }) @@ -335,6 +345,7 @@ func fetchChatList(postbox: Postbox, network: Network, location: FetchChatListLo notificationSettings.merge(folderChats.notificationSettings, uniquingKeysWith: { _, updated in updated }) readStates.merge(folderChats.readStates, uniquingKeysWith: { _, updated in updated }) mentionTagSummaries.merge(folderChats.mentionTagSummaries, uniquingKeysWith: { _, updated in updated }) + reactionTagSummaries.merge(folderChats.reactionTagSummaries, uniquingKeysWith: { _, updated in updated }) channelStates.merge(folderChats.channelStates, uniquingKeysWith: { _, updated in updated }) storeMessages.append(contentsOf: folderChats.storeMessages) } @@ -368,6 +379,7 @@ func fetchChatList(postbox: Postbox, network: Network, location: FetchChatListLo notificationSettings: notificationSettings, readStates: readStates, mentionTagSummaries: mentionTagSummaries, + reactionTagSummaries: reactionTagSummaries, channelStates: channelStates, storeMessages: storeMessages, topMessageIds: topMessageIds, diff --git a/submodules/TelegramCore/Sources/State/Holes.swift b/submodules/TelegramCore/Sources/State/Holes.swift index 61f25a6b04..8ca1cbe565 100644 --- a/submodules/TelegramCore/Sources/State/Holes.swift +++ b/submodules/TelegramCore/Sources/State/Holes.swift @@ -341,6 +341,54 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH } request = source.request(Api.functions.messages.getUnreadMentions(peer: inputPeer, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId)) + } else if tag == .unseenReaction { + let offsetId: Int32 + let addOffset: Int32 + let selectedLimit = count + let maxId: Int32 + let minId: Int32 + + switch direction { + case let .range(start, end): + if start.id <= end.id { + offsetId = start.id <= 1 ? 1 : (start.id - 1) + addOffset = Int32(-selectedLimit) + maxId = end.id + minId = start.id - 1 + + let rangeStartId = start.id + let rangeEndId = min(end.id, Int32.max - 1) + if rangeStartId <= rangeEndId { + minMaxRange = rangeStartId ... rangeEndId + } else { + minMaxRange = rangeStartId ... rangeStartId + assertionFailure() + } + } else { + offsetId = start.id == Int32.max ? start.id : (start.id + 1) + addOffset = 0 + maxId = start.id == Int32.max ? start.id : (start.id + 1) + minId = end.id + + let rangeStartId = end.id + let rangeEndId = min(start.id, Int32.max - 1) + if rangeStartId <= rangeEndId { + minMaxRange = rangeStartId ... rangeEndId + } else { + minMaxRange = rangeStartId ... rangeStartId + assertionFailure() + } + } + case let .aroundId(id): + offsetId = id.id + addOffset = Int32(-selectedLimit / 2) + maxId = Int32.max + minId = 1 + + minMaxRange = 1 ... Int32.max - 1 + } + + request = source.request(Api.functions.messages.getUnreadReactions(peer: inputPeer, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId)) } else if tag == .liveLocation { let selectedLimit = count @@ -661,6 +709,9 @@ func fetchChatListHole(postbox: Postbox, network: Network, accountPeerId: PeerId for (peerId, summary) in fetchedChats.mentionTagSummaries { transaction.replaceMessageTagSummary(peerId: peerId, tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud, count: summary.count, maxId: summary.range.maxId) } + for (peerId, summary) in fetchedChats.reactionTagSummaries { + transaction.replaceMessageTagSummary(peerId: peerId, tagMask: .unseenReaction, namespace: Namespaces.Message.Cloud, count: summary.count, maxId: summary.range.maxId) + } for (groupId, summary) in fetchedChats.folderSummaries { transaction.resetPeerGroupSummary(groupId: groupId, namespace: Namespaces.Message.Cloud, summary: summary) diff --git a/submodules/TelegramCore/Sources/State/ManagedConsumePersonalMessagesActions.swift b/submodules/TelegramCore/Sources/State/ManagedConsumePersonalMessagesActions.swift index 6b124c2cb7..5228ddd67e 100644 --- a/submodules/TelegramCore/Sources/State/ManagedConsumePersonalMessagesActions.swift +++ b/submodules/TelegramCore/Sources/State/ManagedConsumePersonalMessagesActions.swift @@ -76,11 +76,11 @@ private final class ManagedConsumePersonalMessagesActionsHelper { } } -private func withTakenAction(postbox: Postbox, type: PendingMessageActionType, id: MessageId, _ f: @escaping (Transaction, PendingMessageActionsEntry?) -> Signal) -> Signal { +private func withTakenAction(postbox: Postbox, type: PendingMessageActionType, actionType: T.Type, id: MessageId, _ f: @escaping (Transaction, PendingMessageActionsEntry?) -> Signal) -> Signal { return postbox.transaction { transaction -> Signal in var result: PendingMessageActionsEntry? - if let action = transaction.getPendingMessageAction(type: type, id: id) as? ConsumePersonalMessageAction { + if let action = transaction.getPendingMessageAction(type: type, id: id) as? T { result = PendingMessageActionsEntry(id: id, action: action) } @@ -113,7 +113,7 @@ func managedConsumePersonalMessagesActions(postbox: Postbox, network: Network, s } for (entry, disposable) in beginOperations { - let signal = withTakenAction(postbox: postbox, type: .consumeUnseenPersonalMessage, id: entry.id, { transaction, entry -> Signal in + let signal = withTakenAction(postbox: postbox, type: .consumeUnseenPersonalMessage, actionType: ConsumePersonalMessageAction.self, id: entry.id, { transaction, entry -> Signal in if let entry = entry { if let _ = entry.action as? ConsumePersonalMessageAction { return synchronizeConsumeMessageContents(transaction: transaction, postbox: postbox, network: network, stateManager: stateManager, id: entry.id) @@ -151,6 +151,69 @@ func managedConsumePersonalMessagesActions(postbox: Postbox, network: Network, s } } +func managedReadReactionActions(postbox: Postbox, network: Network, stateManager: AccountStateManager) -> Signal { + return Signal { _ in + let helper = Atomic(value: ManagedConsumePersonalMessagesActionsHelper()) + + let actionsKey = PostboxViewKey.pendingMessageActions(type: .readReaction) + let invalidateKey = PostboxViewKey.invalidatedMessageHistoryTagSummaries(tagMask: .unseenReaction, namespace: Namespaces.Message.Cloud) + let disposable = postbox.combinedView(keys: [actionsKey, invalidateKey]).start(next: { view in + var entries: [PendingMessageActionsEntry] = [] + var invalidateEntries = Set() + if let v = view.views[actionsKey] as? PendingMessageActionsView { + entries = v.entries + } + if let v = view.views[invalidateKey] as? InvalidatedMessageHistoryTagSummariesView { + invalidateEntries = v.entries + } + + let (disposeOperations, beginOperations, beginValidateOperations) = helper.with { helper -> (disposeOperations: [Disposable], beginOperations: [(PendingMessageActionsEntry, MetaDisposable)], beginValidateOperations: [(InvalidatedMessageHistoryTagsSummaryEntry, MetaDisposable)]) in + return helper.update(entries: entries, invalidateEntries: invalidateEntries) + } + + for disposable in disposeOperations { + disposable.dispose() + } + + for (entry, disposable) in beginOperations { + let signal = withTakenAction(postbox: postbox, type: .readReaction, actionType: ReadReactionAction.self, id: entry.id, { transaction, entry -> Signal in + if let entry = entry { + if let _ = entry.action as? ReadReactionAction { + return synchronizeReadMessageReactions(transaction: transaction, postbox: postbox, network: network, stateManager: stateManager, id: entry.id) + } else { + assertionFailure() + } + } + return .complete() + }) + |> then(postbox.transaction { transaction -> Void in + transaction.setPendingMessageAction(type: .readReaction, id: entry.id, action: nil) + }) + + disposable.set(signal.start()) + } + + for (entry, disposable) in beginValidateOperations { + let signal = synchronizeUnseenReactionsTag(postbox: postbox, network: network, entry: entry) + |> then(postbox.transaction { transaction -> Void in + transaction.removeInvalidatedMessageHistoryTagsSummaryEntry(entry) + }) + disposable.set(signal.start()) + } + }) + + return ActionDisposable { + let disposables = helper.with { helper -> [Disposable] in + return helper.reset() + } + for disposable in disposables { + disposable.dispose() + } + disposable.dispose() + } + } +} + private func synchronizeConsumeMessageContents(transaction: Transaction, postbox: Postbox, network: Network, stateManager: AccountStateManager, id: MessageId) -> Signal { if id.peerId.namespace == Namespaces.Peer.CloudUser || id.peerId.namespace == Namespaces.Peer.CloudGroup { return network.request(Api.functions.messages.readMessageContents(id: [id.id])) @@ -219,6 +282,44 @@ private func synchronizeConsumeMessageContents(transaction: Transaction, postbox } } +private func synchronizeReadMessageReactions(transaction: Transaction, postbox: Postbox, network: Network, stateManager: AccountStateManager, id: MessageId) -> Signal { + guard let inputPeer = transaction.getPeer(id.peerId).flatMap(apiInputPeer) else { + return .complete() + } + return network.request(Api.functions.messages.readReactions(peer: inputPeer)) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal in + if let result = result { + switch result { + case let .affectedHistory(pts, ptsCount, _): + stateManager.addUpdateGroups([.updatePts(pts: pts, ptsCount: ptsCount)]) + } + } + return postbox.transaction { transaction -> Void in + transaction.setPendingMessageAction(type: .readReaction, id: id, action: nil) + transaction.updateMessage(id, update: { currentMessage in + var storeForwardInfo: StoreMessageForwardInfo? + if let forwardInfo = currentMessage.forwardInfo { + storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType, flags: forwardInfo.flags) + } + var attributes = currentMessage.attributes + loop: for j in 0 ..< attributes.count { + if let attribute = attributes[j] as? ReactionsMessageAttribute, attribute.hasUnseen { + attributes[j] = attribute.withAllSeen() + break loop + } + } + var updatedTags = currentMessage.tags + updatedTags.remove(.unseenReaction) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: updatedTags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + }) + } + } +} + private func synchronizeUnseenPersonalMentionsTag(postbox: Postbox, network: Network, entry: InvalidatedMessageHistoryTagsSummaryEntry) -> Signal { return postbox.transaction { transaction -> Signal in if let peer = transaction.getPeer(entry.key.peerId), let inputPeer = apiInputPeer(peer) { @@ -235,7 +336,7 @@ private func synchronizeUnseenPersonalMentionsTag(postbox: Postbox, network: Net let apiTopMessage: Int32 let apiUnreadMentionsCount: Int32 switch dialog { - case let .dialog(_, _, topMessage, _, _, _, unreadMentionsCount, _, _, _, _): + case let .dialog(_, _, topMessage, _, _, _, unreadMentionsCount, _, _, _, _, _): apiTopMessage = topMessage apiUnreadMentionsCount = unreadMentionsCount @@ -260,3 +361,45 @@ private func synchronizeUnseenPersonalMentionsTag(postbox: Postbox, network: Net } } |> switchToLatest } + +private func synchronizeUnseenReactionsTag(postbox: Postbox, network: Network, entry: InvalidatedMessageHistoryTagsSummaryEntry) -> Signal { + return postbox.transaction { transaction -> Signal in + if let peer = transaction.getPeer(entry.key.peerId), let inputPeer = apiInputPeer(peer) { + return network.request(Api.functions.messages.getPeerDialogs(peers: [.inputDialogPeer(peer: inputPeer)])) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal in + if let result = result { + switch result { + case let .peerDialogs(dialogs, _, _, _, _): + if let dialog = dialogs.filter({ $0.peerId == entry.key.peerId }).first { + let apiTopMessage: Int32 + let apiUnreadReactionsCount: Int32 + switch dialog { + case let .dialog(_, _, topMessage, _, _, _, _, unreadReactionsCount, _, _, _, _): + apiTopMessage = topMessage + apiUnreadReactionsCount = unreadReactionsCount + + case .dialogFolder: + assertionFailure() + return .complete() + } + + return postbox.transaction { transaction -> Void in + transaction.replaceMessageTagSummary(peerId: entry.key.peerId, tagMask: entry.key.tagMask, namespace: entry.key.namespace, count: apiUnreadReactionsCount, maxId: apiTopMessage) + } + } else { + return .complete() + } + } + } else { + return .complete() + } + } + } else { + return .complete() + } + } |> switchToLatest +} diff --git a/submodules/TelegramCore/Sources/State/ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift b/submodules/TelegramCore/Sources/State/ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift index ccee06a92a..46dd09621c 100644 --- a/submodules/TelegramCore/Sources/State/ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift +++ b/submodules/TelegramCore/Sources/State/ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift @@ -232,3 +232,31 @@ func markUnseenPersonalMessage(transaction: Transaction, id: MessageId, addSynch } } } + +func markUnseenReactionMessage(transaction: Transaction, id: MessageId, addSynchronizeAction: Bool) { + if let message = transaction.getMessage(id) { + var consume = false + inner: for attribute in message.attributes { + if let attribute = attribute as? ReactionsMessageAttribute, !attribute.hasUnseen { + consume = true + break inner + } + } + if consume { + transaction.updateMessage(id, update: { currentMessage in + var attributes = currentMessage.attributes + loop: for j in 0 ..< attributes.count { + if let attribute = attributes[j] as? ReactionsMessageAttribute { + attributes[j] = attribute.withAllSeen() + break loop + } + } + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + }) + + if addSynchronizeAction { + transaction.setPendingMessageAction(type: .readReaction, id: id, action: ReadReactionAction()) + } + } + } +} diff --git a/submodules/TelegramCore/Sources/State/ManagedSynchronizePinnedChatsOperations.swift b/submodules/TelegramCore/Sources/State/ManagedSynchronizePinnedChatsOperations.swift index 0c45508c65..ef5528b424 100644 --- a/submodules/TelegramCore/Sources/State/ManagedSynchronizePinnedChatsOperations.swift +++ b/submodules/TelegramCore/Sources/State/ManagedSynchronizePinnedChatsOperations.swift @@ -163,7 +163,7 @@ private func synchronizePinnedChats(transaction: Transaction, postbox: Postbox, var apiChannelPts: Int32? let apiNotificationSettings: Api.PeerNotifySettings switch dialog { - case let .dialog(flags, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, _, peerNotificationSettings, pts, _, _): + case let .dialog(flags, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, _, _, peerNotificationSettings, pts, _, _): apiPeer = peer apiTopMessage = topMessage apiReadInboxMaxId = readInboxMaxId diff --git a/submodules/TelegramCore/Sources/State/MessageReactions.swift b/submodules/TelegramCore/Sources/State/MessageReactions.swift index d964dc54e0..416599b48b 100644 --- a/submodules/TelegramCore/Sources/State/MessageReactions.swift +++ b/submodules/TelegramCore/Sources/State/MessageReactions.swift @@ -4,7 +4,7 @@ import SwiftSignalKit import TelegramApi import MtProtoKit -public func updateMessageReactionsInteractively(account: Account, messageId: MessageId, reaction: String?) -> Signal { +public func updateMessageReactionsInteractively(account: Account, messageId: MessageId, reaction: String?, isLarge: Bool) -> Signal { return account.postbox.transaction { transaction -> Void in transaction.setPendingMessageAction(type: .updateReaction, id: messageId, action: UpdateMessageReactionsAction()) transaction.updateMessage(messageId, update: { currentMessage in @@ -19,7 +19,7 @@ public func updateMessageReactionsInteractively(account: Account, messageId: Mes break loop } } - attributes.append(PendingReactionsMessageAttribute(accountPeerId: account.peerId, value: reaction)) + attributes.append(PendingReactionsMessageAttribute(accountPeerId: account.peerId, value: reaction, isLarge: isLarge)) return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) }) } @@ -31,7 +31,7 @@ private enum RequestUpdateMessageReactionError { } private func requestUpdateMessageReaction(postbox: Postbox, network: Network, stateManager: AccountStateManager, messageId: MessageId) -> Signal { - return postbox.transaction { transaction -> (Peer, String?)? in + return postbox.transaction { transaction -> (Peer, String?, Bool)? in guard let peer = transaction.getPeer(messageId.peerId) else { return nil } @@ -39,17 +39,19 @@ private func requestUpdateMessageReaction(postbox: Postbox, network: Network, st return nil } var value: String? + var isLarge: Bool = false for attribute in message.attributes { if let attribute = attribute as? PendingReactionsMessageAttribute { value = attribute.value + isLarge = attribute.isLarge break } } - return (peer, value) + return (peer, value, isLarge) } |> castError(RequestUpdateMessageReactionError.self) |> mapToSignal { peerAndValue in - guard let (peer, value) = peerAndValue else { + guard let (peer, value, isLarge) = peerAndValue else { return .fail(.generic) } guard let inputPeer = apiInputPeer(peer) else { @@ -58,7 +60,16 @@ private func requestUpdateMessageReaction(postbox: Postbox, network: Network, st if messageId.namespace != Namespaces.Message.Cloud { return .fail(.generic) } - let signal: Signal = network.request(Api.functions.messages.sendReaction(flags: value == nil ? 0 : 1, peer: inputPeer, msgId: messageId.id, reaction: value)) + + var flags: Int32 = 0 + if value != nil { + flags |= 1 << 0 + if isLarge { + flags |= 1 << 1 + } + } + + let signal: Signal = network.request(Api.functions.messages.sendReaction(flags: flags, peer: inputPeer, msgId: messageId.id, reaction: value)) |> mapError { _ -> RequestUpdateMessageReactionError in return .generic } @@ -382,7 +393,7 @@ public final class EngineMessageReactionListContext { |> mapToSignal { result -> Signal in return account.postbox.transaction { transaction -> InternalState in switch result { - case let .messageReactionsList(_, count, reactions, users, nextOffset): + case let .messageReactionsList(_, count, reactions, chats, users, nextOffset): var peers: [Peer] = [] var peerPresences: [PeerId: PeerPresence] = [:] @@ -393,6 +404,11 @@ public final class EngineMessageReactionListContext { peerPresences[telegramUser.id] = presence } } + for chat in chats { + if let peer = parseTelegramGroupOrChannel(chat: chat) { + peers.append(peer) + } + } updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in return updated @@ -402,8 +418,8 @@ public final class EngineMessageReactionListContext { var items: [EngineMessageReactionListContext.Item] = [] for reaction in reactions { switch reaction { - case let .messageUserReaction(userId, reaction): - if let peer = transaction.getPeer(PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))) { + case let .messagePeerReaction(_, peer, reaction): + if let peer = transaction.getPeer(peer.peerId) { items.append(EngineMessageReactionListContext.Item(peer: EnginePeer(peer), reaction: reaction)) } } diff --git a/submodules/TelegramCore/Sources/State/Serialization.swift b/submodules/TelegramCore/Sources/State/Serialization.swift index cc3ce23ec2..13969ad1e9 100644 --- a/submodules/TelegramCore/Sources/State/Serialization.swift +++ b/submodules/TelegramCore/Sources/State/Serialization.swift @@ -210,7 +210,7 @@ public class BoxedMessage: NSObject { public class Serialization: NSObject, MTSerialization { public func currentLayer() -> UInt { - return 137 + return 138 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/submodules/TelegramCore/Sources/State/SynchronizePeerReadState.swift b/submodules/TelegramCore/Sources/State/SynchronizePeerReadState.swift index cdeb66041a..37ffdbb01f 100644 --- a/submodules/TelegramCore/Sources/State/SynchronizePeerReadState.swift +++ b/submodules/TelegramCore/Sources/State/SynchronizePeerReadState.swift @@ -87,7 +87,7 @@ private func dialogReadState(network: Network, postbox: Postbox, peerId: PeerId) let apiMarkedUnread: Bool var apiChannelPts: Int32 = 0 switch dialog { - case let .dialog(flags, _, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, _, _, pts, _, _): + case let .dialog(flags, _, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, _, _, _, pts, _, _): apiTopMessage = topMessage apiReadInboxMaxId = readInboxMaxId apiReadOutboxMaxId = readOutboxMaxId diff --git a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift index ad371823e9..416aa4d2c0 100644 --- a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift +++ b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift @@ -181,7 +181,7 @@ extension Api.Peer { extension Api.Dialog { var peerId: PeerId? { switch self { - case let .dialog(_, peer, _, _, _, _, _, _, _, _, _): + case let .dialog(_, peer, _, _, _, _, _, _, _, _, _, _): return peer.peerId case .dialogFolder: return nil diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_ConsumePersonalMessageAction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_ConsumePersonalMessageAction.swift index 1ac5ce6a2e..0d9dfd4632 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_ConsumePersonalMessageAction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_ConsumePersonalMessageAction.swift @@ -19,3 +19,22 @@ public final class ConsumePersonalMessageAction: PendingMessageActionData { } } } + +public final class ReadReactionAction: PendingMessageActionData { + public init() { + } + + public init(decoder: PostboxDecoder) { + } + + public func encode(_ encoder: PostboxEncoder) { + } + + public func isEqual(to: PendingMessageActionData) -> Bool { + if let _ = to as? ReadReactionAction { + return true + } else { + return false + } + } +} diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift index bc1b8723cb..8c69db3f22 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift @@ -108,8 +108,9 @@ public extension MessageTags { static let photo = MessageTags(rawValue: 1 << 8) static let video = MessageTags(rawValue: 1 << 9) static let pinned = MessageTags(rawValue: 1 << 10) + static let unseenReaction = MessageTags(rawValue: 1 << 11) - static let all: MessageTags = [.photoOrVideo, .file, .music, .webPage, .voiceOrInstantVideo, .unseenPersonalMessage, .liveLocation, .gif, .photo, .video, .pinned] + static let all: MessageTags = [.photoOrVideo, .file, .music, .webPage, .voiceOrInstantVideo, .unseenPersonalMessage, .liveLocation, .gif, .photo, .video, .pinned, .unseenReaction] } public extension GlobalMessageTags { @@ -128,6 +129,7 @@ public extension PendingMessageActionType { static let consumeUnseenPersonalMessage = PendingMessageActionType(rawValue: 0) static let updateReaction = PendingMessageActionType(rawValue: 1) static let sendScheduledMessageImmediately = PendingMessageActionType(rawValue: 2) + static let readReaction = PendingMessageActionType(rawValue: 3) } public let peerIdNamespacesWithInitialCloudMessageHoles = [Namespaces.Peer.CloudUser, Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel] diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_ReactionsMessageAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_ReactionsMessageAttribute.swift index 4315937127..237ed3e02a 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_ReactionsMessageAttribute.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_ReactionsMessageAttribute.swift @@ -6,12 +6,6 @@ public struct MessageReaction: Equatable, PostboxCoding { public var isSelected: Bool public init(value: String, count: Int32, isSelected: Bool) { - var value = value - - if value == "❤️" { - value = "❤" - } - self.value = value self.count = count self.isSelected = isSelected @@ -33,20 +27,28 @@ public struct MessageReaction: Equatable, PostboxCoding { public final class ReactionsMessageAttribute: Equatable, MessageAttribute { public struct RecentPeer: Equatable, PostboxCoding { public var value: String + public var isLarge: Bool + public var isUnseen: Bool public var peerId: PeerId - public init(value: String, peerId: PeerId) { + public init(value: String, isLarge: Bool, isUnseen: Bool, peerId: PeerId) { self.value = value + self.isLarge = isLarge + self.isUnseen = isUnseen self.peerId = peerId } public init(decoder: PostboxDecoder) { self.value = decoder.decodeStringForKey("v", orElse: "") + self.isLarge = decoder.decodeInt32ForKey("l", orElse: 0) != 0 + self.isUnseen = decoder.decodeInt32ForKey("u", orElse: 0) != 0 self.peerId = PeerId(decoder.decodeInt64ForKey("p", orElse: 0)) } public func encode(_ encoder: PostboxEncoder) { encoder.encodeString(self.value, forKey: "v") + encoder.encodeInt32(self.isLarge ? 1 : 0, forKey: "l") + encoder.encodeInt32(self.isUnseen ? 1 : 0, forKey: "u") encoder.encodeInt64(self.peerId.toInt64(), forKey: "p") } } @@ -89,11 +91,33 @@ public final class ReactionsMessageAttribute: Equatable, MessageAttribute { } return true } + + public var hasUnseen: Bool { + for recentPeer in self.recentPeers { + if recentPeer.isUnseen { + return true + } + } + return false + } + + public func withAllSeen() -> ReactionsMessageAttribute { + return ReactionsMessageAttribute( + canViewList: self.canViewList, + reactions: self.reactions, + recentPeers: self.recentPeers.map { recentPeer in + var recentPeer = recentPeer + recentPeer.isUnseen = false + return recentPeer + } + ) + } } public final class PendingReactionsMessageAttribute: MessageAttribute { public let accountPeerId: PeerId? public let value: String? + public let isLarge: Bool public var associatedPeerIds: [PeerId] { if let accountPeerId = self.accountPeerId { @@ -103,14 +127,16 @@ public final class PendingReactionsMessageAttribute: MessageAttribute { } } - public init(accountPeerId: PeerId?, value: String?) { + public init(accountPeerId: PeerId?, value: String?, isLarge: Bool) { self.accountPeerId = accountPeerId self.value = value + self.isLarge = isLarge } required public init(decoder: PostboxDecoder) { self.accountPeerId = decoder.decodeOptionalInt64ForKey("ap").flatMap(PeerId.init) self.value = decoder.decodeOptionalStringForKey("v") + self.isLarge = decoder.decodeInt32ForKey("l", orElse: 0) != 0 } public func encode(_ encoder: PostboxEncoder) { @@ -124,5 +150,6 @@ public final class PendingReactionsMessageAttribute: MessageAttribute { } else { encoder.encodeNil(forKey: "v") } + encoder.encodeInt32(self.isLarge ? 1 : 0, forKey: "l") } } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift index 18ace49fe7..4699366d32 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift @@ -44,7 +44,7 @@ public let telegramPostboxSeedConfiguration: SeedConfiguration = { upgradedMessageHoles: upgradedMessageHoles, messageThreadHoles: messageThreadHoles, existingMessageTags: MessageTags.all, - messageTagsWithSummary: [.unseenPersonalMessage, .pinned, .video, .photo, .gif, .music, .voiceOrInstantVideo, .webPage, .file], + messageTagsWithSummary: [.unseenPersonalMessage, .pinned, .video, .photo, .gif, .music, .voiceOrInstantVideo, .webPage, .file, .unseenReaction], existingGlobalMessageTags: GlobalMessageTags.all, peerNamespacesRequiringMessageTextIndex: [Namespaces.Peer.SecretChat], peerSummaryCounterTags: { peer, isContact in diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift index c49756c107..4278c8dea9 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift @@ -28,6 +28,7 @@ public final class EngineChatList { public let renderedPeer: EngineRenderedPeer public let presence: EnginePeer.Presence? public let hasUnseenMentions: Bool + public let hasUnseenReactions: Bool public let hasFailed: Bool public let isContact: Bool @@ -40,6 +41,7 @@ public final class EngineChatList { renderedPeer: EngineRenderedPeer, presence: EnginePeer.Presence?, hasUnseenMentions: Bool, + hasUnseenReactions: Bool, hasFailed: Bool, isContact: Bool ) { @@ -51,6 +53,7 @@ public final class EngineChatList { self.renderedPeer = renderedPeer self.presence = presence self.hasUnseenMentions = hasUnseenMentions + self.hasUnseenReactions = hasUnseenReactions self.hasFailed = hasFailed self.isContact = isContact } @@ -205,7 +208,7 @@ public extension EngineChatList.RelativePosition { extension EngineChatList.Item { convenience init?(_ entry: ChatListEntry) { switch entry { - case let .MessageEntry(index, messages, readState, isRemovedFromTotalUnreadCount, embeddedState, renderedPeer, presence, summaryInfo, hasFailed, isContact): + case let .MessageEntry(index, messages, readState, isRemovedFromTotalUnreadCount, embeddedState, renderedPeer, presence, tagSummaryInfo, hasFailed, isContact): var draftText: String? if let embeddedState = embeddedState, let _ = embeddedState.overrideChatTimestamp { if let opaqueState = _internal_decodeStoredChatInterfaceState(state: embeddedState) { @@ -214,6 +217,23 @@ extension EngineChatList.Item { } } } + + var hasUnseenMentions = false + if let info = tagSummaryInfo[ChatListEntryMessageTagSummaryKey( + tag: .unseenPersonalMessage, + actionType: PendingMessageActionType.consumeUnseenPersonalMessage + )] { + hasUnseenMentions = (info.tagSummaryCount ?? 0) > (info.actionsSummaryCount ?? 0) + } + + var hasUnseenReactions = false + if let info = tagSummaryInfo[ChatListEntryMessageTagSummaryKey( + tag: .unseenReaction, + actionType: PendingMessageActionType.readReaction + )] { + hasUnseenReactions = (info.tagSummaryCount ?? 0) > (info.actionsSummaryCount ?? 0) + } + self.init( index: index, messages: messages.map(EngineMessage.init), @@ -222,7 +242,8 @@ extension EngineChatList.Item { draftText: draftText, renderedPeer: EngineRenderedPeer(renderedPeer), presence: presence.flatMap(EnginePeer.Presence.init), - hasUnseenMentions: (summaryInfo.tagSummaryCount ?? 0) > (summaryInfo.actionsSummaryCount ?? 0), + hasUnseenMentions: hasUnseenMentions, + hasUnseenReactions: hasUnseenReactions, hasFailed: hasFailed, isContact: isContact ) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/EarliestUnseenPersonalMentionMessage.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/EarliestUnseenPersonalMentionMessage.swift index 0e18f38102..350389d9d9 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/EarliestUnseenPersonalMentionMessage.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/EarliestUnseenPersonalMentionMessage.swift @@ -72,3 +72,67 @@ func _internal_earliestUnseenPersonalMentionMessage(account: Account, peerId: Pe } }) } + +func _internal_earliestUnseenPersonalReactionMessage(account: Account, peerId: PeerId) -> Signal { + return account.viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId), index: .lowerBound, anchorIndex: .lowerBound, count: 4, fixedCombinedReadStates: nil, tagMask: .unseenReaction, additionalData: [.peerChatState(peerId)]) + |> mapToSignal { view -> Signal in + if view.0.isLoading { + return .single(.loading) + } + if let message = view.0.entries.first?.message { + if peerId.namespace == Namespaces.Peer.CloudChannel { + var invalidatedPts: Int32? + for data in view.0.additionalData { + switch data { + case let .peerChatState(_, state): + if let state = state as? ChannelState { + invalidatedPts = state.invalidatedPts + } + default: + break + } + } + if let invalidatedPts = invalidatedPts { + var messagePts: Int32? + for attribute in message.attributes { + if let attribute = attribute as? ChannelMessageStateVersionAttribute { + messagePts = attribute.pts + break + } + } + + if let messagePts = messagePts { + if messagePts < invalidatedPts { + return .single(.loading) + } + } + } + return .single(.result(message.id)) + } else { + return .single(.result(message.id)) + } + } else { + return account.postbox.transaction { transaction -> EarliestUnseenPersonalMentionMessageResult in + if let topId = transaction.getTopPeerMessageId(peerId: peerId, namespace: Namespaces.Message.Cloud) { + transaction.replaceMessageTagSummary(peerId: peerId, tagMask: .unseenReaction, namespace: Namespaces.Message.Cloud, count: 0, maxId: topId.id) + + transaction.removeHole(peerId: peerId, namespace: Namespaces.Message.Cloud, space: .tag(.unseenReaction), range: 1 ... (Int32.max - 1)) + let ids = transaction.getMessageIndicesWithTag(peerId: peerId, namespace: Namespaces.Message.Cloud, tag: .unseenReaction).map({ $0.id }) + for id in ids { + markUnseenReactionMessage(transaction: transaction, id: id, addSynchronizeAction: false) + } + } + + return .result(nil) + } + } + } + |> distinctUntilChanged + |> take(until: { value in + if case .result = value { + return SignalTakeAction(passthrough: true, complete: true) + } else { + return SignalTakeAction(passthrough: true, complete: false) + } + }) +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/InstallInteractiveReadMessagesAction.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/InstallInteractiveReadMessagesAction.swift index fbbcf8d8bc..56658b5a8e 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/InstallInteractiveReadMessagesAction.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/InstallInteractiveReadMessagesAction.swift @@ -7,6 +7,7 @@ import SwiftSignalKit func _internal_installInteractiveReadMessagesAction(postbox: Postbox, stateManager: AccountStateManager, peerId: PeerId) -> Disposable { return postbox.installStoreMessageAction(peerId: peerId, { messages, transaction in var consumeMessageIds: [MessageId] = [] + var readReactionIds: [MessageId] = [] var readMessageIndexByNamespace: [MessageId.Namespace: MessageIndex] = [:] @@ -14,6 +15,7 @@ func _internal_installInteractiveReadMessagesAction(postbox: Postbox, stateManag if case let .Id(id) = message.id { var hasUnconsumedMention = false var hasUnconsumedContent = false + var hasUnseenReactions = false if message.tags.contains(.unseenPersonalMessage) { inner: for attribute in message.attributes { @@ -21,6 +23,8 @@ func _internal_installInteractiveReadMessagesAction(postbox: Postbox, stateManag hasUnconsumedMention = true } else if let attribute = attribute as? ConsumableContentMessageAttribute, !attribute.consumed { hasUnconsumedContent = true + } else if let attribute = attribute as? ReactionsMessageAttribute, attribute.hasUnseen { + hasUnseenReactions = true } } } @@ -28,6 +32,9 @@ func _internal_installInteractiveReadMessagesAction(postbox: Postbox, stateManag if hasUnconsumedMention && !hasUnconsumedContent { consumeMessageIds.append(id) } + if hasUnseenReactions { + readReactionIds.append(id) + } if !message.flags.intersection(.IsIncomingMask).isEmpty { let index = MessageIndex(id: id, timestamp: message.timestamp) @@ -39,19 +46,34 @@ func _internal_installInteractiveReadMessagesAction(postbox: Postbox, stateManag } } - for id in consumeMessageIds { + for id in Set(consumeMessageIds + readReactionIds) { transaction.updateMessage(id, update: { currentMessage in var attributes = currentMessage.attributes - loop: for j in 0 ..< attributes.count { - if let attribute = attributes[j] as? ConsumablePersonalMentionMessageAttribute { - attributes[j] = ConsumablePersonalMentionMessageAttribute(consumed: attribute.consumed, pending: true) - break loop + if consumeMessageIds.contains(id) { + mentionsLoop: for j in 0 ..< attributes.count { + if let attribute = attributes[j] as? ConsumablePersonalMentionMessageAttribute { + attributes[j] = ConsumablePersonalMentionMessageAttribute(consumed: attribute.consumed, pending: true) + break mentionsLoop + } + } + } + if readReactionIds.contains(id) { + reactionsLoop: for j in 0 ..< attributes.count { + if let attribute = attributes[j] as? ReactionsMessageAttribute { + attributes[j] = attribute.withAllSeen() + break reactionsLoop + } } } return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) }) - transaction.setPendingMessageAction(type: .consumeUnseenPersonalMessage, id: id, action: ConsumePersonalMessageAction()) + if consumeMessageIds.contains(id) { + transaction.setPendingMessageAction(type: .consumeUnseenPersonalMessage, id: id, action: ConsumePersonalMessageAction()) + } + if readReactionIds.contains(id) { + transaction.setPendingMessageAction(type: .readReaction, id: id, action: ReadReactionAction()) + } } for (_, index) in readMessageIndexByNamespace { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/MarkMessageContentAsConsumedInteractively.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/MarkMessageContentAsConsumedInteractively.swift index ab79a3687c..5e15fc9fd8 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/MarkMessageContentAsConsumedInteractively.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/MarkMessageContentAsConsumedInteractively.swift @@ -3,7 +3,6 @@ import Postbox import TelegramApi import SwiftSignalKit - func _internal_markMessageContentAsConsumedInteractively(postbox: Postbox, messageId: MessageId) -> Signal { return postbox.transaction { transaction -> Void in if let message = transaction.getMessage(messageId), message.flags.contains(.Incoming) { @@ -128,6 +127,39 @@ func _internal_markMessageContentAsConsumedInteractively(postbox: Postbox, messa } } +func _internal_markReactionsAsSeenInteractively(postbox: Postbox, messageId: MessageId) -> Signal { + return postbox.transaction { transaction -> Void in + if let message = transaction.getMessage(messageId), message.tags.contains(.unseenReaction) { + var updateMessage = false + var updatedAttributes = message.attributes + + for i in 0 ..< updatedAttributes.count { + if let attribute = updatedAttributes[i] as? ReactionsMessageAttribute, attribute.hasUnseen { + updatedAttributes[i] = attribute.withAllSeen() + updateMessage = true + + if message.id.peerId.namespace == Namespaces.Peer.SecretChat { + } else { + transaction.setPendingMessageAction(type: .readReaction, id: messageId, action: ReadReactionAction()) + } + } + } + + if updateMessage { + transaction.updateMessage(message.id, update: { currentMessage in + var storeForwardInfo: StoreMessageForwardInfo? + if let forwardInfo = currentMessage.forwardInfo { + storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType, flags: forwardInfo.flags) + } + var tags = currentMessage.tags + tags.remove(.unseenReaction) + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media)) + }) + } + } + } +} + func markMessageContentAsConsumedRemotely(transaction: Transaction, messageId: MessageId) { if let message = transaction.getMessage(messageId) { var updateMessage = false diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index 2d9120b97f..077f491f67 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -147,6 +147,10 @@ public extension TelegramEngine { public func earliestUnseenPersonalMentionMessage(peerId: PeerId) -> Signal { return _internal_earliestUnseenPersonalMentionMessage(account: self.account, peerId: peerId) } + + public func earliestUnseenPersonalReactionMessage(peerId: PeerId) -> Signal { + return _internal_earliestUnseenPersonalReactionMessage(account: self.account, peerId: peerId) + } public func exportMessageLink(peerId: PeerId, messageId: MessageId, isThread: Bool = false) -> Signal { return _internal_exportMessageLink(account: self.account, peerId: peerId, messageId: messageId, isThread: isThread) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift index 729ee284da..22668d0803 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift @@ -647,7 +647,7 @@ private func loadAndStorePeerChatInfos(accountPeerId: PeerId, postbox: Postbox, for dialog in dialogs { switch dialog { - case let .dialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, notifySettings, pts, _, folderId): + case let .dialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, notifySettings, pts, _, folderId): let peerId = peer.peerId if topMessage != 0 { @@ -704,6 +704,7 @@ private func loadAndStorePeerChatInfos(accountPeerId: PeerId, postbox: Postbox, transaction.resetIncomingReadStates([peerId: [Namespaces.Message.Cloud: .idBased(maxIncomingReadId: readInboxMaxId, maxOutgoingReadId: readOutboxMaxId, maxKnownId: topMessage, count: unreadCount, markedUnread: false)]]) transaction.replaceMessageTagSummary(peerId: peerId, tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud, count: unreadMentionsCount, maxId: topMessage) + transaction.replaceMessageTagSummary(peerId: peerId, tagMask: .unseenReaction, namespace: Namespaces.Message.Cloud, count: unreadReactionsCount, maxId: topMessage) if let pts = pts { if transaction.getPeerChatState(peerId) == nil { diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift index 5a73f815c6..9d0f2d2e2d 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift @@ -210,6 +210,7 @@ public enum PresentationResourceKey: Int32 { case chatHistoryNavigationButtonImage case chatHistoryMentionsButtonImage + case chatHistoryReactionsButtonImage case chatHistoryNavigationButtonBadgeImage case chatMessageAttachedContentButtonIncoming diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift index 93cb1a8d53..96e88b8502 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift @@ -673,6 +673,23 @@ public struct PresentationResourcesChat { }) } + public static func chatHistoryReactionsButtonImage(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.chatHistoryReactionsButtonImage.rawValue, { theme in + return generateImage(CGSize(width: 38.0, height: 38.0), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(theme.chat.historyNavigation.fillColor.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.5, y: 0.5), size: CGSize(width: size.width - 1.0, height: size.height - 1.0))) + context.setLineWidth(0.5) + context.setStrokeColor(theme.chat.historyNavigation.strokeColor.cgColor) + context.strokeEllipse(in: CGRect(origin: CGPoint(x: 0.25, y: 0.25), size: CGSize(width: size.width - 0.5, height: size.height - 0.5))) + + if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/ScheduleIcon"), color: theme.chat.historyNavigation.foregroundColor), let cgImage = image.cgImage { + context.draw(cgImage, in: CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size)) + } + }) + }) + } + public static func chatHistoryNavigationButtonBadgeImage(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.chatHistoryNavigationButtonBadgeImage.rawValue, { theme in return generateStretchableFilledCircleImage(diameter: 18.0, color: theme.chat.historyNavigation.badgeBackgroundColor, strokeColor: theme.chat.historyNavigation.badgeStrokeColor, strokeWidth: 1.0, backgroundColor: nil) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index c45a7e865c..3e241dabd7 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -1044,7 +1044,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatMessageContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, postbox: strongSelf.context.account.postbox, message: message, selectAll: selectAll)), items: .single(actions), recognizer: recognizer, gesture: gesture) strongSelf.currentContextController = controller - controller.reactionSelected = { [weak controller] value in + controller.reactionSelected = { [weak controller] value, isLarge in guard let strongSelf = self, let message = messages.first else { return } @@ -1110,7 +1110,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } } - let _ = updateMessageReactionsInteractively(account: strongSelf.context.account, messageId: message.id, reaction: updatedReaction).start() + let _ = updateMessageReactionsInteractively(account: strongSelf.context.account, messageId: message.id, reaction: updatedReaction, isLarge: isLarge).start() } strongSelf.forEachController({ controller in @@ -1328,7 +1328,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - let _ = updateMessageReactionsInteractively(account: strongSelf.context.account, messageId: message.id, reaction: updatedReaction).start() + let _ = updateMessageReactionsInteractively(account: strongSelf.context.account, messageId: message.id, reaction: updatedReaction, isLarge: false).start() } }) }, activateMessagePinch: { [weak self] sourceNode in @@ -5864,6 +5864,24 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.present(actionSheet, in: .window(.root)) } + self.chatDisplayNode.navigateButtons.reactionsPressed = { [weak self] in + if let strongSelf = self, strongSelf.isNodeLoaded, case let .peer(peerId) = strongSelf.chatLocation { + let signal = strongSelf.context.engine.messages.earliestUnseenPersonalReactionMessage(peerId: peerId) + strongSelf.navigationActionDisposable.set((signal |> deliverOnMainQueue).start(next: { result in + if let strongSelf = self { + switch result { + case let .result(messageId): + if let messageId = messageId { + strongSelf.navigateToMessage(from: nil, to: .id(messageId, nil)) + } + case .loading: + break + } + } + })) + } + } + let interfaceInteraction = ChatPanelInterfaceInteraction(setupReplyMessage: { [weak self] messageId, completion in guard let strongSelf = self, strongSelf.isNodeLoaded else { return @@ -8003,12 +8021,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }) - self.chatUnreadMentionCountDisposable = (self.context.account.viewTracker.unseenPersonalMessagesCount(peerId: peerId) |> deliverOnMainQueue).start(next: { [weak self] count in + self.chatUnreadMentionCountDisposable = (self.context.account.viewTracker.unseenPersonalMessagesAndReactionCount(peerId: peerId) |> deliverOnMainQueue).start(next: { [weak self] mentionCount, reactionCount in if let strongSelf = self { if case let .standard(previewing) = strongSelf.presentationInterfaceState.mode, previewing { strongSelf.chatDisplayNode.navigateButtons.mentionCount = 0 + strongSelf.chatDisplayNode.navigateButtons.reactionsCount = 0 } else { - strongSelf.chatDisplayNode.navigateButtons.mentionCount = count + strongSelf.chatDisplayNode.navigateButtons.mentionCount = mentionCount + strongSelf.chatDisplayNode.navigateButtons.reactionsCount = reactionCount } } }) diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 576b1d1328..c97220caeb 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -452,6 +452,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { private var canReadHistoryDisposable: Disposable? private var messageIdsScheduledForMarkAsSeen = Set() + private var messageIdsWithReactionsScheduledForMarkAsSeen = Set() private var chatHistoryLocationValue: ChatHistoryLocationInput? { didSet { @@ -486,6 +487,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { private let unsupportedMessageProcessingManager = ChatMessageThrottledProcessingManager() private let refreshMediaProcessingManager = ChatMessageThrottledProcessingManager() private let messageMentionProcessingManager = ChatMessageThrottledProcessingManager(delay: 0.2) + private let unseenReactionsProcessingManager = ChatMessageThrottledProcessingManager(delay: 0.2, submitInterval: 0.0) let prefetchManager: InChatPrefetchManager private var currentEarlierPrefetchMessages: [(Message, Media)] = [] private var currentLaterPrefetchMessages: [(Message, Media)] = [] @@ -726,6 +728,17 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } } + self.unseenReactionsProcessingManager.process = { [weak self] messageIds in + guard let strongSelf = self else { + return + } + if strongSelf.canReadHistoryValue { + strongSelf.context.account.viewTracker.updateMarkReactionsSeenForMessageIds(messageIds: messageIds) + } else { + strongSelf.messageIdsWithReactionsScheduledForMarkAsSeen.formUnion(messageIds) + } + } + self.preloadPages = false switch self.mode { case .bubbles: @@ -1328,6 +1341,12 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { strongSelf.messageIdsScheduledForMarkAsSeen.removeAll() context?.account.viewTracker.updateMarkMentionsSeenForMessageIds(messageIds: messageIds) } + + if strongSelf.canReadHistoryValue && !strongSelf.messageIdsWithReactionsScheduledForMarkAsSeen.isEmpty { + let messageIds = strongSelf.messageIdsWithReactionsScheduledForMarkAsSeen + strongSelf.messageIdsWithReactionsScheduledForMarkAsSeen.removeAll() + context?.account.viewTracker.updateMarkReactionsSeenForMessageIds(messageIds: messageIds) + } } } }) @@ -1803,6 +1822,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { var messageIdsWithUnsupportedMedia: [MessageId] = [] var messageIdsWithRefreshMedia: [MessageId] = [] var messageIdsWithUnseenPersonalMention: [MessageId] = [] + var messageIdsWithUnseenReactions: [MessageId] = [] if indexRange.0 <= indexRange.1 { for i in (indexRange.0 ... indexRange.1) { @@ -1819,6 +1839,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } var contentRequiredValidation = false var mediaRequiredValidation = false + var hasUnseenReactions = false for attribute in message.attributes { if attribute is ViewCountMessageAttribute { if message.id.namespace == Namespaces.Message.Cloud { @@ -1832,6 +1853,8 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { hasUnconsumedContent = true } else if let _ = attribute as? ContentRequiresValidationMessageAttribute { contentRequiredValidation = true + } else if let attribute = attribute as? ReactionsMessageAttribute, attribute.hasUnseen { + hasUnseenReactions = true } } for media in message.media { @@ -1871,6 +1894,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { if hasUnconsumedMention && !hasUnconsumedContent { messageIdsWithUnseenPersonalMention.append(message.id) } + if hasUnseenReactions { + messageIdsWithUnseenReactions.append(message.id) + } if case let .replyThread(replyThreadMessage) = self.chatLocation, replyThreadMessage.effectiveTopId == message.id { isTopReplyThreadMessageShownValue = true } @@ -1883,10 +1909,13 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { for (message, _, _, _, _) in messages { var hasUnconsumedMention = false var hasUnconsumedContent = false + var hasUnseenReactions = false if message.tags.contains(.unseenPersonalMessage) { for attribute in message.attributes { if let attribute = attribute as? ConsumablePersonalMentionMessageAttribute, !attribute.pending { hasUnconsumedMention = true + } else if let attribute = attribute as? ReactionsMessageAttribute, attribute.hasUnseen { + hasUnseenReactions = true } } } @@ -1906,6 +1935,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { if hasUnconsumedMention && !hasUnconsumedContent { messageIdsWithUnseenPersonalMention.append(message.id) } + if hasUnseenReactions { + messageIdsWithUnseenReactions.append(message.id) + } if case let .replyThread(replyThreadMessage) = self.chatLocation, replyThreadMessage.effectiveTopId == message.id { isTopReplyThreadMessageShownValue = true } @@ -2039,6 +2071,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { if !messageIdsWithUnseenPersonalMention.isEmpty { self.messageMentionProcessingManager.add(messageIdsWithUnseenPersonalMention) } + if !messageIdsWithUnseenReactions.isEmpty { + self.unseenReactionsProcessingManager.add(messageIdsWithUnseenReactions) + } if !messageIdsWithPossibleReactions.isEmpty { self.messageWithReactionsProcessingManager.add(messageIdsWithPossibleReactions) } @@ -2347,7 +2382,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { let completion: (Bool, ListViewDisplayedItemRange) -> Void = { [weak self] wasTransformed, visibleRange in if let strongSelf = self { var newIncomingReactions: [MessageId: String] = [:] - if case let .peer(peerId) = strongSelf.chatLocation, peerId.namespace == Namespaces.Peer.CloudUser, let previousHistoryView = strongSelf.historyView { + if case .peer = strongSelf.chatLocation, let previousHistoryView = strongSelf.historyView { var updatedIncomingReactions: [MessageId: String] = [:] for entry in transition.historyView.filteredEntries { switch entry { @@ -2356,9 +2391,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { continue } if let reactions = message.reactionsAttribute { - for reaction in reactions.reactions { - if !reaction.isSelected { - updatedIncomingReactions[message.id] = reaction.value + for recentPeer in reactions.recentPeers { + if recentPeer.isUnseen { + updatedIncomingReactions[message.id] = recentPeer.value } } } @@ -2368,9 +2403,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { continue } if let reactions = message.0.reactionsAttribute { - for reaction in reactions.reactions { - if !reaction.isSelected { - updatedIncomingReactions[message.0.id] = reaction.value + for recentPeer in reactions.recentPeers { + if recentPeer.isUnseen { + updatedIncomingReactions[message.0.id] = recentPeer.value } } } @@ -2385,9 +2420,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { if let updatedReaction = updatedIncomingReactions[message.id] { var previousReaction: String? if let reactions = message.reactionsAttribute { - for reaction in reactions.reactions { - if !reaction.isSelected { - previousReaction = reaction.value + for recentPeer in reactions.recentPeers { + if recentPeer.isUnseen { + previousReaction = recentPeer.value } } } @@ -2400,9 +2435,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { if let updatedReaction = updatedIncomingReactions[message.0.id] { var previousReaction: String? if let reactions = message.0.reactionsAttribute { - for reaction in reactions.reactions { - if !reaction.isSelected { - previousReaction = reaction.value + for recentPeer in reactions.recentPeers { + if recentPeer.isUnseen { + previousReaction = recentPeer.value } } } @@ -2565,8 +2600,14 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } if !newIncomingReactions.isEmpty, let chatDisplayNode = strongSelf.controllerInteraction.chatControllerNode() as? ChatControllerNode { + var visibleNewIncomingReactionMessageIds: [MessageId] = [] strongSelf.forEachVisibleItemNode { itemNode in - if let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item, let updatedReaction = newIncomingReactions[item.content.firstMessage.id], let availableReactions = item.associatedData.availableReactions, let targetView = itemNode.targetReactionView(value: updatedReaction) { + guard let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item, let updatedReaction = newIncomingReactions[item.content.firstMessage.id] else { + return + } + visibleNewIncomingReactionMessageIds.append(item.content.firstMessage.id) + + if let availableReactions = item.associatedData.availableReactions, let targetView = itemNode.targetReactionView(value: updatedReaction) { for reaction in availableReactions.reactions { guard let centerAnimation = reaction.centerAnimation else { continue @@ -2603,6 +2644,10 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } } } + + if !visibleNewIncomingReactionMessageIds.isEmpty { + strongSelf.unseenReactionsProcessingManager.add(visibleNewIncomingReactionMessageIds) + } } strongSelf.hasActiveTransition = false diff --git a/submodules/TelegramUI/Sources/ChatHistoryNavigationButtonNode.swift b/submodules/TelegramUI/Sources/ChatHistoryNavigationButtonNode.swift index ed35ecf4e2..015866b4aa 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryNavigationButtonNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryNavigationButtonNode.swift @@ -9,6 +9,7 @@ private let badgeFont = Font.regular(13.0) enum ChatHistoryNavigationButtonType { case down case mentions + case reactions } class ChatHistoryNavigationButtonNode: ASControlNode { @@ -53,6 +54,8 @@ class ChatHistoryNavigationButtonNode: ASControlNode { self.imageNode.image = PresentationResourcesChat.chatHistoryNavigationButtonImage(theme) case .mentions: self.imageNode.image = PresentationResourcesChat.chatHistoryMentionsButtonImage(theme) + case .reactions: + self.imageNode.image = PresentationResourcesChat.chatHistoryReactionsButtonImage(theme) } self.imageNode.isLayerBacked = true @@ -92,6 +95,8 @@ class ChatHistoryNavigationButtonNode: ASControlNode { self.imageNode.image = PresentationResourcesChat.chatHistoryNavigationButtonImage(theme) case .mentions: self.imageNode.image = PresentationResourcesChat.chatHistoryMentionsButtonImage(theme) + case .reactions: + self.imageNode.image = PresentationResourcesChat.chatHistoryReactionsButtonImage(theme) } self.badgeBackgroundNode.image = PresentationResourcesChat.chatHistoryNavigationButtonBadgeImage(theme) diff --git a/submodules/TelegramUI/Sources/ChatHistoryNavigationButtons.swift b/submodules/TelegramUI/Sources/ChatHistoryNavigationButtons.swift index 77538962d5..ad87c59d1e 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryNavigationButtons.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryNavigationButtons.swift @@ -8,6 +8,8 @@ final class ChatHistoryNavigationButtons: ASDisplayNode { private var theme: PresentationTheme private var dateTimeFormat: PresentationDateTimeFormat + private let reactionsButton: ChatHistoryNavigationButtonNode + private let reactionsButtonTapNode: ASDisplayNode private let mentionsButton: ChatHistoryNavigationButtonNode private let mentionsButtonTapNode: ASDisplayNode private let downButton: ChatHistoryNavigationButtonNode @@ -18,6 +20,8 @@ final class ChatHistoryNavigationButtons: ASDisplayNode { } } + var reactionsPressed: (() -> Void)? + var reactionsMenu: (() -> Void)? var mentionsPressed: (() -> Void)? var mentionsMenu: (() -> Void)? @@ -53,6 +57,20 @@ final class ChatHistoryNavigationButtons: ASDisplayNode { } } + var reactionsCount: Int32 = 0 { + didSet { + if self.reactionsCount != 0 { + self.reactionsButton.badge = compactNumericCountString(Int(self.reactionsCount), decimalSeparator: self.dateTimeFormat.decimalSeparator) + } else { + self.reactionsButton.badge = "" + } + + if (oldValue != 0) != (self.reactionsCount != 0) { + let _ = self.updateLayout(transition: .animated(duration: 0.3, curve: .spring)) + } + } + } + init(theme: PresentationTheme, dateTimeFormat: PresentationDateTimeFormat) { self.theme = theme self.dateTimeFormat = dateTimeFormat @@ -62,6 +80,11 @@ final class ChatHistoryNavigationButtons: ASDisplayNode { self.mentionsButton.isHidden = true self.mentionsButtonTapNode = ASDisplayNode() + self.reactionsButton = ChatHistoryNavigationButtonNode(theme: theme, type: .reactions) + self.reactionsButton.alpha = 0.0 + self.reactionsButton.isHidden = true + self.reactionsButtonTapNode = ASDisplayNode() + self.downButton = ChatHistoryNavigationButtonNode(theme: theme, type: .down) self.downButton.alpha = 0.0 self.downButton.isHidden = true @@ -70,6 +93,8 @@ final class ChatHistoryNavigationButtons: ASDisplayNode { self.mentionsButton.isUserInteractionEnabled = false + self.addSubnode(self.reactionsButton) + self.addSubnode(self.reactionsButtonTapNode) self.addSubnode(self.mentionsButton) self.addSubnode(self.mentionsButtonTapNode) self.addSubnode(self.downButton) @@ -78,11 +103,17 @@ final class ChatHistoryNavigationButtons: ASDisplayNode { override func didLoad() { super.didLoad() - let tapRecognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.mentionsTap(_:))) - tapRecognizer.tapActionAtPoint = { _ in + let reactionsTapRecognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.reactionsTap(_:))) + reactionsTapRecognizer.tapActionAtPoint = { _ in return .waitForSingleTap } - self.mentionsButtonTapNode.view.addGestureRecognizer(tapRecognizer) + self.reactionsButtonTapNode.view.addGestureRecognizer(reactionsTapRecognizer) + + let mentionsTapRecognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.mentionsTap(_:))) + mentionsTapRecognizer.tapActionAtPoint = { _ in + return .waitForSingleTap + } + self.mentionsButtonTapNode.view.addGestureRecognizer(mentionsTapRecognizer) } func update(theme: PresentationTheme, dateTimeFormat: PresentationDateTimeFormat) { @@ -99,9 +130,10 @@ final class ChatHistoryNavigationButtons: ASDisplayNode { let buttonSize = CGSize(width: 38.0, height: 38.0) let completeSize = CGSize(width: buttonSize.width, height: buttonSize.height * 2.0 + 12.0) var mentionsOffset: CGFloat = 0.0 + var reactionsOffset: CGFloat = 0.0 if self.displayDownButton { - mentionsOffset = buttonSize.height + 12.0 + mentionsOffset += buttonSize.height + 12.0 self.downButton.isHidden = false transition.updateAlpha(node: self.downButton, alpha: 1.0) @@ -117,6 +149,8 @@ final class ChatHistoryNavigationButtons: ASDisplayNode { } if self.mentionCount != 0 { + reactionsOffset += buttonSize.height + 12.0 + self.mentionsButton.isHidden = false transition.updateAlpha(node: self.mentionsButton, alpha: 1.0) transition.updateTransformScale(node: self.mentionsButton, scale: 1.0) @@ -132,11 +166,30 @@ final class ChatHistoryNavigationButtons: ASDisplayNode { self.mentionsButtonTapNode.isHidden = true } + if self.reactionsCount != 0 { + self.reactionsButton.isHidden = false + transition.updateAlpha(node: self.reactionsButton, alpha: 1.0) + transition.updateTransformScale(node: self.reactionsButton, scale: 1.0) + self.reactionsButtonTapNode.isHidden = false + } else { + transition.updateAlpha(node: self.reactionsButton, alpha: 0.0, completion: { [weak self] completed in + guard let strongSelf = self, completed else { + return + } + strongSelf.reactionsButton.isHidden = true + }) + transition.updateTransformScale(node: self.reactionsButton, scale: 0.2) + self.reactionsButtonTapNode.isHidden = true + } + transition.updatePosition(node: self.downButton, position: CGRect(origin: CGPoint(x: 0.0, y: completeSize.height - buttonSize.height), size: buttonSize).center) transition.updatePosition(node: self.mentionsButton, position: CGRect(origin: CGPoint(x: 0.0, y: completeSize.height - buttonSize.height - mentionsOffset), size: buttonSize).center) self.mentionsButtonTapNode.frame = CGRect(origin: CGPoint(x: 0.0, y: completeSize.height - buttonSize.height - mentionsOffset), size: buttonSize) + transition.updatePosition(node: self.reactionsButton, position: CGRect(origin: CGPoint(x: 0.0, y: completeSize.height - buttonSize.height - mentionsOffset - reactionsOffset), size: buttonSize).center) + self.reactionsButtonTapNode.frame = CGRect(origin: CGPoint(x: 0.0, y: completeSize.height - buttonSize.height - mentionsOffset), size: buttonSize) + return completeSize } @@ -154,6 +207,16 @@ final class ChatHistoryNavigationButtons: ASDisplayNode { return nil } + @objc private func reactionsTap(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { + if case .ended = recognizer.state, let gesture = recognizer.lastRecognizedGestureAndLocation?.0 { + if case .tap = gesture { + self.reactionsPressed?() + } else if case .longTap = gesture { + self.reactionsMenu?() + } + } + } + @objc private func mentionsTap(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { if case .ended = recognizer.state, let gesture = recognizer.lastRecognizedGestureAndLocation?.0 { if case .tap = gesture { diff --git a/submodules/TelegramUI/Sources/ChatMessageThrottledProcessingManager.swift b/submodules/TelegramUI/Sources/ChatMessageThrottledProcessingManager.swift index 5816f49a00..4def50a932 100644 --- a/submodules/TelegramUI/Sources/ChatMessageThrottledProcessingManager.swift +++ b/submodules/TelegramUI/Sources/ChatMessageThrottledProcessingManager.swift @@ -33,7 +33,7 @@ final class ChatMessageThrottledProcessingManager { for id in messageIds { if let processedTimestamp = self.processed[id] { - if let submitInterval = self.submitInterval, (timestamp - processedTimestamp) >= submitInterval { + if let submitInterval = self.submitInterval, (submitInterval.isZero || (timestamp - processedTimestamp) >= submitInterval) { self.processed[id] = timestamp self.processedList.append(id) self.buffer.insert(id) diff --git a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift index b158091475..5c862878df 100644 --- a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift @@ -90,6 +90,7 @@ private enum ChatListSearchEntry: Comparable, Identifiable { isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, + hasUnseenReactions: false, draftState: nil, inputActivities: nil, promoInfo: nil, @@ -230,7 +231,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe return } switch item.content { - case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _): + case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _, _): if let message = messages.first { let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(peer.peerId), subject: .message(id: .id(message.id), highlight: true, timecode: nil), botStart: nil, mode: .standard(previewing: true)) chatController.canReadHistory.set(false)