From bc99f54b638162e545e4b439e5aa74e18aad4049 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Wed, 9 Nov 2022 10:47:14 +0400 Subject: [PATCH] [WIP] General history loading and topic improvements --- Telegram/SiriIntents/IntentMessages.swift | 2 +- .../Sources/ChatListController.swift | 4 +- .../Sources/ChatListControllerNode.swift | 2 +- .../Sources/ChatListSearchListPaneNode.swift | 8 +- .../Sources/Node/ChatListItem.swift | 275 ++++++++++++++---- .../Sources/Node/ChatListNode.swift | 42 +-- .../Sources/Node/ChatListNodeEntries.swift | 24 +- .../Sources/Node/ChatListNodeLocation.swift | 1 + .../Sources/ContactListNode.swift | 4 +- .../Display/Source/LinkHighlightingNode.swift | 39 ++- submodules/Postbox/Sources/ChatListView.swift | 50 +++- .../Postbox/Sources/ChatListViewState.swift | 40 ++- .../MessageHistoryHoleIndexTable.swift | 3 +- .../Postbox/Sources/MessageHistoryView.swift | 4 +- submodules/Postbox/Sources/Postbox.swift | 4 +- .../TextSizeSelectionController.swift | 3 +- .../ThemeAccentColorControllerNode.swift | 3 +- .../Themes/ThemePreviewControllerNode.swift | 3 +- .../Sources/ShareController.swift | 2 +- .../Sources/ShareControllerNode.swift | 1 + .../TelegramCore/Sources/State/Holes.swift | 5 +- .../TelegramEngine/Messages/ChatList.swift | 27 +- .../Context Menu/Chats.imageset/Contents.json | 12 + .../Context Menu/Chats.imageset/chats_24.pdf | 261 +++++++++++++++++ .../TelegramUI/Sources/ChatController.swift | 2 + .../Sources/ChatHistoryListNode.swift | 14 +- .../Sources/ChatHistoryViewForLocation.swift | 6 +- .../ChatSearchResultsContollerNode.swift | 5 +- .../WatchBridge/Sources/WatchBridge.swift | 2 +- 29 files changed, 689 insertions(+), 159 deletions(-) create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Chats.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Chats.imageset/chats_24.pdf diff --git a/Telegram/SiriIntents/IntentMessages.swift b/Telegram/SiriIntents/IntentMessages.swift index 497a1c6d93..a85b6e4a8e 100644 --- a/Telegram/SiriIntents/IntentMessages.swift +++ b/Telegram/SiriIntents/IntentMessages.swift @@ -36,7 +36,7 @@ func unreadMessages(account: Account) -> Signal<[INMessage], NoError> { |> mapToSignal { view -> Signal<[INMessage], NoError> in var signals: [Signal<[INMessage], NoError>] = [] for entry in view.0.entries { - if case let .MessageEntry(index, _, readState, isMuted, _, _, _, _, _, _, _) = entry { + if case let .MessageEntry(index, _, readState, isMuted, _, _, _, _, _, _, _, _) = entry { if index.messageIndex.id.peerId.namespace != Namespaces.Peer.CloudUser { continue } diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 380be7e9bb..634b387fd9 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1661,7 +1661,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } var joined = false - if case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = item.content, let message = messages.first { + if case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = item.content, let message = messages.first { for media in message.media { if let action = media as? TelegramMediaAction, action.action == .peerJoined { joined = true @@ -1675,7 +1675,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController chatListController.navigationPresentation = .master let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatListController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: archiveContextMenuItems(context: strongSelf.context, groupId: groupId._asGroup(), chatListController: strongSelf) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) strongSelf.presentInGlobalOverlay(contextController) - case let .peer(_, peer, _, _, _, _, _, _, _, _, promoInfo, _, _, _, _): + case let .peer(_, peer, _, _, _, _, _, _, _, _, promoInfo, _, _, _, _, _): switch item.index { case .chatList: if case let .channel(channel) = peer.peer, channel.flags.contains(.isForum) { diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 1a6d901517..221da39ac0 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -214,7 +214,7 @@ private final class ChatListShimmerNode: ASDisplayNode { ) let readState = EnginePeerReadCounters() - return ChatListItem(presentationData: chatListPresentationData, context: context, chatListLocation: .chatList(groupId: .root), filterData: nil, index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: 0, messageIndex: EngineMessage.Index(id: EngineMessage.Id(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1))), content: .peer(messages: [message], peer: EngineRenderedPeer(peer: peer1), threadInfo: nil, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false, forumTopicData: nil), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) + return ChatListItem(presentationData: chatListPresentationData, context: context, chatListLocation: .chatList(groupId: .root), filterData: nil, index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: 0, messageIndex: EngineMessage.Index(id: EngineMessage.Id(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1))), content: .peer(messages: [message], peer: EngineRenderedPeer(peer: peer1), threadInfo: nil, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false, forumTopicData: nil, topForumTopicItems: []), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) } var itemNodes: [ChatListItemNode] = [] diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index f3caf18460..76bf442de3 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -745,7 +745,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { index = .chatList( EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: message.index)) } } - return ChatListItem(presentationData: presentationData, context: context, chatListLocation: .chatList(groupId: .root), filterData: nil, index: index, content: .peer(messages: [message], peer: peer, threadInfo: chatThreadInfo, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: true, displayAsMessage: false, hasFailedMessages: false, forumTopicData: nil), editing: false, hasActiveRevealControls: false, selected: false, header: tagMask == nil ? header : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) + return ChatListItem(presentationData: presentationData, context: context, chatListLocation: .chatList(groupId: .root), filterData: nil, index: index, content: .peer(messages: [message], peer: peer, threadInfo: chatThreadInfo, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: true, displayAsMessage: false, hasFailedMessages: false, forumTopicData: nil, topForumTopicItems: []), editing: false, hasActiveRevealControls: false, selected: false, header: tagMask == nil ? header : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) } case let .addContact(phoneNumber, theme, strings): return ContactsAddItem(theme: theme, strings: strings, phoneNumber: phoneNumber, header: ChatListSearchItemHeader(type: .phoneNumber, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { @@ -1853,7 +1853,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { return } switch item.content { - case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _, _, _, _, _): if let peer = peer.peer, let message = messages.first { peerContextAction(peer, .search(message.id), node, gesture, location) } @@ -2897,7 +2897,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { bounds = selectedItemNode.bounds } switch item.content { - case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return (selectedItemNode.view, bounds, messages.last?.id ?? peer.peerId) case let .groupReference(groupId, _, _, _, _): return (selectedItemNode.view, bounds, groupId) @@ -3086,7 +3086,7 @@ private final class ChatListSearchShimmerNode: ASDisplayNode { associatedThreadInfo: nil ) let readState = EnginePeerReadCounters() - return ChatListItem(presentationData: chatListPresentationData, context: context, chatListLocation: .chatList(groupId: .root), filterData: nil, index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: 0, messageIndex: EngineMessage.Index(id: EngineMessage.Id(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1))), content: .peer(messages: [message], peer: EngineRenderedPeer(peer: peer1), threadInfo: nil, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false, forumTopicData: nil), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) + return ChatListItem(presentationData: chatListPresentationData, context: context, chatListLocation: .chatList(groupId: .root), filterData: nil, index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: 0, messageIndex: EngineMessage.Index(id: EngineMessage.Id(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1))), content: .peer(messages: [message], peer: EngineRenderedPeer(peer: peer1), threadInfo: nil, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false, forumTopicData: nil, topForumTopicItems: []), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) case .media: return nil case .links: diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index d24ec3f57e..3a717ba13a 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -63,12 +63,12 @@ public enum ChatListItemContent { } } - case peer(messages: [EngineMessage], peer: EngineRenderedPeer, threadInfo: ThreadInfo?, combinedReadState: EnginePeerReadCounters?, isRemovedFromTotalUnreadCount: Bool, presence: EnginePeer.Presence?, hasUnseenMentions: Bool, hasUnseenReactions: Bool, draftState: DraftState?, inputActivities: [(EnginePeer, PeerInputActivity)]?, promoInfo: ChatListNodeEntryPromoInfo?, ignoreUnreadBadge: Bool, displayAsMessage: Bool, hasFailedMessages: Bool, forumTopicData: EngineChatList.ForumTopicData?) + case peer(messages: [EngineMessage], peer: EngineRenderedPeer, threadInfo: ThreadInfo?, combinedReadState: EnginePeerReadCounters?, isRemovedFromTotalUnreadCount: Bool, presence: EnginePeer.Presence?, hasUnseenMentions: Bool, hasUnseenReactions: Bool, draftState: DraftState?, inputActivities: [(EnginePeer, PeerInputActivity)]?, promoInfo: ChatListNodeEntryPromoInfo?, ignoreUnreadBadge: Bool, displayAsMessage: Bool, hasFailedMessages: Bool, forumTopicData: EngineChatList.ForumTopicData?, topForumTopicItems: [EngineChatList.ForumTopicData]) case groupReference(groupId: EngineChatList.Group, peers: [EngineChatList.GroupItem.Item], message: EngineMessage?, unreadCount: Int, hiddenByDefault: Bool) public var chatLocation: ChatLocation? { switch self { - case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return .peer(id: peer.peerId) case .groupReference: return nil @@ -172,7 +172,7 @@ public class ChatListItem: ListViewItem, ChatListSearchItemNeighbour { public func selected(listView: ListView) { switch self.content { - case let .peer(messages, peer, _, _, _, _, _, _, _, _, promoInfo, _, _, _, _): + case let .peer(messages, peer, _, _, _, _, _, _, _, _, promoInfo, _, _, _, _, _): if let message = messages.last, let peer = peer.peer { var threadId: Int64? if case let .forum(_, _, threadIdValue, _, _) = self.index { @@ -503,23 +503,101 @@ private final class ChatListMediaPreviewNode: ASDisplayNode { private let maxVideoLoopCount = 3 class ChatListItemNode: ItemListRevealOptionsItemNode { - final class AuthorNode: ASDisplayNode { - let authorNode: TextNode - var titleTopicArrowNode: ASImageNode? - var topicTitleNode: TextNode? - var titleTopicIconView: ComponentHostView? - var titleTopicIconComponent: EmojiStatusComponent? + final class TopicItemNode: ASDisplayNode { + let topicTitleNode: TextNode + let titleTopicIconView: ComponentHostView + var titleTopicIconComponent: EmojiStatusComponent var visibilityStatus: Bool = false { didSet { if self.visibilityStatus != oldValue { - if let titleTopicIconView = self.titleTopicIconView, let titleTopicIconComponent = self.titleTopicIconComponent { - let _ = titleTopicIconView.update( - transition: .immediate, - component: AnyComponent(titleTopicIconComponent.withVisibleForAnimations(self.visibilityStatus)), - environment: {}, - containerSize: titleTopicIconView.bounds.size - ) + let _ = self.titleTopicIconView.update( + transition: .immediate, + component: AnyComponent(self.titleTopicIconComponent.withVisibleForAnimations(self.visibilityStatus)), + environment: {}, + containerSize: self.titleTopicIconView.bounds.size + ) + } + } + } + + private init(topicTitleNode: TextNode, titleTopicIconView: ComponentHostView, titleTopicIconComponent: EmojiStatusComponent) { + self.topicTitleNode = topicTitleNode + self.titleTopicIconView = titleTopicIconView + self.titleTopicIconComponent = titleTopicIconComponent + + super.init() + + self.addSubnode(self.topicTitleNode) + self.view.addSubview(self.titleTopicIconView) + } + + static func asyncLayout(_ currentNode: TopicItemNode?) -> (_ constrainedWidth: CGFloat, _ context: AccountContext, _ theme: PresentationTheme, _ title: NSAttributedString, _ iconId: Int64?, _ iconColor: Int32) -> (CGSize, () -> TopicItemNode) { + let makeTopicTitleLayout = TextNode.asyncLayout(currentNode?.topicTitleNode) + + return { constrainedWidth, context, theme, title, iconId, iconColor in + let remainingWidth = max(1.0, constrainedWidth - (22.0 + 2.0)) + + let topicTitleArguments = TextNodeLayoutArguments(attributedString: title, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: remainingWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0)) + + let topicTitleLayout = makeTopicTitleLayout(topicTitleArguments) + + return (CGSize(width: 22.0 + 2.0 + topicTitleLayout.0.size.width, height: topicTitleLayout.0.size.height), { + let topicTitleNode = topicTitleLayout.1() + + let titleTopicIconView: ComponentHostView + if let current = currentNode?.titleTopicIconView { + titleTopicIconView = current + } else { + titleTopicIconView = ComponentHostView() + } + + let titleTopicIconContent: EmojiStatusComponent.Content + if let fileId = iconId, fileId != 0 { + titleTopicIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 36.0, height: 36.0), placeholderColor: theme.list.mediaPlaceholderColor, themeColor: theme.list.itemAccentColor, loopMode: .count(2)) + } else { + titleTopicIconContent = .topic(title: String(title.string.prefix(1)), color: iconColor, size: CGSize(width: 22.0, height: 22.0)) + } + + let titleTopicIconComponent = EmojiStatusComponent( + context: context, + animationCache: context.animationCache, + animationRenderer: context.animationRenderer, + content: titleTopicIconContent, + isVisibleForAnimations: currentNode?.visibilityStatus ?? false, + action: nil + ) + + let targetNode = currentNode ?? TopicItemNode(topicTitleNode: topicTitleNode, titleTopicIconView: titleTopicIconView, titleTopicIconComponent: titleTopicIconComponent) + + targetNode.titleTopicIconComponent = titleTopicIconComponent + + let iconSize = titleTopicIconView.update( + transition: .immediate, + component: AnyComponent(titleTopicIconComponent), + environment: {}, + containerSize: CGSize(width: 22.0, height: 22.0) + ) + titleTopicIconView.frame = CGRect(origin: CGPoint(x: 0.0, y: UIScreenPixel), size: iconSize) + + topicTitleNode.frame = CGRect(origin: CGPoint(x: 22.0 + 2.0, y: 0.0), size: topicTitleLayout.0.size) + + return targetNode + }) + } + } + } + + final class AuthorNode: ASDisplayNode { + let authorNode: TextNode + var titleTopicArrowNode: ASImageNode? + var topicNodes: [Int64: TopicItemNode] = [:] + + var visibilityStatus: Bool = false { + didSet { + if self.visibilityStatus != oldValue { + for (_, topicNode) in self.topicNodes { + topicNode.visibilityStatus = self.visibilityStatus } } } @@ -534,13 +612,16 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { self.addSubnode(self.authorNode) } - func asyncLayout() -> (_ context: AccountContext, _ constrainedWidth: CGFloat, _ theme: PresentationTheme, _ authorTitle: NSAttributedString?, _ topic: (title: NSAttributedString, iconId: Int64?, iconColor: Int32)?) -> (CGSize, () -> Void) { + func asyncLayout() -> (_ context: AccountContext, _ constrainedWidth: CGFloat, _ theme: PresentationTheme, _ authorTitle: NSAttributedString?, _ topics: [(id: Int64, title: NSAttributedString, iconId: Int64?, iconColor: Int32)]) -> (CGSize, () -> CGRect?) { let makeAuthorLayout = TextNode.asyncLayout(self.authorNode) - let makeTopicTitleLayout = TextNode.asyncLayout(self.topicTitleNode) + var makeExistingTopicLayouts: [Int64: (_ constrainedWidth: CGFloat, _ context: AccountContext, _ theme: PresentationTheme, _ title: NSAttributedString, _ iconId: Int64?, _ iconColor: Int32) -> (CGSize, () -> TopicItemNode)] = [:] + for (topicId, topicNode) in self.topicNodes { + makeExistingTopicLayouts[topicId] = TopicItemNode.asyncLayout(topicNode) + } - return { [weak self] context, constrainedWidth, theme, authorTitle, topic in + return { [weak self] context, constrainedWidth, theme, authorTitle, topics in var maxTitleWidth = constrainedWidth - if let _ = topic { + if !topics.isEmpty { maxTitleWidth = floor(constrainedWidth * 0.7) } @@ -548,32 +629,40 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { var remainingWidth = constrainedWidth - authorTitleLayout.0.size.width - var topicTitleArguments: TextNodeLayoutArguments? var arrowIconImage: UIImage? - if let topic = topic { - remainingWidth -= 22.0 + 2.0 - + if !topics.isEmpty { if authorTitle != nil { arrowIconImage = PresentationResourcesChatList.topicArrowIcon(theme) if let arrowIconImage = arrowIconImage { remainingWidth -= arrowIconImage.size.width + 6.0 * 2.0 } } - - topicTitleArguments = TextNodeLayoutArguments(attributedString: topic.title, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: remainingWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0)) } - let topicTitleLayout = topicTitleArguments.flatMap(makeTopicTitleLayout) + var topicsSizeAndApply: [(Int64, CGSize, () -> TopicItemNode)] = [] + for topic in topics { + if remainingWidth <= 22.0 + 2.0 + 10.0 { + break + } + + let makeTopicLayout = makeExistingTopicLayouts[topic.id] ?? TopicItemNode.asyncLayout(nil) + let (topicSize, topicApply) = makeTopicLayout(remainingWidth, context, theme, topic.title, topic.iconId, topic.iconColor) + topicsSizeAndApply.append((topic.id, topicSize, topicApply)) + + remainingWidth -= topicSize.width + 1.0 + } var size = authorTitleLayout.0.size - if let topicTitleLayout = topicTitleLayout { - size.height = max(size.height, topicTitleLayout.0.size.height) - size.width += 10.0 + topicTitleLayout.0.size.width + if !topicsSizeAndApply.isEmpty { + for item in topicsSizeAndApply { + size.height = max(size.height, item.1.height) + size.width += 10.0 + item.1.width + } } return (size, { guard let self else { - return + return nil } let _ = authorTitleLayout.1() @@ -605,7 +694,33 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } } - if let topic { + var topTopicRect: CGRect? + for item in topicsSizeAndApply { + let itemNode = item.2() + if self.topicNodes[item.0] != itemNode { + self.topicNodes[item.0]?.removeFromSupernode() + self.topicNodes[item.0] = itemNode + self.addSubnode(itemNode) + } + let itemFrame = CGRect(origin: CGPoint(x: nextX - 1.0, y: 0.0), size: item.1) + itemNode.frame = itemFrame + if topTopicRect == nil { + topTopicRect = itemFrame + } + nextX += item.1.width + 1.0 + } + var removeIds: [Int64] = [] + for (id, itemNode) in self.topicNodes { + if !topicsSizeAndApply.contains(where: { $0.0 == id }) { + removeIds.append(id) + itemNode.removeFromSupernode() + } + } + for id in removeIds { + self.topicNodes.removeValue(forKey: id) + } + + /*if let topic { let titleTopicIconView: ComponentHostView if let current = self.titleTopicIconView { titleTopicIconView = current @@ -658,7 +773,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } else if let topicTitleNode = self.topicTitleNode { self.topicTitleNode = nil topicTitleNode.removeFromSupernode() - } + }*/ + + return topTopicRect }) } } @@ -681,6 +798,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let titleNode: TextNode let authorNode: AuthorNode + private var compoundHighlightingNode: LinkHighlightingNode? let measureNode: TextNode private var currentItemHeight: CGFloat? let textNode: TextNodeWithEntities @@ -761,7 +879,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { result += "\n\(item.presentationData.strings.VoiceOver_Chat_UnreadMessages(Int32(allCount)))" } return result - case let .peer(_, peer, _, combinedReadState, _, _, _, _, _, _, _, _, _, _, _): + case let .peer(_, peer, _, combinedReadState, _, _, _, _, _, _, _, _, _, _, _, _): guard let chatMainPeer = peer.chatMainPeer else { return nil } @@ -821,7 +939,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } else { return item.presentationData.strings.VoiceOver_ChatList_MessageEmpty } - case let .peer(messages, peer, _, combinedReadState, _, _, _, _, _, _, _, _, _, _, _): + case let .peer(messages, peer, _, combinedReadState, _, _, _, _, _, _, _, _, _, _, _, _): if let message = messages.last { var result = "" if message.flags.contains(.Incoming) { @@ -1014,7 +1132,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { var displayAsMessage = false var enablePreview = true switch item.content { - case let .peer(messages, peerValue, _, _, _, _, _, _, _, _, _, _, displayAsMessageValue, _, _): + case let .peer(messages, peerValue, _, _, _, _, _, _, _, _, _, _, displayAsMessageValue, _, _, _): displayAsMessage = displayAsMessageValue if displayAsMessage, case let .user(author) = messages.last?.author { peer = .user(author) @@ -1184,7 +1302,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { guard let item = self.item, item.editing else { return } - if case let .peer(_, peer, _, _, _, _, _, _, _, _, promoInfo, _, _, _, _) = item.content { + if case let .peer(_, peer, _, _, _, _, _, _, _, _, promoInfo, _, _, _, _, _) = item.content { if promoInfo == nil, let mainPeer = peer.peer { switch item.index { case let .forum(_, _, threadIdValue, _, _): @@ -1240,11 +1358,13 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let hasFailedMessages: Bool var threadInfo: ChatListItemContent.ThreadInfo? var forumTopicData: EngineChatList.ForumTopicData? + var topForumTopicItems: [EngineChatList.ForumTopicData] = [] + topForumTopicItems.removeAll() var groupHiddenByDefault = false switch item.content { - case let .peer(messagesValue, peerValue, threadInfoValue, combinedReadStateValue, isRemovedFromTotalUnreadCountValue, peerPresenceValue, hasUnseenMentionsValue, hasUnseenReactionsValue, draftStateValue, inputActivitiesValue, promoInfoValue, ignoreUnreadBadge, displayAsMessageValue, _, forumTopicDataValue): + case let .peer(messagesValue, peerValue, threadInfoValue, combinedReadStateValue, isRemovedFromTotalUnreadCountValue, peerPresenceValue, hasUnseenMentionsValue, hasUnseenReactionsValue, draftStateValue, inputActivitiesValue, promoInfoValue, ignoreUnreadBadge, displayAsMessageValue, _, forumTopicDataValue, topForumTopicItemsValue): messages = messagesValue contentPeer = .chat(peerValue) combinedReadState = combinedReadStateValue @@ -1266,6 +1386,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { hasUnseenMentions = hasUnseenMentionsValue hasUnseenReactions = hasUnseenReactionsValue forumTopicData = forumTopicDataValue + topForumTopicItems = topForumTopicItemsValue switch peerValue.peer { case .user, .secretChat: @@ -1442,7 +1563,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let contentImageSpacing: CGFloat = 2.0 let contentImageTrailingSpace: CGFloat = 5.0 var contentImageSpecs: [(message: EngineMessage, media: EngineMedia, size: CGSize)] = [] - var forumThread: (title: String, iconId: Int64?, iconColor: Int32)? + var forumThread: (id: Int64, title: String, iconId: Int64?, iconColor: Int32)? switch contentData { case let .chat(itemPeer, _, _, _, text, spoilers, customEmojiRanges): @@ -1470,9 +1591,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { if let _ = peerText, case let .channel(channel) = itemPeer.chatMainPeer, channel.flags.contains(.isForum), threadInfo == nil { if let forumTopicData = forumTopicData { - forumThread = (forumTopicData.title, forumTopicData.iconFileId, forumTopicData.iconColor) - } else if let threadInfo = threadInfo?.info { - forumThread = (threadInfo.title, threadInfo.icon, threadInfo.iconColor) + forumThread = (forumTopicData.id, forumTopicData.title, forumTopicData.iconFileId, forumTopicData.iconColor) + } else if let threadInfo = threadInfo { + forumThread = (threadInfo.id, threadInfo.info.title, threadInfo.info.icon, threadInfo.info.iconColor) } } @@ -1695,7 +1816,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { switch item.content { case let .groupReference(_, _, message, _, _): topIndex = message?.index - case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): topIndex = messages.first?.index } if let topIndex { @@ -1849,7 +1970,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { if !isPeerGroup && !isAccountPeer { if displayAsMessage { switch item.content { - case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): if let peer = messages.last?.author { if case let .user(user) = peer, let emojiStatus = user.emojiStatus, !premiumConfiguration.isPremiumDisabled { currentCredibilityIconContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(2)) @@ -1933,14 +2054,33 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { var effectiveAuthorTitle = (hideAuthor && !hasDraft) ? nil : authorAttributedString - var forumThreadTitle: (title: NSAttributedString, iconId: Int64?, iconColor: Int32)? - if let _ = effectiveAuthorTitle, let forumThread { - if authorIsCurrentChat { - effectiveAuthorTitle = nil + var forumThreads: [(id: Int64, title: NSAttributedString, iconId: Int64?, iconColor: Int32)] = [] + if forumThread != nil || !topForumTopicItems.isEmpty { + if let forumThread = forumThread { + forumThreads.append((id: forumThread.id, title: NSAttributedString(string: forumThread.title, font: textFont, textColor: theme.authorNameColor), iconId: forumThread.iconId, iconColor: forumThread.iconColor)) } - forumThreadTitle = (NSAttributedString(string: forumThread.title, font: textFont, textColor: theme.authorNameColor), forumThread.iconId, forumThread.iconColor) + for item in topForumTopicItems { + if forumThread?.id != item.id { + forumThreads.append((id: item.id, title: NSAttributedString(string: item.title, font: textFont, textColor: theme.authorNameColor), iconId: item.iconFileId, iconColor: item.iconColor)) + } + } + + if let effectiveAuthorTitle, let textAttributedStringValue = textAttributedString { + let mutableTextAttributedString = NSMutableAttributedString() + mutableTextAttributedString.append(NSAttributedString(string: effectiveAuthorTitle.string + ": ", font: textFont, textColor: theme.authorNameColor)) + mutableTextAttributedString.append(textAttributedStringValue) + + textAttributedString = mutableTextAttributedString + } + + effectiveAuthorTitle = nil } - let (authorLayout, authorApply) = authorLayout(item.context, rawContentWidth - badgeSize, item.presentationData.theme, effectiveAuthorTitle, forumThreadTitle) + + if authorIsCurrentChat { + effectiveAuthorTitle = nil + } + + let (authorLayout, authorApply) = authorLayout(item.context, rawContentWidth - badgeSize, item.presentationData.theme, effectiveAuthorTitle, forumThreads) var textCutout: TextNodeCutout? if !textLeftCutout.isZero { @@ -1993,7 +2133,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let peerRevealOptions: [ItemListRevealOption] let peerLeftRevealOptions: [ItemListRevealOption] switch item.content { - case let .peer(_, renderedPeer, _, _, _, presence, _, _, _, _, _, _, displayAsMessage, _, _): + case let .peer(_, renderedPeer, _, _, _, presence, _, _, _, _, _, _, displayAsMessage, _, _, _): if !displayAsMessage { if case let .user(peer) = renderedPeer.chatMainPeer, let presence = presence, !isServicePeer(peer) && !peer.flags.contains(.isSupport) && peer.id != item.context.account.peerId { let updatedPresence = EnginePeer.Presence(status: presence.status, lastActivity: 0) @@ -2273,7 +2413,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { attemptSynchronous: synchronousLoads )) - let _ = authorApply() + let topForumTopicRect = authorApply() let _ = titleApply() let _ = badgeApply(animateBadges, !isMuted) let _ = mentionBadgeApply(animateBadges, true) @@ -2379,6 +2519,33 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let textNodeFrame = CGRect(origin: CGPoint(x: contentRect.origin.x - 1.0, y: contentRect.minY + titleLayout.size.height - 1.0 + UIScreenPixel + (authorLayout.height.isZero ? 0.0 : (authorLayout.height - 3.0))), size: textLayout.size) strongSelf.textNode.textNode.frame = textNodeFrame + if let topForumTopicRect { + let compoundHighlightingNode: LinkHighlightingNode + if let current = strongSelf.compoundHighlightingNode { + compoundHighlightingNode = current + } else { + compoundHighlightingNode = LinkHighlightingNode(color: theme.itemHighlightedBackgroundColor) + strongSelf.compoundHighlightingNode = compoundHighlightingNode + strongSelf.contextContainer.insertSubnode(compoundHighlightingNode, at: 0) + } + compoundHighlightingNode.outerRadius = 8.0 + compoundHighlightingNode.innerRadius = 8.0 + compoundHighlightingNode.frame = CGRect(origin: CGPoint(x: authorNodeFrame.minX, y: authorNodeFrame.minY), size: CGSize(width: textNodeFrame.maxX - authorNodeFrame.minX, height: textNodeFrame.maxY - authorNodeFrame.minY)) + var topRect = topForumTopicRect + topRect.origin.y += 1.0 + var textRect = textNodeFrame.offsetBy(dx: -authorNodeFrame.minX, dy: -authorNodeFrame.minY) + textRect.origin.x = topRect.minX + textRect.size.height -= 3.0 + let midY = floor((topForumTopicRect.minY + textRect.maxY) / 2.0) + 3.0 + compoundHighlightingNode.updateRects([ + CGRect(origin: topRect.origin, size: CGSize(width: topRect.width, height: midY - topRect.minY)), + CGRect(origin: CGPoint(x: textRect.minX, y: midY), size: CGSize(width: textRect.width, height: textRect.maxY - midY)) + ]) + } else if let compoundHighlightingNode = strongSelf.compoundHighlightingNode { + strongSelf.compoundHighlightingNode = nil + compoundHighlightingNode.removeFromSupernode() + } + if !textLayout.spoilers.isEmpty { let dustNode: InvisibleInkDustNode if let current = strongSelf.dustNode { @@ -2932,7 +3099,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { close = false case RevealOptionKey.delete.rawValue: var joined = false - if case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = item.content, let message = messages.first { + if case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = item.content, let message = messages.first { for media in message.media { if let action = media as? TelegramMediaAction, action.action == .peerJoined { joined = true @@ -2968,7 +3135,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { item.interaction.toggleArchivedFolderHiddenByDefault() close = false case RevealOptionKey.hidePsa.rawValue: - if let item = self.item, case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, _, _) = item.content { + if let item = self.item, case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = item.content { item.interaction.hidePsa(peer.peerId) } close = false diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 0ab3d0eb17..e9bd29bbe3 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -296,7 +296,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL nodeInteraction.additionalCategorySelected(id) } ), directionHint: entry.directionHint) - case let .PeerEntry(index, presentationData, messages, combinedReadState, isRemovedFromTotalUnreadCount, draftState, peer, threadInfo, presence, hasUnseenMentions, hasUnseenReactions, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact, forumTopicData): + case let .PeerEntry(index, presentationData, messages, combinedReadState, isRemovedFromTotalUnreadCount, draftState, peer, threadInfo, presence, hasUnseenMentions, hasUnseenReactions, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact, forumTopicData, topForumTopicItems): switch mode { case .chatList: return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem( @@ -320,7 +320,8 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: hasFailedMessages, - forumTopicData: forumTopicData + forumTopicData: forumTopicData, + topForumTopicItems: topForumTopicItems ), editing: editing, hasActiveRevealControls: hasActiveRevealControls, @@ -517,7 +518,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, filterData: ChatListItemFilterData?, mode: ChatListNodeMode, entries: [ChatListNodeViewTransitionUpdateEntry]) -> [ListViewUpdateItem] { return entries.map { entry -> ListViewUpdateItem in switch entry.entry { - case let .PeerEntry(index, presentationData, messages, combinedReadState, isRemovedFromTotalUnreadCount, draftState, peer, threadInfo, presence, hasUnseenMentions, hasUnseenReactions, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact, forumTopicData): + case let .PeerEntry(index, presentationData, messages, combinedReadState, isRemovedFromTotalUnreadCount, draftState, peer, threadInfo, presence, hasUnseenMentions, hasUnseenReactions, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact, forumTopicData, topForumTopicItems): switch mode { case .chatList: return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem( @@ -541,7 +542,8 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: hasFailedMessages, - forumTopicData: forumTopicData + forumTopicData: forumTopicData, + topForumTopicItems: topForumTopicItems ), editing: editing, hasActiveRevealControls: hasActiveRevealControls, @@ -1210,7 +1212,7 @@ public final class ChatListNode: ListView { let (rawEntries, isLoading) = chatListNodeEntriesForView(update.list, state: state, savedMessagesPeer: savedMessagesPeer, foundPeers: state.foundPeers, hideArchivedFolderByDefault: hideArchivedFolderByDefault, displayArchiveIntro: displayArchiveIntro, mode: mode, chatListLocation: location) let entries = rawEntries.filter { entry in switch entry { - case let .PeerEntry(_, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _): + case let .PeerEntry(_, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _, _): switch mode { case .chatList: return true @@ -1384,7 +1386,7 @@ public final class ChatListNode: ListView { var didIncludeHiddenByDefaultArchive = false if let previous = previousView { for entry in previous.filteredEntries { - if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = entry { + if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = entry { if case let .chatList(chatListIndex) = index { if chatListIndex.pinningIndex != nil { previousPinnedChats.append(chatListIndex.messageIndex.id.peerId) @@ -1409,7 +1411,7 @@ public final class ChatListNode: ListView { var doesIncludeArchive = false var doesIncludeHiddenByDefaultArchive = false for entry in processedView.filteredEntries { - if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = entry { + if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = entry { if case let .chatList(index) = index, index.pinningIndex != nil { updatedPinnedChats.append(index.messageIndex.id.peerId) } else if case let .forum(pinnedIndex, _, threadId, _, _) = index { @@ -1712,7 +1714,7 @@ public final class ChatListNode: ListView { var referenceId: EngineChatList.PinnedItem.Id? var beforeAll = false switch toEntry { - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _, _): + case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _, _, _): if promoInfo != nil { beforeAll = true } else { @@ -1739,7 +1741,7 @@ public final class ChatListNode: ListView { var itemId: EngineChatList.PinnedItem.Id? switch fromEntry { - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): if case let .chatList(index) = index { itemId = .peer(index.messageIndex.id.peerId) } @@ -1785,7 +1787,7 @@ public final class ChatListNode: ListView { var referenceId: Int64? var beforeAll = false switch toEntry { - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _, _): + case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _, _, _): if promoInfo != nil { beforeAll = true } else { @@ -1805,7 +1807,7 @@ public final class ChatListNode: ListView { var itemId: Int64? switch fromEntry { - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): if case let .forum(_, _, threadId, _, _) = index { itemId = threadId } @@ -2066,7 +2068,7 @@ public final class ChatListNode: ListView { if !transition.chatListView.originalList.hasLater { for entry in filteredEntries.reversed() { switch entry { - case let .PeerEntry(index, _, _, combinedReadState, isMuted, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _, _): + case let .PeerEntry(index, _, _, combinedReadState, isMuted, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _, _, _): if promoInfo == nil { var hasUnread = false if let combinedReadState = combinedReadState { @@ -2192,7 +2194,7 @@ public final class ChatListNode: ListView { for item in transition.insertItems { if let item = item.item as? ChatListItem { switch item.content { - case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, _, _, _): insertedPeerIds.append(peer.peerId) case .groupReference: break @@ -2368,7 +2370,7 @@ public final class ChatListNode: ListView { continue } switch chatListView.filteredEntries[entryCount - i - 1] { - case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _): + case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _, _): if interaction.highlightedChatLocation?.location == ChatLocation.peer(id: peer.peerId) { current = (index, peer.peer!, entryCount - i - 1) break outer @@ -2415,10 +2417,10 @@ public final class ChatListNode: ListView { case .previous(unread: false), .next(unread: false): var target: (EngineChatList.Item.Index, EnginePeer)? = nil if let current = current, entryCount > 1 { - if current.2 > 0, case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[current.2 - 1] { + if current.2 > 0, case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[current.2 - 1] { next = (index, peer.peer!) } - if current.2 <= entryCount - 2, case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[current.2 + 1] { + if current.2 <= entryCount - 2, case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[current.2 + 1] { previous = (index, peer.peer!) } if case .previous = option { @@ -2427,7 +2429,7 @@ public final class ChatListNode: ListView { target = next } } else if entryCount > 0 { - if case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[entryCount - 1] { + if case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[entryCount - 1] { target = (index, peer.peer!) } } @@ -2505,7 +2507,7 @@ public final class ChatListNode: ListView { continue } switch chatListView.filteredEntries[entryCount - i - 1] { - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return index default: break @@ -2521,7 +2523,7 @@ public final class ChatListNode: ListView { if resultPeer == nil, let itemNode = itemNode as? ListViewItemNode, itemNode.frame.contains(point) { if let itemNode = itemNode as? ChatListItemNode, let item = itemNode.item { switch item.content { - case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, _, _, _): resultPeer = peer.peer default: break @@ -2538,7 +2540,7 @@ public final class ChatListNode: ListView { if resultThreadId == nil, let itemNode = itemNode as? ListViewItemNode, itemNode.frame.contains(point) { if let itemNode = itemNode as? ChatListItemNode, let item = itemNode.item { switch item.content { - case let .peer(_, _, threadInfo, _, _, _, _, _, _, _, _, _, _, _, _): + case let .peer(_, _, threadInfo, _, _, _, _, _, _, _, _, _, _, _, _, _): resultThreadId = threadInfo?.id default: break diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift index 98bb0792ba..78ea88f682 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift @@ -66,7 +66,8 @@ enum ChatListNodeEntry: Comparable, Identifiable { promoInfo: ChatListNodeEntryPromoInfo?, hasFailedMessages: Bool, isContact: Bool, - forumTopicData: EngineChatList.ForumTopicData? + forumTopicData: EngineChatList.ForumTopicData?, + topForumTopicItems: [EngineChatList.ForumTopicData] ) case HoleEntry(EngineMessage.Index, theme: PresentationTheme) case GroupReferenceEntry(index: EngineChatList.Item.Index, presentationData: ChatListPresentationData, groupId: EngineChatList.Group, peers: [EngineChatList.GroupItem.Item], message: EngineMessage?, editing: Bool, unreadCount: Int, revealed: Bool, hiddenByDefault: Bool) @@ -77,7 +78,7 @@ enum ChatListNodeEntry: Comparable, Identifiable { switch self { case .HeaderEntry: return .index(.chatList(.absoluteUpperBound)) - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return .index(index) case let .HoleEntry(holeIndex, _): return .index(.chatList(EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: holeIndex))) @@ -94,7 +95,7 @@ enum ChatListNodeEntry: Comparable, Identifiable { switch self { case .HeaderEntry: return .Header - case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): switch index { case let .chatList(index): return .PeerId(index.messageIndex.id.peerId.toInt64()) @@ -124,9 +125,9 @@ enum ChatListNodeEntry: Comparable, Identifiable { } else { return false } - case let .PeerEntry(lhsIndex, lhsPresentationData, lhsMessages, lhsUnreadCount, lhsIsRemovedFromTotalUnreadCount, lhsEmbeddedState, lhsPeer, lhsThreadInfo, lhsPresence, lhsHasUnseenMentions, lhsHasUnseenReactions, lhsEditing, lhsHasRevealControls, lhsSelected, lhsInputActivities, lhsAd, lhsHasFailedMessages, lhsIsContact, lhsForumThreadTitle): + case let .PeerEntry(lhsIndex, lhsPresentationData, lhsMessages, lhsUnreadCount, lhsIsRemovedFromTotalUnreadCount, lhsEmbeddedState, lhsPeer, lhsThreadInfo, lhsPresence, lhsHasUnseenMentions, lhsHasUnseenReactions, lhsEditing, lhsHasRevealControls, lhsSelected, lhsInputActivities, lhsAd, lhsHasFailedMessages, lhsIsContact, lhsForumThreadTitle, lhsTopForumTopicItems): switch rhs { - case let .PeerEntry(rhsIndex, rhsPresentationData, rhsMessages, rhsUnreadCount, rhsIsRemovedFromTotalUnreadCount, rhsEmbeddedState, rhsPeer, rhsThreadInfo, rhsPresence, rhsHasUnseenMentions, rhsHasUnseenReactions, rhsEditing, rhsHasRevealControls, rhsSelected, rhsInputActivities, rhsAd, rhsHasFailedMessages, rhsIsContact, rhsForumThreadTitle): + case let .PeerEntry(rhsIndex, rhsPresentationData, rhsMessages, rhsUnreadCount, rhsIsRemovedFromTotalUnreadCount, rhsEmbeddedState, rhsPeer, rhsThreadInfo, rhsPresence, rhsHasUnseenMentions, rhsHasUnseenReactions, rhsEditing, rhsHasRevealControls, rhsSelected, rhsInputActivities, rhsAd, rhsHasFailedMessages, rhsIsContact, rhsForumThreadTitle, rhsTopForumTopicItems): if lhsIndex != rhsIndex { return false } @@ -224,6 +225,9 @@ enum ChatListNodeEntry: Comparable, Identifiable { if lhsForumThreadTitle != rhsForumThreadTitle { return false } + if lhsTopForumTopicItems != rhsTopForumTopicItems { + return false + } return true default: return false @@ -393,7 +397,7 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState threadInfo = ChatListItemContent.ThreadInfo(id: threadId, info: threadData.info, isOwnedByMe: threadData.isOwnedByMe, isClosed: threadData.isClosed) } - result.append(.PeerEntry(index: offsetPinnedIndex(entry.index, offset: pinnedIndexOffset), presentationData: state.presentationData, messages: updatedMessages, readState: updatedCombinedReadState, isRemovedFromTotalUnreadCount: entry.isMuted, draftState: draftState, peer: entry.renderedPeer, threadInfo: threadInfo, presence: entry.presence, hasUnseenMentions: entry.hasUnseenMentions, hasUnseenReactions: entry.hasUnseenReactions, editing: state.editing, hasActiveRevealControls: hasActiveRevealControls, selected: isSelected, inputActivities: inputActivities, promoInfo: nil, hasFailedMessages: entry.hasFailed, isContact: entry.isContact, forumTopicData: entry.forumTopicData)) + result.append(.PeerEntry(index: offsetPinnedIndex(entry.index, offset: pinnedIndexOffset), presentationData: state.presentationData, messages: updatedMessages, readState: updatedCombinedReadState, isRemovedFromTotalUnreadCount: entry.isMuted, draftState: draftState, peer: entry.renderedPeer, threadInfo: threadInfo, presence: entry.presence, hasUnseenMentions: entry.hasUnseenMentions, hasUnseenReactions: entry.hasUnseenReactions, editing: state.editing, hasActiveRevealControls: hasActiveRevealControls, selected: isSelected, inputActivities: inputActivities, promoInfo: nil, hasFailedMessages: entry.hasFailed, isContact: entry.isContact, forumTopicData: entry.forumTopicData, topForumTopicItems: entry.topForumTopicItems)) } if !view.hasLater { var pinningIndex: UInt16 = UInt16(pinnedIndexOffset == 0 ? 0 : (pinnedIndexOffset - 1)) @@ -427,7 +431,8 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState promoInfo: nil, hasFailedMessages: false, isContact: false, - forumTopicData: nil + forumTopicData: nil, + topForumTopicItems: [] )) if foundPinningIndex != 0 { foundPinningIndex -= 1 @@ -435,7 +440,7 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState } } - result.append(.PeerEntry(index: .chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.predecessor), presentationData: state.presentationData, messages: [], readState: nil, isRemovedFromTotalUnreadCount: false, draftState: nil, peer: EngineRenderedPeer(peerId: savedMessagesPeer.id, peers: [savedMessagesPeer.id: savedMessagesPeer], associatedMedia: [:]), threadInfo: nil, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, editing: state.editing, hasActiveRevealControls: false, selected: state.selectedPeerIds.contains(savedMessagesPeer.id), inputActivities: nil, promoInfo: nil, hasFailedMessages: false, isContact: false, forumTopicData: nil)) + result.append(.PeerEntry(index: .chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.predecessor), presentationData: state.presentationData, messages: [], readState: nil, isRemovedFromTotalUnreadCount: false, draftState: nil, peer: EngineRenderedPeer(peerId: savedMessagesPeer.id, peers: [savedMessagesPeer.id: savedMessagesPeer], associatedMedia: [:]), threadInfo: nil, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, editing: state.editing, hasActiveRevealControls: false, selected: state.selectedPeerIds.contains(savedMessagesPeer.id), inputActivities: nil, promoInfo: nil, hasFailedMessages: false, isContact: false, forumTopicData: nil, topForumTopicItems: [])) } else { if !filteredAdditionalItemEntries.isEmpty { for item in filteredAdditionalItemEntries.reversed() { @@ -482,7 +487,8 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState promoInfo: promoInfo, hasFailedMessages: item.item.hasFailed, isContact: item.item.isContact, - forumTopicData: item.item.forumTopicData + forumTopicData: item.item.forumTopicData, + topForumTopicItems: item.item.topForumTopicItems )) if pinningIndex != 0 { pinningIndex -= 1 diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift index 2f1421e815..fd5f286a55 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift @@ -267,6 +267,7 @@ func chatListViewForLocation(chatListLocation: ChatListControllerLocation, locat hasUnseenMentions: hasUnseenMentions, hasUnseenReactions: hasUnseenReactions, forumTopicData: nil, + topForumTopicItems: [], hasFailed: false, isContact: false )) diff --git a/submodules/ContactListUI/Sources/ContactListNode.swift b/submodules/ContactListUI/Sources/ContactListNode.swift index 36168ccab1..0d188d8657 100644 --- a/submodules/ContactListUI/Sources/ContactListNode.swift +++ b/submodules/ContactListUI/Sources/ContactListNode.swift @@ -1267,7 +1267,7 @@ public final class ContactListNode: ASDisplayNode { return context.engine.data.get(EngineDataMap( view.entries.compactMap { entry -> EnginePeer.Id? in switch entry { - case let .MessageEntry(_, _, _, _, _, renderedPeer, _, _, _, _, _): + case let .MessageEntry(_, _, _, _, _, renderedPeer, _, _, _, _, _, _): if let peer = renderedPeer.peer { if let channel = peer as? TelegramChannel, case .group = channel.info { return peer.id @@ -1283,7 +1283,7 @@ public final class ContactListNode: ASDisplayNode { var peers: [(EnginePeer, Int32)] = [] for entry in view.entries { switch entry { - case let .MessageEntry(_, _, _, _, _, renderedPeer, _, _, _, _, _): + case let .MessageEntry(_, _, _, _, _, renderedPeer, _, _, _, _, _, _): if let peer = renderedPeer.peer { if peer is TelegramGroup { peers.append((EnginePeer(peer), 0)) diff --git a/submodules/Display/Source/LinkHighlightingNode.swift b/submodules/Display/Source/LinkHighlightingNode.swift index 48f4a4d762..2d0440bcf7 100644 --- a/submodules/Display/Source/LinkHighlightingNode.swift +++ b/submodules/Display/Source/LinkHighlightingNode.swift @@ -77,6 +77,22 @@ private func generateRectsImage(color: UIColor, rects: [CGRect], inset: CGFloat, context.setBlendMode(.copy) + /*context.move(to: CGPoint(x: rects[0].midX, y: rects[0].minY)) + + for i in 0 ..< rects.count { + let rect = rects[i].insetBy(dx: -inset, dy: -inset).offsetBy(dx: -topLeft.x, dy: -topLeft.y) + var next: CGRect? + if i + 1 < rects.count { + next = rects[i + 1].insetBy(dx: -inset, dy: -inset).offsetBy(dx: -topLeft.x, dy: -topLeft.y) + } + + if let next = next { + + } else { + + } + }*/ + for i in 0 ..< rects.count { let rect = rects[i].insetBy(dx: -inset, dy: -inset) context.fill(rect.offsetBy(dx: -topLeft.x, dy: -topLeft.y)) @@ -107,14 +123,12 @@ private func generateRectsImage(color: UIColor, rects: [CGRect], inset: CGFloat, } else { drawFullCorner(context: context, color: color, at: rect.topLeft, type: .topLeft, radius: outerRadius) } - if previous.contains(rect.topRight.offsetBy(dx: -1.0, dy: 0.0)) { - if abs(rect.topRight.x - previous.maxX) >= innerRadius { - var radius = innerRadius - if let next = next { - radius = min(radius, floor((next.minY - previous.maxY) / 2.0)) - } - drawConnectingCorner(context: context, color: color, at: CGPoint(x: rect.topRight.x, y: previous.maxY), type: .topRight, radius: radius) - } + if previous.maxY >= rect.minY && (rect.topRight.x >= previous.minX && rect.topRight.x <= previous.maxX) { + let radius = min(innerRadius, max(0.0, floor((previous.maxX - rect.topRight.x) / 2.0))) + drawConnectingCorner(context: context, color: color, at: CGPoint(x: rect.topRight.x, y: previous.maxY), type: .topRight, radius: radius) + } else if previous.maxY >= rect.minY && rect.topRight.x - previous.maxX < outerRadius * 2.0 { + let radius = max(0.0, floor((rect.bottomRight.x - previous.maxX) * 0.5)) + drawFullCorner(context: context, color: color, at: rect.topRight, type: .topRight, radius: radius) } else { drawFullCorner(context: context, color: color, at: rect.topRight, type: .topRight, radius: outerRadius) } @@ -135,14 +149,19 @@ private func generateRectsImage(color: UIColor, rects: [CGRect], inset: CGFloat, } else { drawFullCorner(context: context, color: color, at: rect.bottomLeft, type: .bottomLeft, radius: outerRadius) } - if next.contains(rect.bottomRight.offsetBy(dx: -1.0, dy: 0.0)) { + if next.minY <= rect.maxY && (rect.bottomRight.x >= next.minX && rect.bottomRight.x <= next.maxX) { if abs(rect.bottomRight.x - next.maxX) >= innerRadius { var radius = innerRadius if let previous = previous { radius = min(radius, floor((next.minY - previous.maxY) / 2.0)) } - drawConnectingCorner(context: context, color: color, at: CGPoint(x: rect.bottomRight.x, y: next.minY), type: .bottomRight, radius: radius) + drawFullCorner(context: context, color: color, at: rect.bottomRight, type: .bottomRight, radius: radius) + } else { + drawFullCorner(context: context, color: color, at: rect.bottomRight, type: .bottomRight, radius: outerRadius) } + } else if next.minY <= rect.maxY && rect.bottomRight.x - next.maxX < outerRadius * 2.0 { + let radius = max(0.0, floor((rect.bottomRight.x - next.maxX) * 0.5)) + drawFullCorner(context: context, color: color, at: rect.bottomRight, type: .bottomRight, radius: radius) } else { drawFullCorner(context: context, color: color, at: rect.bottomRight, type: .bottomRight, radius: outerRadius) } diff --git a/submodules/Postbox/Sources/ChatListView.swift b/submodules/Postbox/Sources/ChatListView.swift index 263b8a3b76..4820ff8e96 100644 --- a/submodules/Postbox/Sources/ChatListView.swift +++ b/submodules/Postbox/Sources/ChatListView.swift @@ -100,13 +100,23 @@ public struct ChatListGroupReferenceEntry: Equatable { } } +public struct ChatListForumTopicData: Equatable { + public var id: Int64 + public var info: StoredMessageHistoryThreadInfo + + public init(id: Int64, info: StoredMessageHistoryThreadInfo) { + self.id = id + self.info = info + } +} + public enum ChatListEntry: Comparable { - case MessageEntry(index: ChatListIndex, messages: [Message], readState: ChatListViewReadState?, isRemovedFromTotalUnreadCount: Bool, embeddedInterfaceState: StoredPeerChatInterfaceState?, renderedPeer: RenderedPeer, presence: PeerPresence?, summaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo], forumTopicData: StoredMessageHistoryThreadInfo?, hasFailed: Bool, isContact: Bool) + case MessageEntry(index: ChatListIndex, messages: [Message], readState: ChatListViewReadState?, isRemovedFromTotalUnreadCount: Bool, embeddedInterfaceState: StoredPeerChatInterfaceState?, renderedPeer: RenderedPeer, presence: PeerPresence?, summaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo], forumTopicData: ChatListForumTopicData?, topForumTopics: [ChatListForumTopicData], hasFailed: Bool, isContact: Bool) case HoleEntry(ChatListHole) public var index: ChatListIndex { switch self { - case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _): + case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _): return index case let .HoleEntry(hole): return ChatListIndex(pinningIndex: nil, messageIndex: hole.index) @@ -115,9 +125,9 @@ public enum ChatListEntry: Comparable { public static func ==(lhs: ChatListEntry, rhs: ChatListEntry) -> Bool { switch lhs { - case let .MessageEntry(lhsIndex, lhsMessages, lhsReadState, lhsIsRemovedFromTotalUnreadCount, lhsEmbeddedState, lhsPeer, lhsPresence, lhsInfo, lhsForumTopicData, lhsHasFailed, lhsIsContact): + case let .MessageEntry(lhsIndex, lhsMessages, lhsReadState, lhsIsRemovedFromTotalUnreadCount, lhsEmbeddedState, lhsPeer, lhsPresence, lhsInfo, lhsForumTopicData, lhsTopForumTopics, lhsHasFailed, lhsIsContact): switch rhs { - case let .MessageEntry(rhsIndex, rhsMessages, rhsReadState, rhsIsRemovedFromTotalUnreadCount, rhsEmbeddedState, rhsPeer, rhsPresence, rhsInfo, rhsForumTopicData, rhsHasFailed, rhsIsContact): + case let .MessageEntry(rhsIndex, rhsMessages, rhsReadState, rhsIsRemovedFromTotalUnreadCount, rhsEmbeddedState, rhsPeer, rhsPresence, rhsInfo, rhsForumTopicData, rhsTopForumTopics, rhsHasFailed, rhsIsContact): if lhsIndex != rhsIndex { return false } @@ -158,6 +168,9 @@ public enum ChatListEntry: Comparable { if lhsForumTopicData != rhsForumTopicData { return false } + if lhsTopForumTopics != rhsTopForumTopics { + return false + } if lhsHasFailed != rhsHasFailed { return false } @@ -184,7 +197,7 @@ public enum ChatListEntry: Comparable { enum MutableChatListEntry: Equatable { case IntermediateMessageEntry(index: ChatListIndex, messageIndex: MessageIndex?) - case MessageEntry(index: ChatListIndex, messages: [Message], readState: ChatListViewReadState?, notificationSettings: PeerNotificationSettings?, isRemovedFromTotalUnreadCount: Bool, embeddedInterfaceState: StoredPeerChatInterfaceState?, renderedPeer: RenderedPeer, presence: PeerPresence?, tagSummaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo], forumTopicData: StoredMessageHistoryThreadInfo?, hasFailedMessages: Bool, isContact: Bool) + case MessageEntry(index: ChatListIndex, messages: [Message], readState: ChatListViewReadState?, notificationSettings: PeerNotificationSettings?, isRemovedFromTotalUnreadCount: Bool, embeddedInterfaceState: StoredPeerChatInterfaceState?, renderedPeer: RenderedPeer, presence: PeerPresence?, tagSummaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo], forumTopicData: ChatListForumTopicData?, topForumTopics: [ChatListForumTopicData], hasFailedMessages: Bool, isContact: Bool) case HoleEntry(ChatListHole) init(_ intermediateEntry: ChatListIntermediateEntry, cachedDataTable: CachedPeerDataTable, readStateTable: MessageHistoryReadStateTable, messageHistoryTable: MessageHistoryTable) { @@ -200,7 +213,7 @@ enum MutableChatListEntry: Equatable { switch self { case let .IntermediateMessageEntry(index, _): return index - case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _): + case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _, _): return index case let .HoleEntry(hole): return ChatListIndex(pinningIndex: nil, messageIndex: hole.index) @@ -666,9 +679,20 @@ final class MutableChatListView { } } - var forumTopicData: StoredMessageHistoryThreadInfo? + let renderedPeer = RenderedPeer(peerId: index.messageIndex.id.peerId, peers: peers, associatedMedia: renderAssociatedMediaForPeers(postbox: postbox, peers: peers)) + + var forumTopicData: ChatListForumTopicData? if let message = renderedMessages.first, let threadId = message.threadId { - forumTopicData = postbox.messageHistoryThreadIndexTable.get(peerId: message.id.peerId, threadId: threadId) + if let info = postbox.messageHistoryThreadIndexTable.get(peerId: message.id.peerId, threadId: threadId) { + forumTopicData = ChatListForumTopicData(id: threadId, info: info) + } + } + + var topForumTopics: [ChatListForumTopicData] = [] + if let peer = renderedPeer.peer, postbox.seedConfiguration.peerSummaryIsThreadBased(peer) { + for item in postbox.messageHistoryThreadIndexTable.fetch(peerId: peer.id, namespace: 0, start: .upperBound, end: .lowerBound, limit: 5) { + topForumTopics.append(ChatListForumTopicData(id: item.threadId, info: item.info)) + } } let readState: ChatListViewReadState? @@ -689,7 +713,7 @@ final class MutableChatListView { } } - return .MessageEntry(index: index, messages: renderedMessages, readState: readState, notificationSettings: notificationSettings, isRemovedFromTotalUnreadCount: false, embeddedInterfaceState: postbox.peerChatInterfaceStateTable.get(index.messageIndex.id.peerId), renderedPeer: RenderedPeer(peerId: index.messageIndex.id.peerId, peers: peers, associatedMedia: renderAssociatedMediaForPeers(postbox: postbox, peers: peers)), presence: presence, tagSummaryInfo: [:], forumTopicData: forumTopicData, hasFailedMessages: postbox.messageHistoryFailedTable.contains(peerId: index.messageIndex.id.peerId), isContact: isContact) + return .MessageEntry(index: index, messages: renderedMessages, readState: readState, notificationSettings: notificationSettings, isRemovedFromTotalUnreadCount: false, embeddedInterfaceState: postbox.peerChatInterfaceStateTable.get(index.messageIndex.id.peerId), renderedPeer: renderedPeer, presence: presence, tagSummaryInfo: [:], forumTopicData: forumTopicData, topForumTopics: topForumTopics, hasFailedMessages: postbox.messageHistoryFailedTable.contains(peerId: index.messageIndex.id.peerId), isContact: isContact) default: return nil } @@ -718,8 +742,8 @@ public final class ChatListView { var entries: [ChatListEntry] = [] for entry in mutableView.sampledState.entries { switch entry { - case let .MessageEntry(index, messages, combinedReadState, _, isRemovedFromTotalUnreadCount, embeddedState, peer, peerPresence, summaryInfo, forumTopicData, hasFailed, isContact): - entries.append(.MessageEntry(index: index, messages: messages, readState: combinedReadState, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: embeddedState, renderedPeer: peer, presence: peerPresence, summaryInfo: summaryInfo, forumTopicData: forumTopicData, hasFailed: hasFailed, isContact: isContact)) + case let .MessageEntry(index, messages, combinedReadState, _, isRemovedFromTotalUnreadCount, embeddedState, peer, peerPresence, summaryInfo, forumTopicData, topForumTopics, hasFailed, isContact): + entries.append(.MessageEntry(index: index, messages: messages, readState: combinedReadState, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: embeddedState, renderedPeer: peer, presence: peerPresence, summaryInfo: summaryInfo, forumTopicData: forumTopicData, topForumTopics: topForumTopics, hasFailed: hasFailed, isContact: isContact)) case let .HoleEntry(hole): entries.append(.HoleEntry(hole)) case .IntermediateMessageEntry: @@ -736,9 +760,9 @@ public final class ChatListView { var additionalItemEntries: [ChatListAdditionalItemEntry] = [] for entry in mutableView.additionalItemEntries { switch entry.entry { - case let .MessageEntry(index, messages, combinedReadState, _, isExcludedFromUnreadCount, embeddedState, peer, peerPresence, summaryInfo, forumTopicData, hasFailed, isContact): + case let .MessageEntry(index, messages, combinedReadState, _, isExcludedFromUnreadCount, embeddedState, peer, peerPresence, summaryInfo, forumTopicData, topForumTopics, hasFailed, isContact): additionalItemEntries.append(ChatListAdditionalItemEntry( - entry: .MessageEntry(index: index, messages: messages, readState: combinedReadState, isRemovedFromTotalUnreadCount: isExcludedFromUnreadCount, embeddedInterfaceState: embeddedState, renderedPeer: peer, presence: peerPresence, summaryInfo: summaryInfo, forumTopicData: forumTopicData, hasFailed: hasFailed, isContact: isContact), + entry: .MessageEntry(index: index, messages: messages, readState: combinedReadState, isRemovedFromTotalUnreadCount: isExcludedFromUnreadCount, embeddedInterfaceState: embeddedState, renderedPeer: peer, presence: peerPresence, summaryInfo: summaryInfo, forumTopicData: forumTopicData, topForumTopics: topForumTopics, hasFailed: hasFailed, isContact: isContact), info: entry.info )) case .HoleEntry: diff --git a/submodules/Postbox/Sources/ChatListViewState.swift b/submodules/Postbox/Sources/ChatListViewState.swift index 1a52c171fe..09d48236a1 100644 --- a/submodules/Postbox/Sources/ChatListViewState.swift +++ b/submodules/Postbox/Sources/ChatListViewState.swift @@ -515,7 +515,7 @@ private final class ChatListViewSpaceState { let entryPeer: Peer let entryNotificationsPeerId: PeerId switch entry { - case let .MessageEntry(_, _, _, _, _, _, renderedPeer, _, _, _, _, _): + case let .MessageEntry(_, _, _, _, _, _, renderedPeer, _, _, _, _, _, _): if let peer = renderedPeer.peer { entryPeer = peer entryNotificationsPeerId = peer.notificationSettingsPeerId ?? peer.id @@ -630,13 +630,13 @@ private final class ChatListViewSpaceState { if self.orderedEntries.mutableScan({ entry in switch entry { - case let .MessageEntry(index, messages, readState, _, _, embeddedInterfaceState, renderedPeer, presence, tagSummaryInfo, forumTopicData, hasFailedMessages, isContact): + case let .MessageEntry(index, messages, readState, _, _, embeddedInterfaceState, renderedPeer, presence, tagSummaryInfo, forumTopicData, topForumTopics, hasFailedMessages, isContact): if let peer = renderedPeer.peer { let notificationsPeerId = peer.notificationSettingsPeerId ?? peer.id if let (_, updated) = transaction.currentUpdatedPeerNotificationSettings[notificationsPeerId] { let isRemovedFromTotalUnreadCount = resolvedIsRemovedFromTotalUnreadCount(globalSettings: globalNotificationSettings, peer: peer, peerSettings: updated) - return .MessageEntry(index: index, messages: messages, readState: readState, notificationSettings: updated, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: embeddedInterfaceState, renderedPeer: renderedPeer, presence: presence, tagSummaryInfo: tagSummaryInfo, forumTopicData: forumTopicData, hasFailedMessages: hasFailedMessages, isContact: isContact) + return .MessageEntry(index: index, messages: messages, readState: readState, notificationSettings: updated, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: embeddedInterfaceState, renderedPeer: renderedPeer, presence: presence, tagSummaryInfo: tagSummaryInfo, forumTopicData: forumTopicData, topForumTopics: topForumTopics, hasFailedMessages: hasFailedMessages, isContact: isContact) } else { return nil } @@ -654,7 +654,7 @@ private final class ChatListViewSpaceState { if !transaction.updatedFailedMessagePeerIds.isEmpty { if self.orderedEntries.mutableScan({ entry in switch entry { - case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, renderedPeer, presence, tagSummaryInfo, forumTopicData, _, isContact): + case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, renderedPeer, presence, tagSummaryInfo, forumTopicData, topForumTopics, _, isContact): if transaction.updatedFailedMessagePeerIds.contains(index.messageIndex.id.peerId) { return .MessageEntry( index: index, @@ -667,6 +667,7 @@ private final class ChatListViewSpaceState { presence: presence, tagSummaryInfo: tagSummaryInfo, forumTopicData: forumTopicData, + topForumTopics: topForumTopics, hasFailedMessages: postbox.messageHistoryFailedTable.contains(peerId: index.messageIndex.id.peerId), isContact: isContact ) @@ -684,7 +685,7 @@ private final class ChatListViewSpaceState { if !transaction.currentUpdatedPeers.isEmpty { if self.orderedEntries.mutableScan({ entry in switch entry { - case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, entryRenderedPeer, presence, tagSummaryInfo, forumTopicData, hasFailedMessages, isContact): + case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, entryRenderedPeer, presence, tagSummaryInfo, forumTopicData, topForumTopics, hasFailedMessages, isContact): var updatedMessages: [Message] = messages var hasUpdatedMessages = false for i in 0 ..< updatedMessages.count { @@ -707,6 +708,7 @@ private final class ChatListViewSpaceState { presence: presence, tagSummaryInfo: tagSummaryInfo, forumTopicData: forumTopicData, + topForumTopics: topForumTopics, hasFailedMessages: hasFailedMessages, isContact: isContact) } else { @@ -723,7 +725,7 @@ private final class ChatListViewSpaceState { if !transaction.currentUpdatedPeerPresences.isEmpty { if self.orderedEntries.mutableScan({ entry in switch entry { - case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, entryRenderedPeer, _, tagSummaryInfo, forumTopicData, hasFailedMessages, isContact): + case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, entryRenderedPeer, _, tagSummaryInfo, forumTopicData, topForumTopics, hasFailedMessages, isContact): var presencePeerId = entryRenderedPeer.peerId if let peer = entryRenderedPeer.peers[entryRenderedPeer.peerId], let associatedPeerId = peer.associatedPeerId { presencePeerId = associatedPeerId @@ -740,6 +742,7 @@ private final class ChatListViewSpaceState { presence: presence, tagSummaryInfo: tagSummaryInfo, forumTopicData: forumTopicData, + topForumTopics: topForumTopics, hasFailedMessages: hasFailedMessages, isContact: isContact ) @@ -760,7 +763,7 @@ private final class ChatListViewSpaceState { let entryPeer: Peer let entryNotificationsPeerId: PeerId switch entry { - case let .MessageEntry(_, _, _, _, _, _, entryRenderedPeer, _, _, _, _, _): + case let .MessageEntry(_, _, _, _, _, _, entryRenderedPeer, _, _, _, _, _, _): if let peer = entryRenderedPeer.peer { entryPeer = peer entryNotificationsPeerId = peer.notificationSettingsPeerId ?? peer.id @@ -881,7 +884,7 @@ private final class ChatListViewSpaceState { if !transaction.currentUpdatedMessageTagSummaries.isEmpty || !transaction.currentUpdatedMessageActionsSummaries.isEmpty || !transaction.updatedPeerThreadsSummaries.isEmpty { if self.orderedEntries.mutableScan({ entry in switch entry { - case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, entryRenderedPeer, presence, tagSummaryInfo, forumTopicData, hasFailedMessages, isContact): + case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, entryRenderedPeer, presence, tagSummaryInfo, forumTopicData, topForumTopics, hasFailedMessages, isContact): var updatedChatListMessageTagSummaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo] = tagSummaryInfo var didUpdateSummaryInfo = false @@ -947,6 +950,7 @@ private final class ChatListViewSpaceState { presence: presence, tagSummaryInfo: updatedChatListMessageTagSummaryInfo, forumTopicData: forumTopicData, + topForumTopics: topForumTopics, hasFailedMessages: hasFailedMessages, isContact: isContact ) @@ -1082,7 +1086,7 @@ private extension MutableChatListEntry { switch self { case let .IntermediateMessageEntry(index, _): return index.messageIndex.id.peerId - case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _): + case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _, _): return index.messageIndex.id.peerId case .HoleEntry: return nil @@ -1093,7 +1097,7 @@ private extension MutableChatListEntry { switch self { case let .IntermediateMessageEntry(index, _): return MutableChatListEntryIndex(index: index, isMessage: true) - case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _): + case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _, _): return MutableChatListEntryIndex(index: index, isMessage: true) case let .HoleEntry(hole): return MutableChatListEntryIndex(index: ChatListIndex(pinningIndex: nil, messageIndex: hole.index), isMessage: false) @@ -1104,7 +1108,7 @@ private extension MutableChatListEntry { switch self { case let .IntermediateMessageEntry(index, _): return .peer(index.messageIndex.id.peerId) - case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _): + case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _, _): return .peer(index.messageIndex.id.peerId) case let .HoleEntry(hole): return .hole(hole.index) @@ -1521,13 +1525,21 @@ struct ChatListViewState { } } - var forumTopicData: StoredMessageHistoryThreadInfo? + var forumTopicData: ChatListForumTopicData? if let message = renderedMessages.first, let threadId = message.threadId { - forumTopicData = postbox.messageHistoryThreadIndexTable.get(peerId: message.id.peerId, threadId: threadId) + if let info = postbox.messageHistoryThreadIndexTable.get(peerId: message.id.peerId, threadId: threadId) { + forumTopicData = ChatListForumTopicData(id: threadId, info: info) + } } + var topForumTopics: [ChatListForumTopicData] = [] let readState: ChatListViewReadState? + if let peer = postbox.peerTable.get(index.messageIndex.id.peerId), postbox.seedConfiguration.peerSummaryIsThreadBased(peer) { + for item in postbox.messageHistoryThreadIndexTable.fetch(peerId: peer.id, namespace: 0, start: .upperBound, end: .lowerBound, limit: 5) { + topForumTopics.append(ChatListForumTopicData(id: item.threadId, info: item.info)) + } + let summary = postbox.peerThreadsSummaryTable.get(peerId: peer.id) var count: Int32 = 0 @@ -1546,7 +1558,7 @@ struct ChatListViewState { } } - let updatedEntry: MutableChatListEntry = .MessageEntry(index: index, messages: renderedMessages, readState: readState, notificationSettings: notificationSettings, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: postbox.peerChatInterfaceStateTable.get(index.messageIndex.id.peerId), renderedPeer: renderedPeer, presence: presence, tagSummaryInfo: tagSummaryInfo, forumTopicData: forumTopicData, hasFailedMessages: false, isContact: postbox.contactsTable.isContact(peerId: index.messageIndex.id.peerId)) + let updatedEntry: MutableChatListEntry = .MessageEntry(index: index, messages: renderedMessages, readState: readState, notificationSettings: notificationSettings, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: postbox.peerChatInterfaceStateTable.get(index.messageIndex.id.peerId), renderedPeer: renderedPeer, presence: presence, tagSummaryInfo: tagSummaryInfo, forumTopicData: forumTopicData, topForumTopics: topForumTopics, hasFailedMessages: false, isContact: postbox.contactsTable.isContact(peerId: index.messageIndex.id.peerId)) if directionIndex == 0 { self.stateBySpace[space]!.orderedEntries.setLowerOrAtAnchorAtArrayIndex(listIndex, to: updatedEntry) } else { diff --git a/submodules/Postbox/Sources/MessageHistoryHoleIndexTable.swift b/submodules/Postbox/Sources/MessageHistoryHoleIndexTable.swift index e50c7d58be..e616ba34b8 100644 --- a/submodules/Postbox/Sources/MessageHistoryHoleIndexTable.swift +++ b/submodules/Postbox/Sources/MessageHistoryHoleIndexTable.swift @@ -394,8 +394,6 @@ final class MessageHistoryHoleIndexTable: Table { } private func removeInternal(peerId: PeerId, namespace: MessageId.Namespace, space: MessageHistoryHoleSpace, range: ClosedRange, operations: inout [MessageHistoryIndexHoleOperationKey: [MessageHistoryIndexHoleOperation]]) { - postboxLog("MessageHistoryHoleIndexTable: removeInternal peerId: \(peerId) namespace: \(namespace) space: \(space) range: \(range)") - var removeKeys: [Int32] = [] var insertRanges = IndexSet() @@ -440,6 +438,7 @@ final class MessageHistoryHoleIndexTable: Table { if !removeKeys.isEmpty { addMessageHistoryHoleOperation(.remove(range), peerId: peerId, threadId: nil, namespace: namespace, space: space, to: &operations) + postboxLog("MessageHistoryHoleIndexTable: removeInternal peerId: \(peerId) namespace: \(namespace) space: \(space) range: \(range)") } } diff --git a/submodules/Postbox/Sources/MessageHistoryView.swift b/submodules/Postbox/Sources/MessageHistoryView.swift index 8f6549342d..e681c13b48 100644 --- a/submodules/Postbox/Sources/MessageHistoryView.swift +++ b/submodules/Postbox/Sources/MessageHistoryView.swift @@ -914,7 +914,7 @@ final class MutableMessageHistoryView { case .ready: return nil case let .loadHole(peerId, namespace, _, threadId, id): - return (.peer(MessageHistoryViewPeerHole(peerId: peerId, namespace: namespace, threadId: threadId)), .aroundId(MessageId(peerId: peerId, namespace: namespace, id: id)), self.fillCount * 2, self.userId) + return (.peer(MessageHistoryViewPeerHole(peerId: peerId, namespace: namespace, threadId: threadId)), .aroundId(MessageId(peerId: peerId, namespace: namespace, id: id)), 100, self.userId) } case let .loaded(loadedSample): if let hole = loadedSample.hole { @@ -924,7 +924,7 @@ final class MutableMessageHistoryView { } else { direction = .aroundId(MessageId(peerId: hole.peerId, namespace: hole.namespace, id: hole.startId)) } - return (.peer(MessageHistoryViewPeerHole(peerId: hole.peerId, namespace: hole.namespace, threadId: hole.threadId)), direction, self.fillCount * 2, self.userId) + return (.peer(MessageHistoryViewPeerHole(peerId: hole.peerId, namespace: hole.namespace, threadId: hole.threadId)), direction, 100, self.userId) } else { return nil } diff --git a/submodules/Postbox/Sources/Postbox.swift b/submodules/Postbox/Sources/Postbox.swift index f085c1710c..04494158b3 100644 --- a/submodules/Postbox/Sources/Postbox.swift +++ b/submodules/Postbox/Sources/Postbox.swift @@ -1272,8 +1272,8 @@ public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration, } #if DEBUG - //debugSaveState(basePath: basePath + "/db", name: "previous1") - //debugRestoreState(basePath: basePath + "/db", name: "previous1") + //debugSaveState(basePath: basePath + "/db", name: "previous2") + debugRestoreState(basePath: basePath + "/db", name: "previous2") #endif let startTime = CFAbsoluteTimeGetCurrent() diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index e9497fb24f..2470b63321 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -284,7 +284,8 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false, - forumTopicData: nil + forumTopicData: nil, + topForumTopicItems: [] ), editing: false, hasActiveRevealControls: false, diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift index 9a66cf1c91..7745b9f833 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift @@ -904,7 +904,8 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false, - forumTopicData: nil + forumTopicData: nil, + topForumTopicItems: [] ), editing: false, hasActiveRevealControls: false, diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index d20afe24df..f17b12a0bd 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -427,7 +427,8 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false, - forumTopicData: nil + forumTopicData: nil, + topForumTopicItems: [] ), editing: false, hasActiveRevealControls: false, diff --git a/submodules/ShareController/Sources/ShareController.swift b/submodules/ShareController/Sources/ShareController.swift index 42c91ab674..6aa5b49cc3 100644 --- a/submodules/ShareController/Sources/ShareController.swift +++ b/submodules/ShareController/Sources/ShareController.swift @@ -1028,7 +1028,7 @@ public final class ShareController: ViewController { var peers: [EngineRenderedPeer] = [] for entry in view.0.entries.reversed() { switch entry { - case let .MessageEntry(_, _, _, _, _, renderedPeer, _, _, _, _, _): + case let .MessageEntry(_, _, _, _, _, renderedPeer, _, _, _, _, _, _): if let peer = renderedPeer.peers[renderedPeer.peerId], peer.id != accountPeer.id, canSendMessagesToPeer(peer) { peers.append(EngineRenderedPeer(renderedPeer)) } diff --git a/submodules/ShareController/Sources/ShareControllerNode.swift b/submodules/ShareController/Sources/ShareControllerNode.swift index 3954c7131c..1490d54b74 100644 --- a/submodules/ShareController/Sources/ShareControllerNode.swift +++ b/submodules/ShareController/Sources/ShareControllerNode.swift @@ -1313,6 +1313,7 @@ private func threadList(context: AccountContext, peerId: EnginePeer.Id) -> Signa hasUnseenMentions: false, hasUnseenReactions: false, forumTopicData: nil, + topForumTopicItems: [], hasFailed: false, isContact: false )) diff --git a/submodules/TelegramCore/Sources/State/Holes.swift b/submodules/TelegramCore/Sources/State/Holes.swift index 04c6db137d..9a05d2475e 100644 --- a/submodules/TelegramCore/Sources/State/Holes.swift +++ b/submodules/TelegramCore/Sources/State/Holes.swift @@ -119,8 +119,9 @@ private func withResolvedAssociatedMessages(postbox: Postbox, source: FetchMe } } - let allPossiblyStoredReferencedIds = storedIds.union(referencedGeneralIds).union(referencedReplyIds.targetIdsBySourceId.keys) - let allStoredReferencedIds = transaction.filterStoredMessageIds(allPossiblyStoredReferencedIds) + let allPossiblyStoredReferencedIds = referencedGeneralIds.union(referencedReplyIds.targetIdsBySourceId.keys) + + let allStoredReferencedIds = transaction.filterStoredMessageIds(allPossiblyStoredReferencedIds).union(storedIds) referencedReplyIds = referencedReplyIds.subtractingStoredIds(allStoredReferencedIds) referencedGeneralIds.subtract(allStoredReferencedIds) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift index 38e66f82e8..e7a71e75ea 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift @@ -28,12 +28,14 @@ public final class EngineChatList: Equatable { } public struct ForumTopicData: Equatable { + public var id: Int64 public var title: String public let iconFileId: Int64? public let iconColor: Int32 public var maxOutgoingReadMessageId: EngineMessage.Id - public init(title: String, iconFileId: Int64?, iconColor: Int32, maxOutgoingReadMessageId: EngineMessage.Id) { + public init(id: Int64, title: String, iconFileId: Int64?, iconColor: Int32, maxOutgoingReadMessageId: EngineMessage.Id) { + self.id = id self.title = title self.iconFileId = iconFileId self.iconColor = iconColor @@ -119,6 +121,7 @@ public final class EngineChatList: Equatable { public let hasUnseenMentions: Bool public let hasUnseenReactions: Bool public let forumTopicData: ForumTopicData? + public let topForumTopicItems: [EngineChatList.ForumTopicData] public let hasFailed: Bool public let isContact: Bool @@ -135,6 +138,7 @@ public final class EngineChatList: Equatable { hasUnseenMentions: Bool, hasUnseenReactions: Bool, forumTopicData: ForumTopicData?, + topForumTopicItems: [EngineChatList.ForumTopicData], hasFailed: Bool, isContact: Bool ) { @@ -150,6 +154,7 @@ public final class EngineChatList: Equatable { self.hasUnseenMentions = hasUnseenMentions self.hasUnseenReactions = hasUnseenReactions self.forumTopicData = forumTopicData + self.topForumTopicItems = topForumTopicItems self.hasFailed = hasFailed self.isContact = isContact } @@ -191,6 +196,9 @@ public final class EngineChatList: Equatable { if lhs.forumTopicData != rhs.forumTopicData { return false } + if lhs.topForumTopicItems != rhs.topForumTopicItems { + return false + } if lhs.hasFailed != rhs.hasFailed { return false } @@ -398,7 +406,7 @@ public extension EngineChatList.RelativePosition { extension EngineChatList.Item { convenience init?(_ entry: ChatListEntry) { switch entry { - case let .MessageEntry(index, messages, readState, isRemovedFromTotalUnreadCount, embeddedState, renderedPeer, presence, tagSummaryInfo, forumTopicData, hasFailed, isContact): + case let .MessageEntry(index, messages, readState, isRemovedFromTotalUnreadCount, embeddedState, renderedPeer, presence, tagSummaryInfo, forumTopicData, topForumTopics, hasFailed, isContact): var draft: EngineChatList.Draft? if let embeddedState = embeddedState, let _ = embeddedState.overrideChatTimestamp { if let opaqueState = _internal_decodeStoredChatInterfaceState(state: embeddedState) { @@ -425,8 +433,18 @@ extension EngineChatList.Item { } var forumTopicDataValue: EngineChatList.ForumTopicData? - if let forumTopicData = forumTopicData?.data.get(MessageHistoryThreadData.self) { - forumTopicDataValue = EngineChatList.ForumTopicData(title: forumTopicData.info.title, iconFileId: forumTopicData.info.icon, iconColor: forumTopicData.info.iconColor, maxOutgoingReadMessageId: MessageId(peerId: index.messageIndex.id.peerId, namespace: Namespaces.Message.Cloud, id: forumTopicData.maxOutgoingReadId)) + if let forumTopicData = forumTopicData { + let id = forumTopicData.id + if let forumTopicData = forumTopicData.info.data.get(MessageHistoryThreadData.self) { + forumTopicDataValue = EngineChatList.ForumTopicData(id: id, title: forumTopicData.info.title, iconFileId: forumTopicData.info.icon, iconColor: forumTopicData.info.iconColor, maxOutgoingReadMessageId: MessageId(peerId: index.messageIndex.id.peerId, namespace: Namespaces.Message.Cloud, id: forumTopicData.maxOutgoingReadId)) + } + } + + var topForumTopicItems: [EngineChatList.ForumTopicData] = [] + for item in topForumTopics { + if let forumTopicData = item.info.data.get(MessageHistoryThreadData.self) { + topForumTopicItems.append(EngineChatList.ForumTopicData(id: item.id, title: forumTopicData.info.title, iconFileId: forumTopicData.info.icon, iconColor: forumTopicData.info.iconColor, maxOutgoingReadMessageId: MessageId(peerId: index.messageIndex.id.peerId, namespace: Namespaces.Message.Cloud, id: forumTopicData.maxOutgoingReadId))) + } } let readCounters = readState.flatMap(EnginePeerReadCounters.init) @@ -444,6 +462,7 @@ extension EngineChatList.Item { hasUnseenMentions: hasUnseenMentions, hasUnseenReactions: hasUnseenReactions, forumTopicData: forumTopicDataValue, + topForumTopicItems: topForumTopicItems, hasFailed: hasFailed, isContact: isContact ) diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Chats.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Chats.imageset/Contents.json new file mode 100644 index 0000000000..c11b3169aa --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Chats.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "chats_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Chats.imageset/chats_24.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Chats.imageset/chats_24.pdf new file mode 100644 index 0000000000..d45a8d20f7 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Chats.imageset/chats_24.pdf @@ -0,0 +1,261 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 3.000000 3.630371 cm +0.000000 0.000000 0.000000 scn +6.036711 3.044792 m +5.888044 2.396624 l +6.036711 3.044792 l +h +4.213051 1.739638 m +3.887771 2.319654 l +3.887771 2.319654 l +4.213051 1.739638 l +h +1.977577 1.386564 m +1.720352 0.773327 l +1.977577 1.386564 l +h +2.890895 4.595274 m +2.498061 4.058705 l +2.890895 4.595274 l +h +8.000000 16.704628 m +12.112072 16.704628 15.335000 13.688025 15.335000 10.096902 c +16.665001 10.096902 l +16.665001 14.539012 12.724484 18.034630 8.000000 18.034630 c +8.000000 16.704628 l +h +15.335000 10.096902 m +15.335000 6.505779 12.112072 3.489175 8.000000 3.489175 c +8.000000 2.159175 l +12.724484 2.159175 16.665001 5.654792 16.665001 10.096902 c +15.335000 10.096902 l +h +8.000000 3.489175 m +7.372782 3.489175 6.764923 3.560034 6.185379 3.692961 c +5.888044 2.396624 l +6.564841 2.241389 7.272436 2.159175 8.000000 2.159175 c +8.000000 3.489175 l +h +6.185379 3.692961 m +5.804111 3.780411 5.513260 3.569372 5.413143 3.494156 c +5.290826 3.402263 5.147437 3.265127 5.034352 3.161364 c +4.781663 2.929504 4.434368 2.626192 3.887771 2.319654 c +4.538331 1.159622 l +5.211778 1.537300 5.645048 1.916672 5.933547 2.181390 c +6.091055 2.325915 6.158414 2.390541 6.212014 2.430810 c +6.287813 2.487756 6.138047 2.339281 5.888044 2.396624 c +6.185379 3.692961 l +h +3.887771 2.319654 m +3.572365 2.142771 3.165311 2.048659 2.790003 2.014445 c +2.607043 1.997766 2.446026 1.996657 2.327112 2.003073 c +2.267397 2.006294 2.224190 2.011101 2.197881 2.015177 c +2.158949 2.021208 2.183327 2.021393 2.234801 1.999803 c +1.720352 0.773327 l +1.821844 0.730755 1.926930 0.711287 1.994275 0.700853 c +2.074244 0.688465 2.162884 0.679998 2.255463 0.675005 c +2.441139 0.664988 2.666504 0.667671 2.910749 0.689938 c +3.389851 0.733614 4.000499 0.858000 4.538331 1.159622 c +3.887771 2.319654 l +h +2.234801 1.999803 m +2.265677 1.986851 2.404851 1.923249 2.493219 1.747771 c +2.589725 1.556132 2.551083 1.383581 2.526376 1.313473 c +2.504906 1.252550 2.480861 1.226551 2.497980 1.249567 c +2.510608 1.266544 2.534224 1.295128 2.576988 1.342783 c +2.735965 1.519941 3.080277 1.865030 3.376156 2.318596 c +2.262219 3.045261 l +2.039068 2.703184 1.800315 2.468651 1.587115 2.231068 c +1.536834 2.175037 1.480586 2.110241 1.430826 2.043344 c +1.385558 1.982486 1.316601 1.882117 1.271991 1.755535 c +1.224144 1.619764 1.184133 1.390259 1.305339 1.149572 c +1.418406 0.925049 1.608346 0.820307 1.720352 0.773327 c +2.234801 1.999803 l +h +3.376156 2.318596 m +3.725287 2.853793 3.824730 3.434122 3.801171 3.906185 c +3.789388 4.142282 3.746260 4.366606 3.677134 4.560054 c +3.618218 4.724927 3.504569 4.970161 3.283730 5.131843 c +2.498061 4.058705 l +2.393648 4.135148 2.394127 4.198050 2.424695 4.112509 c +2.445051 4.055541 2.466761 3.961368 2.472824 3.839892 c +2.484955 3.596812 2.432118 3.305708 2.262219 3.045261 c +3.376156 2.318596 l +h +3.283730 5.131843 m +1.614924 6.353613 0.665000 8.040656 0.665000 10.096902 c +-0.665000 10.096902 l +-0.665000 7.571545 0.525193 5.503087 2.498061 4.058705 c +3.283730 5.131843 l +h +0.665000 10.096902 m +0.665000 13.688025 3.887928 16.704628 8.000000 16.704628 c +8.000000 18.034630 l +3.275515 18.034630 -0.665000 14.539012 -0.665000 10.096902 c +0.665000 10.096902 l +h +f +n +Q +q +-1.000000 -0.000000 -0.000000 1.000000 21.700195 1.535645 cm +0.000000 0.000000 0.000000 scn +6.036711 3.139519 m +5.888044 2.491350 l +6.036711 3.139519 l +h +4.213051 1.834365 m +3.887771 2.414380 l +3.887771 2.414380 l +4.213051 1.834365 l +h +1.977577 1.481291 m +1.720352 0.868052 l +1.977577 1.481291 l +h +2.890895 4.690001 m +2.498061 4.153432 l +2.890895 4.690001 l +h +1.435227 13.141545 m +1.613510 13.462641 1.497738 13.867466 1.176643 14.045750 c +0.855548 14.224032 0.450722 14.108260 0.272439 13.787166 c +1.435227 13.141545 l +h +9.828376 2.430953 m +10.188738 2.501846 10.423399 2.851448 10.352506 3.211810 c +10.281612 3.572172 9.932011 3.806833 9.571649 3.735940 c +9.828376 2.430953 l +h +8.000000 3.583901 m +7.372782 3.583901 6.764923 3.654760 6.185379 3.787687 c +5.888044 2.491350 l +6.564841 2.336116 7.272436 2.253901 8.000000 2.253901 c +8.000000 3.583901 l +h +6.185379 3.787687 m +5.804111 3.875137 5.513260 3.664099 5.413143 3.588882 c +5.290826 3.496989 5.147437 3.359854 5.034352 3.256090 c +4.781663 3.024231 4.434368 2.720919 3.887771 2.414380 c +4.538331 1.254350 l +5.211778 1.632027 5.645048 2.011398 5.933547 2.276116 c +6.091055 2.420642 6.158414 2.485268 6.212014 2.525537 c +6.287813 2.582482 6.138047 2.434008 5.888044 2.491350 c +6.185379 3.787687 l +h +3.887771 2.414380 m +3.572365 2.237497 3.165311 2.143386 2.790003 2.109172 c +2.607043 2.092493 2.446026 2.091384 2.327112 2.097799 c +2.267397 2.101021 2.224190 2.105827 2.197881 2.109903 c +2.158949 2.115934 2.183327 2.116119 2.234801 2.094529 c +1.720352 0.868052 l +1.821844 0.825482 1.926930 0.806013 1.994275 0.795580 c +2.074244 0.783192 2.162884 0.774725 2.255463 0.769731 c +2.441139 0.759713 2.666504 0.762398 2.910749 0.784664 c +3.389851 0.828341 4.000499 0.952726 4.538331 1.254350 c +3.887771 2.414380 l +h +2.234801 2.094529 m +2.265677 2.081577 2.404851 2.017976 2.493219 1.842498 c +2.589725 1.650859 2.551083 1.478308 2.526376 1.408198 c +2.504906 1.347278 2.480861 1.321278 2.497980 1.344294 c +2.510608 1.361270 2.534224 1.389854 2.576988 1.437510 c +2.735965 1.614668 3.080277 1.959757 3.376156 2.413322 c +2.262219 3.139988 l +2.039068 2.797911 1.800315 2.563377 1.587115 2.325794 c +1.536834 2.269764 1.480586 2.204967 1.430826 2.138070 c +1.385558 2.077212 1.316601 1.976844 1.271991 1.850262 c +1.224144 1.714491 1.184133 1.484985 1.305339 1.244299 c +1.418406 1.019774 1.608346 0.915034 1.720352 0.868052 c +2.234801 2.094529 l +h +3.376156 2.413322 m +3.725287 2.948520 3.824730 3.528849 3.801171 4.000912 c +3.789388 4.237008 3.746260 4.461332 3.677134 4.654780 c +3.618218 4.819654 3.504569 5.064888 3.283730 5.226569 c +2.498061 4.153432 l +2.393648 4.229875 2.394127 4.292777 2.424695 4.207235 c +2.445051 4.150268 2.466761 4.056094 2.472824 3.934619 c +2.484955 3.691539 2.432118 3.400434 2.262219 3.139988 c +3.376156 2.413322 l +h +3.283730 5.226569 m +1.614924 6.448339 0.665000 8.135382 0.665000 10.191628 c +-0.665000 10.191628 l +-0.665000 7.666271 0.525193 5.597813 2.498061 4.153432 c +3.283730 5.226569 l +h +0.665000 10.191628 m +0.665000 11.250835 0.941389 12.252124 1.435227 13.141545 c +0.272439 13.787166 l +-0.326097 12.709177 -0.665000 11.486766 -0.665000 10.191628 c +0.665000 10.191628 l +h +9.571649 3.735940 m +9.065956 3.636456 8.540168 3.583901 8.000000 3.583901 c +8.000000 2.253901 l +8.626554 2.253901 9.238318 2.314872 9.828376 2.430953 c +9.571649 3.735940 l +h +f +n +Q + +endstream +endobj + +3 0 obj + 6559 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 24.000000 24.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000006649 00000 n +0000006672 00000 n +0000006845 00000 n +0000006919 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +6978 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 6e576cb3fe..ca13346278 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -2363,6 +2363,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.openPeer(peer: peer, navigation: .chat(textInputState: ChatTextInputState(inputText: NSAttributedString(string: inputString)), subject: nil, peekData: nil), fromMessage: nil) } }) + } else { + strongSelf.openPeer(peer: nil, navigation: .chat(textInputState: ChatTextInputState(inputText: NSAttributedString(string: inputString)), subject: nil, peekData: nil), fromMessage: nil) } } }, openUrl: { [weak self] url, concealed, _, message in diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index c5df790fc3..5e995ce5f0 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -34,7 +34,7 @@ struct ChatTopVisibleMessageRange: Equatable { var isLast: Bool } -private let historyMessageCount: Int = 90 +private let historyMessageCount: Int = 44 public enum ChatHistoryListDisplayHeaders { case none @@ -1160,14 +1160,14 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { initialSearchLocation = .index(.absoluteUpperBound()) } } - strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: initialSearchLocation, count: 60, highlight: highlight), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0) + strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: initialSearchLocation, count: historyMessageCount, highlight: highlight), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0) } else if let subject = subject, case let .pinnedMessages(maybeMessageId) = subject, let messageId = maybeMessageId { - strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: .id(messageId), count: 60, highlight: true), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0) + strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: .id(messageId), count: historyMessageCount, highlight: true), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0) } else if var chatHistoryLocation = strongSelf.chatHistoryLocationValue { chatHistoryLocation.id += 1 strongSelf.chatHistoryLocationValue = chatHistoryLocation } else { - strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Initial(count: 60), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0) + strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Initial(count: historyMessageCount), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0) } } } @@ -1493,11 +1493,11 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { initialSearchLocation = .index(MessageIndex.absoluteUpperBound()) } } - self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: initialSearchLocation, count: 60, highlight: highlight), id: 0) + self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: initialSearchLocation, count: historyMessageCount, highlight: highlight), id: 0) } else if let subject = subject, case let .pinnedMessages(maybeMessageId) = subject, let messageId = maybeMessageId { - self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: .id(messageId), count: 60, highlight: true), id: 0) + self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: .id(messageId), count: historyMessageCount, highlight: true), id: 0) } else { - self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Initial(count: 60), id: 0) + self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Initial(count: historyMessageCount), id: 0) } self.chatHistoryLocationPromise.set(self.chatHistoryLocationValue!) diff --git a/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift b/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift index ed72d5a18f..7692da292b 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryViewForLocation.swift @@ -129,13 +129,13 @@ func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, ignoreMess } } - let maxIndex = targetIndex + count / 2 - let minIndex = targetIndex - count / 2 + let maxIndex = targetIndex + 40 + let minIndex = targetIndex - 40 if minIndex <= 0 && view.holeEarlier { fadeIn = true return .Loading(initialData: combinedInitialData, type: .Generic(type: updateType)) } - if maxIndex >= targetIndex { + if maxIndex >= view.entries.count { if view.holeLater { fadeIn = true return .Loading(initialData: combinedInitialData, type: .Generic(type: updateType)) diff --git a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift index 01032a01c9..dbd4a6324e 100644 --- a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift @@ -100,7 +100,8 @@ private enum ChatListSearchEntry: Comparable, Identifiable { ignoreUnreadBadge: true, displayAsMessage: true, hasFailedMessages: false, - forumTopicData: nil + forumTopicData: nil, + topForumTopicItems: [] ), editing: false, hasActiveRevealControls: false, @@ -245,7 +246,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe return } switch item.content { - case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _, _, _, _, _): if let message = messages.first { let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: peer.peerId), subject: .message(id: .id(message.id), highlight: true, timecode: nil), botStart: nil, mode: .standard(previewing: true)) chatController.canReadHistory.set(false) diff --git a/submodules/WatchBridge/Sources/WatchBridge.swift b/submodules/WatchBridge/Sources/WatchBridge.swift index b76700f5f3..aa54f8d7f3 100644 --- a/submodules/WatchBridge/Sources/WatchBridge.swift +++ b/submodules/WatchBridge/Sources/WatchBridge.swift @@ -325,7 +325,7 @@ func makeBridgeMedia(message: Message, strings: PresentationStrings, chatPeer: P } func makeBridgeChat(_ entry: ChatListEntry, strings: PresentationStrings) -> (TGBridgeChat, [Int64 : TGBridgeUser])? { - if case let .MessageEntry(index, messages, readState, _, _, renderedPeer, _, _, _, hasFailed, _) = entry { + if case let .MessageEntry(index, messages, readState, _, _, renderedPeer, _, _, _, _, hasFailed, _) = entry { guard index.messageIndex.id.peerId.namespace != Namespaces.Peer.SecretChat else { return nil }