no message

This commit is contained in:
Ali 2022-01-21 20:30:43 +04:00
parent 4447916713
commit 0bdb130775
55 changed files with 1161 additions and 277 deletions

View file

@ -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

View file

@ -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] = []

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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),

View file

@ -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<ContextController.Items, NoError>, recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil, gesture: ContextGesture? = nil) {
self.account = account

View file

@ -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

View file

@ -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
}

View file

@ -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

View file

@ -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 {

View file

@ -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)

View file

@ -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)
}
}
}

View file

@ -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
}
}

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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<Api.messages.TranslatedText>) {
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<Api.messages.Messages>) {
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<Api.messages.AffectedHistory>) {
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<Api.Bool>) {

View file

@ -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())

View file

@ -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) {

View file

@ -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) })

View file

@ -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 {

View file

@ -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)

View file

@ -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)

View file

@ -307,6 +307,7 @@ public final class AccountViewTracker {
private var updatedUnsupportedMediaDisposables = DisposableDict<Int32>()
private var updatedSeenPersonalMessageIds = Set<MessageId>()
private var updatedReactionsSeenForMessageIds = Set<MessageId>()
private var cachedDataContexts: [PeerId: PeerCachedDataContext] = [:]
private var cachedChannelParticipantsContexts: [PeerId: CachedChannelParticipantsContext] = [:]
@ -1227,6 +1228,36 @@ public final class AccountViewTracker {
}
}
public func updateMarkReactionsSeenForMessageIds(messageIds: Set<MessageId>) {
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<Int32, NoError> {
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()
}

View file

@ -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,

View file

@ -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)

View file

@ -76,11 +76,11 @@ private final class ManagedConsumePersonalMessagesActionsHelper {
}
}
private func withTakenAction(postbox: Postbox, type: PendingMessageActionType, id: MessageId, _ f: @escaping (Transaction, PendingMessageActionsEntry?) -> Signal<Void, NoError>) -> Signal<Void, NoError> {
private func withTakenAction<T: PendingMessageActionData>(postbox: Postbox, type: PendingMessageActionType, actionType: T.Type, id: MessageId, _ f: @escaping (Transaction, PendingMessageActionsEntry?) -> Signal<Void, NoError>) -> Signal<Void, NoError> {
return postbox.transaction { transaction -> Signal<Void, NoError> 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<Void, NoError> in
let signal = withTakenAction(postbox: postbox, type: .consumeUnseenPersonalMessage, actionType: ConsumePersonalMessageAction.self, id: entry.id, { transaction, entry -> Signal<Void, NoError> 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<Void, NoError> {
return Signal { _ in
let helper = Atomic<ManagedConsumePersonalMessagesActionsHelper>(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<InvalidatedMessageHistoryTagsSummaryEntry>()
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<Void, NoError> 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<Void, NoError> {
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<Void, NoError> {
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<Api.messages.AffectedHistory?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<Void, NoError> 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<Void, NoError> {
return postbox.transaction { transaction -> Signal<Void, NoError> 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<Void, NoError> {
return postbox.transaction { transaction -> Signal<Void, NoError> 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<Api.messages.PeerDialogs?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<Void, NoError> 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
}

View file

@ -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())
}
}
}
}

View file

@ -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

View file

@ -4,7 +4,7 @@ import SwiftSignalKit
import TelegramApi
import MtProtoKit
public func updateMessageReactionsInteractively(account: Account, messageId: MessageId, reaction: String?) -> Signal<Never, NoError> {
public func updateMessageReactionsInteractively(account: Account, messageId: MessageId, reaction: String?, isLarge: Bool) -> Signal<Never, NoError> {
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<Never, RequestUpdateMessageReactionError> {
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<Never, RequestUpdateMessageReactionError> = 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<Never, RequestUpdateMessageReactionError> = 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<InternalState, NoError> 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))
}
}

View file

@ -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! {

View file

@ -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

View file

@ -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

View file

@ -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
}
}
}

View file

@ -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]

View file

@ -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")
}
}

View file

@ -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

View file

@ -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
)

View file

@ -72,3 +72,67 @@ func _internal_earliestUnseenPersonalMentionMessage(account: Account, peerId: Pe
}
})
}
func _internal_earliestUnseenPersonalReactionMessage(account: Account, peerId: PeerId) -> Signal<EarliestUnseenPersonalMentionMessageResult, NoError> {
return account.viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId), index: .lowerBound, anchorIndex: .lowerBound, count: 4, fixedCombinedReadStates: nil, tagMask: .unseenReaction, additionalData: [.peerChatState(peerId)])
|> mapToSignal { view -> Signal<EarliestUnseenPersonalMentionMessageResult, NoError> 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)
}
})
}

View file

@ -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 {

View file

@ -3,7 +3,6 @@ import Postbox
import TelegramApi
import SwiftSignalKit
func _internal_markMessageContentAsConsumedInteractively(postbox: Postbox, messageId: MessageId) -> Signal<Void, NoError> {
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<Void, NoError> {
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

View file

@ -147,6 +147,10 @@ public extension TelegramEngine {
public func earliestUnseenPersonalMentionMessage(peerId: PeerId) -> Signal<EarliestUnseenPersonalMentionMessageResult, NoError> {
return _internal_earliestUnseenPersonalMentionMessage(account: self.account, peerId: peerId)
}
public func earliestUnseenPersonalReactionMessage(peerId: PeerId) -> Signal<EarliestUnseenPersonalMentionMessageResult, NoError> {
return _internal_earliestUnseenPersonalReactionMessage(account: self.account, peerId: peerId)
}
public func exportMessageLink(peerId: PeerId, messageId: MessageId, isThread: Bool = false) -> Signal<String?, NoError> {
return _internal_exportMessageLink(account: self.account, peerId: peerId, messageId: messageId, isThread: isThread)

View file

@ -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 {

View file

@ -210,6 +210,7 @@ public enum PresentationResourceKey: Int32 {
case chatHistoryNavigationButtonImage
case chatHistoryMentionsButtonImage
case chatHistoryReactionsButtonImage
case chatHistoryNavigationButtonBadgeImage
case chatMessageAttachedContentButtonIncoming

View file

@ -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)

View file

@ -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
}
}
})

View file

@ -452,6 +452,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
private var canReadHistoryDisposable: Disposable?
private var messageIdsScheduledForMarkAsSeen = Set<MessageId>()
private var messageIdsWithReactionsScheduledForMarkAsSeen = Set<MessageId>()
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

View file

@ -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)

View file

@ -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 {

View file

@ -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)

View file

@ -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)