diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 6887e786cb..7037960b3e 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -748,7 +748,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController editItem.accessibilityLabel = strongSelf.presentationData.strings.Common_Edit strongSelf.navigationItem.setRightBarButton(editItem, animated: true) case .forum: - strongSelf.navigationItem.setRightBarButton(strongSelf.moreBarButtonItem, animated: true) + if strongSelf.navigationItem.rightBarButtonItem !== strongSelf.moreBarButtonItem { + strongSelf.navigationItem.setRightBarButton(strongSelf.moreBarButtonItem, animated: true) + } } } diff --git a/submodules/ContextUI/Sources/ContextActionsContainerNode.swift b/submodules/ContextUI/Sources/ContextActionsContainerNode.swift index 8802377b5e..9df04a41c2 100644 --- a/submodules/ContextUI/Sources/ContextActionsContainerNode.swift +++ b/submodules/ContextUI/Sources/ContextActionsContainerNode.swift @@ -344,6 +344,7 @@ private final class InnerActionsContainerNode: ASDisplayNode { final class InnerTextSelectionTipContainerNode: ASDisplayNode { private let presentationData: PresentationData + private let shadowNode: ASImageNode private var effectView: UIVisualEffectView? private let highlightBackgroundNode: ASDisplayNode private let buttonNode: HighlightTrackingButtonNode @@ -368,6 +369,13 @@ final class InnerTextSelectionTipContainerNode: ASDisplayNode { self.tip = tip self.presentationData = presentationData + self.shadowNode = ASImageNode() + self.shadowNode.displaysAsynchronously = false + self.shadowNode.displayWithoutProcessing = true + self.shadowNode.image = UIImage(bundleImageName: "Components/Context Menu/Shadow")?.stretchableImage(withLeftCapWidth: 60, topCapHeight: 60) + self.shadowNode.contentMode = .scaleToFill + self.shadowNode.isHidden = true + self.highlightBackgroundNode = ASDisplayNode() self.highlightBackgroundNode.isAccessibilityElement = false self.highlightBackgroundNode.alpha = 0.0 @@ -408,6 +416,12 @@ final class InnerTextSelectionTipContainerNode: ASDisplayNode { self.targetSelectionIndex = nil icon = nil isUserInteractionEnabled = text != nil + case let .notificationTopicExceptions(text, action): + self.action = action + self.text = text + self.targetSelectionIndex = nil + icon = nil + isUserInteractionEnabled = action != nil } self.iconNode = ASImageNode() @@ -499,7 +513,14 @@ final class InnerTextSelectionTipContainerNode: ASDisplayNode { } } - func updateLayout(widthClass: ContainerViewLayoutSizeClass, width: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { + func updateLayout(widthClass: ContainerViewLayoutSizeClass, presentation: ContextControllerActionsStackNode.Presentation, width: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { + switch presentation { + case .inline: + self.shadowNode.isHidden = true + case .modal: + self.shadowNode.isHidden = false + } + switch widthClass { case .compact: if let effectView = self.effectView { @@ -609,6 +630,9 @@ final class InnerTextSelectionTipContainerNode: ASDisplayNode { func setActualSize(size: CGSize, transition: ContainedViewLayoutTransition) { self.highlightBackgroundNode.frame = CGRect(origin: CGPoint(), size: size) self.buttonNode.frame = CGRect(origin: CGPoint(), size: size) + + let bounds = CGRect(origin: CGPoint(), size: size) + transition.updateFrame(node: self.shadowNode, frame: bounds.insetBy(dx: -30.0, dy: -30.0)) } func updateTheme(presentationData: PresentationData) { @@ -774,7 +798,7 @@ final class ContextActionsContainerNode: ASDisplayNode { self.textSelectionTipNodeDisposable?.dispose() } - func updateLayout(widthClass: ContainerViewLayoutSizeClass, constrainedWidth: CGFloat, constrainedHeight: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { + func updateLayout(widthClass: ContainerViewLayoutSizeClass, presentation: ContextControllerActionsStackNode.Presentation, constrainedWidth: CGFloat, constrainedHeight: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { var widthClass = widthClass if !self.blurBackground { widthClass = .regular @@ -829,7 +853,7 @@ final class ContextActionsContainerNode: ASDisplayNode { if let textSelectionTipNode = self.textSelectionTipNode { contentSize.height += 8.0 - let textSelectionTipSize = textSelectionTipNode.updateLayout(widthClass: widthClass, width: actionsSize.width, transition: transition) + let textSelectionTipSize = textSelectionTipNode.updateLayout(widthClass: widthClass, presentation: presentation, width: actionsSize.width, transition: transition) transition.updateFrame(node: textSelectionTipNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentSize.height), size: textSelectionTipSize)) textSelectionTipNode.setActualSize(size: textSelectionTipSize, transition: transition) contentSize.height += textSelectionTipSize.height diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index a140d8d7a6..4e76219f3c 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -1606,7 +1606,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi let isInitialLayout = self.actionsContainerNode.frame.size.width.isZero let previousContainerFrame = self.view.convert(self.contentContainerNode.frame, from: self.scrollNode.view) - let realActionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, constrainedWidth: layout.size.width - actionsSideInset * 2.0, constrainedHeight: layout.size.height, transition: actionsContainerTransition) + let realActionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, presentation: .inline, constrainedWidth: layout.size.width - actionsSideInset * 2.0, constrainedHeight: layout.size.height, transition: actionsContainerTransition) let adjustedActionsSize = realActionsSize self.actionsContainerNode.updateSize(containerSize: realActionsSize, contentSize: realActionsSize) @@ -1708,7 +1708,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi constrainedActionsBottomInset = 0.0 } - let realActionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, constrainedWidth: layout.size.width - actionsSideInset * 2.0, constrainedHeight: constrainedActionsHeight, transition: actionsContainerTransition) + let realActionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, presentation: .inline, constrainedWidth: layout.size.width - actionsSideInset * 2.0, constrainedHeight: constrainedActionsHeight, transition: actionsContainerTransition) let adjustedActionsSize = realActionsSize self.actionsContainerNode.updateSize(containerSize: realActionsSize, contentSize: realActionsSize) @@ -1867,7 +1867,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi constrainedWidth = floor(layout.size.width / 2.0) } - let actionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, constrainedWidth: constrainedWidth - actionsSideInset * 2.0, constrainedHeight: layout.size.height, transition: actionsContainerTransition) + let actionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, presentation: .inline, constrainedWidth: constrainedWidth - actionsSideInset * 2.0, constrainedHeight: layout.size.height, transition: actionsContainerTransition) let contentScale = (constrainedWidth - actionsSideInset * 2.0) / constrainedWidth var contentUnscaledSize: CGSize if case .compact = layout.metrics.widthClass { @@ -2357,7 +2357,8 @@ public final class ContextController: ViewController, StandalonePresentableContr case textSelection case messageViewsPrivacy case messageCopyProtection(isChannel: Bool) - case animatedEmoji(text: String?, arguments: TextNodeWithEntities.Arguments?, file: TelegramMediaFile?, action: (() -> Void)?) + case animatedEmoji(text: String?, arguments: TextNodeWithEntities.Arguments?, file: TelegramMediaFile?, action: (() -> Void)?) + case notificationTopicExceptions(text: String, action: (() -> Void)?) public static func ==(lhs: Tip, rhs: Tip) -> Bool { switch lhs { @@ -2391,6 +2392,12 @@ public final class ContextController: ViewController, StandalonePresentableContr } else { return false } + case let .notificationTopicExceptions(text, _): + if case .notificationTopicExceptions(text, _) = rhs { + return true + } else { + return false + } } } } diff --git a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift index e2d6de9998..eed5e5a376 100644 --- a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift @@ -963,7 +963,7 @@ final class ContextControllerActionsStackNode: ASDisplayNode { return (size, apparentHeight) } - func updateTip(presentationData: PresentationData, width: CGFloat, transition: ContainedViewLayoutTransition) -> (node: InnerTextSelectionTipContainerNode, height: CGFloat)? { + func updateTip(presentationData: PresentationData, presentation: ContextControllerActionsStackNode.Presentation, width: CGFloat, transition: ContainedViewLayoutTransition) -> (node: InnerTextSelectionTipContainerNode, height: CGFloat)? { if let tip = self.tip { var updatedTransition = transition if let tipNode = self.tipNode, tipNode.tip == tip { @@ -985,7 +985,7 @@ final class ContextControllerActionsStackNode: ASDisplayNode { } if let tipNode = self.tipNode { - let size = tipNode.updateLayout(widthClass: .compact, width: width, transition: updatedTransition) + let size = tipNode.updateLayout(widthClass: .compact, presentation: presentation, width: width, transition: updatedTransition) return (tipNode, size.height) } else { return nil @@ -1229,7 +1229,7 @@ final class ContextControllerActionsStackNode: ASDisplayNode { standardMaxWidth = 240.0 standardMinWidth = standardMaxWidth - if let (tipNode, tipHeight) = itemContainer.updateTip(presentationData: presentationData, width: standardMaxWidth, transition: itemContainerTransition) { + if let (tipNode, tipHeight) = itemContainer.updateTip(presentationData: presentationData, presentation: presentation, width: standardMaxWidth, transition: itemContainerTransition) { tip = TipLayout(tipNode: tipNode, tipHeight: tipHeight) additionalBottomInset = tipHeight + 10.0 } else { @@ -1256,7 +1256,7 @@ final class ContextControllerActionsStackNode: ASDisplayNode { } if !itemContainer.node.wantsFullWidth { - if let (tipNode, tipHeight) = itemContainer.updateTip(presentationData: presentationData, width: itemSize.size.width, transition: itemContainerTransition) { + if let (tipNode, tipHeight) = itemContainer.updateTip(presentationData: presentationData, presentation: presentation, width: itemSize.size.width, transition: itemContainerTransition) { tip = TipLayout(tipNode: tipNode, tipHeight: tipHeight) } } diff --git a/submodules/ContextUI/Sources/PeekControllerNode.swift b/submodules/ContextUI/Sources/PeekControllerNode.swift index 0ffc075cc9..f2bbdafc5b 100644 --- a/submodules/ContextUI/Sources/PeekControllerNode.swift +++ b/submodules/ContextUI/Sources/PeekControllerNode.swift @@ -172,7 +172,7 @@ final class PeekControllerNode: ViewControllerTracingNode { } let actionsSideInset: CGFloat = layout.safeInsets.left + 11.0 - let actionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, constrainedWidth: layout.size.width - actionsSideInset * 2.0, constrainedHeight: layout.size.height, transition: .immediate) + let actionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, presentation: .inline, constrainedWidth: layout.size.width - actionsSideInset * 2.0, constrainedHeight: layout.size.height, transition: .immediate) let containerFrame: CGRect let actionsFrame: CGRect diff --git a/submodules/Postbox/Sources/ChatListIndexTable.swift b/submodules/Postbox/Sources/ChatListIndexTable.swift index a09eff075b..f74c45dfdc 100644 --- a/submodules/Postbox/Sources/ChatListIndexTable.swift +++ b/submodules/Postbox/Sources/ChatListIndexTable.swift @@ -372,9 +372,9 @@ final class ChatListIndexTable: Table { } else { let previousCount: Int32 if let previousSummary = alteredInitialPeerThreadsSummaries[peerId] { - previousCount = previousSummary.unreadCount + previousCount = previousSummary.effectiveUnreadCount } else { - previousCount = postbox.peerThreadsSummaryTable.get(peerId: peerId)?.unreadCount ?? 0 + previousCount = postbox.peerThreadsSummaryTable.get(peerId: peerId)?.effectiveUnreadCount ?? 0 } initialReadState = CombinedPeerReadState(states: [(0, .idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: previousCount, markedUnread: false))]) } @@ -382,9 +382,9 @@ final class ChatListIndexTable: Table { if let peer = postbox.peerTable.get(peerId), postbox.seedConfiguration.peerSummaryIsThreadBased(peer) { let previousCount: Int32 if let previousSummary = alteredInitialPeerThreadsSummaries[peerId] { - previousCount = previousSummary.unreadCount + previousCount = previousSummary.effectiveUnreadCount } else { - previousCount = postbox.peerThreadsSummaryTable.get(peerId: peerId)?.unreadCount ?? 0 + previousCount = postbox.peerThreadsSummaryTable.get(peerId: peerId)?.effectiveUnreadCount ?? 0 } initialReadState = CombinedPeerReadState(states: [(0, .idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: previousCount, markedUnread: false))]) } else { @@ -394,7 +394,7 @@ final class ChatListIndexTable: Table { let currentReadState: CombinedPeerReadState? if let peer = postbox.peerTable.get(peerId), postbox.seedConfiguration.peerSummaryIsThreadBased(peer) { - let count = postbox.peerThreadsSummaryTable.get(peerId: peerId)?.unreadCount ?? 0 + let count = postbox.peerThreadsSummaryTable.get(peerId: peerId)?.effectiveUnreadCount ?? 0 currentReadState = CombinedPeerReadState(states: [(0, .idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: count, markedUnread: false))]) } else { currentReadState = postbox.readStateTable.getCombinedState(peerId) @@ -627,7 +627,7 @@ final class ChatListIndexTable: Table { let combinedState: CombinedPeerReadState? if postbox.seedConfiguration.peerSummaryIsThreadBased(peer) { - let count: Int32 = postbox.peerThreadsSummaryTable.get(peerId: peerId)?.unreadCount ?? 0 + let count: Int32 = postbox.peerThreadsSummaryTable.get(peerId: peerId)?.effectiveUnreadCount ?? 0 combinedState = CombinedPeerReadState(states: [(0, .idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: count, markedUnread: false))]) } else { combinedState = postbox.readStateTable.getCombinedState(peerId) @@ -719,7 +719,7 @@ final class ChatListIndexTable: Table { let combinedState: CombinedPeerReadState? if postbox.seedConfiguration.peerSummaryIsThreadBased(peer) { - let count: Int32 = postbox.peerThreadsSummaryTable.get(peerId: peerId)?.unreadCount ?? 0 + let count: Int32 = postbox.peerThreadsSummaryTable.get(peerId: peerId)?.effectiveUnreadCount ?? 0 combinedState = CombinedPeerReadState(states: [(0, .idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: count, markedUnread: false))]) } else { combinedState = postbox.readStateTable.getCombinedState(peerId) diff --git a/submodules/Postbox/Sources/ChatListView.swift b/submodules/Postbox/Sources/ChatListView.swift index 1374b9354b..6b31632c6f 100644 --- a/submodules/Postbox/Sources/ChatListView.swift +++ b/submodules/Postbox/Sources/ChatListView.swift @@ -492,8 +492,8 @@ final class MutableChatListView { let isUnread: Bool if postbox.seedConfiguration.peerSummaryIsThreadBased(peer) { - let count = postbox.peerThreadsSummaryTable.get(peerId: peer.id)?.unreadCount ?? 0 - isUnread = count > 0 + let hasUnmutedUnread = postbox.peerThreadsSummaryTable.get(peerId: peer.id)?.hasUnmutedUnread ?? false + isUnread = hasUnmutedUnread } else { isUnread = postbox.readStateTable.getCombinedState(peer.id)?.isUnread ?? false } @@ -570,8 +570,7 @@ final class MutableChatListView { let isUnread: Bool if let peer = groupEntries[i].renderedPeers[j].peer.peer, postbox.seedConfiguration.peerSummaryIsThreadBased(peer) { - let count = postbox.peerThreadsSummaryTable.get(peerId: peer.id)?.unreadCount ?? 0 - isUnread = count > 0 + isUnread = postbox.peerThreadsSummaryTable.get(peerId: peer.id)?.hasUnmutedUnread ?? false } else { isUnread = postbox.readStateTable.getCombinedState(groupEntries[i].renderedPeers[j].peer.peerId)?.isUnread ?? false } @@ -664,7 +663,7 @@ final class MutableChatListView { let readState: CombinedPeerReadState? if let peer = postbox.peerTable.get(index.messageIndex.id.peerId), postbox.seedConfiguration.peerSummaryIsThreadBased(peer) { - let count = postbox.peerThreadsSummaryTable.get(peerId: index.messageIndex.id.peerId)?.unreadCount ?? 0 + let count = postbox.peerThreadsSummaryTable.get(peerId: index.messageIndex.id.peerId)?.totalUnreadCount ?? 0 readState = CombinedPeerReadState(states: [(0, .idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: count, markedUnread: false))]) } else { readState = postbox.readStateTable.getCombinedState(index.messageIndex.id.peerId) diff --git a/submodules/Postbox/Sources/ChatListViewState.swift b/submodules/Postbox/Sources/ChatListViewState.swift index deb8d2b5bc..cdfc0e285d 100644 --- a/submodules/Postbox/Sources/ChatListViewState.swift +++ b/submodules/Postbox/Sources/ChatListViewState.swift @@ -61,7 +61,7 @@ private func mappedChatListFilterPredicate(postbox: PostboxImpl, currentTransact if let peer = postbox.peerTable.get(index.messageIndex.id.peerId) { var isUnread: Bool if postbox.seedConfiguration.peerSummaryIsThreadBased(peer) { - isUnread = (postbox.peerThreadsSummaryTable.get(peerId: peer.id)?.unreadCount ?? 0) > 0 + isUnread = (postbox.peerThreadsSummaryTable.get(peerId: peer.id)?.effectiveUnreadCount ?? 0) > 0 } else { isUnread = postbox.readStateTable.getCombinedState(index.messageIndex.id.peerId)?.isUnread ?? false } @@ -412,7 +412,7 @@ private final class ChatListViewSpaceState { var isUnread: Bool if postbox.seedConfiguration.peerSummaryIsThreadBased(peer) { - isUnread = (postbox.peerThreadsSummaryTable.get(peerId: peer.id)?.unreadCount ?? 0) > 0 + isUnread = (postbox.peerThreadsSummaryTable.get(peerId: peer.id)?.effectiveUnreadCount ?? 0) > 0 } else { isUnread = postbox.readStateTable.getCombinedState(index.messageIndex.id.peerId)?.isUnread ?? false } @@ -537,7 +537,7 @@ private final class ChatListViewSpaceState { if settingsChange != nil || transaction.updatedPeerThreadsSummaries.contains(entryNotificationsPeerId) { var isUnread: Bool if postbox.seedConfiguration.peerSummaryIsThreadBased(entryPeer) { - isUnread = (postbox.peerThreadsSummaryTable.get(peerId: entryPeer.id)?.unreadCount ?? 0) > 0 + isUnread = (postbox.peerThreadsSummaryTable.get(peerId: entryPeer.id)?.effectiveUnreadCount ?? 0) > 0 } else { isUnread = postbox.readStateTable.getCombinedState(entryPeer.id)?.isUnread ?? false } @@ -581,7 +581,7 @@ private final class ChatListViewSpaceState { var isUnread: Bool if postbox.seedConfiguration.peerSummaryIsThreadBased(mainPeer) { - isUnread = (postbox.peerThreadsSummaryTable.get(peerId: peerId)?.unreadCount ?? 0) > 0 + isUnread = (postbox.peerThreadsSummaryTable.get(peerId: peerId)?.effectiveUnreadCount ?? 0) > 0 } else { isUnread = postbox.readStateTable.getCombinedState(peerId)?.isUnread ?? false } @@ -784,7 +784,7 @@ private final class ChatListViewSpaceState { if updatedMessageSummary != nil || updatedActionsSummary != nil { var isUnread: Bool if postbox.seedConfiguration.peerSummaryIsThreadBased(entryPeer) { - isUnread = (postbox.peerThreadsSummaryTable.get(peerId: entryPeer.id)?.unreadCount ?? 0) > 0 + isUnread = (postbox.peerThreadsSummaryTable.get(peerId: entryPeer.id)?.effectiveUnreadCount ?? 0) > 0 } else { isUnread = postbox.readStateTable.getCombinedState(entryPeer.id)?.isUnread ?? false } @@ -834,7 +834,7 @@ private final class ChatListViewSpaceState { var isUnread: Bool if postbox.seedConfiguration.peerSummaryIsThreadBased(mainPeer) { - isUnread = (postbox.peerThreadsSummaryTable.get(peerId: peerId)?.unreadCount ?? 0) > 0 + isUnread = (postbox.peerThreadsSummaryTable.get(peerId: peerId)?.effectiveUnreadCount ?? 0) > 0 } else { isUnread = postbox.readStateTable.getCombinedState(peerId)?.isUnread ?? false } @@ -913,7 +913,7 @@ private final class ChatListViewSpaceState { var updatedReadState = readState if let peer = postbox.peerTable.get(index.messageIndex.id.peerId), postbox.seedConfiguration.peerSummaryIsThreadBased(peer) { - let count = postbox.peerThreadsSummaryTable.get(peerId: peer.id)?.unreadCount ?? 0 + let count = postbox.peerThreadsSummaryTable.get(peerId: peer.id)?.totalUnreadCount ?? 0 updatedReadState = CombinedPeerReadState(states: [(0, .idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: count, markedUnread: false))]) } else { updatedReadState = postbox.readStateTable.getCombinedState(index.messageIndex.id.peerId) @@ -1516,7 +1516,7 @@ struct ChatListViewState { let readState: CombinedPeerReadState? if let peer = postbox.peerTable.get(index.messageIndex.id.peerId), postbox.seedConfiguration.peerSummaryIsThreadBased(peer) { - let count = postbox.peerThreadsSummaryTable.get(peerId: peer.id)?.unreadCount ?? 0 + let count = postbox.peerThreadsSummaryTable.get(peerId: peer.id)?.totalUnreadCount ?? 0 readState = CombinedPeerReadState(states: [(0, .idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: count, markedUnread: false))]) } else { readState = postbox.readStateTable.getCombinedState(index.messageIndex.id.peerId) diff --git a/submodules/Postbox/Sources/PeerThreadCombinedStateTable.swift b/submodules/Postbox/Sources/PeerThreadCombinedStateTable.swift index edc428389b..81a3b1739d 100644 --- a/submodules/Postbox/Sources/PeerThreadCombinedStateTable.swift +++ b/submodules/Postbox/Sources/PeerThreadCombinedStateTable.swift @@ -108,26 +108,75 @@ class PeerThreadCombinedStateTable: Table { } struct StoredPeerThreadsSummary: Equatable, Codable { - private enum CodingKeys: String, CodingKey { - case unreadCount = "u" + struct ThreadsTagSummary: Equatable, Codable { + private enum CodingKeys: String, CodingKey { + case tag = "t" + case count = "c" + } + + var tag: MessageTags + var count: Int32 + + init(tag: MessageTags, count: Int32) { + self.tag = tag + self.count = count + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.tag = MessageTags(rawValue: UInt32(bitPattern: try container.decode(Int32.self, forKey: .tag))) + self.count = try container.decode(Int32.self, forKey: .count) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(Int32(bitPattern: self.tag.rawValue), forKey: .tag) + try container.encode(self.count, forKey: .count) + } } - var unreadCount: Int32 + private enum CodingKeys: String, CodingKey { + case totalUnreadCount = "u" + case hasUnmutedUnread = "h" + case tagSummaries = "ts" + } - init(unreadCount: Int32) { - self.unreadCount = unreadCount + var totalUnreadCount: Int32 + var hasUnmutedUnread: Bool + var tagSummaries: [ThreadsTagSummary] + + init(totalUnreadCount: Int32, hasUnmutedUnread: Bool, tagSummaries: [ThreadsTagSummary]) { + self.totalUnreadCount = totalUnreadCount + self.hasUnmutedUnread = hasUnmutedUnread + self.tagSummaries = tagSummaries } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - self.unreadCount = try container.decode(Int32.self, forKey: .unreadCount) + self.totalUnreadCount = try container.decodeIfPresent(Int32.self, forKey: .totalUnreadCount) ?? 0 + self.hasUnmutedUnread = try container.decodeIfPresent(Bool.self, forKey: .hasUnmutedUnread) ?? false + self.tagSummaries = try container.decodeIfPresent([ThreadsTagSummary].self, forKey: .tagSummaries) ?? [] } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(self.unreadCount, forKey: .unreadCount) + try container.encode(self.totalUnreadCount, forKey: .totalUnreadCount) + try container.encode(self.hasUnmutedUnread, forKey: .hasUnmutedUnread) + try container.encode(self.tagSummaries, forKey: .tagSummaries) + } +} + +extension StoredPeerThreadsSummary { + var effectiveUnreadCount: Int32 { + if self.hasUnmutedUnread { + return 1 + } else { + return 0 + } } } @@ -136,11 +185,15 @@ class PeerThreadsSummaryTable: Table { return ValueBoxTable(id: id, keyType: .binary, compactValuesOnCreation: true) } + private let seedConfiguration: SeedConfiguration + private let sharedKey = ValueBoxKey(length: 8) private(set) var updatedIds = Set() - override init(valueBox: ValueBox, table: ValueBoxTable, useCaches: Bool) { + init(valueBox: ValueBox, table: ValueBoxTable, useCaches: Bool, seedConfiguration: SeedConfiguration) { + self.seedConfiguration = seedConfiguration + super.init(valueBox: valueBox, table: table, useCaches: useCaches) } @@ -172,20 +225,33 @@ class PeerThreadsSummaryTable: Table { } } - func update(peerIds: Set, indexTable: MessageHistoryThreadIndexTable, combinedStateTable: PeerThreadCombinedStateTable) -> [PeerId: StoredPeerThreadsSummary] { + func update(peerIds: Set, indexTable: MessageHistoryThreadIndexTable, combinedStateTable: PeerThreadCombinedStateTable, tagsSummaryTable: MessageHistoryTagsSummaryTable) -> [PeerId: StoredPeerThreadsSummary] { var updatedInitialSummaries: [PeerId: StoredPeerThreadsSummary] = [:] for peerId in peerIds { - var unreadCount: Int32 = 0 + var totalUnreadCount: Int32 = 0 + var hasUnmutedUnread: Bool = false + var tagSummaries: [StoredPeerThreadsSummary.ThreadsTagSummary] = [] for item in indexTable.fetch(peerId: peerId, namespace: 0, start: .upperBound, end: .lowerBound, limit: 20) { - if item.info.summary.mutedUntil == nil { - unreadCount += item.info.summary.totalUnreadCount + if item.info.summary.totalUnreadCount > 0 { + totalUnreadCount += 1 + if item.info.summary.mutedUntil == nil { + hasUnmutedUnread = true + } } + + for tag in self.seedConfiguration.messageTagsWithThreadSummary { + if let value = tagsSummaryTable.get(MessageHistoryTagsSummaryKey(tag: tag, peerId: peerId, threadId: item.threadId, namespace: 0)) { + tagSummaries.append(StoredPeerThreadsSummary.ThreadsTagSummary(tag: tag, count: value.count)) + } + } + + tagSummaries.removeAll() } let current = self.get(peerId: peerId) - if current?.unreadCount != unreadCount { - updatedInitialSummaries[peerId] = current ?? StoredPeerThreadsSummary(unreadCount: 0) - self.set(peerId: peerId, state: StoredPeerThreadsSummary(unreadCount: unreadCount)) + if current?.totalUnreadCount != totalUnreadCount || current?.hasUnmutedUnread != hasUnmutedUnread || current?.tagSummaries != tagSummaries { + updatedInitialSummaries[peerId] = current ?? StoredPeerThreadsSummary(totalUnreadCount: 0, hasUnmutedUnread: false, tagSummaries: []) + self.set(peerId: peerId, state: StoredPeerThreadsSummary(totalUnreadCount: totalUnreadCount, hasUnmutedUnread: hasUnmutedUnread, tagSummaries: tagSummaries)) } } diff --git a/submodules/Postbox/Sources/Postbox.swift b/submodules/Postbox/Sources/Postbox.swift index aca278e3c3..750c6d27d2 100644 --- a/submodules/Postbox/Sources/Postbox.swift +++ b/submodules/Postbox/Sources/Postbox.swift @@ -1546,7 +1546,7 @@ final class PostboxImpl { self.messageHistoryTagsTable = MessageHistoryTagsTable(valueBox: self.valueBox, table: MessageHistoryTagsTable.tableSpec(12), useCaches: useCaches, seedConfiguration: self.seedConfiguration, summaryTable: self.messageHistoryTagsSummaryTable) self.messageHistoryThreadsTable = MessageHistoryThreadsTable(valueBox: self.valueBox, table: MessageHistoryThreadsTable.tableSpec(62), useCaches: useCaches) self.peerThreadCombinedStateTable = PeerThreadCombinedStateTable(valueBox: self.valueBox, table: PeerThreadCombinedStateTable.tableSpec(74), useCaches: useCaches) - self.peerThreadsSummaryTable = PeerThreadsSummaryTable(valueBox: self.valueBox, table: PeerThreadsSummaryTable.tableSpec(75), useCaches: useCaches) + self.peerThreadsSummaryTable = PeerThreadsSummaryTable(valueBox: self.valueBox, table: PeerThreadsSummaryTable.tableSpec(75), useCaches: useCaches, seedConfiguration: self.seedConfiguration) 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(72), useCaches: useCaches) @@ -2050,7 +2050,7 @@ final class PostboxImpl { let transactionParticipationInTotalUnreadCountUpdates = self.peerNotificationSettingsTable.transactionParticipationInTotalUnreadCountUpdates(postbox: self, transaction: currentTransaction) let updatedMessageThreadPeerIds = self.messageHistoryThreadIndexTable.replay(threadsTable: self.messageHistoryThreadsTable, namespaces: self.seedConfiguration.chatMessagesNamespaces, updatedIds: self.messageHistoryThreadsTable.updatedIds) - let alteredInitialPeerThreadsSummaries = self.peerThreadsSummaryTable.update(peerIds: updatedMessageThreadPeerIds.union(self.currentUpdatedPeerThreadCombinedStates), indexTable: self.messageHistoryThreadIndexTable, combinedStateTable: self.peerThreadCombinedStateTable) + let alteredInitialPeerThreadsSummaries = self.peerThreadsSummaryTable.update(peerIds: updatedMessageThreadPeerIds.union(self.currentUpdatedPeerThreadCombinedStates), indexTable: self.messageHistoryThreadIndexTable, combinedStateTable: self.peerThreadCombinedStateTable, tagsSummaryTable: self.messageHistoryTagsSummaryTable) self.chatListIndexTable.commitWithTransaction(postbox: self, currentTransaction: currentTransaction, alteredInitialPeerCombinedReadStates: alteredInitialPeerCombinedReadStates, updatedPeers: updatedPeers, transactionParticipationInTotalUnreadCountUpdates: transactionParticipationInTotalUnreadCountUpdates, alteredInitialPeerThreadsSummaries: alteredInitialPeerThreadsSummaries, updatedTotalUnreadStates: &self.currentUpdatedTotalUnreadStates, updatedGroupTotalUnreadSummaries: &self.currentUpdatedGroupTotalUnreadSummaries, currentUpdatedGroupSummarySynchronizeOperations: &self.currentUpdatedGroupSummarySynchronizeOperations) diff --git a/submodules/Postbox/Sources/SeedConfiguration.swift b/submodules/Postbox/Sources/SeedConfiguration.swift index 3cadb5dfa5..4467004572 100644 --- a/submodules/Postbox/Sources/SeedConfiguration.swift +++ b/submodules/Postbox/Sources/SeedConfiguration.swift @@ -62,6 +62,7 @@ public final class SeedConfiguration { public let upgradedMessageHoles: [PeerId.Namespace: [MessageId.Namespace: Set]] public let messageThreadHoles: [PeerId.Namespace: [MessageId.Namespace]] public let messageTagsWithSummary: MessageTags + public let messageTagsWithThreadSummary: MessageTags public let existingGlobalMessageTags: GlobalMessageTags public let peerNamespacesRequiringMessageTextIndex: [PeerId.Namespace] public let peerSummaryCounterTags: (Peer, Bool) -> PeerSummaryCounterTags @@ -85,6 +86,7 @@ public final class SeedConfiguration { messageThreadHoles: [PeerId.Namespace: [MessageId.Namespace]], existingMessageTags: MessageTags, messageTagsWithSummary: MessageTags, + messageTagsWithThreadSummary: MessageTags, existingGlobalMessageTags: GlobalMessageTags, peerNamespacesRequiringMessageTextIndex: [PeerId.Namespace], peerSummaryCounterTags: @escaping (Peer, Bool) -> PeerSummaryCounterTags, @@ -103,6 +105,7 @@ public final class SeedConfiguration { self.upgradedMessageHoles = upgradedMessageHoles self.messageThreadHoles = messageThreadHoles self.messageTagsWithSummary = messageTagsWithSummary + self.messageTagsWithThreadSummary = messageTagsWithThreadSummary self.existingGlobalMessageTags = existingGlobalMessageTags self.peerNamespacesRequiringMessageTextIndex = peerNamespacesRequiringMessageTextIndex self.peerSummaryCounterTags = peerSummaryCounterTags diff --git a/submodules/TelegramCore/Sources/ForumChannels.swift b/submodules/TelegramCore/Sources/ForumChannels.swift index cf11ce5164..8834e4027e 100644 --- a/submodules/TelegramCore/Sources/ForumChannels.swift +++ b/submodules/TelegramCore/Sources/ForumChannels.swift @@ -534,6 +534,38 @@ func _internal_loadMessageHistoryThreads(account: Account, peerId: PeerId) -> Si return signal } +func _internal_forumChannelTopicNotificationExceptions(account: Account, id: EnginePeer.Id) -> Signal<[(threadId: Int64, notificationSettiongs: EnginePeer.NotificationSettings)], NoError> { + return account.postbox.transaction { transaction -> Api.InputPeer? in + return transaction.getPeer(id).flatMap(apiInputPeer) + } + |> mapToSignal { inputPeer -> Signal<[(threadId: Int64, notificationSettiongs: EnginePeer.NotificationSettings)], NoError> in + guard let inputPeer = inputPeer else { + return .single([]) + } + return account.network.request(Api.functions.account.getNotifyExceptions(flags: 1 << 0, peer: Api.InputNotifyPeer.inputNotifyPeer(peer: inputPeer))) + |> map { result -> [(threadId: Int64, notificationSettiongs: EnginePeer.NotificationSettings)] in + var list: [(threadId: Int64, notificationSettiongs: EnginePeer.NotificationSettings)] = [] + for update in result.allUpdates { + switch update { + case let .updateNotifySettings(peer, notifySettings): + switch peer { + case let .notifyForumTopic(_, topMsgId): + list.append((Int64(topMsgId), EnginePeer.NotificationSettings(TelegramPeerNotificationSettings(apiSettings: notifySettings)))) + default: + break + } + default: + break + } + } + return list + } + |> `catch` { _ -> Signal<[(threadId: Int64, notificationSettiongs: EnginePeer.NotificationSettings)], NoError> in + return .single([]) + } + } +} + public final class ForumChannelTopics { private final class Impl { private let queue: Queue diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift index 11ca8c9954..437c5a9f06 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift @@ -45,6 +45,7 @@ public let telegramPostboxSeedConfiguration: SeedConfiguration = { messageThreadHoles: messageThreadHoles, existingMessageTags: MessageTags.all, messageTagsWithSummary: [.unseenPersonalMessage, .pinned, .video, .photo, .gif, .music, .voiceOrInstantVideo, .webPage, .file, .unseenReaction], + messageTagsWithThreadSummary: [.unseenPersonalMessage, .unseenReaction], existingGlobalMessageTags: GlobalMessageTags.all, peerNamespacesRequiringMessageTextIndex: [Namespaces.Peer.SecretChat], peerSummaryCounterTags: { peer, isContact in diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift index 6d053deb1f..c1d5488e0c 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift @@ -828,6 +828,10 @@ public extension TelegramEngine { public func setForumChannelTopicPinned(id: EnginePeer.Id, threadId: Int64, isPinned: Bool) -> Signal { return _internal_setForumChannelTopicPinned(account: self.account, id: id, threadId: threadId, isPinned: isPinned) } + + public func forumChannelTopicNotificationExceptions(id: EnginePeer.Id) -> Signal<[(threadId: Int64, notificationSettiongs: EnginePeer.NotificationSettings)], NoError> { + return _internal_forumChannelTopicNotificationExceptions(account: self.account, id: id) + } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/CachedStickerPack.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/CachedStickerPack.swift index aab409391c..54fce959f9 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/CachedStickerPack.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/CachedStickerPack.swift @@ -46,9 +46,9 @@ func cacheStickerPack(transaction: Transaction, info: StickerPackCollectionInfo, namespace = Namespaces.ItemCollection.CloudStickerPacks } id = _id - default: - assertionFailure() - break + case .name: + namespace = info.id.namespace + id = info.id.id } if let namespace = namespace, let id = id { if let entry = CodableEntry(CachedStickerPack(info: info, items: items, hash: info.hash)) { diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index b5b4244056..3e8cae9b2c 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -1877,6 +1877,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate private weak var copyProtectionTooltipController: TooltipController? weak var emojiStatusSelectionController: ViewController? + private var forumTopicNotificationExceptions: [(threadId: Int64, EnginePeer.NotificationSettings)] = [] + private var forumTopicNotificationExceptionsDisposable: Disposable? + private let _ready = Promise() var ready: Promise { return self._ready @@ -3411,6 +3414,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate self.shareStatusDisposable?.dispose() self.customStatusDisposable?.dispose() self.refreshMessageTagStatsDisposable?.dispose() + self.forumTopicNotificationExceptionsDisposable?.dispose() self.copyProtectionTooltipController?.dismiss() } @@ -3445,6 +3449,19 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate self.groupMembersSearchContext = nil } } + + if let channel = data.peer as? TelegramChannel, channel.flags.contains(.isForum) { + if self.forumTopicNotificationExceptionsDisposable == nil { + self.forumTopicNotificationExceptionsDisposable = (self.context.engine.peers.forumChannelTopicNotificationExceptions(id: channel.id) + |> deliverOnMainQueue).start(next: { [weak self] list in + guard let strongSelf = self else { + return + } + strongSelf.forumTopicNotificationExceptions = list + }) + } + } + if let (layout, navigationHeight) = self.validLayout { var updatedMemberCount: Int? if let data = self.data { @@ -4125,10 +4142,23 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate ], title: nil, text: strongSelf.presentationData.strings.PeerInfo_TooltipMutedForever), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) }))) + var tip: ContextController.Tip? + if !self.forumTopicNotificationExceptions.isEmpty { + //TODO:localize + let text: String + if self.forumTopicNotificationExceptions.count == 1 { + text = "There is 1 topic that is listed as exception." + } else { + text = "There are \(self.forumTopicNotificationExceptions.count) topics that are listed as exceptions." + } + tip = .notificationTopicExceptions(text: text, action: { + }) + } + self.view.endEditing(true) if let sourceNode = self.headerNode.buttonNodes[.mute]?.referenceNode { - let contextController = ContextController(account: self.context.account, presentationData: self.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) + let contextController = ContextController(account: self.context.account, presentationData: self.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: .single(ContextController.Items(content: .list(items), tip: tip)), gesture: gesture) contextController.dismissed = { [weak self] in if let strongSelf = self { strongSelf.state = strongSelf.state.withHighlightedButton(nil)