diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 82819a11d6..a1abd7aa0d 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -373,6 +373,17 @@ public enum ChatLocation: Equatable { case feed(id: Int32) } +public extension ChatLocation { + var normalized: ChatLocation { + switch self { + case .peer, .feed: + return self + case let .replyThread(message): + return .replyThread(message: message.normalized) + } + } +} + public enum ChatControllerActivateInput { case text case entityInput diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index cd8815d8d5..6170df5de8 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1254,7 +1254,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController chatController.canReadHistory.set(false) source = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)) - let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: source, items: chatForumTopicMenuItems(context: strongSelf.context, peerId: peer.peerId, threadId: threadId, isPinned: nil, isClosed: nil, chatListController: strongSelf, joined: joined, canSelect: true) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) + let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: source, items: chatForumTopicMenuItems(context: strongSelf.context, peerId: peer.peerId, threadId: threadId, isPinned: nil, isClosed: nil, chatListController: strongSelf, joined: joined, canSelect: false) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) strongSelf.presentInGlobalOverlay(contextController) } else { let chatListController = ChatListControllerImpl(context: strongSelf.context, location: .forum(peerId: channel.id), controlsHistoryPreload: false, hideNetworkActivityStatus: true, previewing: true, enableDebugActions: false) @@ -1302,21 +1302,28 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController return } - let contextContentSource: ContextContentSource - if peer.id.namespace == Namespaces.Peer.SecretChat, let node = node.subnodes?.first as? ContextExtractedContentContainingNode { - contextContentSource = .extracted(ChatListHeaderBarContextExtractedContentSource(controller: strongSelf, sourceNode: node, keepInPlace: false)) + if case let .channel(channel) = peer, channel.flags.contains(.isForum) { + let chatListController = ChatListControllerImpl(context: strongSelf.context, location: .forum(peerId: channel.id), controlsHistoryPreload: false, hideNetworkActivityStatus: true, previewing: true, enableDebugActions: false) + 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: chatContextMenuItems(context: strongSelf.context, peerId: peer.id, promoInfo: nil, source: .search(source), chatListController: strongSelf, joined: false) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) + strongSelf.presentInGlobalOverlay(contextController) } else { - var subject: ChatControllerSubject? - if case let .search(messageId) = source, let id = messageId { - subject = .message(id: .id(id), highlight: false, timecode: nil) + let contextContentSource: ContextContentSource + if peer.id.namespace == Namespaces.Peer.SecretChat, let node = node.subnodes?.first as? ContextExtractedContentContainingNode { + contextContentSource = .extracted(ChatListHeaderBarContextExtractedContentSource(controller: strongSelf, sourceNode: node, keepInPlace: false)) + } else { + var subject: ChatControllerSubject? + if case let .search(messageId) = source, let id = messageId { + subject = .message(id: .id(id), highlight: false, timecode: nil) + } + let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: peer.id), subject: subject, botStart: nil, mode: .standard(previewing: true)) + chatController.canReadHistory.set(false) + contextContentSource = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)) } - let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: peer.id), subject: subject, botStart: nil, mode: .standard(previewing: true)) - chatController.canReadHistory.set(false) - contextContentSource = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)) + + let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: contextContentSource, items: chatContextMenuItems(context: strongSelf.context, peerId: peer.id, promoInfo: nil, source: .search(source), chatListController: strongSelf, joined: false) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) + strongSelf.presentInGlobalOverlay(contextController) } - - let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: contextContentSource, items: chatContextMenuItems(context: strongSelf.context, peerId: peer.id, promoInfo: nil, source: .search(source), chatListController: strongSelf, joined: false) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) - strongSelf.presentInGlobalOverlay(contextController) } self.tabContainerNode.tabSelected = { [weak self] id, isDisabled in @@ -2048,8 +2055,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController let navigationBarHeight = self.navigationBar?.frame.maxY ?? 0.0 - transition.updateAlpha(node: self.tabContainerNode, alpha: self.chatListDisplayNode.inlineStackContainerTransitionFraction * 0.5 + (1.0 - self.chatListDisplayNode.inlineStackContainerTransitionFraction) * 1.0) - self.tabContainerNode.isUserInteractionEnabled = self.chatListDisplayNode.inlineStackContainerNode == nil + //transition.updateAlpha(node: self.tabContainerNode, alpha: self.chatListDisplayNode.inlineStackContainerTransitionFraction * 0.5 + (1.0 - self.chatListDisplayNode.inlineStackContainerTransitionFraction) * 1.0) + //self.tabContainerNode.isUserInteractionEnabled = self.chatListDisplayNode.inlineStackContainerNode == nil transition.updateFrame(node: self.tabContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight - self.additionalNavigationBarHeight - 46.0 + tabContainerOffset), size: CGSize(width: layout.size.width, height: 46.0))) self.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.chatListDisplayNode.mainContainerNode.currentItemFilter, isReordering: self.chatListDisplayNode.isReorderingFilters || (self.chatListDisplayNode.effectiveContainerNode.currentItemNode.currentState.editing && !self.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: self.chatListDisplayNode.effectiveContainerNode.currentItemNode.currentState.editing, canReorderAllChats: self.isPremium, filtersLimit: self.tabContainerData?.2, transitionFraction: self.chatListDisplayNode.effectiveContainerNode.transitionFraction, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring)) @@ -4689,12 +4696,6 @@ private final class ChatListLocationContext { longTapped: { } ) - self.rightButton = AnyComponentWithIdentity(id: "done", component: AnyComponent(NavigationButtonComponent( - content: .text(title: presentationData.strings.Common_Done, isBold: true), - pressed: { [weak self] _ in - self?.parentController?.donePressed() - } - ))) } else { self.chatTitleComponent = ChatTitleComponent( context: self.context, @@ -4724,7 +4725,16 @@ private final class ChatListLocationContext { self.parentController?.activateSearch() } ) - + } + + if stateAndFilterId.state.editing { + self.rightButton = AnyComponentWithIdentity(id: "done", component: AnyComponent(NavigationButtonComponent( + content: .text(title: presentationData.strings.Common_Done, isBold: true), + pressed: { [weak self] _ in + self?.parentController?.donePressed() + } + ))) + } else { self.rightButton = AnyComponentWithIdentity(id: "more", component: AnyComponent(NavigationButtonComponent( content: .more, pressed: { [weak self] sourceView in diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 23522aaacf..6e6e1bb6c6 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -1510,7 +1510,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { }) let foundThreads: Signal<[EngineChatList.Item], NoError> - if case .forum = location { + if case .forum = location, (key == .topics || key == .chats) { foundThreads = chatListViewForLocation(chatListLocation: location, location: .initial(count: 1000, filter: nil), account: context.account) |> map { view -> [EngineChatList.Item] in var filteredItems: [EngineChatList.Item] = [] diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 670e9563e2..7fdb8f8200 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -1142,7 +1142,12 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } strongSelf.contextContainer.additionalActivationProgressLayer = nil - if item.interaction.inlineNavigationLocation != nil { + if let inlineNavigationLocation = item.interaction.inlineNavigationLocation { + if case let .peer(peerId) = inlineNavigationLocation.location { + if case let .chatList(index) = item.index, index.messageIndex.id.peerId == peerId { + return false + } + } strongSelf.contextContainer.targetNodeForActivationProgress = strongSelf.avatarContainerNode } else if let value = strongSelf.hitTest(location, with: nil), value === strongSelf.compoundTextButtonNode?.view { strongSelf.contextContainer.targetNodeForActivationProgress = strongSelf.compoundTextButtonNode @@ -1313,9 +1318,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { reallyHighlighted = true } } - if item.interaction.inlineNavigationLocation != nil { - reallyHighlighted = false - } } return reallyHighlighted } @@ -1403,7 +1405,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let textFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 15.0 / 17.0)) let dateFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0)) let badgeFont = Font.with(size: floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0), design: .regular, weight: .regular, traits: [.monospacedNumbers]) - let avatarBadgeFont = Font.with(size: 16.0, design: .regular, weight: .regular, traits: [.monospacedNumbers]) + let avatarBadgeFont = Font.with(size: floor(item.presentationData.fontSize.itemListBaseFontSize * 16.0 / 17.0), design: .regular, weight: .regular, traits: [.monospacedNumbers]) let account = item.context.account var messages: [EngineMessage] @@ -1579,7 +1581,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } let badgeDiameter = floor(item.presentationData.fontSize.baseDisplaySize * 20.0 / 17.0) - let avatarBadgeDiameter: CGFloat = 22.0 + let avatarBadgeDiameter: CGFloat = floor(floor(item.presentationData.fontSize.itemListBaseFontSize * 22.0 / 17.0)) let currentAvatarBadgeCleanBackgroundImage: UIImage? = PresentationResourcesChatList.badgeBackgroundBorder(item.presentationData.theme, diameter: avatarBadgeDiameter + 4.0) @@ -2354,7 +2356,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { itemHeight += titleSpacing itemHeight += authorSpacing - let rawContentRect = CGRect(origin: CGPoint(x: 2.0, y: layoutOffset + 8.0), size: CGSize(width: rawContentWidth, height: itemHeight - 12.0 - 9.0)) + let rawContentRect = CGRect(origin: CGPoint(x: 2.0, y: layoutOffset + floor(item.presentationData.fontSize.itemListBaseFontSize * 8.0 / 17.0)), size: CGSize(width: rawContentWidth, height: itemHeight - 12.0 - 9.0)) let insets = ChatListItemNode.insets(first: first, last: last, firstWithHeader: firstWithHeader) var heightOffset: CGFloat = 0.0 @@ -2407,7 +2409,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { var mainContentAlpha: CGFloat = 1.0 if case .chatList = item.chatListLocation { - mainContentFrame = CGRect(origin: CGPoint(x: params.leftInset + 72.0, y: 0.0), size: CGSize(width: layout.contentSize.width, height: layout.contentSize.height)) + mainContentFrame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: layout.contentSize.width, height: layout.contentSize.height)) mainContentBoundsOffset = mainContentFrame.origin.x if let inlineNavigationLocation = item.interaction.inlineNavigationLocation { @@ -2497,7 +2499,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { var avatarScaleOffset: CGFloat = 0.0 var avatarScale: CGFloat = 1.0 if let inlineNavigationLocation = item.interaction.inlineNavigationLocation { - let targetAvatarScale: CGFloat = 54.0 / avatarFrame.width + let targetAvatarScale: CGFloat = floor(item.presentationData.fontSize.itemListBaseFontSize * 54.0 / 17.0) / avatarFrame.width avatarScale = targetAvatarScale * inlineNavigationLocation.progress + 1.0 * (1.0 - inlineNavigationLocation.progress) let targetAvatarScaleOffset: CGFloat = -(avatarFrame.width - avatarFrame.width * avatarScale) * 0.5 @@ -2863,7 +2865,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { finalBottomRect ], color: theme.pinnedItemBackgroundColor.mixedWith(theme.unreadBadgeInactiveBackgroundColor, alpha: 0.1)) - compoundTextButtonNode.frame = compoundHighlightingNode.frame + transition.updateFrame(node: compoundTextButtonNode, frame: compoundHighlightingNode.frame) if let textArrowImage = textArrowImage { let textArrowNode: ASImageNode @@ -2903,8 +2905,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { if let dustNode = strongSelf.dustNode { compoundTextButtonNode.addSubnode(dustNode) } + strongSelf.textNode.textNode.frame = textNodeFrame.offsetBy(dx: -compoundTextButtonNode.frame.minX, dy: -compoundTextButtonNode.frame.minY) } - strongSelf.textNode.textNode.frame = textNodeFrame.offsetBy(dx: -compoundTextButtonNode.frame.minX, dy: -compoundTextButtonNode.frame.minY) strongSelf.authorNode.assignParentNode(parentNode: compoundTextButtonNode) } else { @@ -2913,8 +2915,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { if let dustNode = strongSelf.dustNode { strongSelf.mainContentContainerNode.addSubnode(dustNode) } + strongSelf.textNode.textNode.frame = textNodeFrame } - strongSelf.textNode.textNode.frame = textNodeFrame strongSelf.authorNode.assignParentNode(parentNode: nil) } @@ -3043,9 +3045,11 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let titlePosition = strongSelf.titleNode.position transition.animatePosition(node: strongSelf.titleNode, from: CGPoint(x: titlePosition.x - contentDelta.x, y: titlePosition.y - contentDelta.y)) - transition.animatePositionAdditive(node: strongSelf.textNode.textNode, offset: CGPoint(x: -contentDelta.x, y: -contentDelta.y)) - if let dustNode = strongSelf.dustNode { - transition.animatePositionAdditive(node: dustNode, offset: CGPoint(x: -contentDelta.x, y: -contentDelta.y)) + if strongSelf.textNode.textNode.supernode === strongSelf.mainContentContainerNode { + transition.animatePositionAdditive(node: strongSelf.textNode.textNode, offset: CGPoint(x: -contentDelta.x, y: -contentDelta.y)) + if let dustNode = strongSelf.dustNode { + transition.animatePositionAdditive(node: dustNode, offset: CGPoint(x: -contentDelta.x, y: -contentDelta.y)) + } } let authorPosition = strongSelf.authorNode.position diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index 2757deaabc..4e61232b42 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -659,10 +659,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { hasTimer = true } - var hasSchedule = true - if controller.chatLocation?.threadId != nil { - hasSchedule = false - } + let hasSchedule = true self.openingMedia = true diff --git a/submodules/Postbox/Sources/InvalidatedMessageHistoryTagSummariesView.swift b/submodules/Postbox/Sources/InvalidatedMessageHistoryTagSummariesView.swift index 3ae4264e78..adb313a021 100644 --- a/submodules/Postbox/Sources/InvalidatedMessageHistoryTagSummariesView.swift +++ b/submodules/Postbox/Sources/InvalidatedMessageHistoryTagSummariesView.swift @@ -1,23 +1,58 @@ final class MutableInvalidatedMessageHistoryTagSummariesView: MutablePostboxView { + private let peerId: PeerId? + private let threadId: Int64? private let namespace: MessageId.Namespace private let tagMask: MessageTags var entries = Set() - init(postbox: PostboxImpl, tagMask: MessageTags, namespace: MessageId.Namespace) { + init(postbox: PostboxImpl, peerId: PeerId?, threadId: Int64?, tagMask: MessageTags, namespace: MessageId.Namespace) { + self.peerId = peerId + self.threadId = threadId self.tagMask = tagMask self.namespace = namespace - for entry in postbox.invalidatedMessageHistoryTagsSummaryTable.get(tagMask: tagMask, namespace: namespace) { - self.entries.insert(entry) + if let peerId = self.peerId { + if let entry = postbox.invalidatedMessageHistoryTagsSummaryTable.get(peerId: peerId, threadId: self.threadId, tagMask: self.tagMask, namespace: self.namespace) { + self.entries.insert(entry) + } + } else { + for entry in postbox.invalidatedMessageHistoryTagsSummaryTable.get(tagMask: tagMask, namespace: namespace) { + self.entries.insert(entry) + } } } func replay(postbox: PostboxImpl, transaction: PostboxTransaction) -> Bool { var updated = false - for operation in transaction.currentInvalidateMessageTagSummaries { - switch operation { + + if let peerId = self.peerId { + var maybeUpdated = false + loop: for operation in transaction.currentInvalidateMessageTagSummaries { + switch operation { + case let .add(entry): + if entry.key.peerId == peerId && entry.key.threadId == self.threadId { + maybeUpdated = true + break loop + } + case let .remove(key): + if key.peerId == peerId && key.threadId == self.threadId { + maybeUpdated = true + break loop + } + } + } + if maybeUpdated { + self.entries.removeAll() + if let entry = postbox.invalidatedMessageHistoryTagsSummaryTable.get(peerId: peerId, threadId: self.threadId, tagMask: self.tagMask, namespace: self.namespace) { + self.entries.insert(entry) + } + updated = true + } + } else { + for operation in transaction.currentInvalidateMessageTagSummaries { + switch operation { case let .add(entry): if entry.key.namespace == self.namespace && entry.key.tagMask == self.tagMask { self.entries.insert(entry) @@ -33,8 +68,10 @@ final class MutableInvalidatedMessageHistoryTagSummariesView: MutablePostboxView } updated = true } + } } } + return updated } diff --git a/submodules/Postbox/Sources/InvalidatedMessageHistoryTagsSummaryTable.swift b/submodules/Postbox/Sources/InvalidatedMessageHistoryTagsSummaryTable.swift index d792e02394..6ffa3d062f 100644 --- a/submodules/Postbox/Sources/InvalidatedMessageHistoryTagsSummaryTable.swift +++ b/submodules/Postbox/Sources/InvalidatedMessageHistoryTagsSummaryTable.swift @@ -74,6 +74,10 @@ final class InvalidatedMessageHistoryTagsSummaryTable: Table { return entries } + func get(peerId: PeerId, threadId: Int64?, tagMask: MessageTags, namespace: MessageId.Namespace) -> InvalidatedMessageHistoryTagsSummaryEntry? { + return self.get(InvalidatedMessageHistoryTagsSummaryKey(peerId: peerId, namespace: namespace, tagMask: tagMask, threadId: threadId)) + } + private func get(_ key: InvalidatedMessageHistoryTagsSummaryKey) -> InvalidatedMessageHistoryTagsSummaryEntry? { if let value = self.valueBox.get(self.table, key: self.key(key)) { var version: Int32 = 0 diff --git a/submodules/Postbox/Sources/Views.swift b/submodules/Postbox/Sources/Views.swift index 7a1957c929..eab363d562 100644 --- a/submodules/Postbox/Sources/Views.swift +++ b/submodules/Postbox/Sources/Views.swift @@ -10,7 +10,7 @@ public enum PostboxViewKey: Hashable { case globalMessageTags(globalTag: GlobalMessageTags, position: MessageIndex, count: Int, groupingPredicate: ((Message, Message) -> Bool)?) case peer(peerId: PeerId, components: PeerViewComponents) case pendingMessageActions(type: PendingMessageActionType) - case invalidatedMessageHistoryTagSummaries(tagMask: MessageTags, namespace: MessageId.Namespace) + case invalidatedMessageHistoryTagSummaries(peerId: PeerId?, threadId: Int64?, tagMask: MessageTags, namespace: MessageId.Namespace) case pendingMessageActionsSummary(type: PendingMessageActionType, peerId: PeerId, namespace: MessageId.Namespace) case historyTagSummaryView(tag: MessageTags, peerId: PeerId, threadId: Int64?, namespace: MessageId.Namespace) case cachedPeerData(peerId: PeerId) @@ -61,7 +61,9 @@ public enum PostboxViewKey: Hashable { hasher.combine(peerId) case let .pendingMessageActions(type): hasher.combine(type) - case let .invalidatedMessageHistoryTagSummaries(tagMask, namespace): + case let .invalidatedMessageHistoryTagSummaries(peerId, threadId, tagMask, namespace): + hasher.combine(peerId) + hasher.combine(threadId) hasher.combine(tagMask) hasher.combine(namespace) case let .pendingMessageActionsSummary(type, peerId, namespace): @@ -191,8 +193,8 @@ public enum PostboxViewKey: Hashable { } else { return false } - case .invalidatedMessageHistoryTagSummaries: - if case .invalidatedMessageHistoryTagSummaries = rhs { + case let .invalidatedMessageHistoryTagSummaries(peerId, threadId, tagMask, namespace): + if case .invalidatedMessageHistoryTagSummaries(peerId, threadId, tagMask, namespace) = rhs { return true } else { return false @@ -395,8 +397,8 @@ func postboxViewForKey(postbox: PostboxImpl, key: PostboxViewKey) -> MutablePost return MutablePeerView(postbox: postbox, peerId: peerId, components: components) case let .pendingMessageActions(type): return MutablePendingMessageActionsView(postbox: postbox, type: type) - case let .invalidatedMessageHistoryTagSummaries(tagMask, namespace): - return MutableInvalidatedMessageHistoryTagSummariesView(postbox: postbox, tagMask: tagMask, namespace: namespace) + case let .invalidatedMessageHistoryTagSummaries(peerId, threadId, tagMask, namespace): + return MutableInvalidatedMessageHistoryTagSummariesView(postbox: postbox, peerId: peerId, threadId: threadId, tagMask: tagMask, namespace: namespace) case let .pendingMessageActionsSummary(type, peerId, namespace): return MutablePendingMessageActionsSummaryView(postbox: postbox, type: type, peerId: peerId, namespace: namespace) case let .historyTagSummaryView(tag, peerId, threadId, namespace): diff --git a/submodules/TelegramCore/Sources/Account/Account.swift b/submodules/TelegramCore/Sources/Account/Account.swift index 0cfa17e358..5e970f1973 100644 --- a/submodules/TelegramCore/Sources/Account/Account.swift +++ b/submodules/TelegramCore/Sources/Account/Account.swift @@ -1091,7 +1091,6 @@ public class Account { self.managedOperationsDisposable.add(managedSynchronizeConsumeMessageContentOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start()) self.managedOperationsDisposable.add(managedConsumePersonalMessagesActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start()) self.managedOperationsDisposable.add(managedReadReactionActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start()) - self.managedOperationsDisposable.add(managedSynchronizeMessageHistoryTagSummaries(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start()) self.managedOperationsDisposable.add(managedSynchronizeMarkAllUnseenPersonalMessagesOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start()) self.managedOperationsDisposable.add(managedSynchronizeMarkAllUnseenReactionsOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start()) self.managedOperationsDisposable.add(managedApplyPendingMessageReactionsActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start()) diff --git a/submodules/TelegramCore/Sources/State/ManagedConsumePersonalMessagesActions.swift b/submodules/TelegramCore/Sources/State/ManagedConsumePersonalMessagesActions.swift index 7a5890072d..6a274e41e1 100644 --- a/submodules/TelegramCore/Sources/State/ManagedConsumePersonalMessagesActions.swift +++ b/submodules/TelegramCore/Sources/State/ManagedConsumePersonalMessagesActions.swift @@ -93,7 +93,7 @@ func managedConsumePersonalMessagesActions(postbox: Postbox, network: Network, s let helper = Atomic(value: ManagedConsumePersonalMessagesActionsHelper()) let actionsKey = PostboxViewKey.pendingMessageActions(type: .consumeUnseenPersonalMessage) - let invalidateKey = PostboxViewKey.invalidatedMessageHistoryTagSummaries(tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud) + let invalidateKey = PostboxViewKey.invalidatedMessageHistoryTagSummaries(peerId: nil, threadId: nil, tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud) let disposable = postbox.combinedView(keys: [actionsKey, invalidateKey]).start(next: { view in var entries: [PendingMessageActionsEntry] = [] var invalidateEntries = Set() @@ -156,7 +156,7 @@ func managedReadReactionActions(postbox: Postbox, network: Network, stateManager let helper = Atomic(value: ManagedConsumePersonalMessagesActionsHelper()) let actionsKey = PostboxViewKey.pendingMessageActions(type: .readReaction) - let invalidateKey = PostboxViewKey.invalidatedMessageHistoryTagSummaries(tagMask: .unseenReaction, namespace: Namespaces.Message.Cloud) + let invalidateKey = PostboxViewKey.invalidatedMessageHistoryTagSummaries(peerId: nil, threadId: nil, tagMask: .unseenReaction, namespace: Namespaces.Message.Cloud) let disposable = postbox.combinedView(keys: [actionsKey, invalidateKey]).start(next: { view in var entries: [PendingMessageActionsEntry] = [] var invalidateEntries = Set() @@ -435,11 +435,11 @@ private func synchronizeUnseenReactionsTag(postbox: Postbox, network: Network, e } |> switchToLatest } -func managedSynchronizeMessageHistoryTagSummaries(postbox: Postbox, network: Network, stateManager: AccountStateManager) -> Signal { +func managedSynchronizeMessageHistoryTagSummaries(postbox: Postbox, network: Network, stateManager: AccountStateManager, peerId: PeerId, threadId: Int64) -> Signal { return Signal { _ in let helper = Atomic(value: ManagedConsumePersonalMessagesActionsHelper()) - let invalidateKey = PostboxViewKey.invalidatedMessageHistoryTagSummaries(tagMask: MessageTags(rawValue: 0), namespace: Namespaces.Message.Cloud) + let invalidateKey = PostboxViewKey.invalidatedMessageHistoryTagSummaries(peerId: peerId, threadId: threadId, tagMask: MessageTags(rawValue: 0), namespace: Namespaces.Message.Cloud) let disposable = postbox.combinedView(keys: [invalidateKey]).start(next: { view in var invalidateEntries = Set() if let v = view.views[invalidateKey] as? InvalidatedMessageHistoryTagSummariesView { @@ -480,7 +480,7 @@ private func synchronizeMessageHistoryTagSummary(postbox: Postbox, network: Netw guard let threadId = entry.key.threadId else { return .complete() } - if let peer = transaction.getPeer(entry.key.peerId), let inputPeer = apiInputPeer(peer) { + if let peer = transaction.getPeer(entry.key.peerId) as? TelegramChannel, peer.flags.contains(.isForum), let inputPeer = apiInputPeer(peer) { return network.request(Api.functions.messages.getReplies(peer: inputPeer, msgId: Int32(clamping: threadId), offsetId: 0, offsetDate: 0, addOffset: 0, limit: 1, maxId: 0, minId: 0, hash: 0)) |> map(Optional.init) |> `catch` { _ -> Signal in diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift index 2c914dc948..9f2f6c9cd8 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift @@ -590,6 +590,14 @@ public struct ChatReplyThreadMessage: Equatable { self.initialAnchor = initialAnchor self.isNotAvailable = isNotAvailable } + + public var normalized: ChatReplyThreadMessage { + if self.isForumPost { + return ChatReplyThreadMessage(messageId: self.messageId, channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false) + } else { + return self + } + } } public enum FetchChannelReplyThreadMessageError { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index 542773a5d3..d2f61b6b56 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -494,5 +494,10 @@ public extension TelegramEngine { } |> ignoreValues } + + public func keepMessageCountersSyncrhonized(peerId: EnginePeer.Id, threadId: Int64) -> Signal { + return managedSynchronizeMessageHistoryTagSummaries(postbox: self.account.postbox, network: self.account.network, stateManager: self.account.stateManager, peerId: peerId, threadId: threadId) + |> ignoreValues + } } } diff --git a/submodules/TelegramStringFormatting/Sources/PeerDisplayName.swift b/submodules/TelegramStringFormatting/Sources/PeerDisplayName.swift index 4d176e7f5c..a7603c3d65 100644 --- a/submodules/TelegramStringFormatting/Sources/PeerDisplayName.swift +++ b/submodules/TelegramStringFormatting/Sources/PeerDisplayName.swift @@ -9,6 +9,8 @@ public func stringForFullAuthorName(message: EngineMessage, strings: Presentatio var authorName = "" if author.id == accountPeerId { authorName = strings.DialogList_You + } else if author.isDeleted { + authorName = strings.User_DeletedAccount } else { authorName = author.compactDisplayTitle } diff --git a/submodules/TelegramUI/Sources/ApplicationContext.swift b/submodules/TelegramUI/Sources/ApplicationContext.swift index 0049e43537..f9527ca6eb 100644 --- a/submodules/TelegramUI/Sources/ApplicationContext.swift +++ b/submodules/TelegramUI/Sources/ApplicationContext.swift @@ -317,7 +317,7 @@ final class AuthorizedApplicationContext { if let _ = threadData, let threadId = firstMessage.threadId { chatLocation = .replyThread(ChatReplyThreadMessage( messageId: MessageId(peerId: firstMessage.id.peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId)), channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false - )) + ).normalized) } else { guard let peer = firstMessage.peers[firstMessage.id.peerId] else { return diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 48a571496f..3293308241 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -295,6 +295,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G private var hasActiveGroupCallDisposable: Disposable? private var sendAsPeersDisposable: Disposable? private var preloadAttachBotIconsDisposables: DisposableSet? + private var keepMessageCountersSyncrhonizedDisposable: Disposable? private let editingMessage = ValuePromise(nil, ignoreRepeated: true) private let startingBot = ValuePromise(false, ignoreRepeated: true) @@ -5663,6 +5664,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.inviteRequestsDisposable.dispose() self.sendAsPeersDisposable?.dispose() self.preloadAttachBotIconsDisposables?.dispose() + self.keepMessageCountersSyncrhonizedDisposable?.dispose() } deallocate() } @@ -9326,7 +9328,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self else { return } - guard case let .peer(peerId) = strongSelf.chatLocation else { + guard let peerId = strongSelf.chatLocation.peerId else { return } guard let pinnedMessage = strongSelf.presentationInterfaceState.pinnedMessage else { @@ -9370,7 +9372,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }))) } - let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: peerId), subject: .pinnedMessages(id: pinnedMessage.message.id), botStart: nil, mode: .standard(previewing: true)) + let chatLocation: ChatLocation + if let _ = strongSelf.chatLocation.threadId { + chatLocation = strongSelf.chatLocation + } else { + chatLocation = .peer(id: peerId) + } + + let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: chatLocation, subject: .pinnedMessages(id: pinnedMessage.message.id), botStart: nil, mode: .standard(previewing: true)) chatController.canReadHistory.set(false) strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts() @@ -9986,6 +9995,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } + if case let .replyThread(message) = self.chatLocation, message.isForumPost { + if self.keepMessageCountersSyncrhonizedDisposable == nil { + self.keepMessageCountersSyncrhonizedDisposable = self.context.engine.messages.keepMessageCountersSyncrhonized(peerId: message.messageId.peerId, threadId: Int64(message.messageId.id)).start() + } + } + if let scheduledActivateInput = scheduledActivateInput, case .text = scheduledActivateInput { self.scheduledActivateInput = nil @@ -14964,16 +14979,27 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } if isPinnedMessages, let messageId = messageLocation.messageId { - let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: messageId.peerId)) - |> deliverOnMainQueue).start(next: { [weak self] peer in + let _ = (combineLatest( + self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: messageId.peerId)), + self.context.engine.messages.getMessagesLoadIfNecessary([messageId], strategy: .local) + ) + |> deliverOnMainQueue).start(next: { [weak self] peer, messages in guard let self, let peer = peer else { return } - - if let navigationController = self.effectiveNavigationController { - self.dismiss() - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), subject: .message(id: .id(messageId), highlight: true, timecode: nil), keepStack: .always)) + guard let navigationController = self.effectiveNavigationController else { + return } + + self.dismiss() + + let navigateToLocation: NavigateToChatControllerParams.Location + if let threadId = messages.first?.threadId { + navigateToLocation = .replyThread(ChatReplyThreadMessage(messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId)), channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false)) + } else { + navigateToLocation = .peer(peer) + } + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: navigateToLocation, subject: .message(id: .id(messageId), highlight: true, timecode: nil), keepStack: .always)) }) } else if case let .peer(peerId) = self.chatLocation, let messageId = messageLocation.messageId, (messageId.peerId != peerId && !forceInCurrentChat) || (isScheduledMessages && messageId.id != 0 && !Namespaces.Message.allScheduled.contains(messageId.namespace)) { let _ = (self.context.engine.data.get( diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index ef7b01b756..05f8ab7822 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -2368,24 +2368,57 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { }) if let loaded = displayedRange.visibleRange, let firstEntry = historyView.filteredEntries.first, let lastEntry = historyView.filteredEntries.last { - if loaded.firstIndex < 5 && historyView.originalView.laterId != nil { - if !"".isEmpty { - print("load next") - return + + var mathesFirst = false + if loaded.firstIndex <= 5 { + var firstHasGroups = false + for index in (max(0, historyView.filteredEntries.count - 5) ..< historyView.filteredEntries.count).reversed() { + switch historyView.filteredEntries[index] { + case .MessageEntry: + break + case .MessageGroupEntry: + firstHasGroups = true + default: + break + } } + if firstHasGroups { + mathesFirst = loaded.firstIndex <= 1 + } else { + mathesFirst = loaded.firstIndex <= 5 + } + } + + var mathesLast = false + if loaded.lastIndex >= historyView.filteredEntries.count - 5 { + var lastHasGroups = false + for index in 0 ..< min(5, historyView.filteredEntries.count) { + switch historyView.filteredEntries[index] { + case .MessageEntry: + break + case .MessageGroupEntry: + lastHasGroups = true + default: + break + } + } + if lastHasGroups { + mathesLast = loaded.lastIndex >= historyView.filteredEntries.count - 1 + } else { + mathesLast = loaded.lastIndex >= historyView.filteredEntries.count - 5 + } + } + + if mathesFirst && historyView.originalView.laterId != nil { let locationInput: ChatHistoryLocation = .Navigation(index: .message(lastEntry.index), anchorIndex: .message(lastEntry.index), count: historyMessageCount, highlight: false) if self.chatHistoryLocationValue?.content != locationInput { self.chatHistoryLocationValue = ChatHistoryLocationInput(content: locationInput, id: self.takeNextHistoryLocationId()) } - } else if loaded.firstIndex < 5, historyView.originalView.laterId == nil, !historyView.originalView.holeLater, let chatHistoryLocationValue = self.chatHistoryLocationValue, !chatHistoryLocationValue.isAtUpperBound, historyView.originalView.anchorIndex != .upperBound { + } else if mathesFirst, historyView.originalView.laterId == nil, !historyView.originalView.holeLater, let chatHistoryLocationValue = self.chatHistoryLocationValue, !chatHistoryLocationValue.isAtUpperBound, historyView.originalView.anchorIndex != .upperBound { if self.chatHistoryLocationValue == historyView.locationInput { self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Navigation(index: .upperBound, anchorIndex: .upperBound, count: historyMessageCount, highlight: false), id: self.takeNextHistoryLocationId()) } - } else if loaded.lastIndex >= historyView.filteredEntries.count - 5 && historyView.originalView.earlierId != nil { - if !"".isEmpty { - print("load previous") - return - } + } else if mathesLast && historyView.originalView.earlierId != nil { let locationInput: ChatHistoryLocation = .Navigation(index: .message(firstEntry.index), anchorIndex: .message(firstEntry.index), count: historyMessageCount, highlight: false) if self.chatHistoryLocationValue?.content != locationInput { self.chatHistoryLocationValue = ChatHistoryLocationInput(content: locationInput, id: self.takeNextHistoryLocationId()) diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift index aff0bc9886..ebaea614ee 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift @@ -93,7 +93,10 @@ func rightNavigationButtonForChatInterfaceState(_ presentationInterfaceState: Ch if let channel = presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.flags.contains(.isForum), let moreInfoNavigationButton = moreInfoNavigationButton { if case .replyThread = presentationInterfaceState.chatLocation { } else { - return moreInfoNavigationButton + if case .pinnedMessages = presentationInterfaceState.subject { + } else { + return moreInfoNavigationButton + } } } diff --git a/submodules/TelegramUI/Sources/NavigateToChatController.swift b/submodules/TelegramUI/Sources/NavigateToChatController.swift index 472d7f56fa..a6158e5c3e 100644 --- a/submodules/TelegramUI/Sources/NavigateToChatController.swift +++ b/submodules/TelegramUI/Sources/NavigateToChatController.swift @@ -59,7 +59,11 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam var isFirst = true if params.useExisting { for controller in params.navigationController.viewControllers.reversed() { - if let controller = controller as? ChatControllerImpl, controller.chatLocation == params.chatLocation.asChatLocation && (controller.subject != .scheduledMessages || controller.subject == params.subject) { + guard let controller = controller as? ChatControllerImpl else { + isFirst = false + continue + } + if controller.chatLocation.peerId == params.chatLocation.asChatLocation.peerId && controller.chatLocation.threadId == params.chatLocation.asChatLocation.threadId && (controller.subject != .scheduledMessages || controller.subject == params.subject) { if let updateTextInputState = params.updateTextInputState { controller.updateTextInputState(updateTextInputState) } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index eda0cc3e49..2786a6ee72 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -2180,7 +2180,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { self.usernameNode.displaysAsynchronously = false self.buttonsContainerNode = SparseNode() - self.buttonsContainerNode.clipsToBounds = false + self.buttonsContainerNode.clipsToBounds = true self.regularContentNode = PeerInfoHeaderRegularContentNode() var requestUpdateLayoutImpl: (() -> Void)? @@ -2930,10 +2930,6 @@ final class PeerInfoHeaderNode: ASDisplayNode { } } - if subtitleIsButton { - subtitleFrame.origin.y += 11.0 - } - let singleTitleLockOffset: CGFloat = (peer?.id == self.context.account.peerId || subtitleSize.height.isZero) ? 8.0 : 0.0 let titleLockOffset: CGFloat = 7.0 + singleTitleLockOffset @@ -3028,6 +3024,19 @@ final class PeerInfoHeaderNode: ASDisplayNode { avatarOffset = apparentTitleLockOffset + 0.0 * (1.0 - titleCollapseFraction) + 10.0 * titleCollapseFraction } + if subtitleIsButton { + subtitleFrame.origin.y += 11.0 * (1.0 - titleCollapseFraction) + if let subtitleBackgroundButton = self.subtitleBackgroundButton { + transition.updateAlpha(node: subtitleBackgroundButton, alpha: (1.0 - titleCollapseFraction)) + } + if let subtitleBackgroundNode = self.subtitleBackgroundNode { + transition.updateAlpha(node: subtitleBackgroundNode, alpha: (1.0 - titleCollapseFraction)) + } + if let subtitleArrowNode = self.subtitleArrowNode { + transition.updateAlpha(node: subtitleArrowNode, alpha: (1.0 - titleCollapseFraction)) + } + } + let avatarCornerRadius: CGFloat if let channel = peer as? TelegramChannel, channel.flags.contains(.isForum) { avatarCornerRadius = floor(avatarSize * 0.25) diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index a53f4b1131..89d7ee6c72 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -678,12 +678,27 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) } } } else { - return context.engine.peers.fetchForumChannelTopic(id: channel.id, threadId: Int64(messageId.id)) - |> map { info -> ResolvedUrl? in - if let _ = info { - return .replyThread(messageId: messageId) + return context.engine.messages.getMessagesLoadIfNecessary([messageId], strategy: .cloud(skipLocal: false)) + |> take(1) + |> mapToSignal { messages -> Signal in + if let threadId = messages.first?.threadId { + return context.engine.peers.fetchForumChannelTopic(id: channel.id, threadId: threadId) + |> map { info -> ResolvedUrl? in + if let _ = info { + return .replyThreadMessage(replyThreadMessage: ChatReplyThreadMessage(messageId: MessageId(peerId: channel.id, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId)), channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false), messageId: messageId) + } else { + return .peer(peer?._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil)) + } + } } else { - return .peer(foundPeer._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil)) + return context.engine.peers.fetchForumChannelTopic(id: channel.id, threadId: Int64(messageId.id)) + |> map { info -> ResolvedUrl? in + if let _ = info { + return .replyThread(messageId: messageId) + } else { + return .peer(foundPeer._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil)) + } + } } } }