diff --git a/Telegram/SiriIntents/IntentMessages.swift b/Telegram/SiriIntents/IntentMessages.swift index dc102b2a57..2e05082bbb 100644 --- a/Telegram/SiriIntents/IntentMessages.swift +++ b/Telegram/SiriIntents/IntentMessages.swift @@ -49,7 +49,7 @@ func unreadMessages(account: Account) -> Signal<[INMessage], NoError> { } if !isMuted && hasUnread { - signals.append(account.postbox.aroundMessageHistoryViewForLocation(.peer(peerId: index.messageIndex.id.peerId), anchor: .upperBound, ignoreMessagesInTimestampRange: nil, count: 10, fixedCombinedReadStates: fixedCombinedReadStates, topTaggedMessageIdNamespaces: Set(), tagMask: nil, appendMessagesFromTheSameGroup: false, namespaces: .not(Namespaces.Message.allScheduled), orderStatistics: .combinedLocation) + signals.append(account.postbox.aroundMessageHistoryViewForLocation(.peer(peerId: index.messageIndex.id.peerId, threadId: nil), anchor: .upperBound, ignoreMessagesInTimestampRange: nil, count: 10, fixedCombinedReadStates: fixedCombinedReadStates, topTaggedMessageIdNamespaces: Set(), tagMask: nil, appendMessagesFromTheSameGroup: false, namespaces: .not(Namespaces.Message.allScheduled), orderStatistics: .combinedLocation) |> take(1) |> map { view -> [INMessage] in var messages: [INMessage] = [] diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 0423fbe632..b6ef84422f 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -8114,3 +8114,5 @@ Sorry for the inconvenience."; "ChatList.StartAction" = "Start"; "ChatList.CloseAction" = "Close"; + +"Channel.EditAdmin.PermissionCreateTopics" = "Create Topics"; diff --git a/Telegram/WidgetKitWidget/TodayViewController.swift b/Telegram/WidgetKitWidget/TodayViewController.swift index 3b40feb3e0..6753d73b3b 100644 --- a/Telegram/WidgetKitWidget/TodayViewController.swift +++ b/Telegram/WidgetKitWidget/TodayViewController.swift @@ -159,7 +159,7 @@ private func getCommonTimeline(friends: [Friend]?, in context: TimelineProviderC if let readState = transaction.getCombinedPeerReadState(peer.id), readState.count > 0 { var isMuted = false - if let notificationSettings = transaction.getPeerNotificationSettings(peer.id) as? TelegramPeerNotificationSettings { + if let notificationSettings = transaction.getPeerNotificationSettings(id: peer.id) as? TelegramPeerNotificationSettings { isMuted = notificationSettings.isRemovedFromTotalUnreadCount(default: false) } badge = WidgetDataPeer.Badge( diff --git a/submodules/ChatListUI/Sources/ChatContextMenus.swift b/submodules/ChatListUI/Sources/ChatContextMenus.swift index ba3747dd80..0ea081769f 100644 --- a/submodules/ChatListUI/Sources/ChatContextMenus.swift +++ b/submodules/ChatListUI/Sources/ChatContextMenus.swift @@ -397,7 +397,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch isMuted = true } items.append(.action(ContextMenuActionItem(text: isMuted ? strings.ChatList_Context_Unmute : strings.ChatList_Context_Mute, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) }, action: { _, f in - let _ = (context.engine.peers.togglePeerMuted(peerId: peerId) + let _ = (context.engine.peers.togglePeerMuted(peerId: peerId, threadId: nil) |> deliverOnMainQueue).start(completed: { f(.default) }) diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 638eb80b59..43fa5514cd 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -492,7 +492,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController return } - chatTitleView.titleContent = .peer(peerView: peerView, customTitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: false) + chatTitleView.titleContent = .peer(peerView: peerView, customTitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: false, isMuted: nil) strongSelf.infoReady.set(.single(true)) if let channel = peerView.peers[peerView.peerId] as? TelegramChannel, !channel.flags.contains(.isForum) { @@ -2468,112 +2468,122 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } public static func openMoreMenu(context: AccountContext, peerId: EnginePeer.Id, sourceController: ViewController, isViewingAsTopics: Bool, sourceView: UIView, gesture: ContextGesture?) { - let strings = context.sharedContext.currentPresentationData.with { $0 }.strings - - var items: [ContextMenuItem] = [] - - items.append(.action(ContextMenuActionItem(text: "View as Topics", icon: { theme in - if !isViewingAsTopics { - return nil - } - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) - }, action: { [weak sourceController] _, a in - a(.default) - - guard let sourceController = sourceController, let navigationController = sourceController.navigationController as? NavigationController else { + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> deliverOnMainQueue).start(next: { peer in + guard case let .channel(channel) = peer else { return } - let chatController = context.sharedContext.makeChatListController(context: context, location: .forum(peerId: peerId), controlsHistoryPreload: false, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: false) - navigationController.replaceController(sourceController, with: chatController, animated: false) - }))) - items.append(.action(ContextMenuActionItem(text: "View as Messages", icon: { theme in - if isViewingAsTopics { - return nil - } - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) - }, action: { [weak sourceController] _, a in - a(.default) - - guard let sourceController = sourceController, let navigationController = sourceController.navigationController as? NavigationController else { - return - } + let strings = context.sharedContext.currentPresentationData.with { $0 }.strings - let chatController = context.sharedContext.makeChatController(context: context, chatLocation: .peer(id: peerId), subject: nil, botStart: nil, mode: .standard(previewing: false)) - navigationController.replaceController(sourceController, with: chatController, animated: false) - }))) - items.append(.separator) - - //TODO:localize - items.append(.action(ContextMenuActionItem(text: "Group Info", icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Contact List/CreateGroupActionIcon"), color: theme.contextMenu.primaryColor) - }, action: { [weak sourceController] _, f in - f(.default) + var items: [ContextMenuItem] = [] - let _ = (context.engine.data.get( - TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) - ) - |> deliverOnMainQueue).start(next: { peer in - guard let sourceController = sourceController, let peer = peer, let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else { - return + items.append(.action(ContextMenuActionItem(text: "View as Topics", icon: { theme in + if !isViewingAsTopics { + return nil } - (sourceController.navigationController as? NavigationController)?.pushViewController(controller) - }) - }))) - - //TODO:localize - items.append(.action(ContextMenuActionItem(text: "Add Member", icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddUser"), color: theme.contextMenu.primaryColor) - }, action: { [weak sourceController] _, f in - f(.default) - - let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) - |> deliverOnMainQueue).start(next: { peer in - guard let sourceController = sourceController, let peer = peer else { - return - } - let selectAddMemberDisposable = MetaDisposable() - let addMemberDisposable = MetaDisposable() - context.sharedContext.openAddPeerMembers(context: context, updatedPresentationData: nil, parentController: sourceController, groupPeer: peer._asPeer(), selectAddMemberDisposable: selectAddMemberDisposable, addMemberDisposable: addMemberDisposable) - }) - }))) - - items.append(.separator) - - if let sourceController = sourceController as? ChatController { - items.append(.action(ContextMenuActionItem(text: strings.Conversation_Search, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Search"), color: theme.contextMenu.primaryColor) - }, action: { [weak sourceController] action in - action.dismissWithResult(.default) + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) + }, action: { [weak sourceController] _, a in + a(.default) - sourceController?.beginMessageSearch("") + guard let sourceController = sourceController, let navigationController = sourceController.navigationController as? NavigationController else { + return + } + + let chatController = context.sharedContext.makeChatListController(context: context, location: .forum(peerId: peerId), controlsHistoryPreload: false, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: false) + navigationController.replaceController(sourceController, with: chatController, animated: false) }))) - } else { + items.append(.action(ContextMenuActionItem(text: "View as Messages", icon: { theme in + if isViewingAsTopics { + return nil + } + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) + }, action: { [weak sourceController] _, a in + a(.default) + + guard let sourceController = sourceController, let navigationController = sourceController.navigationController as? NavigationController else { + return + } + + let chatController = context.sharedContext.makeChatController(context: context, chatLocation: .peer(id: peerId), subject: nil, botStart: nil, mode: .standard(previewing: false)) + navigationController.replaceController(sourceController, with: chatController, animated: false) + }))) + items.append(.separator) + //TODO:localize - items.append(.action(ContextMenuActionItem(text: "New Topic", icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) - }, action: { action in - action.dismissWithResult(.default) + items.append(.action(ContextMenuActionItem(text: "Group Info", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Contact List/CreateGroupActionIcon"), color: theme.contextMenu.primaryColor) + }, action: { [weak sourceController] _, f in + f(.default) - let controller = ForumCreateTopicScreen(context: context, peerId: peerId, mode: .create) - controller.navigationPresentation = .modal - controller.completion = { title, fileId in - let availableColors: [Int32] = [0x6FB9F0, 0xFFD67E, 0xCB86DB, 0x8EEE98, 0xFF93B2, 0xFB6F5F] - - let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconColor: availableColors.randomElement()!, iconFileId: fileId) - |> deliverOnMainQueue).start(next: { topicId in - if let navigationController = (sourceController.navigationController as? NavigationController) { - let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: topicId, navigationController: navigationController, activateInput: .text).start() - } - }) - } - sourceController.push(controller) + let _ = (context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) + ) + |> deliverOnMainQueue).start(next: { peer in + guard let sourceController = sourceController, let peer = peer, let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else { + return + } + (sourceController.navigationController as? NavigationController)?.pushViewController(controller) + }) }))) - } + + if channel.hasPermission(.inviteMembers) { + //TODO:localize + items.append(.action(ContextMenuActionItem(text: "Add Member", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddUser"), color: theme.contextMenu.primaryColor) + }, action: { [weak sourceController] _, f in + f(.default) + + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> deliverOnMainQueue).start(next: { peer in + guard let sourceController = sourceController, let peer = peer else { + return + } + let selectAddMemberDisposable = MetaDisposable() + let addMemberDisposable = MetaDisposable() + context.sharedContext.openAddPeerMembers(context: context, updatedPresentationData: nil, parentController: sourceController, groupPeer: peer._asPeer(), selectAddMemberDisposable: selectAddMemberDisposable, addMemberDisposable: addMemberDisposable) + }) + }))) + } + + if let sourceController = sourceController as? ChatController { + items.append(.separator) + items.append(.action(ContextMenuActionItem(text: strings.Conversation_Search, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Search"), color: theme.contextMenu.primaryColor) + }, action: { [weak sourceController] action in + action.dismissWithResult(.default) + + sourceController?.beginMessageSearch("") + }))) + } else if channel.hasPermission(.pinMessages) { + items.append(.separator) + + //TODO:localize + items.append(.action(ContextMenuActionItem(text: "New Topic", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) + }, action: { action in + action.dismissWithResult(.default) + + let controller = ForumCreateTopicScreen(context: context, peerId: peerId, mode: .create) + controller.navigationPresentation = .modal + controller.completion = { title, fileId in + let availableColors: [Int32] = [0x6FB9F0, 0xFFD67E, 0xCB86DB, 0x8EEE98, 0xFF93B2, 0xFB6F5F] + + let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconColor: availableColors.randomElement()!, iconFileId: fileId) + |> deliverOnMainQueue).start(next: { topicId in + if let navigationController = (sourceController.navigationController as? NavigationController) { + let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: topicId, navigationController: navigationController, activateInput: .text).start() + } + }) + } + sourceController.push(controller) + }))) + } - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let contextController = ContextController(account: context.account, presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: sourceController, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) - sourceController.presentInGlobalOverlay(contextController) + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let contextController = ContextController(account: context.account, presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: sourceController, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) + sourceController.presentInGlobalOverlay(contextController) + }) } private var initializedFilters = false diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 17504f94b8..f048e72e81 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -183,7 +183,7 @@ private final class ChatListShimmerNode: ASDisplayNode { let timestamp1: Int32 = 100000 let peers: [EnginePeer.Id: EnginePeer] = [:] let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in - }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in }, activateChatPreview: { _, _, gesture, _ in + }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in }, activateChatPreview: { _, _, gesture, _ in gesture?.cancel() }, present: { _ in }) diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index b7f645a41c..910609959e 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -1741,6 +1741,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in + }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, updatePeerGrouping: { _, _ in @@ -2952,7 +2953,7 @@ private final class ChatListSearchShimmerNode: ASDisplayNode { var peers: [EnginePeer.Id: EnginePeer] = [:] peers[peer1.id] = peer1 let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in - }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in }, activateChatPreview: { _, _, gesture, _ in + }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in }, activateChatPreview: { _, _, gesture, _ in gesture?.cancel() }, present: { _ in }) diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 925a64c66e..231d17309f 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -2688,6 +2688,12 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { switch option.key { case RevealOptionKey.delete.rawValue: item.interaction.deletePeerThread(peerId, threadId) + case RevealOptionKey.mute.rawValue: + item.interaction.setPeerThreadMuted(peerId, threadId, true) + close = false + case RevealOptionKey.unmute.rawValue: + item.interaction.setPeerThreadMuted(peerId, threadId, false) + close = false default: break } diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 9ab2211c87..6f7eda22f4 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -67,6 +67,7 @@ public final class ChatListNodeInteraction { let setPeerIdWithRevealedOptions: (EnginePeer.Id?, EnginePeer.Id?) -> Void let setItemPinned: (EngineChatList.PinnedItem.Id, Bool) -> Void let setPeerMuted: (EnginePeer.Id, Bool) -> Void + let setPeerThreadMuted: (EnginePeer.Id, Int64?, Bool) -> Void let deletePeer: (EnginePeer.Id, Bool) -> Void let deletePeerThread: (EnginePeer.Id, Int64) -> Void let updatePeerGrouping: (EnginePeer.Id, Bool) -> Void @@ -98,6 +99,7 @@ public final class ChatListNodeInteraction { setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, setItemPinned: @escaping (EngineChatList.PinnedItem.Id, Bool) -> Void, setPeerMuted: @escaping (EnginePeer.Id, Bool) -> Void, + setPeerThreadMuted: @escaping (EnginePeer.Id, Int64?, Bool) -> Void, deletePeer: @escaping (EnginePeer.Id, Bool) -> Void, deletePeerThread: @escaping (EnginePeer.Id, Int64) -> Void, updatePeerGrouping: @escaping (EnginePeer.Id, Bool) -> Void, @@ -119,6 +121,7 @@ public final class ChatListNodeInteraction { self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions self.setItemPinned = setItemPinned self.setPeerMuted = setPeerMuted + self.setPeerThreadMuted = setPeerThreadMuted self.deletePeer = deletePeer self.deletePeerThread = deletePeerThread self.updatePeerGrouping = updatePeerGrouping @@ -946,7 +949,7 @@ public final class ChatListNode: ListView { return } strongSelf.setCurrentRemovingPeerId(peerId) - let _ = (context.engine.peers.togglePeerMuted(peerId: peerId) + let _ = (context.engine.peers.togglePeerMuted(peerId: peerId, threadId: nil) |> deliverOnMainQueue).start(completed: { self?.updateState { state in var state = state @@ -955,6 +958,17 @@ public final class ChatListNode: ListView { } self?.setCurrentRemovingPeerId(nil) }) + }, setPeerThreadMuted: { [weak self] peerId, threadId, _ in + //self?.setCurrentRemovingPeerId(peerId) + let _ = (context.engine.peers.togglePeerMuted(peerId: peerId, threadId: threadId) + |> deliverOnMainQueue).start(completed: { + self?.updateState { state in + var state = state + state.peerIdWithRevealedOptions = nil + return state + } + //self?.setCurrentRemovingPeerId(nil) + }) }, deletePeer: { [weak self] peerId, joined in self?.deletePeerChat?(peerId, joined) }, deletePeerThread: { [weak self] peerId, threadId in @@ -1799,8 +1813,11 @@ public final class ChatListNode: ListView { if let combinedReadState = combinedReadState { hasUnread = combinedReadState.count > 0 } - if case let .chatList(index) = index { - preloadItems.append(ChatHistoryPreloadItem(index: index, isMuted: isMuted, hasUnread: hasUnread)) + switch index { + case let .chatList(index): + preloadItems.append(ChatHistoryPreloadItem(index: index, threadId: nil, isMuted: isMuted, hasUnread: hasUnread)) + case .forum: + break } } default: diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift index f80763b74a..ded7bc2ae2 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift @@ -166,7 +166,27 @@ func chatListViewForLocation(chatListLocation: ChatListControllerLocation, locat } } case let .forum(peerId): - let viewKey: PostboxViewKey = .messageHistoryThreadIndex(id: peerId) + let viewKey: PostboxViewKey = .messageHistoryThreadIndex( + id: peerId, + 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) + ) + ] + ) + ) var isFirst = false return account.postbox.combinedView(keys: [viewKey]) |> map { views -> ChatListNodeViewUpdate in @@ -182,18 +202,41 @@ func chatListViewForLocation(chatListLocation: ChatListControllerLocation, locat guard let data = item.info.get(MessageHistoryThreadData.self) else { continue } + + var hasUnseenMentions = false + + var isMuted = false + if case .muted = data.notificationSettings.muteState { + isMuted = true + } + + if let info = item.tagSummaryInfo[ChatListEntryMessageTagSummaryKey( + tag: .unseenPersonalMessage, + actionType: PendingMessageActionType.consumeUnseenPersonalMessage + )] { + hasUnseenMentions = (info.tagSummaryCount ?? 0) > (info.actionsSummaryCount ?? 0) + } + + var hasUnseenReactions = false + if let info = item.tagSummaryInfo[ChatListEntryMessageTagSummaryKey( + tag: .unseenReaction, + actionType: PendingMessageActionType.readReaction + )] { + hasUnseenReactions = (info.tagSummaryCount ?? 0) != 0// > (info.actionsSummaryCount ?? 0) + } + items.append(EngineChatList.Item( id: .forum(item.id), index: .forum(timestamp: item.index.timestamp, threadId: item.id, namespace: item.index.id.namespace, id: item.index.id.id), messages: item.topMessage.flatMap { [EngineMessage($0)] } ?? [], readCounters: EnginePeerReadCounters(state: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, .idBased(maxIncomingReadId: 1, maxOutgoingReadId: 1, maxKnownId: 1, count: data.incomingUnreadCount, markedUnread: false))])), - isMuted: false, + isMuted: isMuted, draft: nil, threadInfo: data.info, renderedPeer: EngineRenderedPeer(peer: EnginePeer(peer)), presence: nil, - hasUnseenMentions: false, - hasUnseenReactions: false, + hasUnseenMentions: hasUnseenMentions, + hasUnseenReactions: hasUnseenReactions, forumTopicTitle: nil, hasFailed: false, isContact: false diff --git a/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift b/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift index 65ddcebf41..0146c15eb4 100644 --- a/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift +++ b/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift @@ -18,6 +18,17 @@ public extension ChatLocation { return nil } } + + var threadId: Int64? { + switch self { + case .peer: + return nil + case let .replyThread(replyThreadMessage): + return Int64(replyThreadMessage.messageId.id) + case .feed: + return nil + } + } } public enum ChatPresentationInputQueryKind: Int32 { diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift index cc809e48d7..b8d5b9d26c 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift @@ -76,6 +76,7 @@ public final class HashtagSearchController: TelegramBaseController { }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in + }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, updatePeerGrouping: { _, _ in diff --git a/submodules/PeerInfoUI/Sources/ChannelAdminController.swift b/submodules/PeerInfoUI/Sources/ChannelAdminController.swift index 15fecf10eb..296d15a299 100644 --- a/submodules/PeerInfoUI/Sources/ChannelAdminController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelAdminController.swift @@ -416,7 +416,7 @@ private struct ChannelAdminControllerState: Equatable { } } -private func stringForRight(strings: PresentationStrings, right: TelegramChatAdminRightsFlags, isGroup: Bool, isChannel: Bool, defaultBannedRights: TelegramChatBannedRights?) -> String { +private func stringForRight(strings: PresentationStrings, right: TelegramChatAdminRightsFlags, isGroup: Bool, isChannel: Bool, isForum: Bool, defaultBannedRights: TelegramChatBannedRights?) -> String { if right.contains(.canChangeInfo) { return isGroup ? strings.Group_EditAdmin_PermissionChangeInfo : strings.Channel_EditAdmin_PermissionChangeInfo } else if right.contains(.canPostMessages) { @@ -438,7 +438,11 @@ private func stringForRight(strings: PresentationStrings, right: TelegramChatAdm return strings.Channel_EditAdmin_PermissionInviteSubscribers } } else if right.contains(.canPinMessages) { - return strings.Channel_EditAdmin_PermissionPinMessages + if isForum { + return strings.Channel_EditAdmin_PermissionCreateTopics + } else { + return strings.Channel_EditAdmin_PermissionPinMessages + } } else if right.contains(.canAddAdmins) { return strings.Channel_EditAdmin_PermissionAddAdmins } else if right.contains(.canBeAnonymous) { @@ -612,7 +616,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s var index = 0 for right in rightsOrder { if accountUserRightsFlags.contains(right) { - entries.append(.rightItem(presentationData.theme, index, stringForRight(strings: presentationData.strings, right: right, isGroup: isGroup, isChannel: isChannel, defaultBannedRights: channel.defaultBannedRights), right, currentRightsFlags, currentRightsFlags.contains(right), right == .canBeAnonymous)) + entries.append(.rightItem(presentationData.theme, index, stringForRight(strings: presentationData.strings, right: right, isGroup: isGroup, isChannel: isChannel, isForum: channel.flags.contains(.isForum), defaultBannedRights: channel.defaultBannedRights), right, currentRightsFlags, currentRightsFlags.contains(right), right == .canBeAnonymous)) index += 1 } } @@ -651,7 +655,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s var index = 0 for right in rightsOrder { if accountUserRightsFlags.contains(right) { - entries.append(.rightItem(presentationData.theme, index, stringForRight(strings: presentationData.strings, right: right, isGroup: isGroup, isChannel: isChannel, defaultBannedRights: channel.defaultBannedRights), right, currentRightsFlags, currentRightsFlags.contains(right), !state.updating && admin.id != accountPeerId && !rightEnabledByDefault(channelPeer: channel, right: right))) + entries.append(.rightItem(presentationData.theme, index, stringForRight(strings: presentationData.strings, right: right, isGroup: isGroup, isChannel: isChannel, isForum: channel.flags.contains(.isForum), defaultBannedRights: channel.defaultBannedRights), right, currentRightsFlags, currentRightsFlags.contains(right), !state.updating && admin.id != accountPeerId && !rightEnabledByDefault(channelPeer: channel, right: right))) index += 1 } } @@ -683,7 +687,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s } else if let initialParticipant = initialParticipant, case let .member(_, _, maybeAdminInfo, _, _) = initialParticipant, let adminInfo = maybeAdminInfo { var index = 0 for right in rightsOrder { - entries.append(.rightItem(presentationData.theme, index, stringForRight(strings: presentationData.strings, right: right, isGroup: isGroup, isChannel: isChannel, defaultBannedRights: channel.defaultBannedRights), right, adminInfo.rights.rights, adminInfo.rights.rights.contains(right), false)) + entries.append(.rightItem(presentationData.theme, index, stringForRight(strings: presentationData.strings, right: right, isGroup: isGroup, isChannel: isChannel, isForum: channel.flags.contains(.isForum), defaultBannedRights: channel.defaultBannedRights), right, adminInfo.rights.rights, adminInfo.rights.rights.contains(right), false)) index += 1 } } @@ -782,7 +786,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s var index = 0 for right in rightsOrder { if accountUserRightsFlags.contains(right) { - entries.append(.rightItem(presentationData.theme, index, stringForRight(strings: presentationData.strings, right: right, isGroup: isGroup, isChannel: isChannel, defaultBannedRights: group.defaultBannedRights), right, currentRightsFlags, currentRightsFlags.contains(right), !state.updating && accountIsCreator)) + entries.append(.rightItem(presentationData.theme, index, stringForRight(strings: presentationData.strings, right: right, isGroup: isGroup, isChannel: isChannel, isForum: false, defaultBannedRights: group.defaultBannedRights), right, currentRightsFlags, currentRightsFlags.contains(right), !state.updating && accountIsCreator)) index += 1 } } diff --git a/submodules/PeerInfoUI/Sources/ChannelBannedMemberController.swift b/submodules/PeerInfoUI/Sources/ChannelBannedMemberController.swift index 14c2562077..42fadf1903 100644 --- a/submodules/PeerInfoUI/Sources/ChannelBannedMemberController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelBannedMemberController.swift @@ -293,7 +293,7 @@ private func channelBannedMemberControllerEntries(presentationData: Presentation var index = 0 for (right, _) in allGroupPermissionList { let defaultEnabled = !defaultBannedRights.flags.contains(right) && channel.hasPermission(.banMembers) - entries.append(.rightItem(presentationData.theme, index, stringForGroupPermission(strings: presentationData.strings, right: right), right, defaultEnabled && !currentRightsFlags.contains(right), defaultEnabled && !state.updating)) + entries.append(.rightItem(presentationData.theme, index, stringForGroupPermission(strings: presentationData.strings, right: right, isForum: channel.flags.contains(.isForum)), right, defaultEnabled && !currentRightsFlags.contains(right), defaultEnabled && !state.updating)) index += 1 } @@ -339,7 +339,7 @@ private func channelBannedMemberControllerEntries(presentationData: Presentation var index = 0 for (right, _) in allGroupPermissionList { let defaultEnabled = !defaultBannedRightsFlags.contains(right) - entries.append(.rightItem(presentationData.theme, index, stringForGroupPermission(strings: presentationData.strings, right: right), right, defaultEnabled && !currentRightsFlags.contains(right), defaultEnabled && !state.updating)) + entries.append(.rightItem(presentationData.theme, index, stringForGroupPermission(strings: presentationData.strings, right: right, isForum: false), right, defaultEnabled && !currentRightsFlags.contains(right), defaultEnabled && !state.updating)) index += 1 } diff --git a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift index 90347c8b60..a1c5463aa7 100644 --- a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift @@ -336,7 +336,7 @@ private struct ChannelPermissionsControllerState: Equatable { var modifiedSlowmodeTimeout: Int32? } -func stringForGroupPermission(strings: PresentationStrings, right: TelegramChatBannedRightsFlags) -> String { +func stringForGroupPermission(strings: PresentationStrings, right: TelegramChatBannedRightsFlags, isForum: Bool) -> String { if right.contains(.banSendMessages) { return strings.Channel_BanUser_PermissionSendMessages } else if right.contains(.banSendMedia) { @@ -352,7 +352,11 @@ func stringForGroupPermission(strings: PresentationStrings, right: TelegramChatB } else if right.contains(.banAddMembers) { return strings.Channel_BanUser_PermissionAddMembers } else if right.contains(.banPinMessages) { - return strings.Channel_EditAdmin_PermissionPinMessages + if isForum { + return strings.Channel_EditAdmin_PermissionCreateTopics + } else { + return strings.Channel_EditAdmin_PermissionPinMessages + } } else { return "" } @@ -442,7 +446,7 @@ private func channelPermissionsControllerEntries(context: AccountContext, presen if !channel.hasPermission(correspondingAdminRight) { enabled = false } - entries.append(.permission(presentationData.theme, rightIndex, stringForGroupPermission(strings: presentationData.strings, right: rights), !effectiveRightsFlags.contains(rights), rights, enabled)) + entries.append(.permission(presentationData.theme, rightIndex, stringForGroupPermission(strings: presentationData.strings, right: rights, isForum: channel.flags.contains(.isForum)), !effectiveRightsFlags.contains(rights), rights, enabled)) rightIndex += 1 } @@ -479,7 +483,7 @@ private func channelPermissionsControllerEntries(context: AccountContext, presen entries.append(.permissionsHeader(presentationData.theme, presentationData.strings.GroupInfo_Permissions_SectionTitle)) var rightIndex: Int = 0 for (rights, _) in allGroupPermissionList { - entries.append(.permission(presentationData.theme, rightIndex, stringForGroupPermission(strings: presentationData.strings, right: rights), !effectiveRightsFlags.contains(rights), rights, true)) + entries.append(.permission(presentationData.theme, rightIndex, stringForGroupPermission(strings: presentationData.strings, right: rights, isForum: false), !effectiveRightsFlags.contains(rights), rights, true)) rightIndex += 1 } diff --git a/submodules/Postbox/Sources/ChatListTable.swift b/submodules/Postbox/Sources/ChatListTable.swift index 3bd7a114b7..01c97f3071 100644 --- a/submodules/Postbox/Sources/ChatListTable.swift +++ b/submodules/Postbox/Sources/ChatListTable.swift @@ -260,7 +260,7 @@ final class ChatListTable: Table { let isRemovedFromTotalUnreadCount = resolvedIsRemovedFromTotalUnreadCount(globalSettings: globalNotificationSettings, peer: peer, peerSettings: postbox.peerNotificationSettingsTable.getEffective(messageIndex.id.peerId)) - let messageTagSummaryResult = resolveChatListMessageTagSummaryResultCalculation(postbox: postbox, peerId: peer.id, calculation: filterPredicate.messageTagSummary) + let messageTagSummaryResult = resolveChatListMessageTagSummaryResultCalculation(postbox: postbox, peerId: peer.id, threadId: nil, calculation: filterPredicate.messageTagSummary) if filterPredicate.pinnedPeerIds.contains(peer.id) { passFilter = true @@ -895,7 +895,7 @@ final class ChatListTable: Table { } var tagSummary: MessageHistoryTagNamespaceSummary? if let summaryTag = summaryTag { - tagSummary = summaryTable.get(MessageHistoryTagsSummaryKey(tag: summaryTag, peerId: peerIndex.messageIndex.id.peerId, namespace: namespace)) + tagSummary = summaryTable.get(MessageHistoryTagsSummaryKey(tag: summaryTag, peerId: peerIndex.messageIndex.id.peerId, threadId: nil, namespace: namespace)) } var topMessageAttributes: [MessageAttribute] = [] if let topMessage = topMessage { diff --git a/submodules/Postbox/Sources/ChatListView.swift b/submodules/Postbox/Sources/ChatListView.swift index 7d4ed384d9..8718c9b02f 100644 --- a/submodules/Postbox/Sources/ChatListView.swift +++ b/submodules/Postbox/Sources/ChatListView.swift @@ -10,7 +10,7 @@ public struct ChatListEntryMessageTagSummaryKey: Hashable { } } -public struct ChatListEntryMessageTagSummaryComponent { +public struct ChatListEntryMessageTagSummaryComponent: Equatable { public let namespace: MessageId.Namespace public init(namespace: MessageId.Namespace) { @@ -18,7 +18,7 @@ public struct ChatListEntryMessageTagSummaryComponent { } } -public struct ChatListEntryPendingMessageActionsSummaryComponent { +public struct ChatListEntryPendingMessageActionsSummaryComponent: Equatable { public let namespace: MessageId.Namespace public init(namespace: MessageId.Namespace) { @@ -26,8 +26,8 @@ public struct ChatListEntryPendingMessageActionsSummaryComponent { } } -public struct ChatListEntrySummaryComponents { - public struct Component { +public struct ChatListEntrySummaryComponents: Equatable { + public struct Component: Equatable { public let tagSummary: ChatListEntryMessageTagSummaryComponent? public let actionsSummary: ChatListEntryPendingMessageActionsSummaryComponent? diff --git a/submodules/Postbox/Sources/ChatListViewState.swift b/submodules/Postbox/Sources/ChatListViewState.swift index 4f40177ce1..588f35b9e0 100644 --- a/submodules/Postbox/Sources/ChatListViewState.swift +++ b/submodules/Postbox/Sources/ChatListViewState.swift @@ -63,7 +63,7 @@ private func mappedChatListFilterPredicate(postbox: PostboxImpl, currentTransact let notificationsPeerId = peer.notificationSettingsPeerId ?? peer.id let isContact = postbox.contactsTable.isContact(peerId: notificationsPeerId) let isRemovedFromTotalUnreadCount = resolvedIsRemovedFromTotalUnreadCount(globalSettings: globalNotificationSettings, peer: peer, peerSettings: postbox.peerNotificationSettingsTable.getEffective(notificationsPeerId)) - let messageTagSummaryResult = resolveChatListMessageTagSummaryResultCalculation(postbox: postbox, peerId: peer.id, calculation: predicate.messageTagSummary) + let messageTagSummaryResult = resolveChatListMessageTagSummaryResultCalculation(postbox: postbox, peerId: peer.id, threadId: nil, calculation: predicate.messageTagSummary) if predicate.includes(peer: peer, groupId: groupId, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, isUnread: isUnread, isContact: isContact, messageTagSummaryResult: messageTagSummaryResult) { return true @@ -402,7 +402,7 @@ private final class ChatListViewSpaceState { let isRemovedFromTotalUnreadCount = resolvedIsRemovedFromTotalUnreadCount(globalSettings: globalNotificationSettingsValue, peer: peer, peerSettings: postbox.peerNotificationSettingsTable.getEffective(notificationsPeerId)) - let messageTagSummaryResult = resolveChatListMessageTagSummaryResultCalculation(postbox: postbox, peerId: peer.id, calculation: filterPredicate.messageTagSummary) + let messageTagSummaryResult = resolveChatListMessageTagSummaryResultCalculation(postbox: postbox, peerId: peer.id, threadId: nil, calculation: filterPredicate.messageTagSummary) if !filterPredicate.includes(peer: peer, groupId: groupId, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, isUnread: postbox.readStateTable.getCombinedState(peer.id)?.isUnread ?? false, isContact: postbox.contactsTable.isContact(peerId: notificationsPeerId), messageTagSummaryResult: messageTagSummaryResult) { continue inner @@ -498,7 +498,7 @@ private final class ChatListViewSpaceState { if !transaction.currentUpdatedPeerNotificationSettings.isEmpty, case let .group(groupId, pinned, maybeFilterPredicate) = self.space, let filterPredicate = maybeFilterPredicate { var removeEntryIndices: [MutableChatListEntryIndex] = [] - let _ = self.orderedEntries.mutableScan { entry in + let _ = self.orderedEntries.mutableScan { entry -> MutableChatListEntry? in let entryPeer: Peer let entryNotificationsPeerId: PeerId switch entry { @@ -532,7 +532,7 @@ private final class ChatListViewSpaceState { let nowRemovedFromTotalUnreadCount = resolvedIsRemovedFromTotalUnreadCount(globalSettings: globalNotificationSettingsValue, peer: entryPeer, peerSettings: settingsChange.1) - let messageTagSummaryResult = resolveChatListMessageTagSummaryResultCalculation(postbox: postbox, peerId: entryPeer.id, calculation: filterPredicate.messageTagSummary) + let messageTagSummaryResult = resolveChatListMessageTagSummaryResultCalculation(postbox: postbox, peerId: entryPeer.id, threadId: nil, calculation: filterPredicate.messageTagSummary) let isIncluded = filterPredicate.includes(peer: entryPeer, groupId: groupId, isRemovedFromTotalUnreadCount: nowRemovedFromTotalUnreadCount, isUnread: isUnread, isContact: postbox.contactsTable.isContact(peerId: entryNotificationsPeerId), messageTagSummaryResult: messageTagSummaryResult) if !isIncluded { @@ -570,7 +570,7 @@ private final class ChatListViewSpaceState { let nowRemovedFromTotalUnreadCount = resolvedIsRemovedFromTotalUnreadCount(globalSettings: globalNotificationSettingsValue, peer: mainPeer, peerSettings: settingsChange.1) - let messageTagSummaryResult = resolveChatListMessageTagSummaryResultCalculation(postbox: postbox, peerId: peerId, calculation: filterPredicate.messageTagSummary) + let messageTagSummaryResult = resolveChatListMessageTagSummaryResultCalculation(postbox: postbox, peerId: peerId, threadId: nil, calculation: filterPredicate.messageTagSummary) let isIncluded = filterPredicate.includes(peer: mainPeer, groupId: groupId, isRemovedFromTotalUnreadCount: nowRemovedFromTotalUnreadCount, isUnread: isUnread, isContact: postbox.contactsTable.isContact(peerId: peerId), messageTagSummaryResult: messageTagSummaryResult) if isIncluded && self.orderedEntries.indicesForPeerId(mainPeer.id) == nil { @@ -730,7 +730,7 @@ private final class ChatListViewSpaceState { if !transaction.currentUpdatedMessageTagSummaries.isEmpty || !transaction.currentUpdatedMessageActionsSummaries.isEmpty, case let .group(groupId, pinned, maybeFilterPredicate) = self.space, let filterPredicate = maybeFilterPredicate, let filterMessageTagSummary = filterPredicate.messageTagSummary { var removeEntryIndices: [MutableChatListEntryIndex] = [] - let _ = self.orderedEntries.mutableScan { entry in + let _ = self.orderedEntries.mutableScan { entry -> MutableChatListEntry? in let entryPeer: Peer let entryNotificationsPeerId: PeerId switch entry { @@ -752,7 +752,7 @@ private final class ChatListViewSpaceState { return nil } - let updatedMessageSummary = transaction.currentUpdatedMessageTagSummaries[MessageHistoryTagsSummaryKey(tag: filterMessageTagSummary.addCount.tag, peerId: entryPeer.id, namespace: filterMessageTagSummary.addCount.namespace)] + let updatedMessageSummary = transaction.currentUpdatedMessageTagSummaries[MessageHistoryTagsSummaryKey(tag: filterMessageTagSummary.addCount.tag, peerId: entryPeer.id, threadId: nil, namespace: filterMessageTagSummary.addCount.namespace)] let updatedActionsSummary = transaction.currentUpdatedMessageActionsSummaries[PendingMessageActionsSummaryKey(type: filterMessageTagSummary.subtractCount.type, peerId: entryPeer.id, namespace: filterMessageTagSummary.subtractCount.namespace)] if updatedMessageSummary != nil || updatedActionsSummary != nil { @@ -768,7 +768,7 @@ private final class ChatListViewSpaceState { let nowRemovedFromTotalUnreadCount = resolvedIsRemovedFromTotalUnreadCount(globalSettings: globalNotificationSettingsValue, peer: entryPeer, peerSettings: postbox.peerNotificationSettingsTable.getEffective(entryPeer.id)) - let messageTagSummaryResult = resolveChatListMessageTagSummaryResultCalculation(postbox: postbox, peerId: entryPeer.id, calculation: filterPredicate.messageTagSummary) + let messageTagSummaryResult = resolveChatListMessageTagSummaryResultCalculation(postbox: postbox, peerId: entryPeer.id, threadId: nil, calculation: filterPredicate.messageTagSummary) let isIncluded = filterPredicate.includes(peer: entryPeer, groupId: groupId, isRemovedFromTotalUnreadCount: nowRemovedFromTotalUnreadCount, isUnread: isUnread, isContact: postbox.contactsTable.isContact(peerId: entryNotificationsPeerId), messageTagSummaryResult: messageTagSummaryResult) if !isIncluded { @@ -813,7 +813,7 @@ private final class ChatListViewSpaceState { let nowRemovedFromTotalUnreadCount = resolvedIsRemovedFromTotalUnreadCount(globalSettings: globalNotificationSettingsValue, peer: mainPeer, peerSettings: postbox.peerNotificationSettingsTable.getEffective(mainPeer.id)) - let messageTagSummaryResult = resolveChatListMessageTagSummaryResultCalculation(postbox: postbox, peerId: peerId, calculation: filterPredicate.messageTagSummary) + let messageTagSummaryResult = resolveChatListMessageTagSummaryResultCalculation(postbox: postbox, peerId: peerId, threadId: nil, calculation: filterPredicate.messageTagSummary) let isIncluded = filterPredicate.includes(peer: mainPeer, groupId: groupId, isRemovedFromTotalUnreadCount: nowRemovedFromTotalUnreadCount, isUnread: isUnread, isContact: postbox.contactsTable.isContact(peerId: peerId), messageTagSummaryResult: messageTagSummaryResult) if isIncluded && self.orderedEntries.indicesForPeerId(mainPeer.id) == nil { @@ -852,7 +852,7 @@ private final class ChatListViewSpaceState { 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) + let key = MessageHistoryTagsSummaryKey(tag: key.tag, peerId: index.messageIndex.id.peerId, threadId: nil, namespace: tagSummary.namespace) if let summary = transaction.currentUpdatedMessageTagSummaries[key] { updatedTagSummaryCount = summary.count } @@ -1428,7 +1428,7 @@ struct ChatListViewState { var actionsSummaryCount: Int32? if let tagSummary = component.tagSummary { - let key = MessageHistoryTagsSummaryKey(tag: key.tag, peerId: index.messageIndex.id.peerId, namespace: tagSummary.namespace) + let key = MessageHistoryTagsSummaryKey(tag: key.tag, peerId: index.messageIndex.id.peerId, threadId: nil, namespace: tagSummary.namespace) if let summary = postbox.messageHistoryTagsSummaryTable.get(key) { tagSummaryCount = summary.count } diff --git a/submodules/Postbox/Sources/ChatLocation.swift b/submodules/Postbox/Sources/ChatLocation.swift index b77e478ed1..45a6eaacd0 100644 --- a/submodules/Postbox/Sources/ChatLocation.swift +++ b/submodules/Postbox/Sources/ChatLocation.swift @@ -2,12 +2,36 @@ import Foundation import SwiftSignalKit public enum ChatLocationInput { - case peer(peerId: PeerId) + case peer(peerId: PeerId, threadId: Int64?) case thread(peerId: PeerId, threadId: Int64, data: Signal) case feed(id: Int32, data: Signal) } +public extension ChatLocationInput { + var peerId: PeerId? { + switch self { + case let .peer(peerId, _): + return peerId + case let .thread(peerId, _, _): + return peerId + case .feed: + return nil + } + } + + var threadId: Int64? { + switch self { + case let .peer(_, threadId): + return threadId + case let .thread(_, threadId, _): + return threadId + case .feed: + return nil + } + } +} + public enum ResolvedChatLocationInput { - case peer(PeerId) + case peer(peerId: PeerId, threadId: Int64?) case external(MessageHistoryViewExternalInput) } diff --git a/submodules/Postbox/Sources/MessageHistoryHoleIndexTable.swift b/submodules/Postbox/Sources/MessageHistoryHoleIndexTable.swift index 138e5a74cc..e50c7d58be 100644 --- a/submodules/Postbox/Sources/MessageHistoryHoleIndexTable.swift +++ b/submodules/Postbox/Sources/MessageHistoryHoleIndexTable.swift @@ -3,7 +3,15 @@ import Foundation struct MessageHistoryIndexHoleOperationKey: Hashable { let peerId: PeerId let namespace: MessageId.Namespace + let threadId: Int64? let space: MessageHistoryHoleSpace + + init(peerId: PeerId, namespace: MessageId.Namespace, threadId: Int64?, space: MessageHistoryHoleSpace) { + self.peerId = peerId + self.namespace = namespace + self.threadId = threadId + self.space = space + } } enum MessageHistoryIndexHoleOperation { @@ -25,8 +33,8 @@ public enum MessageHistoryHoleSpace: Equatable, Hashable, CustomStringConvertibl } } -private func addOperation(_ operation: MessageHistoryIndexHoleOperation, peerId: PeerId, namespace: MessageId.Namespace, space: MessageHistoryHoleSpace, to operations: inout [MessageHistoryIndexHoleOperationKey: [MessageHistoryIndexHoleOperation]]) { - let key = MessageHistoryIndexHoleOperationKey(peerId: peerId, namespace: namespace, space: space) +func addMessageHistoryHoleOperation(_ operation: MessageHistoryIndexHoleOperation, peerId: PeerId, threadId: Int64?, namespace: MessageId.Namespace, space: MessageHistoryHoleSpace, to operations: inout [MessageHistoryIndexHoleOperationKey: [MessageHistoryIndexHoleOperation]]) { + let key = MessageHistoryIndexHoleOperationKey(peerId: peerId, namespace: namespace, threadId: threadId, space: space) if operations[key] == nil { operations[key] = [] } @@ -365,7 +373,7 @@ final class MessageHistoryHoleIndexTable: Table { self.valueBox.set(self.table, key: self.key(id: MessageId(peerId: peerId, namespace: namespace, id: closedRange.upperBound), space: space), value: MemoryBuffer(memory: &lowerBound, capacity: 4, length: 4, freeWhenDone: false)) } - addOperation(.insert(clippedRange), peerId: peerId, namespace: namespace, space: space, to: &operations) + addMessageHistoryHoleOperation(.insert(clippedRange), peerId: peerId, threadId: nil, namespace: namespace, space: space, to: &operations) } func remove(peerId: PeerId, namespace: MessageId.Namespace, space: MessageHistoryHoleSpace, range: ClosedRange, operations: inout [MessageHistoryIndexHoleOperationKey: [MessageHistoryIndexHoleOperation]]) { @@ -431,7 +439,7 @@ final class MessageHistoryHoleIndexTable: Table { } if !removeKeys.isEmpty { - addOperation(.remove(range), peerId: peerId, namespace: namespace, space: space, to: &operations) + addMessageHistoryHoleOperation(.remove(range), peerId: peerId, threadId: nil, namespace: namespace, space: space, to: &operations) } } diff --git a/submodules/Postbox/Sources/MessageHistoryTable.swift b/submodules/Postbox/Sources/MessageHistoryTable.swift index 779f117e26..75dfb842a7 100644 --- a/submodules/Postbox/Sources/MessageHistoryTable.swift +++ b/submodules/Postbox/Sources/MessageHistoryTable.swift @@ -81,6 +81,7 @@ final class MessageHistoryTable: Table { let failedTable: MessageHistoryFailedTable let tagsTable: MessageHistoryTagsTable let threadsTable: MessageHistoryThreadsTable + let threadTagsTable: MessageHistoryThreadTagsTable let globalTagsTable: GlobalMessageHistoryTagsTable let localTagsTable: LocalMessageHistoryTagsTable let timeBasedAttributesTable: TimestampBasedMessageAttributesTable @@ -90,7 +91,7 @@ final class MessageHistoryTable: Table { let summaryTable: MessageHistoryTagsSummaryTable let pendingActionsTable: PendingMessageActionsTable - init(valueBox: ValueBox, table: ValueBoxTable, useCaches: Bool, seedConfiguration: SeedConfiguration, messageHistoryIndexTable: MessageHistoryIndexTable, messageHistoryHoleIndexTable: MessageHistoryHoleIndexTable, messageMediaTable: MessageMediaTable, historyMetadataTable: MessageHistoryMetadataTable, globallyUniqueMessageIdsTable: MessageGloballyUniqueIdTable, unsentTable: MessageHistoryUnsentTable, failedTable: MessageHistoryFailedTable, tagsTable: MessageHistoryTagsTable, threadsTable: MessageHistoryThreadsTable, globalTagsTable: GlobalMessageHistoryTagsTable, localTagsTable: LocalMessageHistoryTagsTable, timeBasedAttributesTable: TimestampBasedMessageAttributesTable, readStateTable: MessageHistoryReadStateTable, synchronizeReadStateTable: MessageHistorySynchronizeReadStateTable, textIndexTable: MessageHistoryTextIndexTable, summaryTable: MessageHistoryTagsSummaryTable, pendingActionsTable: PendingMessageActionsTable) { + init(valueBox: ValueBox, table: ValueBoxTable, useCaches: Bool, seedConfiguration: SeedConfiguration, messageHistoryIndexTable: MessageHistoryIndexTable, messageHistoryHoleIndexTable: MessageHistoryHoleIndexTable, messageMediaTable: MessageMediaTable, historyMetadataTable: MessageHistoryMetadataTable, globallyUniqueMessageIdsTable: MessageGloballyUniqueIdTable, unsentTable: MessageHistoryUnsentTable, failedTable: MessageHistoryFailedTable, tagsTable: MessageHistoryTagsTable, threadsTable: MessageHistoryThreadsTable, threadTagsTable: MessageHistoryThreadTagsTable, globalTagsTable: GlobalMessageHistoryTagsTable, localTagsTable: LocalMessageHistoryTagsTable, timeBasedAttributesTable: TimestampBasedMessageAttributesTable, readStateTable: MessageHistoryReadStateTable, synchronizeReadStateTable: MessageHistorySynchronizeReadStateTable, textIndexTable: MessageHistoryTextIndexTable, summaryTable: MessageHistoryTagsSummaryTable, pendingActionsTable: PendingMessageActionsTable) { self.seedConfiguration = seedConfiguration self.messageHistoryIndexTable = messageHistoryIndexTable self.messageHistoryHoleIndexTable = messageHistoryHoleIndexTable @@ -101,6 +102,7 @@ final class MessageHistoryTable: Table { self.failedTable = failedTable self.tagsTable = tagsTable self.threadsTable = threadsTable + self.threadTagsTable = threadTagsTable self.globalTagsTable = globalTagsTable self.localTagsTable = localTagsTable self.timeBasedAttributesTable = timeBasedAttributesTable @@ -278,6 +280,9 @@ final class MessageHistoryTable: Table { if (currentTags & 1) != 0 { let tag = MessageTags(rawValue: 1 << UInt32(i)) self.tagsTable.add(tags: tag, index: message.index, isNewlyAdded: true, updatedSummaries: &updatedMessageTagSummaries, invalidateSummaries: &invalidateMessageTagSummaries) + if let threadId = message.threadId { + self.threadTagsTable.add(tags: tag, threadId: threadId, index: message.index, isNewlyAdded: true, updatedSummaries: &updatedMessageTagSummaries, invalidateSummaries: &invalidateMessageTagSummaries) + } } } } @@ -434,13 +439,34 @@ final class MessageHistoryTable: Table { return globallyUniqueIdToMessageId } - func removeMessages(_ messageIds: [MessageId], operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], timestampBasedMessageAttributesOperations: inout [TimestampBasedMessageAttributesOperation], forEachMedia: (Media) -> Void) { + func removeMessages(_ messageIds: [MessageId], operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], timestampBasedMessageAttributesOperations: inout [TimestampBasedMessageAttributesOperation], forEachMedia: ((Media) -> Void)?) { for (peerId, messageIds) in self.messageIdsByPeerId(messageIds) { var operations: [MessageHistoryIndexOperation] = [] for id in messageIds { self.messageHistoryIndexTable.removeMessage(id, operations: &operations) } + + if let forEachMedia = forEachMedia { + for operation in operations { + if case let .Remove(index) = operation { + if let message = self.getMessage(index) { + for media in self.renderMessageMedia(referencedMedia: message.referencedMedia, embeddedMediaData: message.embeddedMediaData) { + forEachMedia(media) + } + } + } + } + } + + self.processIndexOperations(peerId, operations: operations, processedOperationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, timestampBasedMessageAttributesOperations: ×tampBasedMessageAttributesOperations) + } + } + + func removeMessagesInRange(peerId: PeerId, namespace: MessageId.Namespace, minId: MessageId.Id, maxId: MessageId.Id, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], timestampBasedMessageAttributesOperations: inout [TimestampBasedMessageAttributesOperation], forEachMedia: ((Media) -> Void)?) { + var operations: [MessageHistoryIndexOperation] = [] + self.messageHistoryIndexTable.removeMessagesInRange(peerId: peerId, namespace: namespace, minId: minId, maxId: maxId, operations: &operations) + if let forEachMedia = forEachMedia { for operation in operations { if case let .Remove(index) = operation { if let message = self.getMessage(index) { @@ -450,27 +476,12 @@ final class MessageHistoryTable: Table { } } } - self.processIndexOperations(peerId, operations: operations, processedOperationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, timestampBasedMessageAttributesOperations: ×tampBasedMessageAttributesOperations) - } - } - - func removeMessagesInRange(peerId: PeerId, namespace: MessageId.Namespace, minId: MessageId.Id, maxId: MessageId.Id, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], timestampBasedMessageAttributesOperations: inout [TimestampBasedMessageAttributesOperation], forEachMedia: (Media) -> Void) { - var operations: [MessageHistoryIndexOperation] = [] - self.messageHistoryIndexTable.removeMessagesInRange(peerId: peerId, namespace: namespace, minId: minId, maxId: maxId, operations: &operations) - for operation in operations { - if case let .Remove(index) = operation { - if let message = self.getMessage(index) { - for media in self.renderMessageMedia(referencedMedia: message.referencedMedia, embeddedMediaData: message.embeddedMediaData) { - forEachMedia(media) - } - } - } } self.processIndexOperations(peerId, operations: operations, processedOperationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, timestampBasedMessageAttributesOperations: ×tampBasedMessageAttributesOperations) } - func clearHistoryInRange(peerId: PeerId, threadId: Int64?, minTimestamp: Int32, maxTimestamp: Int32, namespaces: MessageIdNamespaces, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], timestampBasedMessageAttributesOperations: inout [TimestampBasedMessageAttributesOperation], forEachMedia: (Media) -> Void) { + func clearHistoryInRange(peerId: PeerId, threadId: Int64?, minTimestamp: Int32, maxTimestamp: Int32, namespaces: MessageIdNamespaces, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], timestampBasedMessageAttributesOperations: inout [TimestampBasedMessageAttributesOperation], forEachMedia: ((Media) -> Void)?) { var indices = self.allMessageIndices(peerId: peerId).filter { namespaces.contains($0.id.namespace) } if let threadId = threadId { indices = indices.filter { index in @@ -491,7 +502,7 @@ final class MessageHistoryTable: Table { self.removeMessages(indices.map { $0.id }, operationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, timestampBasedMessageAttributesOperations: ×tampBasedMessageAttributesOperations, forEachMedia: forEachMedia) } - func clearHistory(peerId: PeerId, threadId: Int64?, namespaces: MessageIdNamespaces, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], timestampBasedMessageAttributesOperations: inout [TimestampBasedMessageAttributesOperation], forEachMedia: (Media) -> Void) { + func clearHistory(peerId: PeerId, threadId: Int64?, namespaces: MessageIdNamespaces, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], timestampBasedMessageAttributesOperations: inout [TimestampBasedMessageAttributesOperation], forEachMedia: ((Media) -> Void)?) { var indices = self.allMessageIndices(peerId: peerId).filter { namespaces.contains($0.id.namespace) } if let threadId = threadId { indices = indices.filter { index in @@ -509,12 +520,12 @@ final class MessageHistoryTable: Table { self.removeMessages(indices.map { $0.id }, operationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, timestampBasedMessageAttributesOperations: ×tampBasedMessageAttributesOperations, forEachMedia: forEachMedia) } - func removeAllMessagesWithAuthor(peerId: PeerId, authorId: PeerId, namespace: MessageId.Namespace, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], timestampBasedMessageAttributesOperations: inout [TimestampBasedMessageAttributesOperation], forEachMedia: (Media) -> Void) { + func removeAllMessagesWithAuthor(peerId: PeerId, authorId: PeerId, namespace: MessageId.Namespace, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], timestampBasedMessageAttributesOperations: inout [TimestampBasedMessageAttributesOperation], forEachMedia: ((Media) -> Void)?) { let indices = self.allIndicesWithAuthor(peerId: peerId, authorId: authorId, namespace: namespace) self.removeMessages(indices.map { $0.id }, operationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, timestampBasedMessageAttributesOperations: ×tampBasedMessageAttributesOperations, forEachMedia: forEachMedia) } - func removeAllMessagesWithGlobalTag(tag: GlobalMessageTags, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], timestampBasedMessageAttributesOperations: inout [TimestampBasedMessageAttributesOperation], forEachMedia: (Media) -> Void) { + func removeAllMessagesWithGlobalTag(tag: GlobalMessageTags, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], timestampBasedMessageAttributesOperations: inout [TimestampBasedMessageAttributesOperation], forEachMedia: ((Media) -> Void)?) { var indices: [MessageIndex] = [] for entry in self.allIndicesWithGlobalTag(tag: tag) { switch entry { @@ -527,7 +538,7 @@ final class MessageHistoryTable: Table { self.removeMessages(indices.map { $0.id }, operationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, timestampBasedMessageAttributesOperations: ×tampBasedMessageAttributesOperations, forEachMedia: forEachMedia) } - func removeAllMessagesWithForwardAuthor(peerId: PeerId, forwardAuthorId: PeerId, namespace: MessageId.Namespace, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], timestampBasedMessageAttributesOperations: inout [TimestampBasedMessageAttributesOperation], forEachMedia: (Media) -> Void) { + func removeAllMessagesWithForwardAuthor(peerId: PeerId, forwardAuthorId: PeerId, namespace: MessageId.Namespace, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], timestampBasedMessageAttributesOperations: inout [TimestampBasedMessageAttributesOperation], forEachMedia: ((Media) -> Void)?) { let indices = self.allIndicesWithForwardAuthor(peerId: peerId, forwardAuthorId: forwardAuthorId, namespace: namespace) self.removeMessages(indices.map { $0.id }, operationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, timestampBasedMessageAttributesOperations: ×tampBasedMessageAttributesOperations, forEachMedia: forEachMedia) } @@ -1335,6 +1346,9 @@ final class MessageHistoryTable: Table { for tag in message.tags { self.tagsTable.remove(tags: tag, index: index, updatedSummaries: &updatedMessageTagSummaries, invalidateSummaries: &invalidateMessageTagSummaries) + if let threadId = message.threadId { + self.threadTagsTable.remove(tags: tag, threadId: threadId, index: index, updatedSummaries: &updatedMessageTagSummaries, invalidateSummaries: &invalidateMessageTagSummaries) + } } if let threadId = message.threadId { self.threadsTable.remove(threadId: threadId, index: index) @@ -1539,10 +1553,17 @@ final class MessageHistoryTable: Table { if previousMessage.tags != message.tags || index != updatedIndex { if !previousMessage.tags.isEmpty { self.tagsTable.remove(tags: previousMessage.tags, index: index, updatedSummaries: &updatedMessageTagSummaries, invalidateSummaries: &invalidateMessageTagSummaries) + if let threadId = previousMessage.threadId { + self.threadTagsTable.remove(tags: previousMessage.tags, threadId: threadId, index: index, updatedSummaries: &updatedMessageTagSummaries, invalidateSummaries: &invalidateMessageTagSummaries) + } } if !message.tags.isEmpty { //let isNewlyAdded = previousMessage.tags.isEmpty self.tagsTable.add(tags: message.tags, index: message.index, isNewlyAdded: false, updatedSummaries: &updatedMessageTagSummaries, invalidateSummaries: &invalidateMessageTagSummaries) + + if let threadId = message.threadId { + self.threadTagsTable.add(tags: message.tags, threadId: threadId, index: message.index, isNewlyAdded: false, updatedSummaries: &updatedMessageTagSummaries, invalidateSummaries: &invalidateMessageTagSummaries) + } } } if previousMessage.threadId != message.threadId || index != message.index { @@ -2977,54 +2998,85 @@ final class MessageHistoryTable: Table { precondition(fromIndex.id.namespace == toIndex.id.namespace) var result: [IntermediateMessage] = [] if let threadId = threadId { - var indices: [MessageIndex] = [] - var startIndex = fromIndex - var localIncludeFrom = includeFrom - while true { - let sliceIndices: [MessageIndex] + if let tag = tag { + let indices: [MessageIndex] if fromIndex < toIndex { - sliceIndices = self.threadsTable.laterIndices(threadId: threadId, peerId: peerId, namespace: namespace, index: startIndex, includeFrom: localIncludeFrom, count: limit) + indices = self.threadTagsTable.laterIndices(tag: tag, threadId: threadId, peerId: peerId, namespace: namespace, index: fromIndex, includeFrom: includeFrom, count: limit) } else { - sliceIndices = self.threadsTable.earlierIndices(threadId: threadId, peerId: peerId, namespace: namespace, index: startIndex, includeFrom: localIncludeFrom, count: limit) + indices = self.threadTagsTable.earlierIndices(tag: tag, threadId: threadId, peerId: peerId, namespace: namespace, index: fromIndex, includeFrom: includeFrom, count: limit) } - if sliceIndices.isEmpty { - break - } - startIndex = sliceIndices[sliceIndices.count - 1] - localIncludeFrom = false - - for index in sliceIndices { + for index in indices { if let ignoreMessagesInTimestampRange = ignoreMessagesInTimestampRange { if ignoreMessagesInTimestampRange.contains(index.timestamp) { continue } } - if let tag = tag { - if self.tagsTable.entryExists(tag: tag, index: index) { - indices.append(index) + if fromIndex < toIndex { + if index < fromIndex || index > toIndex { + continue } } else { - indices.append(index) + if index < toIndex || index > fromIndex { + continue + } + } + if let message = self.getMessage(index) { + result.append(message) + } else { + assertionFailure() } } - if indices.count >= limit { - break - } - } - for index in indices { - if fromIndex < toIndex { - if index < fromIndex || index > toIndex { - continue + } else { + var indices: [MessageIndex] = [] + var startIndex = fromIndex + var localIncludeFrom = includeFrom + while true { + let sliceIndices: [MessageIndex] + if fromIndex < toIndex { + sliceIndices = self.threadsTable.laterIndices(threadId: threadId, peerId: peerId, namespace: namespace, index: startIndex, includeFrom: localIncludeFrom, count: limit) + } else { + sliceIndices = self.threadsTable.earlierIndices(threadId: threadId, peerId: peerId, namespace: namespace, index: startIndex, includeFrom: localIncludeFrom, count: limit) } - } else { - if index < toIndex || index > fromIndex { - continue + if sliceIndices.isEmpty { + break + } + + startIndex = sliceIndices[sliceIndices.count - 1] + localIncludeFrom = false + + for index in sliceIndices { + if let ignoreMessagesInTimestampRange = ignoreMessagesInTimestampRange { + if ignoreMessagesInTimestampRange.contains(index.timestamp) { + continue + } + } + if let tag = tag { + if self.tagsTable.entryExists(tag: tag, index: index) { + indices.append(index) + } + } else { + indices.append(index) + } + } + if indices.count >= limit { + break } } - if let message = self.getMessage(index) { - result.append(message) - } else { - assertionFailure() + for index in indices { + if fromIndex < toIndex { + if index < fromIndex || index > toIndex { + continue + } + } else { + if index < toIndex || index > fromIndex { + continue + } + } + if let message = self.getMessage(index) { + result.append(message) + } else { + assertionFailure() + } } } } else if let tag = tag { diff --git a/submodules/Postbox/Sources/MessageHistoryTagSummaryView.swift b/submodules/Postbox/Sources/MessageHistoryTagSummaryView.swift index 8c2d9bd6ab..be8d0c6dba 100644 --- a/submodules/Postbox/Sources/MessageHistoryTagSummaryView.swift +++ b/submodules/Postbox/Sources/MessageHistoryTagSummaryView.swift @@ -3,22 +3,24 @@ import Foundation final class MutableMessageHistoryTagSummaryView: MutablePostboxView { private let tag: MessageTags private let peerId: PeerId + private let threadId: Int64? private let namespace: MessageId.Namespace fileprivate var count: Int32? - init(postbox: PostboxImpl, tag: MessageTags, peerId: PeerId, namespace: MessageId.Namespace) { + init(postbox: PostboxImpl, tag: MessageTags, peerId: PeerId, threadId: Int64?, namespace: MessageId.Namespace) { self.tag = tag self.peerId = peerId + self.threadId = threadId self.namespace = namespace - self.count = postbox.messageHistoryTagsSummaryTable.get(MessageHistoryTagsSummaryKey(tag: tag, peerId: peerId, namespace: namespace))?.count + self.count = postbox.messageHistoryTagsSummaryTable.get(MessageHistoryTagsSummaryKey(tag: tag, peerId: peerId, threadId: threadId, namespace: namespace))?.count } func replay(postbox: PostboxImpl, transaction: PostboxTransaction) -> Bool { var hasChanges = false - if let summary = transaction.currentUpdatedMessageTagSummaries[MessageHistoryTagsSummaryKey(tag: self.tag, peerId: self.peerId, namespace: self.namespace)] { + if let summary = transaction.currentUpdatedMessageTagSummaries[MessageHistoryTagsSummaryKey(tag: self.tag, peerId: self.peerId, threadId: self.threadId, namespace: self.namespace)] { self.count = summary.count hasChanges = true } diff --git a/submodules/Postbox/Sources/MessageHistoryTagsSummaryTable.swift b/submodules/Postbox/Sources/MessageHistoryTagsSummaryTable.swift index 6dc49b60e9..e2fecf2f4e 100644 --- a/submodules/Postbox/Sources/MessageHistoryTagsSummaryTable.swift +++ b/submodules/Postbox/Sources/MessageHistoryTagsSummaryTable.swift @@ -43,6 +43,7 @@ public struct MessageHistoryTagNamespaceSummary: Equatable, CustomStringConverti struct MessageHistoryTagsSummaryKey: Equatable, Hashable { let tag: MessageTags let peerId: PeerId + let threadId: Int64? let namespace: MessageId.Namespace } @@ -80,7 +81,8 @@ class MessageHistoryTagsSummaryTable: Table { private var cachedSummaries: [MessageHistoryTagsSummaryKey: CachedEntry] = [:] private var updatedKeys = Set() - private let sharedKey = ValueBoxKey(length: 4 + 8 + 4) + private let sharedSimpleKey = ValueBoxKey(length: 4 + 8 + 4) + private let sharedThreadKey = ValueBoxKey(length: 4 + 8 + 4 + 8) init(valueBox: ValueBox, table: ValueBoxTable, useCaches: Bool, invalidateTable: InvalidatedMessageHistoryTagsSummaryTable) { self.invalidateTable = invalidateTable @@ -88,17 +90,25 @@ class MessageHistoryTagsSummaryTable: Table { super.init(valueBox: valueBox, table: table, useCaches: useCaches) } - private func key(key: MessageHistoryTagsSummaryKey, sharedKey: ValueBoxKey = ValueBoxKey(length: 4 + 8 + 4)) -> ValueBoxKey { - sharedKey.setUInt32(0, value: key.tag.rawValue) - sharedKey.setInt64(4, value: key.peerId.toInt64()) - sharedKey.setInt32(4 + 8, value: key.namespace) - return sharedKey + private func keyShared(key: MessageHistoryTagsSummaryKey) -> ValueBoxKey { + if let threadId = key.threadId { + self.sharedThreadKey.setUInt32(0, value: key.tag.rawValue) + self.sharedThreadKey.setInt64(4, value: key.peerId.toInt64()) + self.sharedThreadKey.setInt32(4 + 8, value: key.namespace) + self.sharedThreadKey.setInt64(4 + 8 + 4, value: threadId) + return self.sharedSimpleKey + } else { + self.sharedSimpleKey.setUInt32(0, value: key.tag.rawValue) + self.sharedSimpleKey.setInt64(4, value: key.peerId.toInt64()) + self.sharedSimpleKey.setInt32(4 + 8, value: key.namespace) + return self.sharedSimpleKey + } } func get(_ key: MessageHistoryTagsSummaryKey) -> MessageHistoryTagNamespaceSummary? { if let cached = self.cachedSummaries[key] { return cached.summary - } else if let value = self.valueBox.get(self.table, key: self.key(key: key, sharedKey: self.sharedKey)) { + } else if let value = self.valueBox.get(self.table, key: self.keyShared(key: key)) { let entry = readSummary(value) self.cachedSummaries[key] = CachedEntry(summary: entry) return entry @@ -164,10 +174,10 @@ class MessageHistoryTagsSummaryTable: Table { if let summary = cached.summary { buffer.reset() writeSummary(summary, to: buffer) - self.valueBox.set(self.table, key: self.key(key: key, sharedKey: self.sharedKey), value: buffer) + self.valueBox.set(self.table, key: self.keyShared(key: key), value: buffer) } else { assertionFailure() - self.valueBox.remove(self.table, key: self.key(key: key, sharedKey: self.sharedKey), secure: false) + self.valueBox.remove(self.table, key: self.keyShared(key: key), secure: false) } } else { assertionFailure() diff --git a/submodules/Postbox/Sources/MessageHistoryTagsTable.swift b/submodules/Postbox/Sources/MessageHistoryTagsTable.swift index 9bf21b3a3d..6e1a9fc564 100644 --- a/submodules/Postbox/Sources/MessageHistoryTagsTable.swift +++ b/submodules/Postbox/Sources/MessageHistoryTagsTable.swift @@ -46,7 +46,7 @@ class MessageHistoryTagsTable: Table { 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, isNewlyAdded: isNewlyAdded, updatedSummaries: &updatedSummaries, invalidateSummaries: &invalidateSummaries) + self.summaryTable.addMessage(key: MessageHistoryTagsSummaryKey(tag: tag, peerId: index.id.peerId, threadId: nil, namespace: index.id.namespace), id: index.id.id, isNewlyAdded: isNewlyAdded, updatedSummaries: &updatedSummaries, invalidateSummaries: &invalidateSummaries) } } } @@ -56,7 +56,7 @@ class MessageHistoryTagsTable: Table { self.valueBox.remove(self.table, key: self.key(tag: tag, index: index, key: self.sharedKey), secure: false) if self.summaryTags.contains(tag) { - self.summaryTable.removeMessage(key: MessageHistoryTagsSummaryKey(tag: tag, peerId: index.id.peerId, namespace: index.id.namespace), id: index.id.id, updatedSummaries: &updatedSummaries, invalidateSummaries: &invalidateSummaries) + self.summaryTable.removeMessage(key: MessageHistoryTagsSummaryKey(tag: tag, peerId: index.id.peerId, threadId: nil, namespace: index.id.namespace), id: index.id.id, updatedSummaries: &updatedSummaries, invalidateSummaries: &invalidateSummaries) } } } diff --git a/submodules/Postbox/Sources/MessageHistoryThreadHoleIndexTable.swift b/submodules/Postbox/Sources/MessageHistoryThreadHoleIndexTable.swift index 99d08ca410..beb547f201 100644 --- a/submodules/Postbox/Sources/MessageHistoryThreadHoleIndexTable.swift +++ b/submodules/Postbox/Sources/MessageHistoryThreadHoleIndexTable.swift @@ -329,7 +329,7 @@ final class MessageHistoryThreadHoleIndexTable: Table { self.valueBox.set(self.table, key: self.key(threadId: threadId, id: MessageId(peerId: peerId, namespace: namespace, id: closedRange.upperBound), space: space), value: MemoryBuffer(memory: &lowerBound, capacity: 4, length: 4, freeWhenDone: false)) } - //addOperation(.insert(clippedRange), peerId: peerId, namespace: namespace, space: space, to: &operations) + addMessageHistoryHoleOperation(.insert(clippedRange), peerId: peerId, threadId: threadId, namespace: namespace, space: space, to: &operations) } func remove(peerId: PeerId, threadId: Int64, namespace: MessageId.Namespace, space: MessageHistoryHoleSpace, range: ClosedRange, operations: inout [MessageHistoryIndexHoleOperationKey: [MessageHistoryIndexHoleOperation]]) { @@ -337,7 +337,7 @@ final class MessageHistoryThreadHoleIndexTable: Table { self.removeInternal(peerId: peerId, threadId: threadId, namespace: namespace, space: space, range: range, operations: &operations) - /*switch space { + switch space { case .everywhere: if let namespaceHoleTags = self.seedConfiguration.messageHoles[peerId.namespace]?[namespace] { for tag in namespaceHoleTags { @@ -346,7 +346,7 @@ final class MessageHistoryThreadHoleIndexTable: Table { } case .tag: break - }*/ + } } private func removeInternal(peerId: PeerId, threadId: Int64, namespace: MessageId.Namespace, space: MessageHistoryHoleSpace, range: ClosedRange, operations: inout [MessageHistoryIndexHoleOperationKey: [MessageHistoryIndexHoleOperation]]) { @@ -393,9 +393,9 @@ final class MessageHistoryThreadHoleIndexTable: Table { self.valueBox.set(self.table, key: self.key(threadId: threadId, id: MessageId(peerId: peerId, namespace: namespace, id: closedRange.upperBound), space: space), value: MemoryBuffer(memory: &lowerBound, capacity: 4, length: 4, freeWhenDone: false)) } - /*if !removeKeys.isEmpty { - addOperation(.remove(range), peerId: peerId, namespace: namespace, space: space, to: &operations) - }*/ + if !removeKeys.isEmpty { + addMessageHistoryHoleOperation(.remove(range), peerId: peerId, threadId: threadId, namespace: namespace, space: space, to: &operations) + } } func debugList(peerId: PeerId, threadId: Int64, namespace: MessageId.Namespace, space: MessageHistoryHoleSpace) -> [ClosedRange] { diff --git a/submodules/Postbox/Sources/MessageHistoryThreadIndexView.swift b/submodules/Postbox/Sources/MessageHistoryThreadIndexView.swift index 5afdea595d..18f5284742 100644 --- a/submodules/Postbox/Sources/MessageHistoryThreadIndexView.swift +++ b/submodules/Postbox/Sources/MessageHistoryThreadIndexView.swift @@ -5,27 +5,32 @@ final class MutableMessageHistoryThreadIndexView: MutablePostboxView { let id: Int64 let index: MessageIndex var info: CodableEntry + var tagSummaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo] var topMessage: Message? init( id: Int64, index: MessageIndex, info: CodableEntry, + tagSummaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo], topMessage: Message? ) { self.id = id self.index = index self.info = info + self.tagSummaryInfo = tagSummaryInfo self.topMessage = topMessage } } fileprivate let peerId: PeerId + fileprivate let summaryComponents: ChatListEntrySummaryComponents fileprivate var peer: Peer? fileprivate var items: [Item] = [] - init(postbox: PostboxImpl, peerId: PeerId) { + init(postbox: PostboxImpl, peerId: PeerId, summaryComponents: ChatListEntrySummaryComponents) { self.peerId = peerId + self.summaryComponents = summaryComponents self.reload(postbox: postbox) } @@ -36,10 +41,34 @@ final class MutableMessageHistoryThreadIndexView: MutablePostboxView { self.peer = postbox.peerTable.get(self.peerId) for item in postbox.messageHistoryThreadIndexTable.getAll(peerId: self.peerId) { + 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: self.peerId, threadId: item.threadId, 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: self.peerId, namespace: actionsSummary.namespace) + actionsSummaryCount = postbox.pendingMessageActionsMetadataTable.getCount(.peerNamespaceAction(key.peerId, key.namespace, key.type)) + } + + tagSummaryInfo[key] = ChatListMessageTagSummaryInfo( + tagSummaryCount: tagSummaryCount, + actionsSummaryCount: actionsSummaryCount + ) + } + self.items.append(Item( id: item.threadId, index: item.index, info: item.info, + tagSummaryInfo: tagSummaryInfo, topMessage: postbox.getMessage(item.index.id) )) } @@ -48,7 +77,7 @@ final class MutableMessageHistoryThreadIndexView: MutablePostboxView { func replay(postbox: PostboxImpl, transaction: PostboxTransaction) -> Bool { var updated = false - if transaction.updatedMessageThreadPeerIds.contains(self.peerId) { + if transaction.updatedMessageThreadPeerIds.contains(self.peerId) || transaction.currentUpdatedMessageTagSummaries.contains(where: { $0.key.peerId == self.peerId }) || transaction.currentUpdatedMessageActionsSummaries.contains(where: { $0.key.peerId == self.peerId }) { self.reload(postbox: postbox) updated = true } @@ -70,17 +99,20 @@ public final class EngineMessageHistoryThread { public let id: Int64 public let index: MessageIndex public let info: CodableEntry + public let tagSummaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo] public let topMessage: Message? public init( id: Int64, index: MessageIndex, info: CodableEntry, + tagSummaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo], topMessage: Message? ) { self.id = id self.index = index self.info = info + self.tagSummaryInfo = tagSummaryInfo self.topMessage = topMessage } @@ -94,6 +126,9 @@ public final class EngineMessageHistoryThread { if lhs.info != rhs.info { return false } + if lhs.tagSummaryInfo != rhs.tagSummaryInfo { + return false + } if let lhsMessage = lhs.topMessage, let rhsMessage = rhs.topMessage { if lhsMessage.index != rhsMessage.index { return false @@ -123,6 +158,7 @@ public final class MessageHistoryThreadIndexView: PostboxView { id: item.id, index: item.index, info: item.info, + tagSummaryInfo: item.tagSummaryInfo, topMessage: item.topMessage )) } diff --git a/submodules/Postbox/Sources/MessageHistoryThreadsTable.swift b/submodules/Postbox/Sources/MessageHistoryThreadsTable.swift index 47aa7162fe..67d8ce18c3 100644 --- a/submodules/Postbox/Sources/MessageHistoryThreadsTable.swift +++ b/submodules/Postbox/Sources/MessageHistoryThreadsTable.swift @@ -1,9 +1,5 @@ import Foundation -private func extractKey(_ key: ValueBoxKey) -> MessageIndex { - return MessageIndex(id: MessageId(peerId: PeerId(key.getInt64(0)), namespace: key.getInt32(8 + 8), id: key.getInt32(8 + 8 + 4 + 4)), timestamp: key.getInt32(8 + 8 + 4)) -} - class MessageHistoryThreadsTable: Table { struct ItemId: Hashable { var peerId: PeerId @@ -31,6 +27,10 @@ class MessageHistoryThreadsTable: Table { return key } + private func extractKey(_ key: ValueBoxKey) -> MessageIndex { + return MessageIndex(id: MessageId(peerId: PeerId(key.getInt64(0)), namespace: key.getInt32(8 + 8), id: key.getInt32(8 + 8 + 4 + 4)), timestamp: key.getInt32(8 + 8 + 4)) + } + private func lowerBound(threadId: Int64, peerId: PeerId, namespace: MessageId.Namespace) -> ValueBoxKey { let key = ValueBoxKey(length: 8 + 8 + 4) key.setInt64(0, value: peerId.toInt64()) @@ -146,3 +146,199 @@ class MessageHistoryThreadsTable: Table { self.updatedIds.removeAll() } } + +class MessageHistoryThreadTagsTable: Table { + static func tableSpec(_ id: Int32) -> ValueBoxTable { + return ValueBoxTable(id: id, keyType: .binary, compactValuesOnCreation: true) + } + + private let summaryTable: MessageHistoryTagsSummaryTable + private let summaryTags: MessageTags + + private let sharedKey = ValueBoxKey(length: 8 + 8 + 4 + 4 + 4 + 4) + + init(valueBox: ValueBox, table: ValueBoxTable, useCaches: Bool, seedConfiguration: SeedConfiguration, summaryTable: MessageHistoryTagsSummaryTable) { + self.summaryTable = summaryTable + self.summaryTags = seedConfiguration.messageTagsWithSummary + + super.init(valueBox: valueBox, table: table, useCaches: useCaches) + } + + private func key(tag: MessageTags, threadId: Int64, index: MessageIndex, key: ValueBoxKey = ValueBoxKey(length: 8 + 8 + 4 + 4 + 4 + 4)) -> ValueBoxKey { + key.setInt64(0, value: index.id.peerId.toInt64()) + key.setInt64(8, value: threadId) + key.setUInt32(8 + 8, value: tag.rawValue) + key.setInt32(8 + 8 + 4, value: index.id.namespace) + key.setInt32(8 + 8 + 4 + 4, value: index.timestamp) + key.setInt32(8 + 8 + 4 + 4 + 4, value: index.id.id) + return key + } + + private func extractKey(_ key: ValueBoxKey) -> MessageIndex { + return MessageIndex(id: MessageId(peerId: PeerId(key.getInt64(0)), namespace: key.getInt32(8 + 8 + 4), id: key.getInt32(8 + 8 + 4 + 4 + 4)), timestamp: key.getInt32(8 + 8 + 4 + 4)) + } + + private func lowerBound(tag: MessageTags, threadId: Int64, peerId: PeerId, namespace: MessageId.Namespace) -> ValueBoxKey { + let key = ValueBoxKey(length: 8 + 8 + 4 + 4) + key.setInt64(0, value: peerId.toInt64()) + key.setInt64(8, value: threadId) + key.setUInt32(8 + 8, value: tag.rawValue) + key.setInt32(8 + 8 + 4, value: namespace) + return key + } + + private func upperBound(tag: MessageTags, threadId: Int64, peerId: PeerId, namespace: MessageId.Namespace) -> ValueBoxKey { + return self.lowerBound(tag: tag, threadId: threadId, peerId: peerId, namespace: namespace).successor + } + + func add(tags: MessageTags, threadId: Int64, 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, threadId: threadId, index: index, key: self.sharedKey), value: MemoryBuffer()) + if self.summaryTags.contains(tag) { + self.summaryTable.addMessage(key: MessageHistoryTagsSummaryKey(tag: tag, peerId: index.id.peerId, threadId: threadId, namespace: index.id.namespace), id: index.id.id, isNewlyAdded: isNewlyAdded, updatedSummaries: &updatedSummaries, invalidateSummaries: &invalidateSummaries) + } + } + } + + func remove(tags: MessageTags, threadId: Int64, index: MessageIndex, updatedSummaries: inout[MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation]) { + for tag in tags { + self.valueBox.remove(self.table, key: self.key(tag: tag, threadId: threadId, index: index, key: self.sharedKey), secure: false) + if self.summaryTags.contains(tag) { + self.summaryTable.removeMessage(key: MessageHistoryTagsSummaryKey(tag: tag, peerId: index.id.peerId, threadId: threadId, namespace: index.id.namespace), id: index.id.id, updatedSummaries: &updatedSummaries, invalidateSummaries: &invalidateSummaries) + } + } + } + + func entryExists(tag: MessageTags, threadId: Int64, index: MessageIndex) -> Bool { + return self.valueBox.exists(self.table, key: self.key(tag: tag, threadId: threadId, index: index, key: self.sharedKey)) + } + + func entryLocation(at index: MessageIndex, threadId: Int64, tag: MessageTags) -> MessageHistoryEntryLocation? { + if let _ = self.valueBox.get(self.table, key: self.key(tag: tag, threadId: threadId, index: index)) { + var greaterCount = 0 + self.valueBox.range(self.table, start: self.key(tag: tag, threadId: threadId, index: index), end: self.upperBound(tag: tag, threadId: threadId, peerId: index.id.peerId, namespace: index.id.namespace), keys: { _ in + greaterCount += 1 + return true + }, limit: 0) + + var lowerCount = 0 + self.valueBox.range(self.table, start: self.key(tag: tag, threadId: threadId, index: index), end: self.lowerBound(tag: tag, threadId: threadId, peerId: index.id.peerId, namespace: index.id.namespace), keys: { _ in + lowerCount += 1 + return true + }, limit: 0) + + return MessageHistoryEntryLocation(index: lowerCount, count: greaterCount + lowerCount + 1) + } + return nil + } + + func earlierIndices(tag: MessageTags, threadId: Int64, peerId: PeerId, namespace: MessageId.Namespace, index: MessageIndex?, includeFrom: Bool, minIndex: MessageIndex? = nil, count: Int) -> [MessageIndex] { + var indices: [MessageIndex] = [] + let key: ValueBoxKey + if let index = index { + if includeFrom { + key = self.key(tag: tag, threadId: threadId, index: index).successor + } else { + key = self.key(tag: tag, threadId: threadId, index: index) + } + } else { + key = self.upperBound(tag: tag, threadId: threadId, peerId: peerId, namespace: namespace) + } + let endKey: ValueBoxKey + if let minIndex = minIndex { + endKey = self.key(tag: tag, threadId: threadId, index: minIndex) + } else { + endKey = self.lowerBound(tag: tag, threadId: threadId, peerId: peerId, namespace: namespace) + } + self.valueBox.range(self.table, start: key, end: endKey, keys: { key in + indices.append(self.extractKey(key)) + return true + }, limit: count) + return indices + } + + func laterIndices(tag: MessageTags, threadId: Int64, peerId: PeerId, namespace: MessageId.Namespace, index: MessageIndex?, includeFrom: Bool, count: Int) -> [MessageIndex] { + var indices: [MessageIndex] = [] + let key: ValueBoxKey + if let index = index { + if includeFrom { + key = self.key(tag: tag, threadId: threadId, index: index).predecessor + } else { + key = self.key(tag: tag, threadId: threadId, index: index) + } + } else { + key = self.lowerBound(tag: tag, threadId: threadId, peerId: peerId, namespace: namespace) + } + self.valueBox.range(self.table, start: key, end: self.upperBound(tag: tag, threadId: threadId, peerId: peerId, namespace: namespace), keys: { key in + indices.append(self.extractKey(key)) + return true + }, limit: count) + return indices + } + + func getMessageCountInRange(tag: MessageTags, threadId: Int64, peerId: PeerId, namespace: MessageId.Namespace, lowerBound: MessageIndex, upperBound: MessageIndex) -> Int { + precondition(lowerBound.id.namespace == namespace) + precondition(upperBound.id.namespace == namespace) + var lowerBoundKey = self.key(tag: tag, threadId: threadId, index: lowerBound) + if lowerBound.timestamp > 1 { + lowerBoundKey = lowerBoundKey.predecessor + } + var upperBoundKey = self.key(tag: tag, threadId: threadId, index: upperBound) + if upperBound.timestamp < Int32.max - 1 { + upperBoundKey = upperBoundKey.successor + } + return Int(self.valueBox.count(self.table, start: lowerBoundKey, end: upperBoundKey)) + } + + func latestIndex(tag: MessageTags, threadId: Int64, peerId: PeerId, namespace: MessageId.Namespace) -> MessageIndex? { + var result: MessageIndex? + self.valueBox.range(self.table, start: self.lowerBound(tag: tag, threadId: threadId, peerId: peerId, namespace: namespace), end: self.upperBound(tag: tag, threadId: threadId, peerId: peerId, namespace: namespace), keys: { key in + result = extractKey(key) + return true + }, limit: 1) + return result + } + + func findRandomIndex(peerId: PeerId, threadId: Int64, namespace: MessageId.Namespace, tag: MessageTags, ignoreIds: ([MessageId], Set), isMessage: (MessageIndex) -> Bool) -> MessageIndex? { + var indices: [MessageIndex] = [] + self.valueBox.range(self.table, start: self.lowerBound(tag: tag, threadId: threadId, peerId: peerId, namespace: namespace), end: self.upperBound(tag: tag, threadId: threadId, peerId: peerId, namespace: namespace), keys: { key in + indices.append(extractKey(key)) + return true + }, limit: 0) + var checkedIndices = Set() + while checkedIndices.count < indices.count { + let i = Int(arc4random_uniform(UInt32(indices.count))) + if checkedIndices.contains(i) { + continue + } + checkedIndices.insert(i) + let index = indices[i] + if isMessage(index) && !ignoreIds.1.contains(index.id) { + return index + } + } + checkedIndices.removeAll() + let lastId = ignoreIds.0.last + while checkedIndices.count < indices.count { + let i = Int(arc4random_uniform(UInt32(indices.count))) + if checkedIndices.contains(i) { + continue + } + checkedIndices.insert(i) + let index = indices[i] + if isMessage(index) && lastId != index.id { + return index + } + } + return nil + } + + func debugGetAllIndices() -> [MessageIndex] { + var indices: [MessageIndex] = [] + self.valueBox.scan(self.table, values: { key, value in + indices.append(extractKey(key)) + return true + }) + return indices + } +} diff --git a/submodules/Postbox/Sources/MessageHistoryView.swift b/submodules/Postbox/Sources/MessageHistoryView.swift index 8e1c377176..94961af8fa 100644 --- a/submodules/Postbox/Sources/MessageHistoryView.swift +++ b/submodules/Postbox/Sources/MessageHistoryView.swift @@ -285,7 +285,7 @@ public final class MessageHistoryViewExternalInput: Equatable { } public enum MessageHistoryViewInput: Equatable { - case single(PeerId) + case single(peerId: PeerId, threadId: Int64?) case associated(PeerId, MessageId?) case external(MessageHistoryViewExternalInput) } @@ -362,7 +362,7 @@ final class MutableMessageHistoryView { switch peerIds { case let .associated(peerId, _): self.isAddedToChatList = postbox.chatListTable.getPeerChatListIndex(peerId: peerId) != nil - case let .single(peerId): + case let .single(peerId, _): self.isAddedToChatList = postbox.chatListTable.getPeerChatListIndex(peerId: peerId) != nil case let .external(input): switch input.content { @@ -420,7 +420,7 @@ final class MutableMessageHistoryView { func updatePeerIds(transaction: PostboxTransaction) { switch self.peerIds { - case let .single(peerId): + case let .single(peerId, _): if let updatedData = transaction.currentUpdatedCachedPeerData[peerId] { if updatedData.associatedHistoryMessageId != nil { self.peerIds = .associated(peerId, updatedData.associatedHistoryMessageId) @@ -445,7 +445,7 @@ final class MutableMessageHistoryView { switch peerIds { case let .associated(peerId, _): self.isAddedToChatList = postbox.chatListTable.getPeerChatListIndex(peerId: peerId) != nil - case let .single(peerId): + case let .single(peerId, _): self.isAddedToChatList = postbox.chatListTable.getPeerChatListIndex(peerId: peerId) != nil case let .external(input): switch input.content { @@ -458,7 +458,7 @@ final class MutableMessageHistoryView { } switch self.peerIds { - case let .single(peerId): + case let .single(peerId, _): holePeerIdsSet.insert(peerId) if let value = transaction.currentOperationsByPeerId[peerId] { operations.append(value) @@ -497,7 +497,10 @@ final class MutableMessageHistoryView { let externalThreadId: Int64? let isExternal: Bool switch self.peerIds { - case .single, .associated: + case let .single(_, threadId): + externalThreadId = threadId + isExternal = false + case .associated: externalThreadId = nil isExternal = false case let .external(input): @@ -527,7 +530,7 @@ final class MutableMessageHistoryView { } } if matchesSpace { - if holePeerIdsSet.contains(key.peerId) { + if holePeerIdsSet.contains(key.peerId) && key.threadId == externalThreadId { for operation in holeOperations { switch operation { case let .insert(range): @@ -865,9 +868,11 @@ final class MutableMessageHistoryView { if !transaction.currentPeerHoleOperations.isEmpty { var holePeerIdsSet: [PeerId] = [] + var threadId: Int64? switch self.peerIds { - case let .single(peerId): + case let .single(peerId, threadIdValue): holePeerIdsSet.append(peerId) + threadId = threadIdValue case let .associated(peerId, associatedId): holePeerIdsSet.append(peerId) if let associatedId = associatedId { @@ -878,7 +883,7 @@ final class MutableMessageHistoryView { } let space: MessageHistoryHoleSpace = self.tag.flatMap(MessageHistoryHoleSpace.tag) ?? .everywhere for key in transaction.currentPeerHoleOperations.keys { - if holePeerIdsSet.contains(key.peerId) && key.space == space { + if holePeerIdsSet.contains(key.peerId) && threadId == key.threadId && key.space == space { hasChanges = true } } @@ -1108,7 +1113,7 @@ public final class MessageHistoryView { } } } - if let maxNamespaceIndex = maxNamespaceIndex , maxIndex == nil || maxIndex! < maxNamespaceIndex { + if let maxNamespaceIndex = maxNamespaceIndex, maxIndex == nil || maxIndex! < maxNamespaceIndex { maxIndex = maxNamespaceIndex } } @@ -1177,4 +1182,83 @@ public final class MessageHistoryView { self.entries = entries } + + public init(base: MessageHistoryView, fixed combinedReadStates: MessageHistoryViewReadState?, transient transientReadStates: MessageHistoryViewReadState?) { + self.tagMask = base.tagMask + self.namespaces = base.namespaces + self.anchorIndex = base.anchorIndex + self.earlierId = base.earlierId + self.laterId = base.laterId + self.holeEarlier = base.holeEarlier + self.holeLater = base.holeLater + self.entries = base.entries + self.fixedReadStates = combinedReadStates + self.transientReadStates = transientReadStates + self.topTaggedMessages = base.topTaggedMessages + self.additionalData = base.additionalData + self.isLoading = base.isLoading + self.isAddedToChatList = base.isAddedToChatList + + if let combinedReadStates = combinedReadStates { + switch combinedReadStates { + case let .peer(states): + var hasUnread = false + for (_, readState) in states { + if readState.count > 0 { + hasUnread = true + break + } + } + + var maxIndex: MessageIndex? + + if hasUnread { + var peerIds = Set() + for entry in entries { + peerIds.insert(entry.index.id.peerId) + } + for peerId in peerIds { + if let combinedReadState = states[peerId] { + for (namespace, state) in combinedReadState.states { + var maxNamespaceIndex: MessageIndex? + var index = entries.count - 1 + for entry in entries.reversed() { + if entry.index.id.peerId == peerId && entry.index.id.namespace == namespace && state.isIncomingMessageIndexRead(entry.index) { + maxNamespaceIndex = entry.index + break + } + index -= 1 + } + if maxNamespaceIndex == nil && index == -1 && entries.count != 0 { + index = 0 + for entry in entries { + if entry.index.id.peerId == peerId && entry.index.id.namespace == namespace { + maxNamespaceIndex = entry.index.peerLocalPredecessor() + break + } + index += 1 + } + } + if let _ = maxNamespaceIndex , index + 1 < entries.count { + for i in index + 1 ..< entries.count { + if entries[i].message.flags.intersection(.IsIncomingMask).isEmpty { + maxNamespaceIndex = entries[i].message.index + } else { + break + } + } + } + if let maxNamespaceIndex = maxNamespaceIndex, maxIndex == nil || maxIndex! < maxNamespaceIndex { + maxIndex = maxNamespaceIndex + } + } + } + } + } + self.maxReadIndex = maxIndex + } + } else { + self.maxReadIndex = nil + } + } } diff --git a/submodules/Postbox/Sources/MessageHistoryViewState.swift b/submodules/Postbox/Sources/MessageHistoryViewState.swift index 5457bc92d5..ea0dcc41f5 100644 --- a/submodules/Postbox/Sources/MessageHistoryViewState.swift +++ b/submodules/Postbox/Sources/MessageHistoryViewState.swift @@ -11,7 +11,7 @@ public enum MessageHistoryInput: Equatable, Hashable { } } - case automatic(Automatic?) + case automatic(threadId: Int64?, info: Automatic?) case external(MessageHistoryViewExternalInput, MessageTags?) public func hash(into hasher: inout Hasher) { @@ -27,8 +27,8 @@ public enum MessageHistoryInput: Equatable, Hashable { private extension MessageHistoryInput { func fetch(postbox: PostboxImpl, peerId: PeerId, namespace: MessageId.Namespace, from fromIndex: MessageIndex, includeFrom: Bool, to toIndex: MessageIndex, ignoreMessagesInTimestampRange: ClosedRange?, limit: Int) -> [IntermediateMessage] { switch self { - case let .automatic(automatic): - var items = postbox.messageHistoryTable.fetch(peerId: peerId, namespace: namespace, tag: automatic?.tag, threadId: nil, from: fromIndex, includeFrom: includeFrom, to: toIndex, ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, limit: limit) + case let .automatic(threadId, automatic): + var items = postbox.messageHistoryTable.fetch(peerId: peerId, namespace: namespace, tag: automatic?.tag, threadId: threadId, from: fromIndex, includeFrom: includeFrom, to: toIndex, ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, limit: limit) if let automatic = automatic, automatic.appendMessagesFromTheSameGroup { enum Direction { case lowToHigh @@ -171,11 +171,19 @@ private extension MessageHistoryInput { func getMessageCountInRange(postbox: PostboxImpl, peerId: PeerId, namespace: MessageId.Namespace, lowerBound: MessageIndex, upperBound: MessageIndex) -> Int { switch self { - case let .automatic(automatic): + case let .automatic(threadId, automatic): if let automatic = automatic { - return postbox.messageHistoryTagsTable.getMessageCountInRange(tag: automatic.tag, peerId: peerId, namespace: namespace, lowerBound: lowerBound, upperBound: upperBound) + if let threadId = threadId { + return postbox.messageHistoryThreadTagsTable.getMessageCountInRange(tag: automatic.tag, threadId: threadId, peerId: peerId, namespace: namespace, lowerBound: lowerBound, upperBound: upperBound) + } else { + return postbox.messageHistoryTagsTable.getMessageCountInRange(tag: automatic.tag, peerId: peerId, namespace: namespace, lowerBound: lowerBound, upperBound: upperBound) + } } else { - return postbox.messageHistoryTable.getMessageCountInRange(peerId: peerId, namespace: namespace, tag: nil, lowerBound: lowerBound, upperBound: upperBound) + if let threadId = threadId { + return postbox.messageHistoryThreadsTable.getMessageCountInRange(threadId: threadId, peerId: peerId, namespace: namespace, lowerBound: lowerBound, upperBound: upperBound) + } else { + return postbox.messageHistoryTable.getMessageCountInRange(peerId: peerId, namespace: namespace, tag: nil, lowerBound: lowerBound, upperBound: upperBound) + } } case .external: return 0 @@ -492,8 +500,9 @@ private func sampleHoleRanges(input: MessageHistoryInput, orderedEntriesBySpace: var tag: MessageTags? var threadId: Int64? switch input { - case let .automatic(automatic): + case let .automatic(threadIdValue, automatic): tag = automatic?.tag + threadId = threadIdValue case let .external(value, _): switch value.content { case let .thread(_, id, _): @@ -1016,9 +1025,9 @@ final class HistoryViewLoadedState { let input: MessageHistoryInput switch locations { - case let .single(peerId): + case let .single(peerId, threadId): peerIds.append(peerId) - input = .automatic(tag.flatMap { tag in + input = .automatic(threadId: threadId, info: tag.flatMap { tag in MessageHistoryInput.Automatic(tag: tag, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup) }) case let .associated(peerId, associatedId): @@ -1026,7 +1035,7 @@ final class HistoryViewLoadedState { if let associatedId = associatedId { peerIds.append(associatedId.peerId) } - input = .automatic(tag.flatMap { tag in + input = .automatic(threadId: nil, info: tag.flatMap { tag in MessageHistoryInput.Automatic(tag: tag, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup) }) case let .external(external): @@ -1482,9 +1491,11 @@ final class HistoryViewLoadedState { private func fetchHoles(postbox: PostboxImpl, locations: MessageHistoryViewInput, tag: MessageTags?, namespaces: MessageIdNamespaces) -> [PeerIdAndNamespace: IndexSet] { var peerIds: [PeerId] = [] + var threadId: Int64? switch locations { - case let .single(peerId): + case let .single(peerId, threadIdValue): peerIds.append(peerId) + threadId = threadIdValue case let .associated(peerId, associatedId): peerIds.append(peerId) if let associatedId = associatedId { @@ -1508,15 +1519,30 @@ private func fetchHoles(postbox: PostboxImpl, locations: MessageHistoryViewInput var holesBySpace: [PeerIdAndNamespace: IndexSet] = [:] let holeSpace = tag.flatMap(MessageHistoryHoleSpace.tag) ?? .everywhere for peerId in peerIds { - for namespace in postbox.messageHistoryHoleIndexTable.existingNamespaces(peerId: peerId, holeSpace: holeSpace) { - if namespaces.contains(namespace) { - let indices = postbox.messageHistoryHoleIndexTable.closest(peerId: peerId, namespace: namespace, space: holeSpace, range: 1 ... (Int32.max - 1)) - if !indices.isEmpty { - let peerIdAndNamespace = PeerIdAndNamespace(peerId: peerId, namespace: namespace) - assert(canContainHoles(peerIdAndNamespace, input: .automatic(tag.flatMap { tag in - MessageHistoryInput.Automatic(tag: tag, appendMessagesFromTheSameGroup: false) - }), seedConfiguration: postbox.seedConfiguration)) - holesBySpace[peerIdAndNamespace] = indices + if let threadId = threadId { + for namespace in postbox.messageHistoryThreadHoleIndexTable.existingNamespaces(peerId: peerId, threadId: threadId, holeSpace: holeSpace) { + if namespaces.contains(namespace) { + let indices = postbox.messageHistoryThreadHoleIndexTable.closest(peerId: peerId, threadId: threadId, namespace: namespace, space: holeSpace, range: 1 ... (Int32.max - 1)) + if !indices.isEmpty { + let peerIdAndNamespace = PeerIdAndNamespace(peerId: peerId, namespace: namespace) + assert(canContainHoles(peerIdAndNamespace, input: .automatic(threadId: threadId, info: tag.flatMap { tag in + MessageHistoryInput.Automatic(tag: tag, appendMessagesFromTheSameGroup: false) + }), seedConfiguration: postbox.seedConfiguration)) + holesBySpace[peerIdAndNamespace] = indices + } + } + } + } else { + for namespace in postbox.messageHistoryHoleIndexTable.existingNamespaces(peerId: peerId, holeSpace: holeSpace) { + if namespaces.contains(namespace) { + let indices = postbox.messageHistoryHoleIndexTable.closest(peerId: peerId, namespace: namespace, space: holeSpace, range: 1 ... (Int32.max - 1)) + if !indices.isEmpty { + let peerIdAndNamespace = PeerIdAndNamespace(peerId: peerId, namespace: namespace) + assert(canContainHoles(peerIdAndNamespace, input: .automatic(threadId: nil, info: tag.flatMap { tag in + MessageHistoryInput.Automatic(tag: tag, appendMessagesFromTheSameGroup: false) + }), seedConfiguration: postbox.seedConfiguration)) + holesBySpace[peerIdAndNamespace] = indices + } } } } @@ -1612,8 +1638,12 @@ enum HistoryViewState { case .unread: let anchorPeerId: PeerId switch locations { - case let .single(peerId): + case let .single(peerId, threadId): anchorPeerId = peerId + if threadId != nil { + self = .loaded(HistoryViewLoadedState(anchor: .upperBound, tag: tag, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, namespaces: namespaces, statistics: statistics, ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, halfLimit: halfLimit, locations: locations, postbox: postbox, holes: HistoryViewHoles(holesBySpace: fetchHoles(postbox: postbox, locations: locations, tag: tag, namespaces: namespaces)))) + return + } case let .associated(peerId, _): anchorPeerId = peerId case .external: @@ -1661,6 +1691,8 @@ enum HistoryViewState { case let .message(messageId): var threadId: Int64? switch locations { + case let .single(_, threadIdValue): + threadId = threadIdValue case let .external(input): switch input.content { case let .thread(_, id, _): diff --git a/submodules/Postbox/Sources/MessageOfInterestHolesView.swift b/submodules/Postbox/Sources/MessageOfInterestHolesView.swift index 1c1f068609..978b7b52c3 100644 --- a/submodules/Postbox/Sources/MessageOfInterestHolesView.swift +++ b/submodules/Postbox/Sources/MessageOfInterestHolesView.swift @@ -25,7 +25,7 @@ public struct MessageOfInterestHole: Hashable, Equatable, CustomStringConvertibl } public enum MessageOfInterestViewLocation: Hashable { - case peer(PeerId) + case peer(peerId: PeerId, threadId: Int64?) } final class MutableMessageOfInterestHolesView: MutablePostboxView { @@ -45,9 +45,9 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView { let mainPeerId: PeerId let peerIds: MessageHistoryViewInput switch self.location { - case let .peer(id): + case let .peer(id, threadId): mainPeerId = id - peerIds = postbox.peerIdsForLocation(.peer(id), ignoreRelatedChats: false) + peerIds = postbox.peerIdsForLocation(.peer(peerId: id, threadId: threadId), ignoreRelatedChats: false) } self.peerIds = peerIds var anchor: HistoryViewInputAnchor = .upperBound @@ -107,12 +107,14 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView { func replay(postbox: PostboxImpl, transaction: PostboxTransaction) -> Bool { var peerId: PeerId + var threadId: Int64? switch self.location { - case let .peer(id): - peerId = id + case let .peer(id, threadIdValue): + peerId = id + threadId = threadIdValue } var anchor: HistoryViewInputAnchor = self.anchor - if transaction.alteredInitialPeerCombinedReadStates[peerId] != nil { + if threadId == nil, transaction.alteredInitialPeerCombinedReadStates[peerId] != nil { let updatedAnchor: HistoryViewInputAnchor = .upperBound if let combinedState = postbox.readStateTable.getCombinedState(peerId), let state = combinedState.states.first, state.1.count != 0 { switch state.1 { @@ -129,8 +131,8 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView { self.anchor = anchor let peerIds: MessageHistoryViewInput switch self.location { - case let .peer(id): - peerIds = postbox.peerIdsForLocation(.peer(id), ignoreRelatedChats: false) + case let .peer(id, threadId): + peerIds = postbox.peerIdsForLocation(.peer(peerId: id, threadId: threadId), ignoreRelatedChats: false) } self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], clipHoles: true, peerIds: peerIds, ignoreMessagesInTimestampRange: nil, anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, appendMessagesFromTheSameGroup: false, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0}) return self.updateFromView() @@ -138,9 +140,11 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView { var reloadView = false if !transaction.currentPeerHoleOperations.isEmpty { var allPeerIds: [PeerId] + var threadId: Int64? switch peerIds { - case let .single(peerId): + case let .single(peerId, threadIdValue): allPeerIds = [peerId] + threadId = threadIdValue case let .associated(peerId, attachedMessageId): allPeerIds = [peerId] if let attachedMessageId = attachedMessageId { @@ -151,7 +155,7 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView { break } for (key, _) in transaction.currentPeerHoleOperations { - if allPeerIds.contains(key.peerId) { + if allPeerIds.contains(key.peerId) && key.threadId == threadId { reloadView = true break } @@ -160,8 +164,8 @@ final class MutableMessageOfInterestHolesView: MutablePostboxView { if reloadView { let peerIds: MessageHistoryViewInput switch self.location { - case let .peer(id): - peerIds = postbox.peerIdsForLocation(.peer(id), ignoreRelatedChats: false) + case let .peer(id, threadId): + peerIds = postbox.peerIdsForLocation(.peer(peerId: id, threadId: threadId), ignoreRelatedChats: false) } self.wrappedView = MutableMessageHistoryView(postbox: postbox, orderStatistics: [], clipHoles: true, peerIds: peerIds, ignoreMessagesInTimestampRange: nil, anchor: self.anchor, combinedReadStates: nil, transientReadStates: nil, tag: nil, appendMessagesFromTheSameGroup: false, namespaces: .all, count: self.count, topTaggedMessages: [:], additionalDatas: [], getMessageCountInRange: { _, _ in return 0}) } diff --git a/submodules/Postbox/Sources/Postbox.swift b/submodules/Postbox/Sources/Postbox.swift index 207bc12cbc..1e6e315e5a 100644 --- a/submodules/Postbox/Sources/Postbox.swift +++ b/submodules/Postbox/Sources/Postbox.swift @@ -67,14 +67,14 @@ public final class Transaction { } } - public func addHole(peerId: PeerId, namespace: MessageId.Namespace, space: MessageHistoryHoleSpace, range: ClosedRange) { + public func addHole(peerId: PeerId, threadId: Int64?, namespace: MessageId.Namespace, space: MessageHistoryHoleSpace, range: ClosedRange) { assert(!self.disposed) - self.postbox?.addHole(peerId: peerId, namespace: namespace, space: space, range: range) + self.postbox?.addHole(peerId: peerId, threadId: threadId, namespace: namespace, space: space, range: range) } - public func removeHole(peerId: PeerId, namespace: MessageId.Namespace, space: MessageHistoryHoleSpace, range: ClosedRange) { + public func removeHole(peerId: PeerId, threadId: Int64?, namespace: MessageId.Namespace, space: MessageHistoryHoleSpace, range: ClosedRange) { assert(!self.disposed) - self.postbox?.removeHole(peerId: peerId, namespace: namespace, space: space, range: range) + self.postbox?.removeHole(peerId: peerId, threadId: threadId, namespace: namespace, space: space, range: range) } public func getHole(containing id: MessageId) -> [MessageHistoryHoleSpace: ClosedRange] { @@ -87,16 +87,6 @@ public final class Transaction { return self.postbox?.messageHistoryHoleIndexTable.closest(peerId: peerId, namespace: namespace, space: .everywhere, range: 1 ... (Int32.max - 1)) ?? IndexSet() } - public func addThreadIndexHole(peerId: PeerId, threadId: Int64, namespace: MessageId.Namespace, space: MessageHistoryHoleSpace, range: ClosedRange) { - assert(!self.disposed) - self.postbox?.addThreadIndexHole(peerId: peerId, threadId: threadId, namespace: namespace, space: space, range: range) - } - - public func removeThreadIndexHole(peerId: PeerId, threadId: Int64, namespace: MessageId.Namespace, space: MessageHistoryHoleSpace, range: ClosedRange) { - assert(!self.disposed) - self.postbox?.removeThreadIndexHole(peerId: peerId, threadId: threadId, namespace: namespace, space: space, range: range) - } - public func getThreadIndexHoles(peerId: PeerId, threadId: Int64, namespace: MessageId.Namespace) -> IndexSet { assert(!self.disposed) return self.postbox!.messageHistoryThreadHoleIndexTable.closest(peerId: peerId, threadId: threadId, namespace: namespace, space: .everywhere, range: 1 ... (Int32.max - 1)) @@ -137,12 +127,12 @@ public final class Transaction { self.postbox?.replaceChatListHole(groupId: groupId, index: index, hole: hole) } - public func deleteMessages(_ messageIds: [MessageId], forEachMedia: (Media) -> Void) { + public func deleteMessages(_ messageIds: [MessageId], forEachMedia: ((Media) -> Void)?) { assert(!self.disposed) self.postbox?.deleteMessages(messageIds, forEachMedia: forEachMedia) } - public func deleteMessagesInRange(peerId: PeerId, namespace: MessageId.Namespace, minId: MessageId.Id, maxId: MessageId.Id, forEachMedia: (Media) -> Void) { + public func deleteMessagesInRange(peerId: PeerId, namespace: MessageId.Namespace, minId: MessageId.Id, maxId: MessageId.Id, forEachMedia: ((Media) -> Void)?) { assert(!self.disposed) self.postbox?.deleteMessagesInRange(peerId: peerId, namespace: namespace, minId: minId, maxId: maxId, forEachMedia: forEachMedia) } @@ -151,12 +141,12 @@ public final class Transaction { self.postbox?.withAllMessages(peerId: peerId, namespace: namespace, f) } - public func clearHistory(_ peerId: PeerId, threadId: Int64?, minTimestamp: Int32?, maxTimestamp: Int32?, namespaces: MessageIdNamespaces, forEachMedia: (Media) -> Void) { + public func clearHistory(_ peerId: PeerId, threadId: Int64?, minTimestamp: Int32?, maxTimestamp: Int32?, namespaces: MessageIdNamespaces, forEachMedia: ((Media) -> Void)?) { assert(!self.disposed) self.postbox?.clearHistory(peerId, threadId: threadId, minTimestamp: minTimestamp, maxTimestamp: maxTimestamp, namespaces: namespaces, forEachMedia: forEachMedia) } - public func removeAllMessagesWithAuthor(_ peerId: PeerId, authorId: PeerId, namespace: MessageId.Namespace, forEachMedia: (Media) -> Void) { + public func removeAllMessagesWithAuthor(_ peerId: PeerId, authorId: PeerId, namespace: MessageId.Namespace, forEachMedia: ((Media) -> Void)?) { assert(!self.disposed) self.postbox?.removeAllMessagesWithAuthor(peerId, authorId: authorId, namespace: namespace, forEachMedia: forEachMedia) } @@ -166,7 +156,7 @@ public final class Transaction { self.postbox?.removeAllMessagesWithGlobalTag(tag: tag) } - public func removeAllMessagesWithForwardAuthor(_ peerId: PeerId, forwardAuthorId: PeerId, namespace: MessageId.Namespace, forEachMedia: (Media) -> Void) { + public func removeAllMessagesWithForwardAuthor(_ peerId: PeerId, forwardAuthorId: PeerId, namespace: MessageId.Namespace, forEachMedia: ((Media) -> Void)?) { assert(!self.disposed) self.postbox?.removeAllMessagesWithForwardAuthor(peerId, forwardAuthorId: forwardAuthorId, namespace: namespace, forEachMedia: forEachMedia) } @@ -188,7 +178,7 @@ public final class Transaction { } } - public func deleteMessagesWithGlobalIds(_ ids: [Int32], forEachMedia: (Media) -> Void) { + public func deleteMessagesWithGlobalIds(_ ids: [Int32], forEachMedia: ((Media) -> Void)?) { assert(!self.disposed) if let postbox = self.postbox { let messageIds = postbox.messageIdsForGlobalIds(ids) @@ -229,7 +219,7 @@ public final class Transaction { public func applyInteractiveReadMaxIndex(_ messageIndex: MessageIndex) -> [MessageId] { assert(!self.disposed) if let postbox = self.postbox { - return postbox.applyInteractiveReadMaxIndex(messageIndex) + return postbox.applyInteractiveReadMaxIndex(messageIndex: messageIndex) } else { return [] } @@ -328,7 +318,7 @@ public final class Transaction { return self.postbox?.readStateTable.getCombinedState(id) } - public func getPeerNotificationSettings(_ id: PeerId) -> PeerNotificationSettings? { + public func getPeerNotificationSettings(id: PeerId) -> PeerNotificationSettings? { assert(!self.disposed) return self.postbox?.peerNotificationSettingsTable.getEffective(id) } @@ -894,7 +884,7 @@ public final class Transaction { let notificationsPeerId = peer.notificationSettingsPeerId ?? peerId let isContact = postbox.contactsTable.isContact(peerId: notificationsPeerId) let isRemovedFromTotalUnreadCount = resolvedIsRemovedFromTotalUnreadCount(globalSettings: globalNotificationSettings, peer: peer, peerSettings: postbox.peerNotificationSettingsTable.getEffective(notificationsPeerId)) - let messageTagSummaryResult = resolveChatListMessageTagSummaryResultCalculation(postbox: postbox, peerId: peer.id, calculation: predicate.messageTagSummary) + let messageTagSummaryResult = resolveChatListMessageTagSummaryResultCalculation(postbox: postbox, peerId: peer.id, threadId: nil, calculation: predicate.messageTagSummary) if predicate.includes(peer: peer, groupId: groupId, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, isUnread: isUnread, isContact: isContact, messageTagSummaryResult: messageTagSummaryResult) { includedPeerIds[peer.id] = true @@ -1002,14 +992,14 @@ public final class Transaction { return self.postbox?.getPendingMessageAction(type: type, id: id) } - public func getMessageTagSummary(peerId: PeerId, tagMask: MessageTags, namespace: MessageId.Namespace) -> MessageHistoryTagNamespaceSummary? { + public func getMessageTagSummary(peerId: PeerId, threadId: Int64?, tagMask: MessageTags, namespace: MessageId.Namespace) -> MessageHistoryTagNamespaceSummary? { assert(!self.disposed) - return self.postbox?.messageHistoryTagsSummaryTable.get(MessageHistoryTagsSummaryKey(tag: tagMask, peerId: peerId, namespace: namespace)) + return self.postbox?.messageHistoryTagsSummaryTable.get(MessageHistoryTagsSummaryKey(tag: tagMask, peerId: peerId, threadId: threadId, namespace: namespace)) } - public func replaceMessageTagSummary(peerId: PeerId, tagMask: MessageTags, namespace: MessageId.Namespace, count: Int32, maxId: MessageId.Id) { + public func replaceMessageTagSummary(peerId: PeerId, threadId: Int64?, tagMask: MessageTags, namespace: MessageId.Namespace, count: Int32, maxId: MessageId.Id) { assert(!self.disposed) - self.postbox?.replaceMessageTagSummary(peerId: peerId, tagMask: tagMask, namespace: namespace, count: count, maxId: maxId) + self.postbox?.replaceMessageTagSummary(peerId: peerId, threadId: threadId, tagMask: tagMask, namespace: namespace, count: count, maxId: maxId) } public func getPendingMessageActionsSummary(peerId: PeerId, type: PendingMessageActionType, namespace: MessageId.Namespace) -> Int32? { @@ -1017,12 +1007,16 @@ public final class Transaction { return self.postbox?.pendingMessageActionsMetadataTable.getCount(.peerNamespaceAction(peerId, namespace, type)) } - public func getMessageIndicesWithTag(peerId: PeerId, namespace: MessageId.Namespace, tag: MessageTags) -> [MessageIndex] { + public func getMessageIndicesWithTag(peerId: PeerId, threadId: Int64?, namespace: MessageId.Namespace, tag: MessageTags) -> [MessageIndex] { assert(!self.disposed) guard let postbox = self.postbox else { return [] } - return postbox.messageHistoryTagsTable.earlierIndices(tag: tag, peerId: peerId, namespace: namespace, index: nil, includeFrom: false, count: 1000) + if let threadId = threadId { + return postbox.messageHistoryThreadTagsTable.earlierIndices(tag: tag, threadId: threadId, peerId: peerId, namespace: namespace, index: nil, includeFrom: false, count: 1000) + } else { + return postbox.messageHistoryTagsTable.earlierIndices(tag: tag, peerId: peerId, namespace: namespace, index: nil, includeFrom: false, count: 1000) + } } public func getMessagesWithThreadId(peerId: PeerId, namespace: MessageId.Namespace, threadId: Int64, from: MessageIndex, includeFrom: Bool, to: MessageIndex, limit: Int) -> [Message] { @@ -1447,6 +1441,7 @@ final class PostboxImpl { let messageHistoryFailedTable: MessageHistoryFailedTable let messageHistoryTagsTable: MessageHistoryTagsTable let messageHistoryThreadsTable: MessageHistoryThreadsTable + let messageHistoryThreadTagsTable: MessageHistoryThreadTagsTable let messageHistoryThreadHoleIndexTable: MessageHistoryThreadHoleIndexTable let messageHistoryThreadReverseIndexTable: MessageHistoryThreadReverseIndexTable let messageHistoryThreadIndexTable: MessageHistoryThreadIndexTable @@ -1524,6 +1519,7 @@ final class PostboxImpl { self.pendingMessageActionsTable = PendingMessageActionsTable(valueBox: self.valueBox, table: PendingMessageActionsTable.tableSpec(46), useCaches: useCaches, metadataTable: self.pendingMessageActionsMetadataTable) self.messageHistoryTagsTable = MessageHistoryTagsTable(valueBox: self.valueBox, table: MessageHistoryTagsTable.tableSpec(12), useCaches: useCaches, seedConfiguration: self.seedConfiguration, summaryTable: self.messageHistoryTagsSummaryTable) self.messageHistoryThreadsTable = MessageHistoryThreadsTable(valueBox: self.valueBox, table: MessageHistoryThreadsTable.tableSpec(62), useCaches: useCaches) + self.messageHistoryThreadTagsTable = MessageHistoryThreadTagsTable(valueBox: self.valueBox, table: MessageHistoryThreadTagsTable.tableSpec(71), useCaches: useCaches, seedConfiguration: self.seedConfiguration, summaryTable: self.messageHistoryTagsSummaryTable) self.messageHistoryThreadHoleIndexTable = MessageHistoryThreadHoleIndexTable(valueBox: self.valueBox, table: MessageHistoryThreadHoleIndexTable.tableSpec(63), useCaches: useCaches, metadataTable: self.messageHistoryMetadataTable, seedConfiguration: self.seedConfiguration) self.messageHistoryThreadReverseIndexTable = MessageHistoryThreadReverseIndexTable(valueBox: self.valueBox, table: MessageHistoryThreadReverseIndexTable.tableSpec(69), useCaches: useCaches) self.messageHistoryThreadIndexTable = MessageHistoryThreadIndexTable(valueBox: self.valueBox, table: MessageHistoryThreadIndexTable.tableSpec(70), reverseIndexTable: self.messageHistoryThreadReverseIndexTable, useCaches: useCaches) @@ -1538,7 +1534,7 @@ final class PostboxImpl { self.timestampBasedMessageAttributesTable = TimestampBasedMessageAttributesTable(valueBox: self.valueBox, table: TimestampBasedMessageAttributesTable.tableSpec(34), useCaches: useCaches, indexTable: self.timestampBasedMessageAttributesIndexTable) self.textIndexTable = MessageHistoryTextIndexTable(valueBox: self.valueBox, table: MessageHistoryTextIndexTable.tableSpec(41)) self.additionalChatListItemsTable = AdditionalChatListItemsTable(valueBox: self.valueBox, table: AdditionalChatListItemsTable.tableSpec(55), useCaches: useCaches) - self.messageHistoryTable = MessageHistoryTable(valueBox: self.valueBox, table: MessageHistoryTable.tableSpec(7), useCaches: useCaches, seedConfiguration: seedConfiguration, messageHistoryIndexTable: self.messageHistoryIndexTable, messageHistoryHoleIndexTable: self.messageHistoryHoleIndexTable, messageMediaTable: self.mediaTable, historyMetadataTable: self.messageHistoryMetadataTable, globallyUniqueMessageIdsTable: self.globallyUniqueMessageIdsTable, unsentTable: self.messageHistoryUnsentTable, failedTable: self.messageHistoryFailedTable, tagsTable: self.messageHistoryTagsTable, threadsTable: self.messageHistoryThreadsTable, globalTagsTable: self.globalMessageHistoryTagsTable, localTagsTable: self.localMessageHistoryTagsTable, timeBasedAttributesTable: self.timestampBasedMessageAttributesTable, readStateTable: self.readStateTable, synchronizeReadStateTable: self.synchronizeReadStateTable, textIndexTable: self.textIndexTable, summaryTable: self.messageHistoryTagsSummaryTable, pendingActionsTable: self.pendingMessageActionsTable) + self.messageHistoryTable = MessageHistoryTable(valueBox: self.valueBox, table: MessageHistoryTable.tableSpec(7), useCaches: useCaches, seedConfiguration: seedConfiguration, messageHistoryIndexTable: self.messageHistoryIndexTable, messageHistoryHoleIndexTable: self.messageHistoryHoleIndexTable, messageMediaTable: self.mediaTable, historyMetadataTable: self.messageHistoryMetadataTable, globallyUniqueMessageIdsTable: self.globallyUniqueMessageIdsTable, unsentTable: self.messageHistoryUnsentTable, failedTable: self.messageHistoryFailedTable, tagsTable: self.messageHistoryTagsTable, threadsTable: self.messageHistoryThreadsTable, threadTagsTable: self.messageHistoryThreadTagsTable, globalTagsTable: self.globalMessageHistoryTagsTable, localTagsTable: self.localMessageHistoryTagsTable, timeBasedAttributesTable: self.timestampBasedMessageAttributesTable, readStateTable: self.readStateTable, synchronizeReadStateTable: self.synchronizeReadStateTable, textIndexTable: self.textIndexTable, summaryTable: self.messageHistoryTagsSummaryTable, pendingActionsTable: self.pendingMessageActionsTable) self.peerChatStateTable = PeerChatStateTable(valueBox: self.valueBox, table: PeerChatStateTable.tableSpec(13), useCaches: useCaches) self.peerNameTokenIndexTable = ReverseIndexReferenceTable(valueBox: self.valueBox, table: ReverseIndexReferenceTable.tableSpec(26), useCaches: useCaches) self.peerNameIndexTable = PeerNameIndexTable(valueBox: self.valueBox, table: PeerNameIndexTable.tableSpec(27), useCaches: useCaches, peerTable: self.peerTable, peerNameTokenIndexTable: self.peerNameTokenIndexTable) @@ -1582,6 +1578,7 @@ final class PostboxImpl { tables.append(self.messageHistoryFailedTable) tables.append(self.messageHistoryTagsTable) tables.append(self.messageHistoryThreadsTable) + tables.append(self.messageHistoryThreadTagsTable) tables.append(self.messageHistoryThreadHoleIndexTable) tables.append(self.messageHistoryThreadReverseIndexTable) tables.append(self.messageHistoryThreadIndexTable) @@ -1818,20 +1815,20 @@ final class PostboxImpl { } } - fileprivate func addHole(peerId: PeerId, namespace: MessageId.Namespace, space: MessageHistoryHoleSpace, range: ClosedRange) { - self.messageHistoryHoleIndexTable.add(peerId: peerId, namespace: namespace, space: space, range: range, operations: &self.currentPeerHoleOperations) + fileprivate func addHole(peerId: PeerId, threadId: Int64?, namespace: MessageId.Namespace, space: MessageHistoryHoleSpace, range: ClosedRange) { + if let threadId = threadId { + self.messageHistoryThreadHoleIndexTable.add(peerId: peerId, threadId: threadId, namespace: namespace, space: space, range: range, operations: &self.currentPeerHoleOperations) + } else { + self.messageHistoryHoleIndexTable.add(peerId: peerId, namespace: namespace, space: space, range: range, operations: &self.currentPeerHoleOperations) + } } - fileprivate func removeHole(peerId: PeerId, namespace: MessageId.Namespace, space: MessageHistoryHoleSpace, range: ClosedRange) { - self.messageHistoryHoleIndexTable.remove(peerId: peerId, namespace: namespace, space: space, range: range, operations: &self.currentPeerHoleOperations) - } - - fileprivate func addThreadIndexHole(peerId: PeerId, threadId: Int64, namespace: MessageId.Namespace, space: MessageHistoryHoleSpace, range: ClosedRange) { - self.messageHistoryThreadHoleIndexTable.add(peerId: peerId, threadId: threadId, namespace: namespace, space: space, range: range, operations: &self.currentPeerHoleOperations) - } - - fileprivate func removeThreadIndexHole(peerId: PeerId, threadId: Int64, namespace: MessageId.Namespace, space: MessageHistoryHoleSpace, range: ClosedRange) { - self.messageHistoryThreadHoleIndexTable.remove(peerId: peerId, threadId: threadId, namespace: namespace, space: space, range: range, operations: &self.currentPeerHoleOperations) + fileprivate func removeHole(peerId: PeerId, threadId: Int64?, namespace: MessageId.Namespace, space: MessageHistoryHoleSpace, range: ClosedRange) { + if let threadId = threadId { + self.messageHistoryThreadHoleIndexTable.remove(peerId: peerId, threadId: threadId, namespace: namespace, space: space, range: range, operations: &self.currentPeerHoleOperations) + } else { + self.messageHistoryHoleIndexTable.remove(peerId: peerId, namespace: namespace, space: space, range: range, operations: &self.currentPeerHoleOperations) + } } fileprivate func recalculateChatListGroupStats(groupId: PeerGroupId) { @@ -1844,11 +1841,11 @@ final class PostboxImpl { self.chatListTable.replaceHole(groupId: groupId, index: index, hole: hole, operations: &self.currentChatListOperations) } - fileprivate func deleteMessages(_ messageIds: [MessageId], forEachMedia: (Media) -> Void) { + fileprivate func deleteMessages(_ messageIds: [MessageId], forEachMedia: ((Media) -> Void)?) { self.messageHistoryTable.removeMessages(messageIds, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: ¤tUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, timestampBasedMessageAttributesOperations: &self.currentTimestampBasedMessageAttributesOperations, forEachMedia: forEachMedia) } - fileprivate func deleteMessagesInRange(peerId: PeerId, namespace: MessageId.Namespace, minId: MessageId.Id, maxId: MessageId.Id, forEachMedia: (Media) -> Void) { + fileprivate func deleteMessagesInRange(peerId: PeerId, namespace: MessageId.Namespace, minId: MessageId.Id, maxId: MessageId.Id, forEachMedia: ((Media) -> Void)?) { self.messageHistoryTable.removeMessagesInRange(peerId: peerId, namespace: namespace, minId: minId, maxId: maxId, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: ¤tUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, timestampBasedMessageAttributesOperations: &self.currentTimestampBasedMessageAttributesOperations, forEachMedia: forEachMedia) } @@ -1864,7 +1861,7 @@ final class PostboxImpl { } } - fileprivate func clearHistory(_ peerId: PeerId, threadId: Int64?, minTimestamp: Int32?, maxTimestamp: Int32?, namespaces: MessageIdNamespaces, forEachMedia: (Media) -> Void) { + fileprivate func clearHistory(_ peerId: PeerId, threadId: Int64?, minTimestamp: Int32?, maxTimestamp: Int32?, namespaces: MessageIdNamespaces, forEachMedia: ((Media) -> Void)?) { if let minTimestamp = minTimestamp, let maxTimestamp = maxTimestamp { self.messageHistoryTable.clearHistoryInRange(peerId: peerId, threadId: threadId, minTimestamp: minTimestamp, maxTimestamp: maxTimestamp, namespaces: namespaces, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: ¤tUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, timestampBasedMessageAttributesOperations: &self.currentTimestampBasedMessageAttributesOperations, forEachMedia: forEachMedia) } else { @@ -1875,7 +1872,7 @@ final class PostboxImpl { } } - fileprivate func removeAllMessagesWithAuthor(_ peerId: PeerId, authorId: PeerId, namespace: MessageId.Namespace, forEachMedia: (Media) -> Void) { + fileprivate func removeAllMessagesWithAuthor(_ peerId: PeerId, authorId: PeerId, namespace: MessageId.Namespace, forEachMedia: ((Media) -> Void)?) { self.messageHistoryTable.removeAllMessagesWithAuthor(peerId: peerId, authorId: authorId, namespace: namespace, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: ¤tUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, timestampBasedMessageAttributesOperations: &self.currentTimestampBasedMessageAttributesOperations, forEachMedia: forEachMedia) } @@ -1883,7 +1880,7 @@ final class PostboxImpl { self.messageHistoryTable.removeAllMessagesWithGlobalTag(tag: tag, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: ¤tUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, timestampBasedMessageAttributesOperations: &self.currentTimestampBasedMessageAttributesOperations, forEachMedia: { _ in }) } - fileprivate func removeAllMessagesWithForwardAuthor(_ peerId: PeerId, forwardAuthorId: PeerId, namespace: MessageId.Namespace, forEachMedia: (Media) -> Void) { + fileprivate func removeAllMessagesWithForwardAuthor(_ peerId: PeerId, forwardAuthorId: PeerId, namespace: MessageId.Namespace, forEachMedia: ((Media) -> Void)?) { self.messageHistoryTable.removeAllMessagesWithForwardAuthor(peerId: peerId, forwardAuthorId: forwardAuthorId, namespace: namespace, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: ¤tUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, timestampBasedMessageAttributesOperations: &self.currentTimestampBasedMessageAttributesOperations, forEachMedia: forEachMedia) } @@ -1907,17 +1904,17 @@ final class PostboxImpl { self.messageHistoryTable.applyOutgoingReadMaxId(messageId, operationsByPeerId: &self.currentOperationsByPeerId, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations) } - fileprivate func applyInteractiveReadMaxIndex(_ messageIndex: MessageIndex) -> [MessageId] { - let peerIds = self.peerIdsForLocation(.peer(messageIndex.id.peerId), ignoreRelatedChats: false) + fileprivate func applyInteractiveReadMaxIndex(messageIndex: MessageIndex) -> [MessageId] { + let peerIds = self.peerIdsForLocation(.peer(peerId: messageIndex.id.peerId, threadId: nil), ignoreRelatedChats: false) switch peerIds { - case let .associated(_, messageId): - if let messageId = messageId, let readState = self.readStateTable.getCombinedState(messageId.peerId), readState.count != 0 { - if let topMessage = self.messageHistoryTable.topMessage(peerId: messageId.peerId) { - let _ = self.messageHistoryTable.applyInteractiveMaxReadIndex(postbox: self, messageIndex: topMessage.index, operationsByPeerId: &self.currentOperationsByPeerId, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations) - } + case let .associated(_, messageId): + if let messageId = messageId, let readState = self.readStateTable.getCombinedState(messageId.peerId), readState.count != 0 { + if let topMessage = self.messageHistoryTable.topMessage(peerId: messageId.peerId) { + let _ = self.messageHistoryTable.applyInteractiveMaxReadIndex(postbox: self, messageIndex: topMessage.index, operationsByPeerId: &self.currentOperationsByPeerId, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations) } - default: - break + } + default: + break } let initialCombinedStates = self.readStateTable.getCombinedState(messageIndex.id.peerId) var resultIds = self.messageHistoryTable.applyInteractiveMaxReadIndex(postbox: self, messageIndex: messageIndex, operationsByPeerId: &self.currentOperationsByPeerId, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations) @@ -2487,8 +2484,8 @@ final class PostboxImpl { return self.pendingMessageActionsTable.getAction(id: id, type: type) } - fileprivate func replaceMessageTagSummary(peerId: PeerId, tagMask: MessageTags, namespace: MessageId.Namespace, count: Int32, maxId: MessageId.Id) { - let key = MessageHistoryTagsSummaryKey(tag: tagMask, peerId: peerId, namespace: namespace) + fileprivate func replaceMessageTagSummary(peerId: PeerId, threadId: Int64?, tagMask: MessageTags, namespace: MessageId.Namespace, count: Int32, maxId: MessageId.Id) { + let key = MessageHistoryTagsSummaryKey(tag: tagMask, peerId: peerId, threadId: threadId, namespace: namespace) self.messageHistoryTagsSummaryTable.replace(key: key, count: count, maxId: maxId, updatedSummaries: &self.currentUpdatedMessageTagSummaries) } @@ -2611,8 +2608,8 @@ final class PostboxImpl { func resolvedChatLocationInput(chatLocation: ChatLocationInput) -> Signal<(ResolvedChatLocationInput, Bool), NoError> { switch chatLocation { - case let .peer(peerId): - return .single((.peer(peerId), false)) + case let .peer(peerId, threadId): + return .single((.peer(peerId: peerId, threadId: threadId), false)) case .thread(_, _, let data), .feed(_, let data): return Signal { subscriber in var isHoleFill = false @@ -2629,9 +2626,9 @@ final class PostboxImpl { func peerIdsForLocation(_ chatLocation: ResolvedChatLocationInput, ignoreRelatedChats: Bool) -> MessageHistoryViewInput { var peerIds: MessageHistoryViewInput switch chatLocation { - case let .peer(peerId): - peerIds = .single(peerId) - if !ignoreRelatedChats, let associatedMessageId = self.cachedPeerDataTable.get(peerId)?.associatedHistoryMessageId, associatedMessageId.peerId != peerId { + case let .peer(peerId, threadId): + peerIds = .single(peerId: peerId, threadId: threadId) + if !ignoreRelatedChats, threadId == nil, let associatedMessageId = self.cachedPeerDataTable.get(peerId)?.associatedHistoryMessageId, associatedMessageId.peerId != peerId { peerIds = .associated(peerId, associatedMessageId) } case let .external(input): @@ -2640,7 +2637,7 @@ final class PostboxImpl { return peerIds } - public func aroundMessageOfInterestHistoryViewForChatLocation(_ chatLocation: ChatLocationInput, ignoreMessagesInTimestampRange: ClosedRange?, count: Int, clipHoles: Bool = true, topTaggedMessageIdNamespaces: Set, tagMask: MessageTags?, appendMessagesFromTheSameGroup: Bool, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, additionalData: [AdditionalMessageHistoryViewData]) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + public func aroundMessageOfInterestHistoryViewForChatLocation(_ chatLocation: ChatLocationInput, ignoreMessagesInTimestampRange: ClosedRange?, count: Int, clipHoles: Bool = true, topTaggedMessageIdNamespaces: Set, tagMask: MessageTags?, appendMessagesFromTheSameGroup: Bool, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, customUnreadMessageId: MessageId?, additionalData: [AdditionalMessageHistoryViewData]) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { return self.resolvedChatLocationInput(chatLocation: chatLocation) |> mapToSignal { chatLocationData in let (chatLocation, isHoleFill) = chatLocationData @@ -2650,8 +2647,10 @@ final class PostboxImpl { var anchor: HistoryViewInputAnchor = .upperBound switch peerIds { - case let .single(peerId): - if self.chatListTable.getPeerChatListIndex(peerId: peerId) != nil { + case let .single(peerId, threadId): + if let customUnreadMessageId = customUnreadMessageId { + anchor = .message(customUnreadMessageId) + } else if threadId == nil, self.chatListTable.getPeerChatListIndex(peerId: peerId) != nil { if let combinedState = self.readStateTable.getCombinedState(peerId), let state = combinedState.states.first, state.1.count != 0 { switch state.1 { case let .idBased(maxIncomingReadId, _, _, _, _): @@ -2770,8 +2769,10 @@ final class PostboxImpl { var topTaggedMessages: [MessageId.Namespace: MessageHistoryTopTaggedMessage?] = [:] var mainPeerIdForTopTaggedMessages: PeerId? switch peerIds { - case let .single(id): - mainPeerIdForTopTaggedMessages = id + case let .single(id, threadId): + if threadId == nil { + mainPeerIdForTopTaggedMessages = id + } case let .associated(id, _): mainPeerIdForTopTaggedMessages = id case .external: @@ -2844,8 +2845,8 @@ final class PostboxImpl { var readStates: MessageHistoryViewReadState? var transientReadStates: MessageHistoryViewReadState? switch peerIds { - case let .single(peerId): - if let readState = self.readStateTable.getCombinedState(peerId) { + case let .single(peerId, threadId): + if threadId == nil, let readState = self.readStateTable.getCombinedState(peerId) { transientReadStates = .peer([peerId: readState]) } case let .associated(peerId, _): @@ -2876,8 +2877,8 @@ final class PostboxImpl { let initialData: InitialMessageHistoryData switch peerIds { - case let .single(peerId): - initialData = self.initialMessageHistoryData(peerId: peerId, threadId: nil) + case let .single(peerId, threadId): + initialData = self.initialMessageHistoryData(peerId: peerId, threadId: threadId) case let .associated(peerId, _): initialData = self.initialMessageHistoryData(peerId: peerId, threadId: nil) case let .external(input): @@ -3671,7 +3672,7 @@ final class PostboxImpl { fileprivate func addHolesEverywhere(peerNamespaces: [PeerId.Namespace], holeNamespace: MessageId.Namespace) { for peerId in self.chatListIndexTable.getAllPeerIds() { if peerNamespaces.contains(peerId.namespace) && self.messageHistoryMetadataTable.isInitialized(peerId) { - self.addHole(peerId: peerId, namespace: holeNamespace, space: .everywhere, range: 1 ... Int32.max - 1) + self.addHole(peerId: peerId, threadId: nil, namespace: holeNamespace, space: .everywhere, range: 1 ... Int32.max - 1) } } } @@ -3818,6 +3819,7 @@ public class Postbox { appendMessagesFromTheSameGroup: Bool, namespaces: MessageIdNamespaces, orderStatistics: MessageHistoryViewOrderStatistics, + customUnreadMessageId: MessageId?, additionalData: [AdditionalMessageHistoryViewData] ) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { return Signal { subscriber in @@ -3834,6 +3836,7 @@ public class Postbox { appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, namespaces: namespaces, orderStatistics: orderStatistics, + customUnreadMessageId: customUnreadMessageId, additionalData: additionalData ).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion)) } diff --git a/submodules/Postbox/Sources/SeedConfiguration.swift b/submodules/Postbox/Sources/SeedConfiguration.swift index 64b9e78a0a..53561a25ab 100644 --- a/submodules/Postbox/Sources/SeedConfiguration.swift +++ b/submodules/Postbox/Sources/SeedConfiguration.swift @@ -45,11 +45,11 @@ func resolveChatListMessageTagSummaryResultCalculation(addSummary: MessageHistor return count > 0 } -func resolveChatListMessageTagSummaryResultCalculation(postbox: PostboxImpl, peerId: PeerId, calculation: ChatListMessageTagSummaryResultCalculation?) -> Bool? { +func resolveChatListMessageTagSummaryResultCalculation(postbox: PostboxImpl, peerId: PeerId, threadId: Int64?, calculation: ChatListMessageTagSummaryResultCalculation?) -> Bool? { guard let calculation = calculation else { return nil } - let addSummary = postbox.messageHistoryTagsSummaryTable.get(MessageHistoryTagsSummaryKey(tag: calculation.addCount.tag, peerId: peerId, namespace: calculation.addCount.namespace)) + let addSummary = postbox.messageHistoryTagsSummaryTable.get(MessageHistoryTagsSummaryKey(tag: calculation.addCount.tag, peerId: peerId, threadId: threadId, namespace: calculation.addCount.namespace)) let subtractSummary = postbox.pendingMessageActionsMetadataTable.getCount(.peerNamespaceAction(peerId, calculation.subtractCount.namespace, calculation.subtractCount.type)) let count = (addSummary?.count ?? 0) - subtractSummary return count > 0 diff --git a/submodules/Postbox/Sources/ViewTracker.swift b/submodules/Postbox/Sources/ViewTracker.swift index 05b45e0c73..fa275fc344 100644 --- a/submodules/Postbox/Sources/ViewTracker.swift +++ b/submodules/Postbox/Sources/ViewTracker.swift @@ -269,9 +269,9 @@ final class ViewTracker { var updateType: ViewUpdateType = .Generic switch mutableView.peerIds { - case let .single(peerId): + case let .single(peerId, threadId): for key in transaction.currentPeerHoleOperations.keys { - if key.peerId == peerId { + if key.peerId == peerId && key.threadId == threadId { updateType = .FillHole break } diff --git a/submodules/Postbox/Sources/Views.swift b/submodules/Postbox/Sources/Views.swift index 8c6a3e1926..b72a9f2833 100644 --- a/submodules/Postbox/Sources/Views.swift +++ b/submodules/Postbox/Sources/Views.swift @@ -12,7 +12,7 @@ public enum PostboxViewKey: Hashable { case pendingMessageActions(type: PendingMessageActionType) case invalidatedMessageHistoryTagSummaries(tagMask: MessageTags, namespace: MessageId.Namespace) case pendingMessageActionsSummary(type: PendingMessageActionType, peerId: PeerId, namespace: MessageId.Namespace) - case historyTagSummaryView(tag: MessageTags, peerId: PeerId, namespace: MessageId.Namespace) + case historyTagSummaryView(tag: MessageTags, peerId: PeerId, threadId: Int64?, namespace: MessageId.Namespace) case cachedPeerData(peerId: PeerId) case unreadCounts(items: [UnreadMessageCountsItem]) case combinedReadState(peerId: PeerId) @@ -38,7 +38,7 @@ public enum PostboxViewKey: Hashable { case isContact(id: PeerId) case chatListIndex(id: PeerId) case peerTimeoutAttributes - case messageHistoryThreadIndex(id: PeerId) + case messageHistoryThreadIndex(id: PeerId, summaryComponents: ChatListEntrySummaryComponents) case messageHistoryThreadInfo(peerId: PeerId, threadId: Int64) public func hash(into hasher: inout Hasher) { @@ -68,9 +68,10 @@ public enum PostboxViewKey: Hashable { hasher.combine(type) hasher.combine(peerId) hasher.combine(namespace) - case let .historyTagSummaryView(tag, peerId, namespace): + case let .historyTagSummaryView(tag, peerId, threadId, namespace): hasher.combine(tag) hasher.combine(peerId) + hasher.combine(threadId) hasher.combine(namespace) case let .cachedPeerData(peerId): hasher.combine(peerId) @@ -126,7 +127,7 @@ public enum PostboxViewKey: Hashable { hasher.combine(id) case .peerTimeoutAttributes: hasher.combine(17) - case let .messageHistoryThreadIndex(id): + case let .messageHistoryThreadIndex(id, _): hasher.combine(id) case let .messageHistoryThreadInfo(peerId, threadId): hasher.combine(peerId) @@ -202,8 +203,8 @@ public enum PostboxViewKey: Hashable { } else { return false } - case let .historyTagSummaryView(tag, peerId, namespace): - if case .historyTagSummaryView(tag, peerId, namespace) = rhs { + case let .historyTagSummaryView(tag, peerId, threadId, namespace): + if case .historyTagSummaryView(tag, peerId, threadId, namespace) = rhs { return true } else { return false @@ -358,8 +359,8 @@ public enum PostboxViewKey: Hashable { } else { return false } - case let .messageHistoryThreadIndex(id): - if case .messageHistoryThreadIndex(id) = rhs { + case let .messageHistoryThreadIndex(id, summaryComponents): + if case .messageHistoryThreadIndex(id, summaryComponents) = rhs { return true } else { return false @@ -398,8 +399,8 @@ func postboxViewForKey(postbox: PostboxImpl, key: PostboxViewKey) -> MutablePost return MutableInvalidatedMessageHistoryTagSummariesView(postbox: postbox, tagMask: tagMask, namespace: namespace) case let .pendingMessageActionsSummary(type, peerId, namespace): return MutablePendingMessageActionsSummaryView(postbox: postbox, type: type, peerId: peerId, namespace: namespace) - case let .historyTagSummaryView(tag, peerId, namespace): - return MutableMessageHistoryTagSummaryView(postbox: postbox, tag: tag, peerId: peerId, namespace: namespace) + case let .historyTagSummaryView(tag, peerId, threadId, namespace): + return MutableMessageHistoryTagSummaryView(postbox: postbox, tag: tag, peerId: peerId, threadId: threadId, namespace: namespace) case let .cachedPeerData(peerId): return MutableCachedPeerDataView(postbox: postbox, peerId: peerId) case let .unreadCounts(items): @@ -450,8 +451,8 @@ func postboxViewForKey(postbox: PostboxImpl, key: PostboxViewKey) -> MutablePost return MutableChatListIndexView(postbox: postbox, id: id) case .peerTimeoutAttributes: return MutablePeerTimeoutAttributesView(postbox: postbox) - case let .messageHistoryThreadIndex(id): - return MutableMessageHistoryThreadIndexView(postbox: postbox, peerId: id) + case let .messageHistoryThreadIndex(id, summaryComponents): + return MutableMessageHistoryThreadIndexView(postbox: postbox, peerId: id, summaryComponents: summaryComponents) case let .messageHistoryThreadInfo(peerId, threadId): return MutableMessageHistoryThreadInfoView(postbox: postbox, peerId: peerId, threadId: threadId) } diff --git a/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift b/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift index e58d5cf3a7..6321066e46 100644 --- a/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift +++ b/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift @@ -781,16 +781,16 @@ final class NotificationExceptionsControllerNode: ViewControllerTracingNode { let presentationData = context.sharedContext.currentPresentationData.modify {$0} let updatePeerSound: (PeerId, PeerMessageSound) -> Signal = { peerId, sound in - return context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, sound: sound) |> deliverOnMainQueue + return context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, threadId: nil, sound: sound) |> deliverOnMainQueue } let updatePeerNotificationInterval: (PeerId, Int32?) -> Signal = { peerId, muteInterval in - return context.engine.peers.updatePeerMuteSetting(peerId: peerId, muteInterval: muteInterval) |> deliverOnMainQueue + return context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: nil, muteInterval: muteInterval) |> deliverOnMainQueue } let updatePeerDisplayPreviews:(PeerId, PeerNotificationDisplayPreviews) -> Signal = { peerId, displayPreviews in - return context.engine.peers.updatePeerDisplayPreviewsSetting(peerId: peerId, displayPreviews: displayPreviews) |> deliverOnMainQueue + return context.engine.peers.updatePeerDisplayPreviewsSetting(peerId: peerId, threadId: nil, displayPreviews: displayPreviews) |> deliverOnMainQueue } self.backgroundColor = presentationData.theme.list.blocksBackgroundColor @@ -814,7 +814,7 @@ final class NotificationExceptionsControllerNode: ViewControllerTracingNode { let mode = stateValue.with { $0.mode } dismissInputImpl?() - presentControllerImpl?(notificationPeerExceptionController(context: context, peer: peer._asPeer(), mode: mode, updatePeerSound: { peerId, sound in + presentControllerImpl?(notificationPeerExceptionController(context: context, peer: peer._asPeer(), threadId: nil, mode: mode, updatePeerSound: { peerId, sound in _ = updatePeerSound(peer.id, sound).start(next: { _ in updateNotificationsDisposable.set(nil) _ = combineLatest(updatePeerSound(peer.id, sound), context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) |> deliverOnMainQueue).start(next: { _, peer in diff --git a/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionSettingsController.swift b/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionSettingsController.swift index e9303d1bf7..1fca8668cc 100644 --- a/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionSettingsController.swift +++ b/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionSettingsController.swift @@ -365,7 +365,7 @@ private struct NotificationExceptionPeerState : Equatable { } } -public func notificationPeerExceptionController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: Peer, mode: NotificationExceptionMode, edit: Bool = false, updatePeerSound: @escaping(PeerId, PeerMessageSound) -> Void, updatePeerNotificationInterval: @escaping(PeerId, Int32?) -> Void, updatePeerDisplayPreviews: @escaping(PeerId, PeerNotificationDisplayPreviews) -> Void, removePeerFromExceptions: @escaping () -> Void, modifiedPeer: @escaping () -> Void) -> ViewController { +public func notificationPeerExceptionController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: Peer, threadId: Int64?, mode: NotificationExceptionMode, edit: Bool = false, updatePeerSound: @escaping(PeerId, PeerMessageSound) -> Void, updatePeerNotificationInterval: @escaping(PeerId, Int32?) -> Void, updatePeerDisplayPreviews: @escaping(PeerId, PeerNotificationDisplayPreviews) -> Void, removePeerFromExceptions: @escaping () -> Void, modifiedPeer: @escaping () -> Void) -> ViewController { let initialState = NotificationExceptionPeerState(canRemove: false) let statePromise = Promise(initialState) let stateValue = Atomic(value: initialState) @@ -420,10 +420,13 @@ public func notificationPeerExceptionController(context: AccountContext, updated statePromise.set(context.engine.data.get( TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: peer.id), + EngineDataOptional(threadId.flatMap { TelegramEngine.EngineData.Item.Peer.ThreadNotificationSettings(id: peer.id, threadId: $0) }), TelegramEngine.EngineData.Item.NotificationSettings.Global() ) - |> map { notificationSettings, globalNotificationSettings -> NotificationExceptionPeerState in - var state = NotificationExceptionPeerState(canRemove: mode.peerIds.contains(peer.id), notifications: notificationSettings._asNotificationSettings()) + |> map { peerNotificationSettings, threadNotificationSettings, globalNotificationSettings -> NotificationExceptionPeerState in + let effectiveSettings = threadNotificationSettings ?? peerNotificationSettings + + var state = NotificationExceptionPeerState(canRemove: mode.peerIds.contains(peer.id), notifications: effectiveSettings._asNotificationSettings()) let globalSettings = globalNotificationSettings switch mode { case .channels: diff --git a/submodules/SettingsUI/Sources/NotificationsPeerCategoryController.swift b/submodules/SettingsUI/Sources/NotificationsPeerCategoryController.swift index 2ec033ce42..4b402ff309 100644 --- a/submodules/SettingsUI/Sources/NotificationsPeerCategoryController.swift +++ b/submodules/SettingsUI/Sources/NotificationsPeerCategoryController.swift @@ -438,16 +438,16 @@ public func notificationsPeerCategoryController(context: AccountContext, categor } let updatePeerSound: (PeerId, PeerMessageSound) -> Signal = { peerId, sound in - return context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, sound: sound) |> deliverOnMainQueue + return context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, threadId: nil, sound: sound) |> deliverOnMainQueue } let updatePeerNotificationInterval: (PeerId, Int32?) -> Signal = { peerId, muteInterval in - return context.engine.peers.updatePeerMuteSetting(peerId: peerId, muteInterval: muteInterval) |> deliverOnMainQueue + return context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: nil, muteInterval: muteInterval) |> deliverOnMainQueue } let updatePeerDisplayPreviews:(PeerId, PeerNotificationDisplayPreviews) -> Signal = { peerId, displayPreviews in - return context.engine.peers.updatePeerDisplayPreviewsSetting(peerId: peerId, displayPreviews: displayPreviews) |> deliverOnMainQueue + return context.engine.peers.updatePeerDisplayPreviewsSetting(peerId: peerId, threadId: nil, displayPreviews: displayPreviews) |> deliverOnMainQueue } var peerIds: Set = Set(mode.peerIds) @@ -502,7 +502,7 @@ public func notificationsPeerCategoryController(context: AccountContext, categor } let mode = stateValue.with { $0.mode } - pushControllerImpl?(notificationPeerExceptionController(context: context, peer: peer._asPeer(), mode: mode, updatePeerSound: { peerId, sound in + pushControllerImpl?(notificationPeerExceptionController(context: context, peer: peer._asPeer(), threadId: nil, mode: mode, updatePeerSound: { peerId, sound in _ = updatePeerSound(peer.id, sound).start(next: { _ in updateNotificationsDisposable.set(nil) _ = combineLatest(updatePeerSound(peer.id, sound), context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) |> deliverOnMainQueue).start(next: { _, peer in diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index c14e513cbe..64cec008cb 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -219,7 +219,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView var items: [ChatListItem] = [] let interaction = ChatListNodeInteraction(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in - }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in + }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in }, activateChatPreview: { _, _, gesture, _ in gesture?.cancel() }, present: { _ in }) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift index 585c12bf24..18f75dbecc 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift @@ -839,7 +839,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate var items: [ChatListItem] = [] let interaction = ChatListNodeInteraction(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in - }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in + }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in }, activateChatPreview: { _, _, gesture, _ in gesture?.cancel() }, present: { _ in diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index 11f768e950..8c48f5debe 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -363,7 +363,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { var items: [ChatListItem] = [] let interaction = ChatListNodeInteraction(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in - }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in + }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in }, activateChatPreview: { _, _, gesture, _ in gesture?.cancel() }, present: { _ in diff --git a/submodules/StatisticsUI/Sources/ChannelStatsController.swift b/submodules/StatisticsUI/Sources/ChannelStatsController.swift index dd058ded0b..497f0f737c 100644 --- a/submodules/StatisticsUI/Sources/ChannelStatsController.swift +++ b/submodules/StatisticsUI/Sources/ChannelStatsController.swift @@ -447,7 +447,7 @@ public func channelStatsController(context: AccountContext, updatedPresentationD contextActionImpl?(messageId, node, gesture) }) - let messageView = context.account.viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId: peerId), index: .upperBound, anchorIndex: .upperBound, count: 100, fixedCombinedReadStates: nil) + let messageView = context.account.viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId: peerId, threadId: nil), index: .upperBound, anchorIndex: .upperBound, count: 100, fixedCombinedReadStates: nil) |> map { messageHistoryView, _, _ -> MessageHistoryView? in return messageHistoryView } diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index c9ece079d2..b89cd0655f 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -222,7 +222,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-207944868] = { return Api.FileHash.parse_fileHash($0) } dict[-11252123] = { return Api.Folder.parse_folder($0) } dict[-373643672] = { return Api.FolderPeer.parse_folderPeer($0) } - dict[413771876] = { return Api.ForumTopic.parse_forumTopic($0) } + dict[1495324380] = { return Api.ForumTopic.parse_forumTopic($0) } dict[-1107729093] = { return Api.Game.parse_game($0) } dict[-1297942941] = { return Api.GeoPoint.parse_geoPoint($0) } dict[286776671] = { return Api.GeoPoint.parse_geoPointEmpty($0) } @@ -315,6 +315,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1160215659] = { return Api.InputMessage.parse_inputMessageReplyTo($0) } dict[-1311015810] = { return Api.InputNotifyPeer.parse_inputNotifyBroadcasts($0) } dict[1251338318] = { return Api.InputNotifyPeer.parse_inputNotifyChats($0) } + dict[1548122514] = { return Api.InputNotifyPeer.parse_inputNotifyForumTopic($0) } dict[-1195615476] = { return Api.InputNotifyPeer.parse_inputNotifyPeer($0) } dict[423314455] = { return Api.InputNotifyPeer.parse_inputNotifyUsers($0) } dict[873977640] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentials($0) } diff --git a/submodules/TelegramApi/Sources/Api29.swift b/submodules/TelegramApi/Sources/Api29.swift index 2232c9c612..052b9412ee 100644 --- a/submodules/TelegramApi/Sources/Api29.swift +++ b/submodules/TelegramApi/Sources/Api29.swift @@ -4985,16 +4985,18 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func getSearchCounters(peer: Api.InputPeer, filters: [Api.MessagesFilter]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.messages.SearchCounter]>) { + static func getSearchCounters(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?, filters: [Api.MessagesFilter]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.messages.SearchCounter]>) { let buffer = Buffer() - buffer.appendInt32(1932455680) + buffer.appendInt32(11435201) + serializeInt32(flags, buffer: buffer, boxed: false) peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} buffer.appendInt32(481674261) buffer.appendInt32(Int32(filters.count)) for item in filters { item.serialize(buffer, true) } - return (FunctionDescription(name: "messages.getSearchCounters", parameters: [("peer", String(describing: peer)), ("filters", String(describing: filters))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.messages.SearchCounter]? in + return (FunctionDescription(name: "messages.getSearchCounters", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("topMsgId", String(describing: topMsgId)), ("filters", String(describing: filters))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.messages.SearchCounter]? in let reader = BufferReader(buffer) var result: [Api.messages.SearchCounter]? if let _ = reader.readInt32() { @@ -5470,11 +5472,13 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func readReactions(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func readReactions(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(-2099097129) + buffer.appendInt32(1420459918) + serializeInt32(flags, buffer: buffer, boxed: false) peer.serialize(buffer, true) - return (FunctionDescription(name: "messages.readReactions", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "messages.readReactions", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("topMsgId", String(describing: topMsgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in let reader = BufferReader(buffer) var result: Api.messages.AffectedHistory? if let signature = reader.readInt32() { diff --git a/submodules/TelegramApi/Sources/Api5.swift b/submodules/TelegramApi/Sources/Api5.swift index f88470e925..b938e621d1 100644 --- a/submodules/TelegramApi/Sources/Api5.swift +++ b/submodules/TelegramApi/Sources/Api5.swift @@ -1094,13 +1094,13 @@ public extension Api { } public extension Api { enum ForumTopic: TypeConstructorDescription { - case forumTopic(flags: Int32, id: Int32, date: Int32, title: String, iconColor: Int32, iconEmojiId: Int64?, topMessage: Int32, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, unreadMentionsCount: Int32, unreadReactionsCount: Int32, fromId: Api.Peer) + case forumTopic(flags: Int32, id: Int32, date: Int32, title: String, iconColor: Int32, iconEmojiId: Int64?, topMessage: Int32, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, unreadMentionsCount: Int32, unreadReactionsCount: Int32, fromId: Api.Peer, notifySettings: Api.PeerNotifySettings) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .forumTopic(let flags, let id, let date, let title, let iconColor, let iconEmojiId, let topMessage, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let unreadMentionsCount, let unreadReactionsCount, let fromId): + case .forumTopic(let flags, let id, let date, let title, let iconColor, let iconEmojiId, let topMessage, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let unreadMentionsCount, let unreadReactionsCount, let fromId, let notifySettings): if boxed { - buffer.appendInt32(413771876) + buffer.appendInt32(1495324380) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(id, buffer: buffer, boxed: false) @@ -1115,14 +1115,15 @@ public extension Api { serializeInt32(unreadMentionsCount, buffer: buffer, boxed: false) serializeInt32(unreadReactionsCount, buffer: buffer, boxed: false) fromId.serialize(buffer, true) + notifySettings.serialize(buffer, true) break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .forumTopic(let flags, let id, let date, let title, let iconColor, let iconEmojiId, let topMessage, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let unreadMentionsCount, let unreadReactionsCount, let fromId): - return ("forumTopic", [("flags", String(describing: flags)), ("id", String(describing: id)), ("date", String(describing: date)), ("title", String(describing: title)), ("iconColor", String(describing: iconColor)), ("iconEmojiId", String(describing: iconEmojiId)), ("topMessage", String(describing: topMessage)), ("readInboxMaxId", String(describing: readInboxMaxId)), ("readOutboxMaxId", String(describing: readOutboxMaxId)), ("unreadCount", String(describing: unreadCount)), ("unreadMentionsCount", String(describing: unreadMentionsCount)), ("unreadReactionsCount", String(describing: unreadReactionsCount)), ("fromId", String(describing: fromId))]) + case .forumTopic(let flags, let id, let date, let title, let iconColor, let iconEmojiId, let topMessage, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let unreadMentionsCount, let unreadReactionsCount, let fromId, let notifySettings): + return ("forumTopic", [("flags", String(describing: flags)), ("id", String(describing: id)), ("date", String(describing: date)), ("title", String(describing: title)), ("iconColor", String(describing: iconColor)), ("iconEmojiId", String(describing: iconEmojiId)), ("topMessage", String(describing: topMessage)), ("readInboxMaxId", String(describing: readInboxMaxId)), ("readOutboxMaxId", String(describing: readOutboxMaxId)), ("unreadCount", String(describing: unreadCount)), ("unreadMentionsCount", String(describing: unreadMentionsCount)), ("unreadReactionsCount", String(describing: unreadReactionsCount)), ("fromId", String(describing: fromId)), ("notifySettings", String(describing: notifySettings))]) } } @@ -1155,6 +1156,10 @@ public extension Api { if let signature = reader.readInt32() { _13 = Api.parse(reader, signature: signature) as? Api.Peer } + var _14: Api.PeerNotifySettings? + if let signature = reader.readInt32() { + _14 = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings + } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -1168,8 +1173,9 @@ public extension Api { let _c11 = _11 != nil let _c12 = _12 != nil let _c13 = _13 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 { - return Api.ForumTopic.forumTopic(flags: _1!, id: _2!, date: _3!, title: _4!, iconColor: _5!, iconEmojiId: _6, topMessage: _7!, readInboxMaxId: _8!, readOutboxMaxId: _9!, unreadCount: _10!, unreadMentionsCount: _11!, unreadReactionsCount: _12!, fromId: _13!) + let _c14 = _14 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 { + return Api.ForumTopic.forumTopic(flags: _1!, id: _2!, date: _3!, title: _4!, iconColor: _5!, iconEmojiId: _6, topMessage: _7!, readInboxMaxId: _8!, readOutboxMaxId: _9!, unreadCount: _10!, unreadMentionsCount: _11!, unreadReactionsCount: _12!, fromId: _13!, notifySettings: _14!) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api8.swift b/submodules/TelegramApi/Sources/Api8.swift index 82091170e6..89c644924e 100644 --- a/submodules/TelegramApi/Sources/Api8.swift +++ b/submodules/TelegramApi/Sources/Api8.swift @@ -860,6 +860,7 @@ public extension Api { indirect enum InputNotifyPeer: TypeConstructorDescription { case inputNotifyBroadcasts case inputNotifyChats + case inputNotifyForumTopic(peer: Api.InputPeer, topMsgId: Int32) case inputNotifyPeer(peer: Api.InputPeer) case inputNotifyUsers @@ -876,6 +877,13 @@ public extension Api { buffer.appendInt32(1251338318) } + break + case .inputNotifyForumTopic(let peer, let topMsgId): + if boxed { + buffer.appendInt32(1548122514) + } + peer.serialize(buffer, true) + serializeInt32(topMsgId, buffer: buffer, boxed: false) break case .inputNotifyPeer(let peer): if boxed { @@ -898,6 +906,8 @@ public extension Api { return ("inputNotifyBroadcasts", []) case .inputNotifyChats: return ("inputNotifyChats", []) + case .inputNotifyForumTopic(let peer, let topMsgId): + return ("inputNotifyForumTopic", [("peer", String(describing: peer)), ("topMsgId", String(describing: topMsgId))]) case .inputNotifyPeer(let peer): return ("inputNotifyPeer", [("peer", String(describing: peer))]) case .inputNotifyUsers: @@ -911,6 +921,22 @@ public extension Api { public static func parse_inputNotifyChats(_ reader: BufferReader) -> InputNotifyPeer? { return Api.InputNotifyPeer.inputNotifyChats } + public static func parse_inputNotifyForumTopic(_ reader: BufferReader) -> InputNotifyPeer? { + var _1: Api.InputPeer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputPeer + } + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.InputNotifyPeer.inputNotifyForumTopic(peer: _1!, topMsgId: _2!) + } + else { + return nil + } + } public static func parse_inputNotifyPeer(_ reader: BufferReader) -> InputNotifyPeer? { var _1: Api.InputPeer? if let signature = reader.readInt32() { diff --git a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift index e7bbd4a10c..7ee706bb50 100644 --- a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift +++ b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift @@ -117,7 +117,7 @@ enum AccountStateMutationOperation { case UpdateAudioTranscription(messageId: MessageId, id: Int64, isPending: Bool, text: String) case UpdateConfig case UpdateExtendedMedia(MessageId, Api.MessageExtendedMedia) - case ResetForumTopic(topicId: MessageId, data: MessageHistoryThreadData, pts: Int32) + case ResetForumTopic(topicId: MessageId, data: StoreMessageHistoryThreadData, pts: Int32) } struct HoleFromPreviousState { diff --git a/submodules/TelegramCore/Sources/ForumChannels.swift b/submodules/TelegramCore/Sources/ForumChannels.swift index acdd639c4e..1296baa6b8 100644 --- a/submodules/TelegramCore/Sources/ForumChannels.swift +++ b/submodules/TelegramCore/Sources/ForumChannels.swift @@ -62,8 +62,14 @@ public struct MessageHistoryThreadData: Codable, Equatable { public var maxIncomingReadId: Int32 public var maxKnownMessageId: Int32 public var maxOutgoingReadId: Int32 - public var unreadMentionCount: Int32 - public var unreadReactionCount: Int32 + public var notificationSettings: TelegramPeerNotificationSettings +} + +struct StoreMessageHistoryThreadData { + var data: MessageHistoryThreadData + var topMessageId: Int32 + var unreadMentionCount: Int32 + var unreadReactionCount: Int32 } public enum CreateForumChannelTopicError { @@ -247,7 +253,7 @@ func _internal_loadMessageHistoryThreads(account: Account, peerId: PeerId) -> Si updatePeerPresences(transaction: transaction, accountPeerId: account.peerId, peerPresences: peerPresences) - let _ = transaction.addMessages(messages.compactMap { message -> StoreMessage? in + let _ = InternalAccountState.addMessages(transaction: transaction, messages: messages.compactMap { message -> StoreMessage? in return StoreMessage(apiMessage: message) }, location: .Random) @@ -261,7 +267,7 @@ func _internal_loadMessageHistoryThreads(account: Account, peerId: PeerId) -> Si for topic in topics { switch topic { - case let .forumTopic(_, id, date, title, iconColor, iconEmojiId, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, fromId): + case let .forumTopic(_, id, date, title, iconColor, iconEmojiId, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, fromId, notifySettings): let data = MessageHistoryThreadData( creationDate: date, author: fromId.peerId, @@ -274,13 +280,15 @@ func _internal_loadMessageHistoryThreads(account: Account, peerId: PeerId) -> Si maxIncomingReadId: readInboxMaxId, maxKnownMessageId: topMessage, maxOutgoingReadId: readOutboxMaxId, - unreadMentionCount: unreadMentionsCount, - unreadReactionCount: unreadReactionsCount + notificationSettings: TelegramPeerNotificationSettings(apiSettings: notifySettings) ) guard let info = CodableEntry(data) else { continue } transaction.setMessageHistoryThreadInfo(peerId: peerId, threadId: Int64(id), info: info) + + transaction.replaceMessageTagSummary(peerId: peerId, threadId: Int64(id), tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud, count: unreadMentionsCount, maxId: topMessage) + transaction.replaceMessageTagSummary(peerId: peerId, threadId: Int64(id), tagMask: .unseenReaction, namespace: Namespaces.Message.Cloud, count: unreadReactionsCount, maxId: topMessage) } } } @@ -316,25 +324,6 @@ public final class ForumChannelTopics { let _ = _internal_loadMessageHistoryThreads(account: self.account, peerId: peerId).start() - let viewKey: PostboxViewKey = .messageHistoryThreadIndex(id: self.peerId) - self.statePromise.set(self.account.postbox.combinedView(keys: [viewKey]) - |> map { views -> State in - guard let view = views.views[viewKey] as? MessageHistoryThreadIndexView else { - preconditionFailure() - } - return State(items: view.items.compactMap { item -> ForumChannelTopics.Item? in - guard let data = item.info.get(MessageHistoryThreadData.self) else { - return nil - } - return ForumChannelTopics.Item( - id: item.id, - info: data.info, - index: item.index, - topMessage: item.topMessage.flatMap(EngineMessage.init) - ) - }) - }) - self.updateDisposable.set(account.viewTracker.polledChannel(peerId: peerId).start()) } diff --git a/submodules/TelegramCore/Sources/Settings/PeerContactSettings.swift b/submodules/TelegramCore/Sources/Settings/PeerContactSettings.swift index b63d453e09..c421160341 100644 --- a/submodules/TelegramCore/Sources/Settings/PeerContactSettings.swift +++ b/submodules/TelegramCore/Sources/Settings/PeerContactSettings.swift @@ -65,5 +65,5 @@ public func unarchiveAutomaticallyArchivedPeer(account: Account, peerId: PeerId) } |> deliverOnMainQueue).start() - let _ = _internal_updatePeerMuteSetting(account: account, peerId: peerId, muteInterval: nil).start() + let _ = _internal_updatePeerMuteSetting(account: account, peerId: peerId, threadId: nil, muteInterval: nil).start() } diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index e82cd5369e..05a335705c 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -75,7 +75,7 @@ private func activeChannelsFromUpdateGroups(_ groups: [UpdateGroup]) -> Set= messageId.id { - transaction.addHole(peerId: messageId.peerId, namespace: messageId.namespace, space: .everywhere, range: messageId.id ... upperId) + transaction.addHole(peerId: messageId.peerId, threadId: nil, namespace: messageId.namespace, space: .everywhere, range: messageId.id ... upperId) - transaction.addHole(peerId: messageId.peerId, namespace: messageId.namespace, space: .tag(.pinned), range: 1 ... upperId) + transaction.addHole(peerId: messageId.peerId, threadId: nil, namespace: messageId.namespace, space: .tag(.pinned), range: 1 ... upperId) Logger.shared.log("State", "adding hole for peer \(messageId.peerId), \(messageId.id) ... \(upperId)") } else { @@ -4269,7 +4281,7 @@ func replayFinalState( let currentInclusion = transaction.getPeerChatListInclusion(peerId) if let groupId = currentInclusion.groupId, groupId == Namespaces.PeerGroup.archive { if let peer = transaction.getPeer(peerId) as? TelegramSecretChat { - let isRemovedFromTotalUnreadCount = resolvedIsRemovedFromTotalUnreadCount(globalSettings: transaction.getGlobalNotificationSettings(), peer: peer, peerSettings: transaction.getPeerNotificationSettings(peer.regularPeerId)) + let isRemovedFromTotalUnreadCount = resolvedIsRemovedFromTotalUnreadCount(globalSettings: transaction.getGlobalNotificationSettings(), peer: peer, peerSettings: transaction.getPeerNotificationSettings(id: peer.regularPeerId)) if !isRemovedFromTotalUnreadCount { transaction.updatePeerChatListInclusion(peerId, inclusion: currentInclusion.withGroupId(groupId: .root)) diff --git a/submodules/TelegramCore/Sources/State/AccountStateManager.swift b/submodules/TelegramCore/Sources/State/AccountStateManager.swift index 8800a0119d..957e2ab1cb 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManager.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManager.swift @@ -1358,7 +1358,7 @@ public func messagesForNotification(transaction: Transaction, id: MessageId, alw notificationPeerId = author.id } - if let notificationSettings = transaction.getPeerNotificationSettings(notificationPeerId) as? TelegramPeerNotificationSettings { + if let notificationSettings = transaction.getPeerNotificationSettings(id: notificationPeerId) as? TelegramPeerNotificationSettings { var defaultSound: PeerMessageSound = defaultCloudPeerNotificationSound var defaultNotify: Bool = true if let globalNotificationSettings = transaction.getPreferencesEntry(key: PreferencesKeys.globalNotifications)?.get(GlobalNotificationSettings.self) { diff --git a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift index 07676c9cbd..faa73cc986 100644 --- a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift +++ b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift @@ -181,7 +181,7 @@ private func fetchPoll(account: Account, messageId: MessageId) -> Signal [AdditionalMessageHistoryViewData] { var result = additionalData switch chatLocation { - case let .peer(peerId): + case let .peer(peerId, _): if peerId.namespace == Namespaces.Peer.CloudChannel { if result.firstIndex(where: { if case .peerChatState = $0 { return true } else { return false } }) == nil { result.append(.peerChatState(peerId)) @@ -1220,13 +1220,13 @@ public final class AccountViewTracker { } } - public func updateMarkAllMentionsSeen(peerId: PeerId) { + public func updateMarkAllMentionsSeen(peerId: PeerId, threadId: Int64?) { self.queue.async { guard let account = self.account else { return } let _ = (account.postbox.transaction { transaction -> Set in - let ids = Set(transaction.getMessageIndicesWithTag(peerId: peerId, namespace: Namespaces.Message.Cloud, tag: .unseenPersonalMessage).map({ $0.id })) + let ids = Set(transaction.getMessageIndicesWithTag(peerId: peerId, threadId: threadId, namespace: Namespaces.Message.Cloud, tag: .unseenPersonalMessage).map({ $0.id })) for id in ids { transaction.updateMessage(id, update: { currentMessage in @@ -1244,13 +1244,13 @@ public final class AccountViewTracker { }) } - if let summary = transaction.getMessageTagSummary(peerId: peerId, tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud), summary.count > 0 { + if let summary = transaction.getMessageTagSummary(peerId: peerId, threadId: threadId, tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud), summary.count > 0 { var maxId: Int32 = summary.range.maxId if let index = transaction.getTopPeerMessageIndex(peerId: peerId, namespace: Namespaces.Message.Cloud) { maxId = index.id.id } - transaction.replaceMessageTagSummary(peerId: peerId, tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud, count: 0, maxId: maxId) + transaction.replaceMessageTagSummary(peerId: peerId, threadId: threadId, tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud, count: 0, maxId: maxId) addSynchronizeMarkAllUnseenPersonalMessagesOperation(transaction: transaction, peerId: peerId, maxId: summary.range.maxId) } @@ -1303,13 +1303,13 @@ public final class AccountViewTracker { } } - public func updateMarkAllReactionsSeen(peerId: PeerId) { + public func updateMarkAllReactionsSeen(peerId: PeerId, threadId: Int64?) { self.queue.async { guard let account = self.account else { return } let _ = (account.postbox.transaction { transaction -> Set in - let ids = Set(transaction.getMessageIndicesWithTag(peerId: peerId, namespace: Namespaces.Message.Cloud, tag: .unseenReaction).map({ $0.id })) + let ids = Set(transaction.getMessageIndicesWithTag(peerId: peerId, threadId: threadId, namespace: Namespaces.Message.Cloud, tag: .unseenReaction).map({ $0.id })) for id in ids { transaction.updateMessage(id, update: { currentMessage in @@ -1327,13 +1327,13 @@ public final class AccountViewTracker { }) } - if let summary = transaction.getMessageTagSummary(peerId: peerId, tagMask: .unseenReaction, namespace: Namespaces.Message.Cloud) { + if let summary = transaction.getMessageTagSummary(peerId: peerId, threadId: threadId, tagMask: .unseenReaction, namespace: Namespaces.Message.Cloud) { var maxId: Int32 = summary.range.maxId if let index = transaction.getTopPeerMessageIndex(peerId: peerId, namespace: Namespaces.Message.Cloud) { maxId = index.id.id } - transaction.replaceMessageTagSummary(peerId: peerId, tagMask: .unseenReaction, namespace: Namespaces.Message.Cloud, count: 0, maxId: maxId) + transaction.replaceMessageTagSummary(peerId: peerId, threadId: threadId, tagMask: .unseenReaction, namespace: Namespaces.Message.Cloud, count: 0, maxId: maxId) addSynchronizeMarkAllUnseenReactionsOperation(transaction: transaction, peerId: peerId, maxId: summary.range.maxId) } @@ -1513,6 +1513,35 @@ public final class AccountViewTracker { } func wrappedMessageHistorySignal(chatLocation: ChatLocationInput, signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError>, addHoleIfNeeded: Bool) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { + var signal = signal + if let postbox = self.account?.postbox, let peerId = chatLocation.peerId, let threadId = chatLocation.threadId { + let viewKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: peerId, threadId: threadId) + let fixedReadStates = Atomic(value: nil) + signal = combineLatest(signal, postbox.combinedView(keys: [viewKey])) + |> map { view, additionalViews -> (MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?) in + var view = view + if let threadInfo = additionalViews.views[viewKey] as? MessageHistoryThreadInfoView, let data = threadInfo.info?.get(MessageHistoryThreadData.self) { + let readState = CombinedPeerReadState(states: [(Namespaces.Message.Cloud, .idBased(maxIncomingReadId: data.maxIncomingReadId, maxOutgoingReadId: data.maxOutgoingReadId, maxKnownId: data.maxKnownMessageId, count: data.incomingUnreadCount, markedUnread: false))]) + + let fixed = fixedReadStates.modify { current in + if let current = current { + return current + } else { + return view.0.fixedReadStates ?? .peer([peerId: readState]) + } + } + + view.0 = MessageHistoryView( + base: view.0, + fixed: fixed, + transient: .peer([peerId: readState]) + ) + } + + return view + } + } + let history = withState(signal, { [weak self] () -> Int32 in if let strongSelf = self { return OSAtomicIncrement32(&strongSelf.nextViewId) @@ -1526,7 +1555,7 @@ public final class AccountViewTracker { strongSelf.updatePendingWebpages(viewId: viewId, messageIds: messageIds, localWebpages: localWebpages) let (pollMessageIds, pollMessageDict) = pollMessages(entries: next.0.entries) strongSelf.updatePolls(viewId: viewId, messageIds: pollMessageIds, messages: pollMessageDict) - if case let .peer(peerId) = chatLocation, peerId.namespace == Namespaces.Peer.CloudChannel { + if case let .peer(peerId, _) = chatLocation, peerId.namespace == Namespaces.Peer.CloudChannel { strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: next.0) } else if case let .thread(peerId, _, _) = chatLocation, peerId.namespace == Namespaces.Peer.CloudChannel { strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: next.0, location: chatLocation) @@ -1539,7 +1568,7 @@ public final class AccountViewTracker { strongSelf.updatePendingWebpages(viewId: viewId, messageIds: [], localWebpages: [:]) strongSelf.updatePolls(viewId: viewId, messageIds: [], messages: [:]) switch chatLocation { - case let .peer(peerId): + case let .peer(peerId, _): if peerId.namespace == Namespaces.Peer.CloudChannel { strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: nil) } @@ -1556,7 +1585,7 @@ public final class AccountViewTracker { let peerId: PeerId? switch chatLocation { - case let .peer(peerIdValue): + case let .peer(peerIdValue, _): peerId = peerIdValue case let .thread(peerIdValue, _, _): peerId = peerIdValue @@ -1583,7 +1612,7 @@ public final class AccountViewTracker { let isAutomaticallyTracked = self.account!.postbox.transaction { transaction -> Bool in if transaction.getPeerChatListIndex(peerId) == nil { if addHole { - transaction.addHole(peerId: peerId, namespace: Namespaces.Message.Cloud, space: .everywhere, range: 1 ... (Int32.max - 1)) + transaction.addHole(peerId: peerId, threadId: nil, namespace: Namespaces.Message.Cloud, space: .everywhere, range: 1 ... (Int32.max - 1)) } return false } else { @@ -1670,7 +1699,40 @@ public final class AccountViewTracker { public func aroundMessageOfInterestHistoryViewForLocation(_ chatLocation: ChatLocationInput, ignoreMessagesInTimestampRange: ClosedRange? = nil, count: Int, tagMask: MessageTags? = nil, appendMessagesFromTheSameGroup: Bool = false, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> { if let account = self.account { - let signal = account.postbox.aroundMessageOfInterestHistoryViewForChatLocation(chatLocation, ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, count: count, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, namespaces: .not(Namespaces.Message.allScheduled), orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData)) + let signal: Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> + if let peerId = chatLocation.peerId, let threadId = chatLocation.threadId, tagMask == nil { + return account.postbox.transaction { transaction -> MessageHistoryThreadData? in + return transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.get(MessageHistoryThreadData.self) + } + |> mapToSignal { threadInfo -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> in + if let threadInfo = threadInfo { + let anchor: HistoryViewInputAnchor + if threadInfo.incomingUnreadCount > 0 && tagMask == nil { + let customUnreadMessageId = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: threadInfo.maxIncomingReadId) + anchor = .message(customUnreadMessageId) + } else { + anchor = .upperBound + } + + return account.postbox.aroundMessageHistoryViewForLocation( + chatLocation, + anchor: anchor, + ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, + count: count, + fixedCombinedReadStates: nil, + topTaggedMessageIdNamespaces: [], + tagMask: tagMask, + appendMessagesFromTheSameGroup: false, + namespaces: .not(Namespaces.Message.allScheduled), + orderStatistics: orderStatistics + ) + } + + return account.postbox.aroundMessageOfInterestHistoryViewForChatLocation(chatLocation, ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, count: count, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, namespaces: .not(Namespaces.Message.allScheduled), orderStatistics: orderStatistics, customUnreadMessageId: nil, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData)) + } + } else { + signal = account.postbox.aroundMessageOfInterestHistoryViewForChatLocation(chatLocation, ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, count: count, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, namespaces: .not(Namespaces.Message.allScheduled), orderStatistics: orderStatistics, customUnreadMessageId: nil, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData)) + } return wrappedMessageHistorySignal(chatLocation: chatLocation, signal: signal, addHoleIfNeeded: true) } else { return .never() @@ -1961,13 +2023,13 @@ public final class AccountViewTracker { } } - public func unseenPersonalMessagesAndReactionCount(peerId: PeerId) -> Signal<(mentionCount: Int32, reactionCount: Int32), NoError> { + public func unseenPersonalMessagesAndReactionCount(peerId: PeerId, threadId: Int64?) -> Signal<(mentionCount: Int32, reactionCount: Int32), NoError> { if let account = self.account { 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 summaryMentionsKey: PostboxViewKey = .historyTagSummaryView(tag: .unseenPersonalMessage, peerId: peerId, threadId: threadId, 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) + let summaryReactionsKey: PostboxViewKey = .historyTagSummaryView(tag: .unseenReaction, peerId: peerId, threadId: threadId, namespace: Namespaces.Message.Cloud) return account.postbox.combinedView(keys: [pendingMentionsKey, summaryMentionsKey, pendingReactionsKey, summaryReactionsKey]) |> map { views -> (mentionCount: Int32, reactionCount: Int32) in diff --git a/submodules/TelegramCore/Sources/State/ChatHistoryPreloadManager.swift b/submodules/TelegramCore/Sources/State/ChatHistoryPreloadManager.swift index f60ae21e00..8e91b7686f 100644 --- a/submodules/TelegramCore/Sources/State/ChatHistoryPreloadManager.swift +++ b/submodules/TelegramCore/Sources/State/ChatHistoryPreloadManager.swift @@ -5,12 +5,14 @@ import SwiftSignalKit public struct HistoryPreloadIndex: Hashable, Comparable, CustomStringConvertible { public let index: ChatListIndex? + public let threadId: Int64? public let hasUnread: Bool public let isMuted: Bool public let isPriority: Bool - public init(index: ChatListIndex?, hasUnread: Bool, isMuted: Bool, isPriority: Bool) { + public init(index: ChatListIndex?, threadId: Int64?, hasUnread: Bool, isMuted: Bool, isPriority: Bool) { self.index = index + self.threadId = threadId self.hasUnread = hasUnread self.isMuted = isMuted self.isPriority = isPriority @@ -38,6 +40,15 @@ public struct HistoryPreloadIndex: Hashable, Comparable, CustomStringConvertible return false } } + + if lhs.index == rhs.index { + if let lhsThreadId = lhs.threadId, let rhsThreadId = rhs.threadId { + if lhsThreadId != rhsThreadId { + return lhsThreadId < rhsThreadId + } + } + } + if let lhsIndex = lhs.index, let rhsIndex = rhs.index { return lhsIndex > rhsIndex } else if lhs.index != nil { @@ -117,6 +128,7 @@ private final class HistoryPreloadEntry: Comparable { private final class HistoryPreloadViewContext { var index: ChatListIndex? + var threadId: Int64? var hasUnread: Bool? var isMuted: Bool? var isPriority: Bool @@ -125,7 +137,7 @@ private final class HistoryPreloadViewContext { var media: [HolesViewMedia] = [] var preloadIndex: HistoryPreloadIndex { - return HistoryPreloadIndex(index: self.index, hasUnread: self.hasUnread ?? false, isMuted: self.isMuted ?? true, isPriority: self.isPriority) + return HistoryPreloadIndex(index: self.index, threadId: self.threadId, hasUnread: self.hasUnread ?? false, isMuted: self.isMuted ?? true, isPriority: self.isPriority) } var currentHole: HistoryPreloadHole? { @@ -136,8 +148,9 @@ private final class HistoryPreloadViewContext { } } - init(index: ChatListIndex?, hasUnread: Bool?, isMuted: Bool?, isPriority: Bool) { + init(index: ChatListIndex?, threadId: Int64?, hasUnread: Bool?, isMuted: Bool?, isPriority: Bool) { self.index = index + self.threadId = threadId self.hasUnread = hasUnread self.isMuted = isMuted self.isPriority = isPriority @@ -149,7 +162,7 @@ private final class HistoryPreloadViewContext { } private enum ChatHistoryPreloadEntity: Hashable { - case peer(PeerId) + case peer(peerId: PeerId, threadId: Int64?) } private struct ChatHistoryPreloadIndex { @@ -236,11 +249,13 @@ private final class AdditionalPreloadPeerIdsContext { public struct ChatHistoryPreloadItem : Equatable { public let index: ChatListIndex + public let threadId: Int64? public let isMuted: Bool public let hasUnread: Bool - public init(index: ChatListIndex, isMuted: Bool, hasUnread: Bool) { + public init(index: ChatListIndex, threadId: Int64?, isMuted: Bool, hasUnread: Bool) { self.index = index + self.threadId = threadId self.isMuted = isMuted self.hasUnread = hasUnread } @@ -365,7 +380,7 @@ final class ChatHistoryPreloadManager { var indices: [(ChatHistoryPreloadIndex, Bool, Bool)] = [] for item in loadItems { - indices.append((ChatHistoryPreloadIndex(index: item.index, entity: .peer(item.index.messageIndex.id.peerId)), item.hasUnread, item.isMuted)) + indices.append((ChatHistoryPreloadIndex(index: item.index, entity: .peer(peerId: item.index.messageIndex.id.peerId, threadId: item.threadId)), item.hasUnread, item.isMuted)) } strongSelf.update(indices: indices, additionalPeerIds: additionalPeerIds) @@ -376,7 +391,7 @@ final class ChatHistoryPreloadManager { self.queue.async { var validEntityIds = Set(indices.map { $0.0.entity }) for peerId in additionalPeerIds { - validEntityIds.insert(.peer(peerId)) + validEntityIds.insert(.peer(peerId: peerId, threadId: nil)) } var removedEntityIds: [ChatHistoryPreloadEntity] = [] @@ -400,7 +415,7 @@ final class ChatHistoryPreloadManager { } for peerId in additionalPeerIds { if !existingPeerIds.contains(peerId) { - combinedIndices.append((ChatHistoryPreloadIndex(index: ChatListIndex.absoluteLowerBound, entity: .peer(peerId)), false, true, true)) + combinedIndices.append((ChatHistoryPreloadIndex(index: ChatListIndex.absoluteLowerBound, entity: .peer(peerId: peerId, threadId: nil)), false, true, true)) } } @@ -418,12 +433,17 @@ final class ChatHistoryPreloadManager { } } } else { - let view = HistoryPreloadViewContext(index: index.index, hasUnread: hasUnread, isMuted: isMuted, isPriority: isPriority) + var threadId: Int64? + switch index.entity { + case let .peer(_, threadIdValue): + threadId = threadIdValue + } + let view = HistoryPreloadViewContext(index: index.index, threadId: threadId, hasUnread: hasUnread, isMuted: isMuted, isPriority: isPriority) self.views[index.entity] = view let key: PostboxViewKey switch index.entity { - case let .peer(peerId): - key = .messageOfInterestHole(location: .peer(peerId), namespace: Namespaces.Message.Cloud, count: 70) + case let .peer(peerId, threadId): + key = .messageOfInterestHole(location: .peer(peerId: peerId, threadId: threadId), namespace: Namespaces.Message.Cloud, count: 70) } view.disposable.set((self.postbox.combinedView(keys: [key]) |> deliverOn(self.queue)).start(next: { [weak self] next in @@ -453,8 +473,8 @@ final class ChatHistoryPreloadManager { let holeIsUpdated = previousHole != updatedHole switch index.entity { - case let .peer(peerId): - Logger.shared.log("HistoryPreload", "view \(peerId) hole \(String(describing: updatedHole)) isUpdated: \(holeIsUpdated)") + case let .peer(peerId, threadId): + Logger.shared.log("HistoryPreload", "view \(peerId) (threadId: \(String(describing: threadId)) hole \(String(describing: updatedHole)) isUpdated: \(holeIsUpdated)") } if previousHole != updatedHole { diff --git a/submodules/TelegramCore/Sources/State/FetchChatList.swift b/submodules/TelegramCore/Sources/State/FetchChatList.swift index 767a23cbbd..2784a77955 100644 --- a/submodules/TelegramCore/Sources/State/FetchChatList.swift +++ b/submodules/TelegramCore/Sources/State/FetchChatList.swift @@ -203,7 +203,7 @@ struct FetchedChatList { var pinnedItemIds: [PeerId]? var folderSummaries: [PeerGroupId: PeerGroupUnreadCountersSummary] var peerGroupIds: [PeerId: PeerGroupId] - var threadInfos: [MessageId: MessageHistoryThreadData] + var threadInfos: [MessageId: StoreMessageHistoryThreadData] } func fetchChatList(postbox: Postbox, network: Network, location: FetchChatListLocation, upperBound: MessageIndex, hash: Int64, limit: Int32) -> Signal { diff --git a/submodules/TelegramCore/Sources/State/ForumChannelState.swift b/submodules/TelegramCore/Sources/State/ForumChannelState.swift index 9a30829567..4babe2368e 100644 --- a/submodules/TelegramCore/Sources/State/ForumChannelState.swift +++ b/submodules/TelegramCore/Sources/State/ForumChannelState.swift @@ -1,3 +1,16 @@ import Foundation +import Postbox - +enum InternalAccountState { + static func addMessages(transaction: Transaction, messages: [StoreMessage], location: AddMessagesLocation) -> [Int64 : MessageId] { + return transaction.addMessages(messages, location: location) + } + + static func deleteMessages(transaction: Transaction, ids: [MessageId], forEachMedia: ((Media) -> Void)?) { + transaction.deleteMessages(ids, forEachMedia: forEachMedia) + } + + static func invalidateChannelState(peerId: PeerId) { + + } +} diff --git a/submodules/TelegramCore/Sources/State/HistoryViewStateValidation.swift b/submodules/TelegramCore/Sources/State/HistoryViewStateValidation.swift index 69c9dfffa5..57ff437018 100644 --- a/submodules/TelegramCore/Sources/State/HistoryViewStateValidation.swift +++ b/submodules/TelegramCore/Sources/State/HistoryViewStateValidation.swift @@ -156,7 +156,7 @@ final class HistoryViewStateValidationContexts { } } - if let location = location, case let .thread(peerId, threadId, _) = location { + if let location = location, let peerId = location.peerId, let threadId = location.threadId { var rangesToInvalidate: [[MessageId]] = [] let addToRange: (MessageId, inout [[MessageId]]) -> Void = { id, ranges in if ranges.isEmpty { @@ -219,7 +219,7 @@ final class HistoryViewStateValidationContexts { context.batchReferences[messageId] = batch } - disposable.set((validateReplyThreadMessagesBatch(postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, peerId: peerId, threadMessageId: makeThreadIdMessageId(peerId: peerId, threadId: threadId).id, messageIds: messages) + disposable.set((validateReplyThreadMessagesBatch(postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, peerId: peerId, threadMessageId: makeThreadIdMessageId(peerId: peerId, threadId: threadId).id, tag: view.tagMask, messageIds: messages) |> deliverOn(self.queue)).start(completed: { [weak self, weak batch] in if let strongSelf = self, let context = strongSelf.contexts[id], let batch = batch { var completedMessageIds: [MessageId] = [] @@ -355,7 +355,7 @@ final class HistoryViewStateValidationContexts { } } else if view.namespaces.contains(Namespaces.Message.ScheduledCloud) { if let _ = self.contexts[id] { - } else if let location = location, case let .peer(peerId) = location { + } else if let location = location, case let .peer(peerId, _) = location { let timestamp = self.network.context.globalTime() if let previousTimestamp = self.previousPeerValidationTimestamps[peerId], timestamp < previousTimestamp + 60 { } else { @@ -543,7 +543,7 @@ private func validateChannelMessagesBatch(postbox: Postbox, network: Network, ac } |> switchToLatest } -private func validateReplyThreadMessagesBatch(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, threadMessageId: Int32, messageIds: [MessageId]) -> Signal { +private func validateReplyThreadMessagesBatch(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, threadMessageId: Int32, tag: MessageTags?, messageIds: [MessageId]) -> Signal { return postbox.transaction { transaction -> Signal in var previousMessages: [Message] = [] var previous: [MessageId: Message] = [:] @@ -556,10 +556,22 @@ private func validateReplyThreadMessagesBatch(postbox: Postbox, network: Network var signal: Signal let hash = hashForMessages(previousMessages, withChannelIds: false) - Logger.shared.log("HistoryValidation", "validate reply thread batch for \(peerId): \(previousMessages.map({ $0.id }))") + Logger.shared.log("HistoryValidation", "validate reply thread batch (tag: \(String(describing: tag?.rawValue)) for \(peerId): \(previousMessages.map({ $0.id }))") if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) { let requestSignal: Signal - requestSignal = network.request(Api.functions.messages.getReplies(peer: inputPeer, msgId: threadMessageId, offsetId: messageIds[messageIds.count - 1].id, offsetDate: 0, addOffset: -1, limit: Int32(messageIds.count), maxId: messageIds[messageIds.count - 1].id + 1, minId: messageIds[0].id - 1, hash: hash)) + + if let tag = tag { + if let filter = messageFilterForTagMask(tag) { + var flags: Int32 = 0 + flags |= (1 << 1) + + requestSignal = network.request(Api.functions.messages.search(flags: flags, peer: inputPeer, q: "", fromId: nil, topMsgId: threadMessageId, filter: filter, minDate: 0, maxDate: 0, offsetId: messageIds[messageIds.count - 1].id + 1, addOffset: 0, limit: Int32(messageIds.count), maxId: messageIds[messageIds.count - 1].id + 1, minId: messageIds[0].id - 1, hash: hash)) + } else { + return .complete() + } + } else { + requestSignal = network.request(Api.functions.messages.getReplies(peer: inputPeer, msgId: threadMessageId, offsetId: messageIds[messageIds.count - 1].id, offsetDate: 0, addOffset: -1, limit: Int32(messageIds.count), maxId: messageIds[messageIds.count - 1].id + 1, minId: messageIds[0].id - 1, hash: hash)) + } signal = requestSignal |> map { result -> ValidatedMessages in diff --git a/submodules/TelegramCore/Sources/State/Holes.swift b/submodules/TelegramCore/Sources/State/Holes.swift index 8e53cd75fc..39c5f1229d 100644 --- a/submodules/TelegramCore/Sources/State/Holes.swift +++ b/submodules/TelegramCore/Sources/State/Holes.swift @@ -406,7 +406,14 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH minMaxRange = 1 ... Int32.max - 1 } - request = source.request(Api.functions.messages.getUnreadMentions(flags: 0, peer: inputPeer, topMsgId: nil, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId)) + var flags: Int32 = 0 + var topMsgId: Int32? + if let threadId = peerInput.requestThreadId { + flags |= (1 << 1) + topMsgId = threadId.id + } + + request = source.request(Api.functions.messages.getUnreadMentions(flags: flags, peer: inputPeer, topMsgId: topMsgId, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId)) } else if tag == .unseenReaction { let offsetId: Int32 let addOffset: Int32 @@ -454,7 +461,14 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH minMaxRange = 1 ... Int32.max - 1 } - request = source.request(Api.functions.messages.getUnreadReactions(flags: 0, peer: inputPeer, topMsgId: nil, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId)) + var flags: Int32 = 0 + var topMsgId: Int32? + if let threadId = peerInput.requestThreadId { + flags |= (1 << 0) + topMsgId = threadId.id + } + + request = source.request(Api.functions.messages.getUnreadReactions(flags: flags, peer: inputPeer, topMsgId: topMsgId, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId)) } else if tag == .liveLocation { let selectedLimit = count @@ -511,7 +525,14 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH minMaxRange = 1 ... (Int32.max - 1) } - request = source.request(Api.functions.messages.search(flags: 0, peer: inputPeer, q: "", fromId: nil, topMsgId: nil, filter: filter, minDate: 0, maxDate: 0, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId, hash: 0)) + var flags: Int32 = 0 + var topMsgId: Int32? + if let threadId = peerInput.requestThreadId { + flags |= (1 << 1) + topMsgId = threadId.id + } + + request = source.request(Api.functions.messages.search(flags: flags, peer: inputPeer, q: "", fromId: nil, topMsgId: topMsgId, filter: filter, minDate: 0, maxDate: 0, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId, hash: 0)) } else { assertionFailure() minMaxRange = 1 ... 1 @@ -669,13 +690,7 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH switch peerInput { case let .direct(peerId, threadId): - if let threadId = threadId { - for range in strictFilledIndices.rangeView { - transaction.removeThreadIndexHole(peerId: peerId, threadId: threadId, namespace: namespace, space: space, range: Int32(range.lowerBound) ... Int32(range.upperBound)) - } - } else { - transaction.removeHole(peerId: peerId, namespace: namespace, space: space, range: filledRange) - } + transaction.removeHole(peerId: peerId, threadId: threadId, namespace: namespace, space: space, range: filledRange) case .threadFromChannel: break } @@ -736,9 +751,11 @@ func fetchChatListHole(postbox: Postbox, network: Network, accountPeerId: PeerId }) for (threadMessageId, data) in fetchedChats.threadInfos { - if let entry = CodableEntry(data) { + if let entry = CodableEntry(data.data) { transaction.setMessageHistoryThreadInfo(peerId: threadMessageId.peerId, threadId: Int64(threadMessageId.id), info: entry) } + transaction.replaceMessageTagSummary(peerId: threadMessageId.peerId, threadId: Int64(threadMessageId.id), tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud, count: data.unreadMentionCount, maxId: data.topMessageId) + transaction.replaceMessageTagSummary(peerId: threadMessageId.peerId, threadId: Int64(threadMessageId.id), tagMask: .unseenReaction, namespace: Namespaces.Message.Cloud, count: data.unreadReactionCount, maxId: data.topMessageId) } updatePeerPresences(transaction: transaction, accountPeerId: accountPeerId, peerPresences: fetchedChats.peerPresences) @@ -778,10 +795,10 @@ 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) + transaction.replaceMessageTagSummary(peerId: peerId, threadId: nil, 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) + transaction.replaceMessageTagSummary(peerId: peerId, threadId: nil, tagMask: .unseenReaction, namespace: Namespaces.Message.Cloud, count: summary.count, maxId: summary.range.maxId) } for (groupId, summary) in fetchedChats.folderSummaries { diff --git a/submodules/TelegramCore/Sources/State/ManagedConsumePersonalMessagesActions.swift b/submodules/TelegramCore/Sources/State/ManagedConsumePersonalMessagesActions.swift index b1d15e0d23..1d466d32f2 100644 --- a/submodules/TelegramCore/Sources/State/ManagedConsumePersonalMessagesActions.swift +++ b/submodules/TelegramCore/Sources/State/ManagedConsumePersonalMessagesActions.swift @@ -377,7 +377,7 @@ private func synchronizeUnseenPersonalMentionsTag(postbox: Postbox, network: Net } return postbox.transaction { transaction -> Void in - transaction.replaceMessageTagSummary(peerId: entry.key.peerId, tagMask: entry.key.tagMask, namespace: entry.key.namespace, count: apiUnreadMentionsCount, maxId: apiTopMessage) + transaction.replaceMessageTagSummary(peerId: entry.key.peerId, threadId: nil, tagMask: entry.key.tagMask, namespace: entry.key.namespace, count: apiUnreadMentionsCount, maxId: apiTopMessage) } } else { return .complete() @@ -419,7 +419,7 @@ private func synchronizeUnseenReactionsTag(postbox: Postbox, network: Network, e } return postbox.transaction { transaction -> Void in - transaction.replaceMessageTagSummary(peerId: entry.key.peerId, tagMask: entry.key.tagMask, namespace: entry.key.namespace, count: apiUnreadReactionsCount, maxId: apiTopMessage) + transaction.replaceMessageTagSummary(peerId: entry.key.peerId, threadId: nil, tagMask: entry.key.tagMask, namespace: entry.key.namespace, count: apiUnreadReactionsCount, maxId: apiTopMessage) } } else { return .complete() diff --git a/submodules/TelegramCore/Sources/State/ManagedMessageHistoryHoles.swift b/submodules/TelegramCore/Sources/State/ManagedMessageHistoryHoles.swift index e448f7c71f..893190a18f 100644 --- a/submodules/TelegramCore/Sources/State/ManagedMessageHistoryHoles.swift +++ b/submodules/TelegramCore/Sources/State/ManagedMessageHistoryHoles.swift @@ -24,13 +24,11 @@ private final class ManagedMessageHistoryHolesState { for entry in entries { switch entry.hole { - case let .peer(hole): - if hole.threadId == nil { - if self.holeDisposables[entry] == nil { - let disposable = MetaDisposable() - self.holeDisposables[entry] = disposable - added[entry] = disposable - } + case .peer: + if self.holeDisposables[entry] == nil { + let disposable = MetaDisposable() + self.holeDisposables[entry] = disposable + added[entry] = disposable } } } @@ -55,7 +53,7 @@ func managedMessageHistoryHoles(accountPeerId: PeerId, network: Network, postbox for (entry, disposable) in added { switch entry.hole { case let .peer(hole): - disposable.set(fetchMessageHistoryHole(accountPeerId: accountPeerId, source: .network(network), postbox: postbox, peerInput: .direct(peerId: hole.peerId, threadId: nil), namespace: hole.namespace, direction: entry.direction, space: entry.space, count: entry.count).start()) + disposable.set(fetchMessageHistoryHole(accountPeerId: accountPeerId, source: .network(network), postbox: postbox, peerInput: .direct(peerId: hole.peerId, threadId: hole.threadId), namespace: hole.namespace, direction: entry.direction, space: entry.space, count: entry.count).start()) } } }) diff --git a/submodules/TelegramCore/Sources/State/ManagedPendingPeerNotificationSettings.swift b/submodules/TelegramCore/Sources/State/ManagedPendingPeerNotificationSettings.swift index 5df8e81646..2ada61fd82 100644 --- a/submodules/TelegramCore/Sources/State/ManagedPendingPeerNotificationSettings.swift +++ b/submodules/TelegramCore/Sources/State/ManagedPendingPeerNotificationSettings.swift @@ -72,7 +72,7 @@ func managedPendingPeerNotificationSettings(postbox: Postbox, network: Network) } for (peerId, settings, disposable) in beginOperations { - let signal = pushPeerNotificationSettings(postbox: postbox, network: network, peerId: peerId, settings: settings) + let signal = pushPeerNotificationSettings(postbox: postbox, network: network, peerId: peerId, threadId: nil, settings: settings) disposable.set(signal.start()) } }) @@ -89,62 +89,109 @@ func managedPendingPeerNotificationSettings(postbox: Postbox, network: Network) } } -private func pushPeerNotificationSettings(postbox: Postbox, network: Network, peerId: PeerId, settings: PeerNotificationSettings) -> Signal { +func pushPeerNotificationSettings(postbox: Postbox, network: Network, peerId: PeerId, threadId: Int64?, settings: PeerNotificationSettings) -> Signal { return postbox.transaction { transaction -> Signal in - if let peer = transaction.getPeer(peerId) { + if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) { var notificationPeerId = peerId if let associatedPeerId = peer.associatedPeerId { notificationPeerId = associatedPeerId } - if let notificationPeer = transaction.getPeer(notificationPeerId), let inputPeer = apiInputPeer(notificationPeer), let settings = settings as? TelegramPeerNotificationSettings { - let showPreviews: Api.Bool? - switch settings.displayPreviews { + if let threadId = threadId { + if let data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.get(MessageHistoryThreadData.self) { + let settings = data.notificationSettings + + let showPreviews: Api.Bool? + switch settings.displayPreviews { case .default: showPreviews = nil case .show: showPreviews = .boolTrue case .hide: showPreviews = .boolFalse - } - let muteUntil: Int32? - switch settings.muteState { + } + let muteUntil: Int32? + switch settings.muteState { case let .muted(until): muteUntil = until case .unmuted: muteUntil = 0 case .default: muteUntil = nil - } - let sound: Api.NotificationSound? = settings.messageSound.apiSound - var flags: Int32 = 0 - if showPreviews != nil { - flags |= (1 << 0) - } - if muteUntil != nil { - flags |= (1 << 2) - } - if sound != nil { - flags |= (1 << 3) - } - let inputSettings = Api.InputPeerNotifySettings.inputPeerNotifySettings(flags: flags, showPreviews: showPreviews, silent: nil, muteUntil: muteUntil, sound: sound) - return network.request(Api.functions.account.updateNotifySettings(peer: .inputNotifyPeer(peer: inputPeer), settings: inputSettings)) - |> `catch` { _ -> Signal in - return .single(.boolFalse) - } - |> mapToSignal { result -> Signal in - return postbox.transaction { transaction -> Void in - transaction.updateCurrentPeerNotificationSettings([notificationPeerId: settings]) - if let pending = transaction.getPendingPeerNotificationSettings(peerId), pending.isEqual(to: settings) { - transaction.updatePendingPeerNotificationSettings(peerId: peerId, settings: nil) + } + let sound: Api.NotificationSound? = settings.messageSound.apiSound + var flags: Int32 = 0 + if showPreviews != nil { + flags |= (1 << 0) + } + if muteUntil != nil { + flags |= (1 << 2) + } + if sound != nil { + flags |= (1 << 3) + } + let inputSettings = Api.InputPeerNotifySettings.inputPeerNotifySettings(flags: flags, showPreviews: showPreviews, silent: nil, muteUntil: muteUntil, sound: sound) + return network.request(Api.functions.account.updateNotifySettings(peer: .inputNotifyForumTopic(peer: inputPeer, topMsgId: Int32(clamping: threadId)), settings: inputSettings)) + |> `catch` { _ -> Signal in + return .single(.boolFalse) + } + |> mapToSignal { result -> Signal in + return postbox.transaction { transaction -> Void in } } + } else { + return .complete() } } else { - if let pending = transaction.getPendingPeerNotificationSettings(peerId), pending.isEqual(to: settings) { - transaction.updatePendingPeerNotificationSettings(peerId: peerId, settings: nil) + if let notificationPeer = transaction.getPeer(notificationPeerId), let inputPeer = apiInputPeer(notificationPeer), let settings = settings as? TelegramPeerNotificationSettings { + let showPreviews: Api.Bool? + switch settings.displayPreviews { + case .default: + showPreviews = nil + case .show: + showPreviews = .boolTrue + case .hide: + showPreviews = .boolFalse + } + let muteUntil: Int32? + switch settings.muteState { + case let .muted(until): + muteUntil = until + case .unmuted: + muteUntil = 0 + case .default: + muteUntil = nil + } + let sound: Api.NotificationSound? = settings.messageSound.apiSound + var flags: Int32 = 0 + if showPreviews != nil { + flags |= (1 << 0) + } + if muteUntil != nil { + flags |= (1 << 2) + } + if sound != nil { + flags |= (1 << 3) + } + let inputSettings = Api.InputPeerNotifySettings.inputPeerNotifySettings(flags: flags, showPreviews: showPreviews, silent: nil, muteUntil: muteUntil, sound: sound) + return network.request(Api.functions.account.updateNotifySettings(peer: .inputNotifyPeer(peer: inputPeer), settings: inputSettings)) + |> `catch` { _ -> Signal in + return .single(.boolFalse) + } + |> mapToSignal { result -> Signal in + return postbox.transaction { transaction -> Void in + transaction.updateCurrentPeerNotificationSettings([notificationPeerId: settings]) + if let pending = transaction.getPendingPeerNotificationSettings(peerId), pending.isEqual(to: settings) { + transaction.updatePendingPeerNotificationSettings(peerId: peerId, settings: nil) + } + } + } + } else { + if let pending = transaction.getPendingPeerNotificationSettings(peerId), pending.isEqual(to: settings) { + transaction.updatePendingPeerNotificationSettings(peerId: peerId, settings: nil) + } + return .complete() } - return .complete() } } else { if let pending = transaction.getPendingPeerNotificationSettings(peerId), pending.isEqual(to: settings) { diff --git a/submodules/TelegramCore/Sources/State/ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift b/submodules/TelegramCore/Sources/State/ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift index f040cf89f7..645e017284 100644 --- a/submodules/TelegramCore/Sources/State/ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift +++ b/submodules/TelegramCore/Sources/State/ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift @@ -284,7 +284,7 @@ private func synchronizeMarkAllUnseenReactions(transaction: Transaction, postbox return .complete() } - let signal = network.request(Api.functions.messages.readReactions(peer: inputPeer)) + let signal = network.request(Api.functions.messages.readReactions(flags: 0, peer: inputPeer, topMsgId: nil)) |> map(Optional.init) |> `catch` { _ -> Signal in return .fail(true) @@ -308,90 +308,6 @@ private func synchronizeMarkAllUnseenReactions(transaction: Transaction, postbox |> `catch` { _ -> Signal in return .complete() } - - /*let inputChannel = transaction.getPeer(peerId).flatMap(apiInputChannel) - let limit: Int32 = 100 - let oneOperation: (Int32) -> Signal = { maxId in - return network.request(Api.functions.messages.getUnreadReactions(peer: inputPeer, offsetId: maxId, addOffset: maxId == 0 ? 0 : -1, limit: limit, maxId: maxId == 0 ? 0 : (maxId + 1), minId: 1)) - |> mapToSignal { result -> Signal<[MessageId], MTRpcError> in - switch result { - case let .messages(messages, _, _): - return .single(messages.compactMap({ $0.id() })) - case let .channelMessages(_, _, _, _, messages, _, _): - return .single(messages.compactMap({ $0.id() })) - case .messagesNotModified: - return .single([]) - case let .messagesSlice(_, _, _, _, messages, _, _): - return .single(messages.compactMap({ $0.id() })) - } - } - |> mapToSignal { ids -> Signal in - let filteredIds = ids.filter { $0.id <= operation.maxId } - if filteredIds.isEmpty { - return .single(ids.min()?.id) - } - if peerId.namespace == Namespaces.Peer.CloudChannel { - guard let inputChannel = inputChannel else { - return .single(nil) - } - return network.request(Api.functions.channels.readMessageContents(channel: inputChannel, id: filteredIds.map { $0.id })) - |> map { result -> Int32? in - if ids.count < limit { - return nil - } else { - return ids.min()?.id - } - } - } else { - return network.request(Api.functions.messages.readMessageContents(id: filteredIds.map { $0.id })) - |> map { result -> Int32? in - switch result { - case let .affectedMessages(pts, ptsCount): - stateManager.addUpdateGroups([.updatePts(pts: pts, ptsCount: ptsCount)]) - } - if ids.count < limit { - return nil - } else { - return ids.min()?.id - } - } - } - } - } - let currentMaxId = Atomic(value: 0) - let loopOperations: Signal = ( - ( - deferred { - return oneOperation(currentMaxId.with { $0 }) - } - |> `catch` { error -> Signal in - return .fail(.error(error)) - } - ) - |> mapToSignal { resultId -> Signal in - if let resultId = resultId { - let previous = currentMaxId.swap(resultId) - if previous == resultId { - return .fail(.done) - } else { - return .complete() - } - } else { - return .fail(.done) - } - } - |> `catch` { error -> Signal in - switch error { - case .done, .error: - return .fail(error) - } - } - |> restart - ) - return loopOperations - |> `catch` { _ -> Signal in - return .complete() - }*/ } func markUnseenReactionMessage(transaction: Transaction, id: MessageId, addSynchronizeAction: Bool) { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift index 6200d78e90..f9d808e19a 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift @@ -68,11 +68,7 @@ public let telegramPostboxSeedConfiguration: SeedConfiguration = { if channel.flags.contains(.isForum) { return [] } - if channel.username != nil { - return .group - } else { - return .group - } + return .group } } else { assertionFailure() diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramPeerNotificationSettings.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramPeerNotificationSettings.swift index 7f56ad8070..982c7a05b9 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramPeerNotificationSettings.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramPeerNotificationSettings.swift @@ -73,11 +73,41 @@ public func getCloudLegacySound(id: Int64) -> (id: Int32, category: CloudSoundBu return nil } -public enum PeerMuteState: Equatable { +public enum PeerMuteState: Codable, Equatable { case `default` case unmuted case muted(until: Int32) + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: StringCodingKey.self) + + switch try container.decode(Int32.self, forKey: "m.v") { + case 0: + self = .default + case 1: + self = .muted(until: try container.decode(Int32.self, forKey: "m.u")) + case 2: + self = .unmuted + default: + assertionFailure() + self = .default + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: StringCodingKey.self) + + switch self { + case .default: + try container.encode(0 as Int32, forKey: "m.v") + case let .muted(until): + try container.encode(1 as Int32, forKey: "m.v") + try container.encode(until, forKey: "m.u") + case .unmuted: + try container.encode(2 as Int32, forKey: "m.v") + } + } + fileprivate static func decodeInline(_ decoder: PostboxDecoder) -> PeerMuteState { switch decoder.decodeInt32ForKey("m.v", orElse: 0) { case 0: @@ -104,15 +134,15 @@ public enum PeerMuteState: Equatable { } } -private enum PeerMessageSoundValue: Int32 { - case none - case bundledModern - case bundledClassic - case `default` - case cloud +private enum PeerMessageSoundValue: Int32, Codable { + case none = 0 + case bundledModern = 1 + case bundledClassic = 2 + case `default` = 3 + case cloud = 4 } -public enum PeerMessageSound: Equatable { +public enum PeerMessageSound: Equatable, Codable { public enum Id: Hashable { case none case `default` @@ -142,6 +172,30 @@ public enum PeerMessageSound: Equatable { } } + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: StringCodingKey.self) + + switch try container.decode(Int32.self, forKey: "s.v") { + case PeerMessageSoundValue.none.rawValue: + self = .none + case PeerMessageSoundValue.bundledModern.rawValue: + self = .cloud(fileId: getCloudSoundOrDefault(id: (try? container.decode(Int32.self, forKey: "s.i")) ?? 0, isModern: true)) + case PeerMessageSoundValue.bundledClassic.rawValue: + self = .cloud(fileId: getCloudSoundOrDefault(id: (try? container.decode(Int32.self, forKey: "s.i")) ?? 0, isModern: false)) + case PeerMessageSoundValue.default.rawValue: + self = .default + case PeerMessageSoundValue.cloud.rawValue: + do { + self = .cloud(fileId: try container.decode(Int64.self, forKey: "s.cloud.fileId")) + } catch { + self = .default + } + default: + assertionFailure() + self = defaultCloudPeerNotificationSound + } + } + static func decodeInline(_ container: KeyedDecodingContainer) throws -> PeerMessageSound { switch try container.decode(Int32.self, forKey: "s.v") { case PeerMessageSoundValue.none.rawValue: @@ -181,6 +235,26 @@ public enum PeerMessageSound: Equatable { return defaultCloudPeerNotificationSound } } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: StringCodingKey.self) + + switch self { + case .none: + try container.encode(PeerMessageSoundValue.none.rawValue, forKey: "s.v") + case let .bundledModern(id): + try container.encode(PeerMessageSoundValue.bundledModern.rawValue, forKey: "s.v") + try container.encode(id, forKey: "s.i") + case let .bundledClassic(id): + try container.encode(PeerMessageSoundValue.bundledClassic.rawValue, forKey: "s.v") + try container.encode(id, forKey: "s.i") + case let .cloud(fileId): + try container.encode(PeerMessageSoundValue.cloud.rawValue, forKey: "s.v") + try container.encode(fileId, forKey: "s.cloud.fileId") + case .default: + try container.encode(PeerMessageSoundValue.default.rawValue, forKey: "s.v") + } + } func encodeInline(_ container: inout KeyedEncodingContainer) throws { switch self { @@ -254,11 +328,40 @@ public enum PeerMessageSound: Equatable { } } -public enum PeerNotificationDisplayPreviews { +public enum PeerNotificationDisplayPreviews: Equatable, Codable { case `default` case show case hide + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: StringCodingKey.self) + + switch try container.decode(Int32.self, forKey: "p.v") { + case 0: + self = .default + case 1: + self = .show + case 2: + self = .hide + default: + assertionFailure() + self = .default + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: StringCodingKey.self) + + switch self { + case .default: + try container.encode(0 as Int32, forKey: "p.v") + case .show: + try container.encode(1 as Int32, forKey: "p.v") + case .hide: + try container.encode(2 as Int32, forKey: "p.v") + } + } + static func decodeInline(_ decoder: PostboxDecoder) -> PeerNotificationDisplayPreviews { switch decoder.decodeInt32ForKey("p.v", orElse: 0) { case 0: @@ -285,7 +388,7 @@ public enum PeerNotificationDisplayPreviews { } } -public final class TelegramPeerNotificationSettings: PeerNotificationSettings, Equatable { +public final class TelegramPeerNotificationSettings: PeerNotificationSettings, Codable, Equatable { public let muteState: PeerMuteState public let messageSound: PeerMessageSound public let displayPreviews: PeerNotificationDisplayPreviews @@ -325,6 +428,22 @@ public final class TelegramPeerNotificationSettings: PeerNotificationSettings, E self.displayPreviews = PeerNotificationDisplayPreviews.decodeInline(decoder) } + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: StringCodingKey.self) + + self.muteState = try container.decode(PeerMuteState.self, forKey: "muteState") + self.messageSound = try container.decode(PeerMessageSound.self, forKey: "messageSound") + self.displayPreviews = try container.decode(PeerNotificationDisplayPreviews.self, forKey: "displayPreviews") + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: StringCodingKey.self) + + try container.encode(self.muteState, forKey: "muteState") + try container.encode(self.messageSound, forKey: "messageSound") + try container.encode(self.displayPreviews, forKey: "displayPreviews") + } + public func encode(_ encoder: PostboxEncoder) { self.muteState.encodeInline(encoder) self.messageSound.encodeInline(encoder) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/MessagesData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/MessagesData.swift index e8e472cb39..7cb98cb811 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/MessagesData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/MessagesData.swift @@ -275,23 +275,26 @@ public extension TelegramEngine.EngineData.Item { public struct ItemKey: Hashable { public var peerId: EnginePeer.Id public var tag: MessageTags + public var threadId: Int64? } public typealias Result = Int? fileprivate var peerId: EnginePeer.Id fileprivate var tag: MessageTags + fileprivate var threadId: Int64? public var mapKey: ItemKey { - return ItemKey(peerId: self.peerId, tag: self.tag) + return ItemKey(peerId: self.peerId, tag: self.tag, threadId: self.threadId) } - public init(peerId: EnginePeer.Id, tag: MessageTags) { + public init(peerId: EnginePeer.Id, threadId: Int64?, tag: MessageTags) { self.peerId = peerId + self.threadId = threadId self.tag = tag } var key: PostboxViewKey { - return .historyTagSummaryView(tag: tag, peerId: peerId, namespace: Namespaces.Message.Cloud) + return .historyTagSummaryView(tag: self.tag, peerId: self.peerId, threadId: self.threadId, namespace: Namespaces.Message.Cloud) } func extract(view: PostboxView) -> Result { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift index a770a53034..c0cbdc19f4 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift @@ -258,6 +258,32 @@ public extension TelegramEngine.EngineData.Item { return EnginePeer.NotificationSettings(notificationSettings) } } + + public struct ThreadNotificationSettings: TelegramEngineDataItem, PostboxViewDataItem { + public typealias Result = EnginePeer.NotificationSettings + + fileprivate var id: EnginePeer.Id + fileprivate var threadId: Int64 + + public init(id: EnginePeer.Id, threadId: Int64) { + self.id = id + self.threadId = threadId + } + + var key: PostboxViewKey { + return .messageHistoryThreadInfo(peerId: self.id, threadId: self.threadId) + } + + func extract(view: PostboxView) -> Result { + guard let view = view as? MessageHistoryThreadInfoView else { + preconditionFailure() + } + guard let data = view.info?.get(MessageHistoryThreadData.self) else { + return EnginePeer.NotificationSettings(TelegramPeerNotificationSettings.defaultSettings) + } + return EnginePeer.NotificationSettings(data.notificationSettings) + } + } public struct ParticipantCount: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem { public typealias Result = Optional diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/TelegramEngineData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/TelegramEngineData.swift index bf03182201..79a6fca323 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/TelegramEngineData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/TelegramEngineData.swift @@ -115,14 +115,14 @@ public final class EngineDataOptional: TelegramEng } func _extract(views: [PostboxViewKey: PostboxView]) -> Any { - var result: [Item.Result] = [] + var result: Item.Result? if let item = self.item { let itemResult = (item as! AnyPostboxViewDataItem)._extract(views: views) - result.append(itemResult as! Item.Result) + result = (itemResult as! Item.Result) } - return result + return result as Any } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ApplyMaxReadIndexInteractively.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ApplyMaxReadIndexInteractively.swift index 6416e61c27..f202afd52b 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ApplyMaxReadIndexInteractively.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ApplyMaxReadIndexInteractively.swift @@ -133,7 +133,7 @@ func _internal_togglePeerUnreadMarkInteractively(transaction: Transaction, viewT } if !hasUnread && peerId.namespace == Namespaces.Peer.SecretChat { - let unseenSummary = transaction.getMessageTagSummary(peerId: peerId, tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud) + let unseenSummary = transaction.getMessageTagSummary(peerId: peerId, threadId: nil, tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud) let actionSummary = transaction.getPendingMessageActionsSummary(peerId: peerId, type: PendingMessageActionType.consumeUnseenPersonalMessage, namespace: Namespaces.Message.Cloud) if (unseenSummary?.count ?? 0) - (actionSummary ?? 0) > 0 { hasUnread = true @@ -147,7 +147,7 @@ func _internal_togglePeerUnreadMarkInteractively(transaction: Transaction, viewT } else { transaction.applyMarkUnread(peerId: peerId, namespace: principalNamespace, value: false, interactive: true) } - viewTracker.updateMarkAllMentionsSeen(peerId: peerId) + viewTracker.updateMarkAllMentionsSeen(peerId: peerId, threadId: nil) } } else { if setToValue == nil || setToValue! { @@ -156,22 +156,22 @@ func _internal_togglePeerUnreadMarkInteractively(transaction: Transaction, viewT } } -public func clearPeerUnseenPersonalMessagesInteractively(account: Account, peerId: PeerId) -> Signal { +public func clearPeerUnseenPersonalMessagesInteractively(account: Account, peerId: PeerId, threadId: Int64?) -> Signal { return account.postbox.transaction { transaction -> Void in if peerId.namespace == Namespaces.Peer.SecretChat { return } - account.viewTracker.updateMarkAllMentionsSeen(peerId: peerId) + account.viewTracker.updateMarkAllMentionsSeen(peerId: peerId, threadId: threadId) } |> ignoreValues } -public func clearPeerUnseenReactionsInteractively(account: Account, peerId: PeerId) -> Signal { +public func clearPeerUnseenReactionsInteractively(account: Account, peerId: PeerId, threadId: Int64?) -> Signal { return account.postbox.transaction { transaction -> Void in if peerId.namespace == Namespaces.Peer.SecretChat { return } - account.viewTracker.updateMarkAllReactionsSeen(peerId: peerId) + account.viewTracker.updateMarkAllReactionsSeen(peerId: peerId, threadId: threadId) } |> ignoreValues } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/EarliestUnseenPersonalMentionMessage.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/EarliestUnseenPersonalMentionMessage.swift index bee21f4b23..f70c294847 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/EarliestUnseenPersonalMentionMessage.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/EarliestUnseenPersonalMentionMessage.swift @@ -9,14 +9,14 @@ public enum EarliestUnseenPersonalMentionMessageResult: Equatable { case result(MessageId?) } -func _internal_earliestUnseenPersonalMentionMessage(account: Account, peerId: PeerId) -> Signal { - return account.viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId: peerId), index: .lowerBound, anchorIndex: .lowerBound, count: 4, fixedCombinedReadStates: nil, tagMask: .unseenPersonalMessage, additionalData: [.peerChatState(peerId)]) +func _internal_earliestUnseenPersonalMentionMessage(account: Account, peerId: PeerId, threadId: Int64?) -> Signal { + return account.viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId: peerId, threadId: threadId), index: .lowerBound, anchorIndex: .lowerBound, count: 4, fixedCombinedReadStates: nil, tagMask: .unseenPersonalMessage, additionalData: [.peerChatState(peerId)]) |> mapToSignal { view -> Signal in if view.0.isLoading { return .single(.loading) } if case .FillHole = view.1 { - return _internal_earliestUnseenPersonalMentionMessage(account: account, peerId: peerId) + return _internal_earliestUnseenPersonalMentionMessage(account: account, peerId: peerId, threadId: threadId) } if let message = view.0.entries.first?.message { if peerId.namespace == Namespaces.Peer.CloudChannel { @@ -53,10 +53,10 @@ func _internal_earliestUnseenPersonalMentionMessage(account: Account, peerId: Pe } else { return account.postbox.transaction { transaction -> EarliestUnseenPersonalMentionMessageResult in if let topId = transaction.getTopPeerMessageId(peerId: peerId, namespace: Namespaces.Message.Cloud) { - transaction.replaceMessageTagSummary(peerId: peerId, tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud, count: 0, maxId: topId.id) + transaction.replaceMessageTagSummary(peerId: peerId, threadId: threadId, tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud, count: 0, maxId: topId.id) - transaction.removeHole(peerId: peerId, namespace: Namespaces.Message.Cloud, space: .tag(.unseenPersonalMessage), range: 1 ... (Int32.max - 1)) - let ids = transaction.getMessageIndicesWithTag(peerId: peerId, namespace: Namespaces.Message.Cloud, tag: .unseenPersonalMessage).map({ $0.id }) + transaction.removeHole(peerId: peerId, threadId: threadId, namespace: Namespaces.Message.Cloud, space: .tag(.unseenPersonalMessage), range: 1 ... (Int32.max - 1)) + let ids = transaction.getMessageIndicesWithTag(peerId: peerId, threadId: threadId, namespace: Namespaces.Message.Cloud, tag: .unseenPersonalMessage).map({ $0.id }) for id in ids { markUnseenPersonalMessage(transaction: transaction, id: id, addSynchronizeAction: false) } @@ -76,14 +76,14 @@ func _internal_earliestUnseenPersonalMentionMessage(account: Account, peerId: Pe }) } -func _internal_earliestUnseenPersonalReactionMessage(account: Account, peerId: PeerId) -> Signal { - return account.viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId: peerId), index: .lowerBound, anchorIndex: .lowerBound, count: 4, fixedCombinedReadStates: nil, tagMask: .unseenReaction, additionalData: [.peerChatState(peerId)]) +func _internal_earliestUnseenPersonalReactionMessage(account: Account, peerId: PeerId, threadId: Int64?) -> Signal { + return account.viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId: peerId, threadId: threadId), index: .lowerBound, anchorIndex: .lowerBound, count: 4, fixedCombinedReadStates: nil, tagMask: .unseenReaction, additionalData: [.peerChatState(peerId)]) |> mapToSignal { view -> Signal in if view.0.isLoading { return .single(.loading) } if case .FillHole = view.1 { - return _internal_earliestUnseenPersonalReactionMessage(account: account, peerId: peerId) + return _internal_earliestUnseenPersonalReactionMessage(account: account, peerId: peerId, threadId: threadId) } if let message = view.0.entries.first?.message { if peerId.namespace == Namespaces.Peer.CloudChannel { @@ -120,10 +120,10 @@ func _internal_earliestUnseenPersonalReactionMessage(account: Account, peerId: P } 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.replaceMessageTagSummary(peerId: peerId, threadId: threadId, 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 }) + transaction.removeHole(peerId: peerId, threadId: threadId, namespace: Namespaces.Message.Cloud, space: .tag(.unseenReaction), range: 1 ... (Int32.max - 1)) + let ids = transaction.getMessageIndicesWithTag(peerId: peerId, threadId: threadId, namespace: Namespaces.Message.Cloud, tag: .unseenReaction).map({ $0.id }) for id in ids { markUnseenReactionMessage(transaction: transaction, id: id, addSynchronizeAction: false) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/PeerLiveLocationsContext.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/PeerLiveLocationsContext.swift index 744f359985..4fc2b124a2 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/PeerLiveLocationsContext.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/PeerLiveLocationsContext.swift @@ -4,7 +4,7 @@ import SwiftSignalKit import TelegramApi func _internal_topPeerActiveLiveLocationMessages(viewTracker: AccountViewTracker, accountPeerId: PeerId, peerId: PeerId) -> Signal<(Peer?, [Message]), NoError> { - return viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId: peerId), index: .upperBound, anchorIndex: .upperBound, count: 50, fixedCombinedReadStates: nil, tagMask: .liveLocation, orderStatistics: [], additionalData: [.peer(accountPeerId)]) + return viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId: peerId, threadId: nil), index: .upperBound, anchorIndex: .upperBound, count: 50, fixedCombinedReadStates: nil, tagMask: .liveLocation, orderStatistics: [], additionalData: [.peer(accountPeerId)]) |> map { (view, _, _) -> (Peer?, [Message]) in var accountPeer: Peer? for entry in view.additionalData { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift index 223aecd05d..cbeabea069 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift @@ -325,10 +325,6 @@ private class ReplyThreadHistoryContextImpl { return } - guard let _ = self.stateValue else { - return - } - let fromIdExclusive: Int32? let toIndex = messageIndex if let maxReadIncomingMessageId = self.maxReadIncomingMessageIdValue { @@ -345,6 +341,7 @@ private class ReplyThreadHistoryContextImpl { if messageIndex.id.id >= data.maxIncomingReadId { if let count = transaction.getThreadMessageCount(peerId: messageId.peerId, threadId: Int64(messageId.id), namespace: messageId.namespace, fromIdExclusive: data.maxIncomingReadId, toIndex: messageIndex) { data.incomingUnreadCount = max(0, data.incomingUnreadCount - Int32(count)) + data.maxIncomingReadId = messageIndex.id.id } data.maxKnownMessageId = max(data.maxKnownMessageId, messageIndex.id.id) @@ -937,7 +934,7 @@ func _internal_fetchChannelReplyThreadMessage(account: Account, messageId: Messa return account.postbox.transaction { transaction -> Signal in if let initialFilledHoles = initialFilledHoles { for range in initialFilledHoles.strictRemovedIndices.rangeView { - transaction.removeThreadIndexHole(peerId: discussionMessage.messageId.peerId, threadId: makeMessageThreadId(discussionMessage.messageId), namespace: Namespaces.Message.Cloud, space: .everywhere, range: Int32(range.lowerBound) ... Int32(range.upperBound)) + transaction.removeHole(peerId: discussionMessage.messageId.peerId, threadId: makeMessageThreadId(discussionMessage.messageId), namespace: Namespaces.Message.Cloud, space: .everywhere, range: Int32(range.lowerBound) ... Int32(range.upperBound)) } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SparseMessageList.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SparseMessageList.swift index 28014193fa..2229234496 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SparseMessageList.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SparseMessageList.swift @@ -189,12 +189,7 @@ public final class SparseMessageList { let count: Int count = 200 - let location: ChatLocationInput - if let threadId = self.threadId { - location = .thread(peerId: self.peerId, threadId: threadId, data: .single(MessageHistoryViewExternalInput(content: .thread(peerId: self.peerId, id: threadId, holes: [:]), maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil))) - } else { - location = .peer(peerId: self.peerId) - } + let location: ChatLocationInput = .peer(peerId: self.peerId, threadId: self.threadId) self.topItemsDisposable.set((self.account.postbox.aroundMessageHistoryViewForLocation(location, anchor: .upperBound, ignoreMessagesInTimestampRange: nil, count: count, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: Set(), tagMask: self.messageTag, appendMessagesFromTheSameGroup: false, namespaces: .not(Set(Namespaces.Message.allScheduled)), orderStatistics: []) |> deliverOn(self.queue)).start(next: { [weak self] view, updateType, _ in diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index 24f9e6dbc6..4f612b4678 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -154,32 +154,32 @@ public extension TelegramEngine { return PollResultsContext(account: self.account, messageId: messageId, poll: poll) } - public func earliestUnseenPersonalMentionMessage(peerId: PeerId) -> Signal { + public func earliestUnseenPersonalMentionMessage(peerId: PeerId, threadId: Int64?) -> Signal { let account = self.account - return _internal_earliestUnseenPersonalMentionMessage(account: self.account, peerId: peerId) + return _internal_earliestUnseenPersonalMentionMessage(account: self.account, peerId: peerId, threadId: threadId) |> mapToSignal { result -> Signal in switch result { case .loading: return .single(result) case let .result(messageId): if messageId == nil { - let _ = clearPeerUnseenPersonalMessagesInteractively(account: account, peerId: peerId).start() + let _ = clearPeerUnseenPersonalMessagesInteractively(account: account, peerId: peerId, threadId: threadId).start() } return .single(result) } } } - public func earliestUnseenPersonalReactionMessage(peerId: PeerId) -> Signal { + public func earliestUnseenPersonalReactionMessage(peerId: PeerId, threadId: Int64?) -> Signal { let account = self.account - return _internal_earliestUnseenPersonalReactionMessage(account: self.account, peerId: peerId) + return _internal_earliestUnseenPersonalReactionMessage(account: self.account, peerId: peerId, threadId: threadId) |> mapToSignal { result -> Signal in switch result { case .loading: return .single(result) case let .result(messageId): if messageId == nil { - let _ = clearPeerUnseenReactionsInteractively(account: account, peerId: peerId).start() + let _ = clearPeerUnseenReactionsInteractively(account: account, peerId: peerId, threadId: threadId).start() } return .single(result) } @@ -297,7 +297,7 @@ public extension TelegramEngine { return SparseMessageScrollingContext(account: self.account, peerId: peerId) } - public func refreshMessageTagStats(peerId: EnginePeer.Id, tags: [EngineMessage.Tags]) -> Signal { + public func refreshMessageTagStats(peerId: EnginePeer.Id, threadId: Int64?, tags: [EngineMessage.Tags]) -> Signal { let account = self.account return self.account.postbox.transaction { transaction -> Api.InputPeer? in return transaction.getPeer(peerId).flatMap(apiInputPeer) @@ -312,7 +312,15 @@ public extension TelegramEngine { signals.append(.single((nil, nil))) continue } - signals.append(self.account.network.request(Api.functions.messages.search(flags: 0, peer: inputPeer, q: "", fromId: nil, topMsgId: nil, filter: filter, minDate: 0, maxDate: 0, offsetId: 0, addOffset: 0, limit: 1, maxId: 0, minId: 0, hash: 0)) + + var flags: Int32 = 0 + var topMsgId: Int32? + if let threadId = threadId { + flags |= (1 << 1) + topMsgId = Int32(clamping: threadId) + } + + signals.append(self.account.network.request(Api.functions.messages.search(flags: flags, peer: inputPeer, q: "", fromId: nil, topMsgId: topMsgId, filter: filter, minDate: 0, maxDate: 0, offsetId: 0, addOffset: 0, limit: 1, maxId: 0, minId: 0, hash: 0)) |> map { result -> (count: Int32?, topId: Int32?) in switch result { case let .messagesSlice(_, count, _, _, messages, _, _): @@ -335,7 +343,7 @@ public extension TelegramEngine { for i in 0 ..< tags.count { let (count, maxId) = counts[i] if let count = count { - transaction.replaceMessageTagSummary(peerId: peerId, tagMask: tags[i], namespace: Namespaces.Message.Cloud, count: count, maxId: maxId ?? 1) + transaction.replaceMessageTagSummary(peerId: peerId, threadId: threadId, tagMask: tags[i], namespace: Namespaces.Message.Cloud, count: count, maxId: maxId ?? 1) } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/UpdatePinnedMessage.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/UpdatePinnedMessage.swift index d50898bcd2..9e251da8a2 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/UpdatePinnedMessage.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/UpdatePinnedMessage.swift @@ -170,7 +170,7 @@ func _internal_requestUnpinAllMessages(account: Account, peerId: PeerId) -> Sign }, delayIncrement: 0.0, maxDelay: 0.0, maxRetries: 100, onQueue: .concurrentDefaultQueue()) |> mapToSignal { _ -> Signal in let signal: Signal = account.postbox.transaction { transaction -> Void in - for index in transaction.getMessageIndicesWithTag(peerId: peerId, namespace: Namespaces.Message.Cloud, tag: .pinned) { + for index in transaction.getMessageIndicesWithTag(peerId: peerId, threadId: nil, namespace: Namespaces.Message.Cloud, tag: .pinned) { transaction.updateMessage(index.id, update: { currentMessage in var storeForwardInfo: StoreMessageForwardInfo? if let forwardInfo = currentMessage.forwardInfo { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChangePeerNotificationSettings.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChangePeerNotificationSettings.swift index ba97962fa9..1e5b574517 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChangePeerNotificationSettings.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChangePeerNotificationSettings.swift @@ -1,17 +1,41 @@ import Foundation import Postbox import SwiftSignalKit +import TelegramApi - -func _internal_togglePeerMuted(account: Account, peerId: PeerId) -> Signal { +func _internal_togglePeerMuted(account: Account, peerId: PeerId, threadId: Int64?) -> Signal { return account.postbox.transaction { transaction -> Void in - if let peer = transaction.getPeer(peerId) { - var notificationPeerId = peerId - if let associatedPeerId = peer.associatedPeerId { - notificationPeerId = associatedPeerId + guard let peer = transaction.getPeer(peerId) else { + return + } + + var notificationPeerId = peerId + if let associatedPeerId = peer.associatedPeerId { + notificationPeerId = associatedPeerId + } + + if let threadId = threadId { + if var data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.get(MessageHistoryThreadData.self) { + var updatedSettings: TelegramPeerNotificationSettings + switch data.notificationSettings.muteState { + case .default: + updatedSettings = data.notificationSettings.withUpdatedMuteState(.muted(until: Int32.max)) + case .unmuted: + updatedSettings = data.notificationSettings.withUpdatedMuteState(.muted(until: Int32.max)) + case .muted: + updatedSettings = data.notificationSettings.withUpdatedMuteState(.unmuted) + } + data.notificationSettings = updatedSettings + + if let entry = CodableEntry(data) { + transaction.setMessageHistoryThreadInfo(peerId: peerId, threadId: threadId, info: entry) + + //TODO:loc + let _ = pushPeerNotificationSettings(postbox: account.postbox, network: account.network, peerId: peerId, threadId: threadId, settings: TelegramPeerNotificationSettings.defaultSettings).start() + } } - - let currentSettings = transaction.getPeerNotificationSettings(notificationPeerId) as? TelegramPeerNotificationSettings + } else { + let currentSettings = transaction.getPeerNotificationSettings(id: notificationPeerId) as? TelegramPeerNotificationSettings let previousSettings: TelegramPeerNotificationSettings if let currentSettings = currentSettings { previousSettings = currentSettings @@ -38,97 +62,159 @@ func _internal_togglePeerMuted(account: Account, peerId: PeerId) -> Signal Signal { +func _internal_updatePeerMuteSetting(account: Account, peerId: PeerId, threadId: Int64?, muteInterval: Int32?) -> Signal { return account.postbox.transaction { transaction -> Void in - updatePeerMuteSetting(transaction: transaction, peerId: peerId, muteInterval: muteInterval) + _internal_updatePeerMuteSetting(account: account, transaction: transaction, peerId: peerId, threadId: threadId, muteInterval: muteInterval) } } -func updatePeerMuteSetting(transaction: Transaction, peerId: PeerId, muteInterval: Int32?) { +func _internal_updatePeerMuteSetting(account: Account, transaction: Transaction, peerId: PeerId, threadId: Int64?, muteInterval: Int32?) { if let peer = transaction.getPeer(peerId) { - var notificationPeerId = peerId - if let associatedPeerId = peer.associatedPeerId { - notificationPeerId = associatedPeerId - } - - let currentSettings = transaction.getPeerNotificationSettings(notificationPeerId) as? TelegramPeerNotificationSettings - let previousSettings: TelegramPeerNotificationSettings - if let currentSettings = currentSettings { - previousSettings = currentSettings - } else { - previousSettings = TelegramPeerNotificationSettings.defaultSettings - } - - let muteState: PeerMuteState - if let muteInterval = muteInterval { - if muteInterval == 0 { - muteState = .unmuted - } else { - let absoluteUntil: Int32 - if muteInterval == Int32.max { - absoluteUntil = Int32.max + if let threadId = threadId { + if var data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.get(MessageHistoryThreadData.self) { + let previousSettings: TelegramPeerNotificationSettings = data.notificationSettings + + let muteState: PeerMuteState + if let muteInterval = muteInterval { + if muteInterval == 0 { + muteState = .unmuted + } else { + let absoluteUntil: Int32 + if muteInterval == Int32.max { + absoluteUntil = Int32.max + } else { + absoluteUntil = Int32(Date().timeIntervalSince1970) + muteInterval + } + muteState = .muted(until: absoluteUntil) + } } else { - absoluteUntil = Int32(Date().timeIntervalSince1970) + muteInterval + muteState = .default } - muteState = .muted(until: absoluteUntil) + + data.notificationSettings = previousSettings.withUpdatedMuteState(muteState) + + if let entry = CodableEntry(data) { + transaction.setMessageHistoryThreadInfo(peerId: peerId, threadId: threadId, info: entry) + } + + //TODO:loc + let _ = pushPeerNotificationSettings(postbox: account.postbox, network: account.network, peerId: peerId, threadId: threadId, settings: TelegramPeerNotificationSettings.defaultSettings).start() } } else { - muteState = .default + var notificationPeerId = peerId + if let associatedPeerId = peer.associatedPeerId { + notificationPeerId = associatedPeerId + } + + let currentSettings = transaction.getPeerNotificationSettings(id: notificationPeerId) as? TelegramPeerNotificationSettings + let previousSettings: TelegramPeerNotificationSettings + if let currentSettings = currentSettings { + previousSettings = currentSettings + } else { + previousSettings = TelegramPeerNotificationSettings.defaultSettings + } + + let muteState: PeerMuteState + if let muteInterval = muteInterval { + if muteInterval == 0 { + muteState = .unmuted + } else { + let absoluteUntil: Int32 + if muteInterval == Int32.max { + absoluteUntil = Int32.max + } else { + absoluteUntil = Int32(Date().timeIntervalSince1970) + muteInterval + } + muteState = .muted(until: absoluteUntil) + } + } else { + muteState = .default + } + + let updatedSettings = previousSettings.withUpdatedMuteState(muteState) + transaction.updatePendingPeerNotificationSettings(peerId: peerId, settings: updatedSettings) } - - let updatedSettings = previousSettings.withUpdatedMuteState(muteState) - transaction.updatePendingPeerNotificationSettings(peerId: peerId, settings: updatedSettings) } } -func _internal_updatePeerDisplayPreviewsSetting(account: Account, peerId: PeerId, displayPreviews: PeerNotificationDisplayPreviews) -> Signal { +func _internal_updatePeerDisplayPreviewsSetting(account: Account, peerId: PeerId, threadId: Int64?, displayPreviews: PeerNotificationDisplayPreviews) -> Signal { return account.postbox.transaction { transaction -> Void in - updatePeerDisplayPreviewsSetting(transaction: transaction, peerId: peerId, displayPreviews: displayPreviews) + _internal_updatePeerDisplayPreviewsSetting(account: account, transaction: transaction, peerId: peerId, threadId: threadId, displayPreviews: displayPreviews) } } -func updatePeerDisplayPreviewsSetting(transaction: Transaction, peerId: PeerId, displayPreviews: PeerNotificationDisplayPreviews) { +func _internal_updatePeerDisplayPreviewsSetting(account: Account, transaction: Transaction, peerId: PeerId, threadId: Int64?, displayPreviews: PeerNotificationDisplayPreviews) { if let peer = transaction.getPeer(peerId) { - var notificationPeerId = peerId - if let associatedPeerId = peer.associatedPeerId { - notificationPeerId = associatedPeerId - } - - let currentSettings = transaction.getPeerNotificationSettings(notificationPeerId) as? TelegramPeerNotificationSettings - let previousSettings: TelegramPeerNotificationSettings - if let currentSettings = currentSettings { - previousSettings = currentSettings + if let threadId = threadId { + if var data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.get(MessageHistoryThreadData.self) { + let previousSettings: TelegramPeerNotificationSettings = data.notificationSettings + + data.notificationSettings = previousSettings.withUpdatedDisplayPreviews(displayPreviews) + + if let entry = CodableEntry(data) { + transaction.setMessageHistoryThreadInfo(peerId: peerId, threadId: threadId, info: entry) + } + + //TODO:loc + let _ = pushPeerNotificationSettings(postbox: account.postbox, network: account.network, peerId: peerId, threadId: threadId, settings: TelegramPeerNotificationSettings.defaultSettings).start() + } } else { - previousSettings = TelegramPeerNotificationSettings.defaultSettings + var notificationPeerId = peerId + if let associatedPeerId = peer.associatedPeerId { + notificationPeerId = associatedPeerId + } + + let currentSettings = transaction.getPeerNotificationSettings(id: notificationPeerId) as? TelegramPeerNotificationSettings + let previousSettings: TelegramPeerNotificationSettings + if let currentSettings = currentSettings { + previousSettings = currentSettings + } else { + previousSettings = TelegramPeerNotificationSettings.defaultSettings + } + + let updatedSettings = previousSettings.withUpdatedDisplayPreviews(displayPreviews) + transaction.updatePendingPeerNotificationSettings(peerId: peerId, settings: updatedSettings) } - - let updatedSettings = previousSettings.withUpdatedDisplayPreviews(displayPreviews) - transaction.updatePendingPeerNotificationSettings(peerId: peerId, settings: updatedSettings) } } -func _internal_updatePeerNotificationSoundInteractive(account: Account, peerId: PeerId, sound: PeerMessageSound) -> Signal { +func _internal_updatePeerNotificationSoundInteractive(account: Account, peerId: PeerId, threadId: Int64?, sound: PeerMessageSound) -> Signal { return account.postbox.transaction { transaction -> Void in - updatePeerNotificationSoundInteractive(transaction: transaction, peerId: peerId, sound: sound) + _internal_updatePeerNotificationSoundInteractive(account: account, transaction: transaction, peerId: peerId, threadId: threadId, sound: sound) } } -func updatePeerNotificationSoundInteractive(transaction: Transaction, peerId: PeerId, sound: PeerMessageSound) { +func _internal_updatePeerNotificationSoundInteractive(account: Account, transaction: Transaction, peerId: PeerId, threadId: Int64?, sound: PeerMessageSound) { if let peer = transaction.getPeer(peerId) { - var notificationPeerId = peerId - if let associatedPeerId = peer.associatedPeerId { - notificationPeerId = associatedPeerId - } - - let currentSettings = transaction.getPeerNotificationSettings(notificationPeerId) as? TelegramPeerNotificationSettings - let previousSettings: TelegramPeerNotificationSettings - if let currentSettings = currentSettings { - previousSettings = currentSettings + if let threadId = threadId { + if var data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.get(MessageHistoryThreadData.self) { + let previousSettings: TelegramPeerNotificationSettings = data.notificationSettings + + data.notificationSettings = previousSettings.withUpdatedMessageSound(sound) + + if let entry = CodableEntry(data) { + transaction.setMessageHistoryThreadInfo(peerId: peerId, threadId: threadId, info: entry) + } + + //TODO:loc + let _ = pushPeerNotificationSettings(postbox: account.postbox, network: account.network, peerId: peerId, threadId: threadId, settings: TelegramPeerNotificationSettings.defaultSettings).start() + } } else { - previousSettings = TelegramPeerNotificationSettings.defaultSettings + var notificationPeerId = peerId + if let associatedPeerId = peer.associatedPeerId { + notificationPeerId = associatedPeerId + } + + let currentSettings = transaction.getPeerNotificationSettings(id: notificationPeerId) as? TelegramPeerNotificationSettings + let previousSettings: TelegramPeerNotificationSettings + if let currentSettings = currentSettings { + previousSettings = currentSettings + } else { + previousSettings = TelegramPeerNotificationSettings.defaultSettings + } + + let updatedSettings = previousSettings.withUpdatedMessageSound(sound) + transaction.updatePendingPeerNotificationSettings(peerId: peerId, settings: updatedSettings) } - - let updatedSettings = previousSettings.withUpdatedMessageSound(sound) - transaction.updatePendingPeerNotificationSettings(peerId: peerId, settings: updatedSettings) } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift index 109ab45420..e22f2c225e 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift @@ -710,8 +710,8 @@ 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) + transaction.replaceMessageTagSummary(peerId: peerId, threadId: nil, tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud, count: unreadMentionsCount, maxId: topMessage) + transaction.replaceMessageTagSummary(peerId: peerId, threadId: nil, tagMask: .unseenReaction, namespace: Namespaces.Message.Cloud, count: unreadReactionsCount, maxId: topMessage) if let pts = pts { if transaction.getPeerChatState(peerId) == nil { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinChannel.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinChannel.swift index 7c73fc2a01..ceff5d6fc9 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinChannel.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinChannel.swift @@ -73,7 +73,7 @@ func _internal_joinChannel(account: Account, peerId: PeerId, hash: String?) -> S } if let channel = transaction.getPeer(peerId) as? TelegramChannel, case .broadcast = channel.info { - let notificationSettings = transaction.getPeerNotificationSettings(peerId) as? TelegramPeerNotificationSettings ?? TelegramPeerNotificationSettings.defaultSettings + let notificationSettings = transaction.getPeerNotificationSettings(id: peerId) as? TelegramPeerNotificationSettings ?? TelegramPeerNotificationSettings.defaultSettings transaction.updateCurrentPeerNotificationSettings([peerId: notificationSettings.withUpdatedMuteState(.muted(until: Int32.max))]) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift index 6cefc2cc13..b7e5cec35d 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift @@ -183,28 +183,28 @@ public extension TelegramEngine { return _internal_reportRepliesMessage(account: self.account, messageId: messageId, deleteMessage: deleteMessage, deleteHistory: deleteHistory, reportSpam: reportSpam) } - public func togglePeerMuted(peerId: PeerId) -> Signal { - return _internal_togglePeerMuted(account: self.account, peerId: peerId) + public func togglePeerMuted(peerId: PeerId, threadId: Int64?) -> Signal { + return _internal_togglePeerMuted(account: self.account, peerId: peerId, threadId: threadId) } - public func updatePeerMuteSetting(peerId: PeerId, muteInterval: Int32?) -> Signal { - return _internal_updatePeerMuteSetting(account: self.account, peerId: peerId, muteInterval: muteInterval) + public func updatePeerMuteSetting(peerId: PeerId, threadId: Int64?, muteInterval: Int32?) -> Signal { + return _internal_updatePeerMuteSetting(account: self.account, peerId: peerId, threadId: threadId, muteInterval: muteInterval) } - public func updatePeerDisplayPreviewsSetting(peerId: PeerId, displayPreviews: PeerNotificationDisplayPreviews) -> Signal { - return _internal_updatePeerDisplayPreviewsSetting(account: self.account, peerId: peerId, displayPreviews: displayPreviews) + public func updatePeerDisplayPreviewsSetting(peerId: PeerId, threadId: Int64?, displayPreviews: PeerNotificationDisplayPreviews) -> Signal { + return _internal_updatePeerDisplayPreviewsSetting(account: self.account, peerId: peerId, threadId: threadId, displayPreviews: displayPreviews) } - public func updatePeerNotificationSoundInteractive(peerId: PeerId, sound: PeerMessageSound) -> Signal { - return _internal_updatePeerNotificationSoundInteractive(account: self.account, peerId: peerId, sound: sound) + public func updatePeerNotificationSoundInteractive(peerId: PeerId, threadId: Int64?, sound: PeerMessageSound) -> Signal { + return _internal_updatePeerNotificationSoundInteractive(account: self.account, peerId: peerId, threadId: threadId, sound: sound) } public func removeCustomNotificationSettings(peerIds: [PeerId]) -> Signal { return self.account.postbox.transaction { transaction -> Void in for peerId in peerIds { - TelegramCore.updatePeerNotificationSoundInteractive(transaction: transaction, peerId: peerId, sound: .default) - TelegramCore.updatePeerMuteSetting(transaction: transaction, peerId: peerId, muteInterval: nil) - TelegramCore.updatePeerDisplayPreviewsSetting(transaction: transaction, peerId: peerId, displayPreviews: .default) + _internal_updatePeerNotificationSoundInteractive(account: self.account, transaction: transaction, peerId: peerId, threadId: nil, sound: .default) + _internal_updatePeerMuteSetting(account: self.account, transaction: transaction, peerId: peerId, threadId: nil, muteInterval: nil) + _internal_updatePeerDisplayPreviewsSetting(account: self.account, transaction: transaction, peerId: peerId, threadId: nil, displayPreviews: .default) } } |> ignoreValues diff --git a/submodules/TelegramCore/Sources/UpdatePeers.swift b/submodules/TelegramCore/Sources/UpdatePeers.swift index a89c273305..cb9315719b 100644 --- a/submodules/TelegramCore/Sources/UpdatePeers.swift +++ b/submodules/TelegramCore/Sources/UpdatePeers.swift @@ -71,7 +71,8 @@ public func updatePeers(transaction: Transaction, peers: [Peer], update: (Peer?, } case Namespaces.Peer.CloudChannel: if let channel = updated as? TelegramChannel { - switch channel.participationStatus { + if case .personal = channel.accessHash { + switch channel.participationStatus { case .member: updatePeerChatInclusionWithMinTimestamp(transaction: transaction, id: peerId, minTimestamp: channel.creationDate, forceRootGroupIfNotExists: true) case .left: @@ -80,6 +81,7 @@ public func updatePeers(transaction: Transaction, peers: [Peer], update: (Peer?, transaction.updatePeerChatListInclusion(peerId, inclusion: .notIncluded) default: transaction.updatePeerChatListInclusion(peerId, inclusion: .notIncluded) + } } } else { assertionFailure() diff --git a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift index 6261360241..ee3b91af17 100644 --- a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift +++ b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift @@ -31,7 +31,7 @@ public enum ChatTitleContent { case replies } - case peer(peerView: PeerView, customTitle: String?, onlineMemberCount: Int32?, isScheduledMessages: Bool) + case peer(peerView: PeerView, customTitle: String?, onlineMemberCount: Int32?, isScheduledMessages: Bool, isMuted: Bool?) case replyThread(type: ReplyThreadType, count: Int) case custom(String, String?, Bool) } @@ -123,7 +123,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { var titleCredibilityIcon: ChatTitleCredibilityIcon = .none var isEnabled = true switch titleContent { - case let .peer(peerView, customTitle, _, isScheduledMessages): + case let .peer(peerView, customTitle, _, isScheduledMessages, isMuted): if peerView.peerId.isReplies { let typeText: String = self.strings.DialogList_Replies segments = [.text(0, NSAttributedString(string: typeText, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))] @@ -166,10 +166,16 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { if peerView.peerId.namespace == Namespaces.Peer.SecretChat { titleLeftIcon = .lock } - if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings { - if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) { - if titleCredibilityIcon != .verified { - titleRightIcon = .mute + if let isMuted { + if isMuted { + titleRightIcon = .mute + } + } else { + if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings { + if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) { + if titleCredibilityIcon != .verified { + titleRightIcon = .mute + } } } } @@ -313,7 +319,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { var inputActivitiesAllowed = true if let titleContent = self.titleContent { switch titleContent { - case let .peer(peerView, _, _, isScheduledMessages): + case let .peer(peerView, _, _, isScheduledMessages, _): if let peer = peerViewMainPeer(peerView) { if peer.id == self.context.account.peerId || isScheduledMessages || peer.id.isReplies { inputActivitiesAllowed = false @@ -414,7 +420,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { } else { if let titleContent = self.titleContent { switch titleContent { - case let .peer(peerView, _, onlineMemberCount, isScheduledMessages): + case let .peer(peerView, _, onlineMemberCount, isScheduledMessages, _): if let peer = peerViewMainPeer(peerView) { let servicePeer = isServicePeer(peer) if peer.id == self.context.account.peerId || isScheduledMessages || peer.id.isReplies { diff --git a/submodules/TelegramUI/Sources/AccountContext.swift b/submodules/TelegramUI/Sources/AccountContext.swift index 7388efa8b3..c12ef414e8 100644 --- a/submodules/TelegramUI/Sources/AccountContext.swift +++ b/submodules/TelegramUI/Sources/AccountContext.swift @@ -342,10 +342,14 @@ public final class AccountContextImpl: AccountContext { public func chatLocationInput(for location: ChatLocation, contextHolder: Atomic) -> ChatLocationInput { switch location { case let .peer(peerId): - return .peer(peerId: peerId) + return .peer(peerId: peerId, threadId: nil) case let .replyThread(data): - let context = chatLocationContext(holder: contextHolder, account: self.account, data: data) - return .thread(peerId: data.messageId.peerId, threadId: makeMessageThreadId(data.messageId), data: context.state) + if data.isForumPost { + return .peer(peerId: data.messageId.peerId, threadId: Int64(data.messageId.id)) + } else { + let context = chatLocationContext(holder: contextHolder, account: self.account, data: data) + return .thread(peerId: data.messageId.peerId, threadId: makeMessageThreadId(data.messageId), data: context.state) + } case let .feed(id): let context = chatLocationContext(holder: contextHolder, account: self.account, feedId: id) return .feed(id: id, data: context.state) @@ -357,8 +361,20 @@ public final class AccountContextImpl: AccountContext { case .peer: return .single(nil) case let .replyThread(data): - let context = chatLocationContext(holder: contextHolder, account: self.account, data: data) - return context.maxReadOutgoingMessageId + if data.isForumPost, let peerId = location.peerId { + let viewKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: data.messageId.peerId, threadId: Int64(data.messageId.id)) + return self.account.postbox.combinedView(keys: [viewKey]) + |> map { views -> MessageId? in + if let threadInfo = views.views[viewKey] as? MessageHistoryThreadInfoView, let data = threadInfo.info?.get(MessageHistoryThreadData.self) { + return MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: data.maxOutgoingReadId) + } else { + return nil + } + } + } else { + let context = chatLocationContext(holder: contextHolder, account: self.account, data: data) + return context.maxReadOutgoingMessageId + } case let .feed(id): let context = chatLocationContext(holder: contextHolder, account: self.account, feedId: id) return context.maxReadOutgoingMessageId @@ -372,18 +388,30 @@ public final class AccountContextImpl: AccountContext { return self.account.postbox.combinedView(keys: [unreadCountsKey]) |> map { views in var unreadCount: Int32 = 0 - + if let view = views.views[unreadCountsKey] as? UnreadMessageCountsView { if let count = view.count(for: .peer(peerId)) { unreadCount = count } } - + return Int(unreadCount) } case let .replyThread(data): - let context = chatLocationContext(holder: contextHolder, account: self.account, data: data) - return context.unreadCount + if data.isForumPost { + let viewKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: data.messageId.peerId, threadId: Int64(data.messageId.id)) + return self.account.postbox.combinedView(keys: [viewKey]) + |> map { views -> Int in + if let threadInfo = views.views[viewKey] as? MessageHistoryThreadInfoView, let data = threadInfo.info?.get(MessageHistoryThreadData.self) { + return Int(data.incomingUnreadCount) + } else { + return 0 + } + } + } else { + let context = chatLocationContext(holder: contextHolder, account: self.account, data: data) + return context.unreadCount + } case let .feed(id): let context = chatLocationContext(holder: contextHolder, account: self.account, feedId: id) return context.unreadCount diff --git a/submodules/TelegramUI/Sources/ChatChannelSubscriberInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatChannelSubscriberInputPanelNode.swift index d75ce83bff..161f511c5d 100644 --- a/submodules/TelegramUI/Sources/ChatChannelSubscriberInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatChannelSubscriberInputPanelNode.swift @@ -252,7 +252,7 @@ final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { break case .muteNotifications, .unmuteNotifications: if let context = self.context, let presentationInterfaceState = self.presentationInterfaceState, let peer = presentationInterfaceState.renderedPeer?.peer { - self.actionDisposable.set(context.engine.peers.togglePeerMuted(peerId: peer.id).start()) + self.actionDisposable.set(context.engine.peers.togglePeerMuted(peerId: peer.id, threadId: nil).start()) } case .hidePinnedMessages, .unpinMessages: self.interfaceInteraction?.unpinAllMessages() diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 3217b9533f..2672a7a8be 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -4384,7 +4384,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if case .pinnedMessages = presentationInterfaceState.subject { strongSelf.chatTitleView?.titleContent = .custom(presentationInterfaceState.strings.Chat_TitlePinnedMessages(Int32(displayedCount ?? 1)), nil, false) } else { - strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, customTitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages) + strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, customTitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages, isMuted: nil) let imageOverride: AvatarNodeImageOverride? if strongSelf.context.account.peerId == peer.id { imageOverride = .savedMessagesIcon @@ -4741,18 +4741,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let peerView = context.account.viewTracker.peerView(peerId) let messageAndTopic = messagePromise.get() - |> mapToSignal { message -> Signal<(message: Message?, threadInfo: EngineMessageHistoryThread.Info?), NoError> in + |> mapToSignal { message -> Signal<(message: Message?, threadData: MessageHistoryThreadData?), NoError> in guard let message = message else { return .single((nil, nil)) } let viewKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: peerId, threadId: Int64(message.id.id)) return context.account.postbox.combinedView(keys: [viewKey]) - |> map { views -> (message: Message?, threadInfo: EngineMessageHistoryThread.Info?) in + |> map { views -> (message: Message?, threadData: MessageHistoryThreadData?) in guard let view = views.views[viewKey] as? MessageHistoryThreadInfoView else { return (message, nil) } - return (message, view.info?.get(MessageHistoryThreadData.self)?.info) + return (message, view.info?.get(MessageHistoryThreadData.self)) } } @@ -4809,8 +4809,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - if let threadInfo = messageAndTopic.threadInfo { - strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, customTitle: threadInfo.title, onlineMemberCount: onlineMemberCount, isScheduledMessages: false) + var peerIsMuted = false + if let threadData = messageAndTopic.threadData { + if case let .muted(until) = threadData.notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) { + peerIsMuted = true + } + } else if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings { + if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) { + peerIsMuted = true + } + } + + if let threadInfo = messageAndTopic.threadData?.info { + strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, customTitle: threadInfo.title, onlineMemberCount: onlineMemberCount, isScheduledMessages: false, isMuted: peerIsMuted) let avatarContent: EmojiStatusComponent.Content if let fileId = threadInfo.icon { @@ -4829,12 +4840,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if strongSelf.isNodeLoaded { strongSelf.chatDisplayNode.peerView = peerView } - var peerIsMuted = false - if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings { - if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) { - peerIsMuted = true - } - } + var peerDiscussionId: PeerId? var peerGeoLocation: PeerGeoLocation? var currentSendAsPeerId: PeerId? @@ -6813,8 +6819,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } self.chatDisplayNode.navigateButtons.mentionsPressed = { [weak self] in - if let strongSelf = self, strongSelf.isNodeLoaded, case let .peer(peerId) = strongSelf.chatLocation { - let signal = strongSelf.context.engine.messages.earliestUnseenPersonalMentionMessage(peerId: peerId) + if let strongSelf = self, strongSelf.isNodeLoaded, let peerId = strongSelf.chatLocation.peerId { + let signal = strongSelf.context.engine.messages.earliestUnseenPersonalMentionMessage(peerId: peerId, threadId: strongSelf.chatLocation.threadId) strongSelf.navigationActionDisposable.set((signal |> deliverOnMainQueue).start(next: { result in if let strongSelf = self { switch result { @@ -6850,10 +6856,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G action: { _, f in f(.dismissWithoutContent) - guard let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation else { + guard let strongSelf = self, let peerId = strongSelf.chatLocation.peerId else { return } - let _ = clearPeerUnseenPersonalMessagesInteractively(account: strongSelf.context.account, peerId: peerId).start() + let _ = clearPeerUnseenPersonalMessagesInteractively(account: strongSelf.context.account, peerId: peerId, threadId: strongSelf.chatLocation.threadId).start() } ))) let items = ContextController.Items(content: .list(menuItems)) @@ -6870,8 +6876,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } 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) + if let strongSelf = self, strongSelf.isNodeLoaded, let peerId = strongSelf.chatLocation.peerId { + let signal = strongSelf.context.engine.messages.earliestUnseenPersonalReactionMessage(peerId: peerId, threadId: strongSelf.chatLocation.threadId) strongSelf.navigationActionDisposable.set((signal |> deliverOnMainQueue).start(next: { result in if let strongSelf = self { switch result { @@ -7017,10 +7023,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G action: { _, f in f(.dismissWithoutContent) - guard let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation else { + guard let strongSelf = self, let peerId = strongSelf.chatLocation.peerId else { return } - let _ = clearPeerUnseenReactionsInteractively(account: strongSelf.context.account, peerId: peerId).start() + let _ = clearPeerUnseenReactionsInteractively(account: strongSelf.context.account, peerId: peerId, threadId: strongSelf.chatLocation.threadId).start() } ))) let items = ContextController.Items(content: .list(menuItems)) @@ -7851,7 +7857,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self?.navigationButtonAction(.openChatInfo(expandAvatar: false)) }, togglePeerNotifications: { [weak self] in if let strongSelf = self, let peerId = strongSelf.chatLocation.peerId { - let _ = strongSelf.context.engine.peers.togglePeerMuted(peerId: peerId).start() + let _ = strongSelf.context.engine.peers.togglePeerMuted(peerId: peerId, threadId: strongSelf.chatLocation.threadId).start() } }, sendContextResult: { [weak self] results, result, node, rect in guard let strongSelf = self else { @@ -9360,7 +9366,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }) - self.chatUnreadMentionCountDisposable = (self.context.account.viewTracker.unseenPersonalMessagesAndReactionCount(peerId: peerId) |> deliverOnMainQueue).start(next: { [weak self] mentionCount, reactionCount in + self.chatUnreadMentionCountDisposable = (self.context.account.viewTracker.unseenPersonalMessagesAndReactionCount(peerId: peerId, threadId: nil) |> 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 = mentionCount + strongSelf.chatDisplayNode.navigateButtons.reactionsCount = reactionCount + } + } + }) + } else if let peerId = self.chatLocation.peerId, let threadId = self.chatLocation.threadId { + self.chatUnreadMentionCountDisposable = (self.context.account.viewTracker.unseenPersonalMessagesAndReactionCount(peerId: peerId, threadId: threadId) |> 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 diff --git a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift index 2fbeddb5d8..39b48591c4 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift @@ -86,6 +86,14 @@ func chatHistoryEntriesForView( continue } + if case let .replyThread(replyThreadMessage) = location, replyThreadMessage.isForumPost { + for media in message.media { + if let action = media as? TelegramMediaAction, case .topicCreated = action.action { + continue loop + } + } + } + if let maybeJoinMessage = joinMessage { if message.timestamp > maybeJoinMessage.timestamp, (!view.holeEarlier || count > 0) { entries.append(.MessageEntry(maybeJoinMessage, presentationData, false, nil, .none, ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false, isCentered: false))) diff --git a/submodules/TelegramUI/Sources/ChatHistorySearchContainerNode.swift b/submodules/TelegramUI/Sources/ChatHistorySearchContainerNode.swift index ad80ec2233..66fccca20c 100644 --- a/submodules/TelegramUI/Sources/ChatHistorySearchContainerNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistorySearchContainerNode.swift @@ -127,7 +127,7 @@ final class ChatHistorySearchContainerNode: SearchDisplayControllerContentNode { return true } - init(context: AccountContext, peerId: PeerId, tagMask: MessageTags, interfaceInteraction: ChatControllerInteraction) { + init(context: AccountContext, peerId: PeerId, threadId: Int64?, tagMask: MessageTags, interfaceInteraction: ChatControllerInteraction) { self.context = context let presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -174,7 +174,7 @@ final class ChatHistorySearchContainerNode: SearchDisplayControllerContentNode { if let strongSelf = self { let signal: Signal<([ChatHistorySearchEntry], [MessageId: Message])?, NoError> if let query = query, !query.isEmpty { - let foundRemoteMessages: Signal<[Message], NoError> = context.engine.messages.searchMessages(location: .peer(peerId: peerId, fromId: nil, tags: tagMask, topMsgId: nil, minDate: nil, maxDate: nil), query: query, state: nil) + let foundRemoteMessages: Signal<[Message], NoError> = context.engine.messages.searchMessages(location: .peer(peerId: peerId, fromId: nil, tags: tagMask, topMsgId: threadId.flatMap { makeThreadIdMessageId(peerId: peerId, threadId: $0) }, minDate: nil, maxDate: nil), query: query, state: nil) |> map { $0.0.messages } |> delay(0.2, queue: Queue.concurrentDefaultQueue()) diff --git a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift index b4f9f4c6ea..d377a652ac 100644 --- a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift @@ -229,6 +229,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in + }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, updatePeerGrouping: { _, _ in diff --git a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift index b59833c101..a8a467523a 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift @@ -147,7 +147,7 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { } self.statusPromise.set(context.engine.data.subscribe( - TelegramEngine.EngineData.Item.Messages.MessageCount(peerId: peerId, tag: tagMask) + TelegramEngine.EngineData.Item.Messages.MessageCount(peerId: peerId, threadId: chatLocation.threadId, tag: tagMask) ) |> map { count -> PeerInfoStatusData? in let count: Int = count ?? 0 diff --git a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift index 56a39b7727..842c815df8 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift @@ -2031,7 +2031,7 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro } return context.engine.data.subscribe(EngineDataMap( - summaries.map { TelegramEngine.EngineData.Item.Messages.MessageCount(peerId: peerId, tag: $0) } + summaries.map { TelegramEngine.EngineData.Item.Messages.MessageCount(peerId: peerId, threadId: chatLocation.threadId, tag: $0) } )) |> map { summaries -> (ContentType, [MessageTags: Int32]) in var result: [MessageTags: Int32] = [:] diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift index 46f26eb955..28c6916643 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift @@ -187,7 +187,7 @@ final class PeerInfoScreenData { let invitations: PeerExportedInvitationsState? let requests: PeerInvitationImportersState? let requestsContext: PeerInvitationImportersContext? - let threadInfo: EngineMessageHistoryThread.Info? + let threadData: MessageHistoryThreadData? init( peer: Peer?, @@ -206,7 +206,7 @@ final class PeerInfoScreenData { invitations: PeerExportedInvitationsState?, requests: PeerInvitationImportersState?, requestsContext: PeerInvitationImportersContext?, - threadInfo: EngineMessageHistoryThread.Info? + threadData: MessageHistoryThreadData? ) { self.peer = peer self.chatPeer = chatPeer @@ -224,7 +224,7 @@ final class PeerInfoScreenData { self.invitations = invitations self.requests = requests self.requestsContext = requestsContext - self.threadInfo = threadInfo + self.threadData = threadData } } @@ -477,7 +477,7 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id, invitations: nil, requests: nil, requestsContext: nil, - threadInfo: nil + threadData: nil ) } } @@ -504,7 +504,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen invitations: nil, requests: nil, requestsContext: nil, - threadInfo: nil + threadData: nil )) case let .user(userPeerId, secretChatId, kind): let groupsInCommon: GroupsInCommonContext? @@ -635,7 +635,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen invitations: nil, requests: nil, requestsContext: nil, - threadInfo: nil + threadData: nil ) } case .channel: @@ -711,7 +711,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen invitations: invitations, requests: requests, requestsContext: currentRequestsContext, - threadInfo: nil + threadData: nil ) } case let .group(groupId): @@ -815,19 +815,19 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen let requestsContextPromise = Promise(nil) let requestsStatePromise = Promise(nil) - let threadInfo: Signal + let threadData: Signal if case let .replyThread(message) = chatLocation { let threadId = Int64(message.messageId.id) let viewKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: peerId, threadId: threadId) - threadInfo = context.account.postbox.combinedView(keys: [viewKey]) - |> map { views -> EngineMessageHistoryThread.Info? in + threadData = context.account.postbox.combinedView(keys: [viewKey]) + |> map { views -> MessageHistoryThreadData? in guard let view = views.views[viewKey] as? MessageHistoryThreadInfoView else { return nil } - return view.info?.get(MessageHistoryThreadData.self)?.info + return view.info?.get(MessageHistoryThreadData.self) } } else { - threadInfo = .single(nil) + threadData = .single(nil) } return combineLatest(queue: .mainQueue(), @@ -840,9 +840,9 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen invitationsStatePromise.get(), requestsContextPromise.get(), requestsStatePromise.get(), - threadInfo + threadData ) - |> map { peerView, availablePanes, globalNotificationSettings, status, membersData, currentInvitationsContext, invitations, currentRequestsContext, requests, threadInfo -> PeerInfoScreenData in + |> map { peerView, availablePanes, globalNotificationSettings, status, membersData, currentInvitationsContext, invitations, currentRequestsContext, requests, threadData -> PeerInfoScreenData in var discussionPeer: Peer? if case let .known(maybeLinkedDiscussionPeerId) = (peerView.cachedData as? CachedChannelData)?.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = peerView.peers[linkedDiscussionPeerId] { discussionPeer = peer @@ -882,13 +882,20 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen requestsStatePromise.set(requestsContext.state |> map(Optional.init)) } } + + var notificationSettings: TelegramPeerNotificationSettings? + if let threadData = threadData { + notificationSettings = threadData.notificationSettings + } else { + notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings + } return PeerInfoScreenData( peer: peerView.peers[groupId], chatPeer: peerView.peers[groupId], cachedData: peerView.cachedData, status: status, - notificationSettings: peerView.notificationSettings as? TelegramPeerNotificationSettings, + notificationSettings: notificationSettings, globalNotificationSettings: globalNotificationSettings, isContact: peerView.peerIsContact, availablePanes: availablePanes ?? [], @@ -900,20 +907,29 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen invitations: invitations, requests: requests, requestsContext: currentRequestsContext, - threadInfo: threadInfo + threadData: threadData ) } } } } -func canEditPeerInfo(context: AccountContext, peer: Peer?) -> Bool { +func canEditPeerInfo(context: AccountContext, peer: Peer?, threadData: MessageHistoryThreadData?) -> Bool { if context.account.peerId == peer?.id { return true } if let channel = peer as? TelegramChannel { - if channel.hasPermission(.changeInfo) { - return true + if let threadData = threadData { + if channel.hasPermission(.pinMessages) { + return true + } + if threadData.author == context.account.peerId { + return true + } + } else { + if channel.hasPermission(.changeInfo) { + return true + } } } else if let group = peer as? TelegramGroup { switch group.role { diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index 0065dec29e..642feed5ab 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -634,7 +634,7 @@ final class PeerInfoEditingAvatarOverlayNode: ASDisplayNode { transition.updateAlpha(node: self, alpha: 1.0 - fraction) } - func update(peer: Peer?, item: PeerInfoAvatarListItem?, updatingAvatar: PeerInfoUpdatingAvatar?, uploadProgress: CGFloat?, theme: PresentationTheme, avatarSize: CGFloat, isEditing: Bool) { + func update(peer: Peer?, threadData: MessageHistoryThreadData?, item: PeerInfoAvatarListItem?, updatingAvatar: PeerInfoUpdatingAvatar?, uploadProgress: CGFloat?, theme: PresentationTheme, avatarSize: CGFloat, isEditing: Bool) { guard let peer = peer else { return } @@ -644,7 +644,7 @@ final class PeerInfoEditingAvatarOverlayNode: ASDisplayNode { let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .linear) - if canEditPeerInfo(context: self.context, peer: peer) { + if canEditPeerInfo(context: self.context, peer: peer, threadData: threadData) { var overlayHidden = true if let updatingAvatar = updatingAvatar { overlayHidden = false @@ -730,12 +730,12 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode { } var removedPhotoResourceIds = Set() - func update(peer: Peer?, item: PeerInfoAvatarListItem?, updatingAvatar: PeerInfoUpdatingAvatar?, uploadProgress: CGFloat?, theme: PresentationTheme, avatarSize: CGFloat, isEditing: Bool) { + func update(peer: Peer?, threadData: MessageHistoryThreadData?, item: PeerInfoAvatarListItem?, updatingAvatar: PeerInfoUpdatingAvatar?, uploadProgress: CGFloat?, theme: PresentationTheme, avatarSize: CGFloat, isEditing: Bool) { guard let peer = peer else { return } - let canEdit = canEditPeerInfo(context: self.context, peer: peer) + let canEdit = canEditPeerInfo(context: self.context, peer: peer, threadData: threadData) let previousItem = self.item var item = item @@ -1906,14 +1906,14 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode { self.itemNodes[key]?.layer.addShakeAnimation() } - func update(width: CGFloat, safeInset: CGFloat, statusBarHeight: CGFloat, navigationHeight: CGFloat, isModalOverlay: Bool, peer: Peer?, cachedData: CachedPeerData?, isContact: Bool, isSettings: Bool, presentationData: PresentationData, transition: ContainedViewLayoutTransition) -> CGFloat { + func update(width: CGFloat, safeInset: CGFloat, statusBarHeight: CGFloat, navigationHeight: CGFloat, isModalOverlay: Bool, peer: Peer?, threadData: MessageHistoryThreadData?, cachedData: CachedPeerData?, isContact: Bool, isSettings: Bool, presentationData: PresentationData, transition: ContainedViewLayoutTransition) -> CGFloat { let avatarSize: CGFloat = isModalOverlay ? 200.0 : 100.0 let avatarFrame = CGRect(origin: CGPoint(x: floor((width - avatarSize) / 2.0), y: statusBarHeight + 13.0), size: CGSize(width: avatarSize, height: avatarSize)) transition.updateFrameAdditiveToCenter(node: self.avatarNode, frame: CGRect(origin: avatarFrame.center, size: CGSize())) var contentHeight: CGFloat = statusBarHeight + 10.0 + avatarSize + 20.0 - if canEditPeerInfo(context: self.context, peer: peer) { + if canEditPeerInfo(context: self.context, peer: peer, threadData: threadData) { if self.avatarButtonNode.supernode == nil { self.addSubnode(self.avatarButtonNode) } @@ -1936,12 +1936,12 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode { } } else if let _ = peer as? TelegramGroup { fieldKeys.append(.title) - if canEditPeerInfo(context: self.context, peer: peer) { + if canEditPeerInfo(context: self.context, peer: peer, threadData: threadData) { fieldKeys.append(.description) } } else if let _ = peer as? TelegramChannel { fieldKeys.append(.title) - if canEditPeerInfo(context: self.context, peer: peer) { + if canEditPeerInfo(context: self.context, peer: peer, threadData: threadData) { fieldKeys.append(.description) } } @@ -1995,10 +1995,10 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode { } else { placeholder = presentationData.strings.GroupInfo_GroupNamePlaceholder } - isEnabled = canEditPeerInfo(context: self.context, peer: peer) + isEnabled = canEditPeerInfo(context: self.context, peer: peer, threadData: threadData) case .description: placeholder = presentationData.strings.Channel_Edit_AboutItem - isEnabled = canEditPeerInfo(context: self.context, peer: peer) + isEnabled = canEditPeerInfo(context: self.context, peer: peer, threadData: threadData) } let itemHeight = itemNode.update(width: width, safeInset: safeInset, isSettings: isSettings, hasPrevious: hasPrevious, hasNext: key != fieldKeys.last, placeholder: placeholder, isEnabled: isEnabled, presentationData: presentationData, updateText: updateText) transition.updateFrame(node: itemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: width, height: itemHeight))) @@ -2029,6 +2029,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { private var presentationData: PresentationData? private var state: PeerInfoState? private var peer: Peer? + private var threadData: MessageHistoryThreadData? private var avatarSize: CGFloat? private let isOpenedFromChat: Bool @@ -2225,7 +2226,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { guard let strongSelf = self, let state = strongSelf.state, let peer = strongSelf.peer, let presentationData = strongSelf.presentationData, let avatarSize = strongSelf.avatarSize else { return } - strongSelf.editingContentNode.avatarNode.update(peer: peer, item: strongSelf.avatarListNode.item, updatingAvatar: state.updatingAvatar, uploadProgress: state.avatarUploadProgress, theme: presentationData.theme, avatarSize: avatarSize, isEditing: state.isEditing) + strongSelf.editingContentNode.avatarNode.update(peer: peer, threadData: strongSelf.threadData, item: strongSelf.avatarListNode.item, updatingAvatar: state.updatingAvatar, uploadProgress: state.avatarUploadProgress, theme: presentationData.theme, avatarSize: avatarSize, isEditing: state.isEditing) } self.avatarListNode.animateOverlaysFadeIn = { [weak self] in @@ -2344,9 +2345,10 @@ final class PeerInfoHeaderNode: ASDisplayNode { private var currentCredibilityIcon: CredibilityIcon? private var currentPanelStatusData: PeerInfoStatusData? - func update(width: CGFloat, containerHeight: CGFloat, containerInset: CGFloat, statusBarHeight: CGFloat, navigationHeight: CGFloat, isModalOverlay: Bool, isMediaOnly: Bool, contentOffset: CGFloat, paneContainerY: CGFloat, presentationData: PresentationData, peer: Peer?, cachedData: CachedPeerData?, threadInfo: EngineMessageHistoryThread.Info?, notificationSettings: TelegramPeerNotificationSettings?, statusData: PeerInfoStatusData?, panelStatusData: (PeerInfoStatusData?, PeerInfoStatusData?, CGFloat?), isSecretChat: Bool, isContact: Bool, isSettings: Bool, state: PeerInfoState, metrics: LayoutMetrics, transition: ContainedViewLayoutTransition, additive: Bool) -> CGFloat { + func update(width: CGFloat, containerHeight: CGFloat, containerInset: CGFloat, statusBarHeight: CGFloat, navigationHeight: CGFloat, isModalOverlay: Bool, isMediaOnly: Bool, contentOffset: CGFloat, paneContainerY: CGFloat, presentationData: PresentationData, peer: Peer?, cachedData: CachedPeerData?, threadData: MessageHistoryThreadData?, notificationSettings: TelegramPeerNotificationSettings?, statusData: PeerInfoStatusData?, panelStatusData: (PeerInfoStatusData?, PeerInfoStatusData?, CGFloat?), isSecretChat: Bool, isContact: Bool, isSettings: Bool, state: PeerInfoState, metrics: LayoutMetrics, transition: ContainedViewLayoutTransition, additive: Bool) -> CGFloat { self.state = state self.peer = peer + self.threadData = threadData self.avatarListNode.listContainerNode.peer = peer let previousPanelStatusData = self.currentPanelStatusData @@ -2503,7 +2505,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { self.buttonsContainerNode.alpha = self.regularContentNode.alpha self.editingContentNode.alpha = state.isEditing ? 1.0 : 0.0 - let editingContentHeight = self.editingContentNode.update(width: width, safeInset: containerInset, statusBarHeight: statusBarHeight, navigationHeight: navigationHeight, isModalOverlay: isModalOverlay, peer: state.isEditing ? peer : nil, cachedData: cachedData, isContact: isContact, isSettings: isSettings, presentationData: presentationData, transition: transition) + let editingContentHeight = self.editingContentNode.update(width: width, safeInset: containerInset, statusBarHeight: statusBarHeight, navigationHeight: navigationHeight, isModalOverlay: isModalOverlay, peer: state.isEditing ? peer : nil, threadData: threadData, cachedData: cachedData, isContact: isContact, isSettings: isSettings, presentationData: presentationData, transition: transition) transition.updateFrame(node: self.editingContentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -contentOffset), size: CGSize(width: width, height: editingContentHeight))) let avatarOverlayFarme = self.editingContentNode.convert(self.editingContentNode.avatarNode.frame, to: self) @@ -2567,7 +2569,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { let expandedAvatarListHeight = min(width, containerHeight - expandedAvatarControlsHeight) let expandedAvatarListSize = CGSize(width: width, height: expandedAvatarListHeight) - let buttonKeys: [PeerInfoHeaderButtonKey] = self.isSettings ? [] : peerInfoHeaderButtons(peer: peer, cachedData: cachedData, isOpenedFromChat: self.isOpenedFromChat, isExpanded: true, videoCallsEnabled: width > 320.0 && self.videoCallsEnabled, isSecretChat: isSecretChat, isContact: isContact, threadInfo: threadInfo) + let buttonKeys: [PeerInfoHeaderButtonKey] = self.isSettings ? [] : peerInfoHeaderButtons(peer: peer, cachedData: cachedData, isOpenedFromChat: self.isOpenedFromChat, isExpanded: true, videoCallsEnabled: width > 320.0 && self.videoCallsEnabled, isSecretChat: isSecretChat, isContact: isContact, threadInfo: threadData?.info) var isPremium = false var isVerified = false @@ -2591,8 +2593,8 @@ final class PeerInfoHeaderNode: ASDisplayNode { title = presentationData.strings.Conversation_SavedMessages } else if peer.id == self.context.account.peerId && !self.isSettings { title = presentationData.strings.DialogList_Replies - } else if let threadInfo = threadInfo { - title = threadInfo.title + } else if let threadData = threadData { + title = threadData.info.title } else { title = EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) } @@ -2619,7 +2621,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { smallSubtitleString = NSAttributedString(string: subtitle, font: Font.regular(15.0), textColor: UIColor(rgb: 0xffffff, alpha: 0.7)) subtitleString = NSAttributedString(string: subtitle, font: Font.regular(17.0), textColor: presentationData.theme.list.itemSecondaryTextColor) usernameString = NSAttributedString(string: "", font: Font.regular(15.0), textColor: presentationData.theme.list.itemSecondaryTextColor) - } else if let _ = threadInfo { + } else if let _ = threadData { let subtitleColor: UIColor = presentationData.theme.list.itemSecondaryTextColor //TODO:localize @@ -2909,9 +2911,9 @@ final class PeerInfoHeaderNode: ASDisplayNode { }) } - self.avatarListNode.update(size: CGSize(), avatarSize: avatarSize, isExpanded: self.isAvatarExpanded, peer: peer, threadInfo: threadInfo, theme: presentationData.theme, transition: transition) - self.editingContentNode.avatarNode.update(peer: peer, item: self.avatarListNode.item, updatingAvatar: state.updatingAvatar, uploadProgress: state.avatarUploadProgress, theme: presentationData.theme, avatarSize: avatarSize, isEditing: state.isEditing) - self.avatarOverlayNode.update(peer: peer, item: self.avatarListNode.item, updatingAvatar: state.updatingAvatar, uploadProgress: state.avatarUploadProgress, theme: presentationData.theme, avatarSize: avatarSize, isEditing: state.isEditing) + self.avatarListNode.update(size: CGSize(), avatarSize: avatarSize, isExpanded: self.isAvatarExpanded, peer: peer, threadInfo: threadData?.info, theme: presentationData.theme, transition: transition) + self.editingContentNode.avatarNode.update(peer: peer, threadData: threadData, item: self.avatarListNode.item, updatingAvatar: state.updatingAvatar, uploadProgress: state.avatarUploadProgress, theme: presentationData.theme, avatarSize: avatarSize, isEditing: state.isEditing) + self.avatarOverlayNode.update(peer: peer, threadData: threadData, item: self.avatarListNode.item, updatingAvatar: state.updatingAvatar, uploadProgress: state.avatarUploadProgress, theme: presentationData.theme, avatarSize: avatarSize, isEditing: state.isEditing) if additive { transition.updateSublayerTransformScaleAdditive(node: self.avatarListNode.avatarContainerNode, scale: avatarScale) transition.updateSublayerTransformScaleAdditive(node: self.avatarOverlayNode, scale: avatarScale) diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index f7185661c3..6b07d42321 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -1078,7 +1078,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese let ItemMembers = 6 let ItemMemberRequests = 7 - if let _ = data.threadInfo { + if let _ = data.threadData { let mainUsername: String if let addressName = channel.addressName { mainUsername = addressName @@ -1599,7 +1599,7 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr } } - if cachedData.flags.contains(.canSetStickerSet) && canEditPeerInfo(context: context, peer: channel) { + if cachedData.flags.contains(.canSetStickerSet) && canEditPeerInfo(context: context, peer: channel, threadData: data.threadData) { items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemStickerPack, label: .text(cachedData.stickerPack?.title ?? presentationData.strings.GroupInfo_SharedMediaNone), text: presentationData.strings.Stickers_GroupStickers, icon: UIImage(bundleImageName: "Settings/Menu/Stickers"), action: { interaction.editingOpenStickerPackSetup() })) @@ -2817,7 +2817,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate case .edit: if case let .replyThread(message) = strongSelf.chatLocation { let threadId = Int64(message.messageId.id) - if let threadInfo = strongSelf.data?.threadInfo { + if let threadInfo = strongSelf.data?.threadData?.info { let controller = ForumCreateTopicScreen(context: strongSelf.context, peerId: strongSelf.peerId, mode: .edit(topic: threadInfo)) controller.navigationPresentation = .modal let context = strongSelf.context @@ -2990,7 +2990,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate } else { strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel, nil, nil) } - } else if let group = data.peer as? TelegramGroup, canEditPeerInfo(context: strongSelf.context, peer: group) { + } else if let group = data.peer as? TelegramGroup, canEditPeerInfo(context: strongSelf.context, peer: group, threadData: data.threadData) { let title = strongSelf.headerNode.editingContentNode.editingTextForKey(.title) ?? "" let description = strongSelf.headerNode.editingContentNode.editingTextForKey(.description) ?? "" @@ -3049,7 +3049,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel, nil, nil) })) } - } else if let channel = data.peer as? TelegramChannel, canEditPeerInfo(context: strongSelf.context, peer: channel) { + } else if let channel = data.peer as? TelegramChannel, canEditPeerInfo(context: strongSelf.context, peer: channel, threadData: data.threadData) { let title = strongSelf.headerNode.editingContentNode.editingTextForKey(.title) ?? "" let description = strongSelf.headerNode.editingContentNode.editingTextForKey(.description) ?? "" @@ -3383,7 +3383,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate } }) - self.refreshMessageTagStatsDisposable = context.engine.messages.refreshMessageTagStats(peerId: peerId, tags: [.video, .photo, .gif, .music, .voiceOrInstantVideo, .webPage, .file]).start() + self.refreshMessageTagStatsDisposable = context.engine.messages.refreshMessageTagStats(peerId: peerId, threadId: chatLocation.threadId, tags: [.video, .photo, .gif, .music, .voiceOrInstantVideo, .webPage, .file]).start() } deinit { @@ -3557,7 +3557,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate } self.view.endEditing(true) - return self.context.sharedContext.openChatMessage(OpenChatMessageParams(context: self.context, chatLocation: nil, chatLocationContextHolder: nil, message: galleryMessage, standalone: false, reverseMessageGalleryOrder: true, navigationController: navigationController, dismissInput: { [weak self] in + return self.context.sharedContext.openChatMessage(OpenChatMessageParams(context: self.context, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, message: galleryMessage, standalone: false, reverseMessageGalleryOrder: true, navigationController: navigationController, dismissInput: { [weak self] in self?.view.endEditing(true) }, present: { [weak self] c, a in self?.controller?.present(c, in: .window(.root), with: a, blockInteraction: true) @@ -3919,7 +3919,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate self.requestCall(isVideo: false, gesture: gesture) case .mute: if let notificationSettings = self.data?.notificationSettings, case .muted = notificationSettings.muteState { - let _ = self.context.engine.peers.updatePeerMuteSetting(peerId: self.peerId, muteInterval: nil).start() + let _ = self.context.engine.peers.updatePeerMuteSetting(peerId: self.peerId, threadId: self.chatLocation.threadId, muteInterval: nil).start() let iconColor: UIColor = .white self.controller?.present(UndoOverlayController(presentationData: self.presentationData, content: .universal(animation: "anim_profileunmute", scale: 0.075, colors: [ @@ -3968,7 +3968,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate guard let strongSelf = self else { return } - let _ = strongSelf.context.engine.peers.updatePeerMuteSetting(peerId: strongSelf.peerId, muteInterval: value).start() + let _ = strongSelf.context.engine.peers.updatePeerMuteSetting(peerId: strongSelf.peerId, threadId: strongSelf.chatLocation.threadId, muteInterval: value).start() strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .universal(animation: "anim_mute_for", scale: 0.066, colors: [:], title: nil, text: strongSelf.presentationData.strings.PeerInfo_TooltipMutedFor(mutedForTimeIntervalString(strings: strongSelf.presentationData.strings, value: value)).string), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) }))) @@ -4006,7 +4006,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate guard let strongSelf = self else { return } - let _ = strongSelf.context.engine.peers.updatePeerNotificationSoundInteractive(peerId: strongSelf.peerId, sound: .default).start() + let _ = strongSelf.context.engine.peers.updatePeerNotificationSoundInteractive(peerId: strongSelf.peerId, threadId: strongSelf.chatLocation.threadId, sound: .default).start() strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .universal(animation: "anim_sound_on", scale: 0.056, colors: [:], title: nil, text: strongSelf.presentationData.strings.PeerInfo_TooltipSoundEnabled), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) }))) @@ -4019,7 +4019,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate guard let strongSelf = self else { return } - let _ = strongSelf.context.engine.peers.updatePeerNotificationSoundInteractive(peerId: strongSelf.peerId, sound: .none).start() + let _ = strongSelf.context.engine.peers.updatePeerNotificationSoundInteractive(peerId: strongSelf.peerId, threadId: strongSelf.chatLocation.threadId, sound: .none).start() strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .universal(animation: "anim_sound_off", scale: 0.056, colors: [:], title: nil, text: strongSelf.presentationData.strings.PeerInfo_TooltipSoundDisabled), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) }))) @@ -4033,19 +4033,20 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate guard let strongSelf = self, let peer = strongSelf.data?.peer else { return } + let threadId = strongSelf.chatLocation.threadId let context = strongSelf.context let updatePeerSound: (PeerId, PeerMessageSound) -> Signal = { peerId, sound in - return context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, sound: sound) |> deliverOnMainQueue + return context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, threadId: threadId, sound: sound) |> deliverOnMainQueue } let updatePeerNotificationInterval: (PeerId, Int32?) -> Signal = { peerId, muteInterval in - return context.engine.peers.updatePeerMuteSetting(peerId: peerId, muteInterval: muteInterval) |> deliverOnMainQueue + return context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: muteInterval) |> deliverOnMainQueue } - let updatePeerDisplayPreviews:(PeerId, PeerNotificationDisplayPreviews) -> Signal = { + let updatePeerDisplayPreviews: (PeerId, PeerNotificationDisplayPreviews) -> Signal = { peerId, displayPreviews in - return context.engine.peers.updatePeerDisplayPreviewsSetting(peerId: peerId, displayPreviews: displayPreviews) |> deliverOnMainQueue + return context.engine.peers.updatePeerDisplayPreviewsSetting(peerId: peerId, threadId: threadId, displayPreviews: displayPreviews) |> deliverOnMainQueue } let mode: NotificationExceptionMode @@ -4063,7 +4064,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate mode = .groups([:]) } - let exceptionController = notificationPeerExceptionController(context: context, updatedPresentationData: strongSelf.controller?.updatedPresentationData, peer: peer, mode: mode, edit: true, updatePeerSound: { peerId, sound in + let exceptionController = notificationPeerExceptionController(context: context, updatedPresentationData: strongSelf.controller?.updatedPresentationData, peer: peer, threadId: threadId, mode: mode, edit: true, updatePeerSound: { peerId, sound in let _ = (updatePeerSound(peer.id, sound) |> deliverOnMainQueue).start(next: { _ in @@ -4107,7 +4108,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate return } - let _ = strongSelf.context.engine.peers.updatePeerMuteSetting(peerId: strongSelf.peerId, muteInterval: Int32.max).start() + let _ = strongSelf.context.engine.peers.updatePeerMuteSetting(peerId: strongSelf.peerId, threadId: strongSelf.chatLocation.threadId, muteInterval: Int32.max).start() let iconColor: UIColor = .white strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .universal(animation: "anim_profilemute", scale: 0.075, colors: [ @@ -4151,8 +4152,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate return .single(items) } - let allHeaderButtons = Set(peerInfoHeaderButtons(peer: peer, cachedData: data.cachedData, isOpenedFromChat: strongSelf.isOpenedFromChat, isExpanded: false, videoCallsEnabled: strongSelf.videoCallsEnabled, isSecretChat: strongSelf.peerId.namespace == Namespaces.Peer.SecretChat, isContact: strongSelf.data?.isContact ?? false, threadInfo: data.threadInfo)) - let headerButtons = Set(peerInfoHeaderButtons(peer: peer, cachedData: data.cachedData, isOpenedFromChat: strongSelf.isOpenedFromChat, isExpanded: true, videoCallsEnabled: strongSelf.videoCallsEnabled, isSecretChat: strongSelf.peerId.namespace == Namespaces.Peer.SecretChat, isContact: strongSelf.data?.isContact ?? false, threadInfo: strongSelf.data?.threadInfo)) + let allHeaderButtons = Set(peerInfoHeaderButtons(peer: peer, cachedData: data.cachedData, isOpenedFromChat: strongSelf.isOpenedFromChat, isExpanded: false, videoCallsEnabled: strongSelf.videoCallsEnabled, isSecretChat: strongSelf.peerId.namespace == Namespaces.Peer.SecretChat, isContact: strongSelf.data?.isContact ?? false, threadInfo: data.threadData?.info)) + let headerButtons = Set(peerInfoHeaderButtons(peer: peer, cachedData: data.cachedData, isOpenedFromChat: strongSelf.isOpenedFromChat, isExpanded: true, videoCallsEnabled: strongSelf.videoCallsEnabled, isSecretChat: strongSelf.peerId.namespace == Namespaces.Peer.SecretChat, isContact: strongSelf.data?.isContact ?? false, threadInfo: strongSelf.data?.threadData?.info)) let filteredButtons = allHeaderButtons.subtracting(headerButtons) @@ -4820,9 +4821,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate return } if value <= 0 { - let _ = strongSelf.context.engine.peers.updatePeerMuteSetting(peerId: strongSelf.peerId, muteInterval: nil).start() + let _ = strongSelf.context.engine.peers.updatePeerMuteSetting(peerId: strongSelf.peerId, threadId: strongSelf.chatLocation.threadId, muteInterval: nil).start() } else { - let _ = strongSelf.context.engine.peers.updatePeerMuteSetting(peerId: strongSelf.peerId, muteInterval: value).start() + let _ = strongSelf.context.engine.peers.updatePeerMuteSetting(peerId: strongSelf.peerId, threadId: strongSelf.chatLocation.threadId, muteInterval: value).start() let timeString = stringForPreciseRelativeTimestamp(strings: strongSelf.presentationData.strings, relativeTimestamp: Int32(Date().timeIntervalSince1970) + value, relativeTo: Int32(Date().timeIntervalSince1970), dateTimeFormat: strongSelf.presentationData.dateTimeFormat) @@ -5269,7 +5270,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate guard let strongSelf = self else { return } - let _ = strongSelf.context.engine.peers.updatePeerNotificationSoundInteractive(peerId: strongSelf.peerId, sound: sound).start() + let _ = strongSelf.context.engine.peers.updatePeerNotificationSoundInteractive(peerId: strongSelf.peerId, threadId: strongSelf.chatLocation.threadId, sound: sound).start() }) soundController.navigationPresentation = .modal strongSelf.controller?.push(soundController) @@ -5277,7 +5278,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate guard let strongSelf = self else { return } - let _ = strongSelf.context.engine.peers.updatePeerMuteSetting(peerId: strongSelf.peerId, muteInterval: value).start() + let _ = strongSelf.context.engine.peers.updatePeerMuteSetting(peerId: strongSelf.peerId, threadId: strongSelf.chatLocation.threadId, muteInterval: value).start() }) strongSelf.view.endEditing(true) strongSelf.controller?.present(muteSettingsController, in: .window(.root)) @@ -5298,7 +5299,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate guard let strongSelf = self else { return } - let _ = strongSelf.context.engine.peers.updatePeerNotificationSoundInteractive(peerId: strongSelf.peerId, sound: sound).start() + let _ = strongSelf.context.engine.peers.updatePeerNotificationSoundInteractive(peerId: strongSelf.peerId, threadId: strongSelf.chatLocation.threadId, sound: sound).start() }) strongSelf.controller?.push(soundController) }) @@ -5310,7 +5311,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate guard let strongSelf = self, let peer = peer else { return } - let _ = strongSelf.context.engine.peers.updatePeerDisplayPreviewsSetting(peerId: peer.id, displayPreviews: value ? .show : .hide).start() + let _ = strongSelf.context.engine.peers.updatePeerDisplayPreviewsSetting(peerId: peer.id, threadId: strongSelf.chatLocation.threadId, displayPreviews: value ? .show : .hide).start() }) } @@ -6526,7 +6527,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate } private func openAvatarForEditing(fromGallery: Bool = false, completion: @escaping () -> Void = {}) { - guard let peer = self.data?.peer, canEditPeerInfo(context: self.context, peer: peer) else { + guard let peer = self.data?.peer, canEditPeerInfo(context: self.context, peer: peer, threadData: self.data?.threadData) else { return } @@ -7308,7 +7309,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate } } - self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, mode: .list, placeholder: self.presentationData.strings.Common_Search, hasBackground: true, contentNode: ChatHistorySearchContainerNode(context: self.context, peerId: self.peerId, tagMask: tagMask, interfaceInteraction: self.chatInterfaceInteraction), cancel: { [weak self] in + self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, mode: .list, placeholder: self.presentationData.strings.Common_Search, hasBackground: true, contentNode: ChatHistorySearchContainerNode(context: self.context, peerId: self.peerId, threadId: self.chatLocation.threadId, tagMask: tagMask, interfaceInteraction: self.chatInterfaceInteraction), cancel: { [weak self] in self?.deactivateSearch() }) } @@ -7364,8 +7365,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate let peerId = self.peerId let _ = (self.context.engine.data.get(EngineDataMap([ - TelegramEngine.EngineData.Item.Messages.MessageCount(peerId: peerId, tag: .photo), - TelegramEngine.EngineData.Item.Messages.MessageCount(peerId: peerId, tag: .video) + TelegramEngine.EngineData.Item.Messages.MessageCount(peerId: peerId, threadId: self.chatLocation.threadId, tag: .photo), + TelegramEngine.EngineData.Item.Messages.MessageCount(peerId: peerId, threadId: self.chatLocation.threadId, tag: .video) ])) |> deliverOnMainQueue).start(next: { [weak self] messageCounts in guard let strongSelf = self else { @@ -7417,17 +7418,19 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate items.append(.action(generateAction(false))) var ignoreNextActions = false - items.append(.action(ContextMenuActionItem(text: strings.SharedMedia_ShowCalendar, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Calendar"), color: theme.contextMenu.primaryColor) - }, action: { _, a in - if ignoreNextActions { - return - } - ignoreNextActions = true - a(.default) - - self?.openMediaCalendar() - }))) + if strongSelf.chatLocation.threadId == nil { + items.append(.action(ContextMenuActionItem(text: strings.SharedMedia_ShowCalendar, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Calendar"), color: theme.contextMenu.primaryColor) + }, action: { _, a in + if ignoreNextActions { + return + } + ignoreNextActions = true + a(.default) + + self?.openMediaCalendar() + }))) + } if photoCount != 0 && videoCount != 0 { items.append(.separator) @@ -7665,7 +7668,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate } let headerInset = sectionInset - var headerHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : self.scrollNode.view.contentOffset.y, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.peer, cachedData: self.data?.cachedData, threadInfo: self.data?.threadInfo, notificationSettings: self.data?.notificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, metrics: layout.metrics, transition: transition, additive: additive) + var headerHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : self.scrollNode.view.contentOffset.y, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.peer, cachedData: self.data?.cachedData, threadData: self.data?.threadData, notificationSettings: self.data?.notificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, metrics: layout.metrics, transition: transition, additive: additive) if !self.isSettings && !self.state.isEditing { headerHeight += 71.0 } @@ -8025,7 +8028,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate } let headerInset = sectionInset - let _ = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : offsetY, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.peer, cachedData: self.data?.cachedData, threadInfo: self.data?.threadInfo, notificationSettings: self.data?.notificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, metrics: layout.metrics, transition: transition, additive: additive) + let _ = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : offsetY, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.peer, cachedData: self.data?.cachedData, threadData: self.data?.threadData, notificationSettings: self.data?.notificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, metrics: layout.metrics, transition: transition, additive: additive) } let paneAreaExpansionDistance: CGFloat = 32.0 @@ -9042,7 +9045,7 @@ private final class PeerInfoNavigationTransitionNode: ASDisplayNode, CustomNavig } let headerInset = sectionInset - topHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: topNavigationBar.bounds.height, isModalOverlay: layout.isModalOverlay, isMediaOnly: false, contentOffset: 0.0, paneContainerY: 0.0, presentationData: self.presentationData, peer: self.screenNode.data?.peer, cachedData: self.screenNode.data?.cachedData, threadInfo: self.screenNode.data?.threadInfo, notificationSettings: self.screenNode.data?.notificationSettings, statusData: self.screenNode.data?.status, panelStatusData: (nil, nil, nil), isSecretChat: self.screenNode.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.screenNode.data?.isContact ?? false, isSettings: self.screenNode.isSettings, state: self.screenNode.state, metrics: layout.metrics, transition: transition, additive: false) + topHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: topNavigationBar.bounds.height, isModalOverlay: layout.isModalOverlay, isMediaOnly: false, contentOffset: 0.0, paneContainerY: 0.0, presentationData: self.presentationData, peer: self.screenNode.data?.peer, cachedData: self.screenNode.data?.cachedData, threadData: self.screenNode.data?.threadData, notificationSettings: self.screenNode.data?.notificationSettings, statusData: self.screenNode.data?.status, panelStatusData: (nil, nil, nil), isSecretChat: self.screenNode.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.screenNode.data?.isContact ?? false, isSettings: self.screenNode.isSettings, state: self.screenNode.state, metrics: layout.metrics, transition: transition, additive: false) } let titleScale = (fraction * previousTitleNode.view.bounds.height + (1.0 - fraction) * self.headerNode.titleNodeRawContainer.bounds.height) / previousTitleNode.view.bounds.height diff --git a/submodules/TelegramUI/Sources/PeerInfoGifPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfoGifPaneNode.swift index 6006332083..098d790c02 100644 --- a/submodules/TelegramUI/Sources/PeerInfoGifPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfoGifPaneNode.swift @@ -880,7 +880,7 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDe animationTimer.start() self.statusPromise.set(context.engine.data.subscribe( - TelegramEngine.EngineData.Item.Messages.MessageCount(peerId: peerId, tag: tagMaskForType(self.contentType)) + TelegramEngine.EngineData.Item.Messages.MessageCount(peerId: peerId, threadId: chatLocation.threadId, tag: tagMaskForType(self.contentType)) ) |> map { count -> PeerInfoStatusData? in let count: Int = count ?? 0 @@ -925,7 +925,7 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDe return } self.isRequestingView = true - self.listDisposable.set((self.context.account.viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId: self.peerId), index: .upperBound, anchorIndex: .upperBound, count: self.numberOfItemsToRequest, fixedCombinedReadStates: nil, tagMask: tagMaskForType(self.contentType)) + self.listDisposable.set((self.context.account.viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId: self.peerId, threadId: self.chatLocation.threadId), index: .upperBound, anchorIndex: .upperBound, count: self.numberOfItemsToRequest, fixedCombinedReadStates: nil, tagMask: tagMaskForType(self.contentType)) |> deliverOnMainQueue).start(next: { [weak self] (view, updateType, _) in guard let strongSelf = self else { return diff --git a/submodules/TelegramUI/Sources/PrefetchManager.swift b/submodules/TelegramUI/Sources/PrefetchManager.swift index a3cc9cc687..a017c0d106 100644 --- a/submodules/TelegramUI/Sources/PrefetchManager.swift +++ b/submodules/TelegramUI/Sources/PrefetchManager.swift @@ -213,7 +213,7 @@ private final class PrefetchManagerInnerImpl { context = PrefetchMediaContext() self.contexts[id] = context - let priority: FetchManagerPriority = .backgroundPrefetch(locationOrder: HistoryPreloadIndex(index: nil, hasUnread: false, isMuted: false, isPriority: true), localOrder: MessageIndex(id: MessageId(peerId: PeerId(0), namespace: 0, id: order), timestamp: 0)) + let priority: FetchManagerPriority = .backgroundPrefetch(locationOrder: HistoryPreloadIndex(index: nil, threadId: nil, hasUnread: false, isMuted: false, isPriority: true), localOrder: MessageIndex(id: MessageId(peerId: PeerId(0), namespace: 0, id: order), timestamp: 0)) if case .full = automaticDownload { let fetchSignal = freeMediaFileInteractiveFetched(fetchManager: self.fetchManager, fileReference: .standalone(media: media), priority: priority) diff --git a/submodules/WatchBridge/Sources/WatchRequestHandlers.swift b/submodules/WatchBridge/Sources/WatchRequestHandlers.swift index d3ec3fb8d3..d900a758e2 100644 --- a/submodules/WatchBridge/Sources/WatchRequestHandlers.swift +++ b/submodules/WatchBridge/Sources/WatchRequestHandlers.swift @@ -91,7 +91,7 @@ final class WatchChatMessagesHandler: WatchRequestHandler { |> take(1) |> mapToSignal({ context -> Signal<(MessageHistoryView, Bool, PresentationData), NoError> in if let context = context { - return context.account.viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId: peerId), index: .upperBound, anchorIndex: .upperBound, count: limit, fixedCombinedReadStates: nil) + return context.account.viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId: peerId, threadId: nil), index: .upperBound, anchorIndex: .upperBound, count: limit, fixedCombinedReadStates: nil) |> map { messageHistoryView, _, _ -> (MessageHistoryView, Bool, PresentationData) in return (messageHistoryView, peerId == context.account.peerId, context.sharedContext.currentPresentationData.with { $0 }) } @@ -833,7 +833,7 @@ final class WatchPeerSettingsHandler: WatchRequestHandler { var signal: Signal? if let args = subscription as? TGBridgePeerUpdateNotificationSettingsSubscription, let peerId = makePeerIdFromBridgeIdentifier(args.peerId) { - signal = context.engine.peers.togglePeerMuted(peerId: peerId) + signal = context.engine.peers.togglePeerMuted(peerId: peerId, threadId: nil) } else if let args = subscription as? TGBridgePeerUpdateBlockStatusSubscription, let peerId = makePeerIdFromBridgeIdentifier(args.peerId) { signal = context.engine.privacy.requestUpdatePeerIsBlocked(peerId: peerId, isBlocked: args.blocked) }