[WIP] Topics

This commit is contained in:
Ali 2022-10-14 23:48:39 +04:00
parent 8838fe095c
commit d91c4a4b02
16 changed files with 222 additions and 54 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<PeerId>()
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<PeerId>, indexTable: MessageHistoryThreadIndexTable, combinedStateTable: PeerThreadCombinedStateTable) -> [PeerId: StoredPeerThreadsSummary] {
func update(peerIds: Set<PeerId>, 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))
}
}

View File

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

View File

@ -62,6 +62,7 @@ public final class SeedConfiguration {
public let upgradedMessageHoles: [PeerId.Namespace: [MessageId.Namespace: Set<MessageTags>]]
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

View File

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

View File

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

View File

@ -828,6 +828,10 @@ public extension TelegramEngine {
public func setForumChannelTopicPinned(id: EnginePeer.Id, threadId: Int64, isPinned: Bool) -> Signal<Never, SetForumChannelTopicPinnedError> {
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)
}
}
}

View File

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

View File

@ -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<Bool>()
var ready: Promise<Bool> {
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)