diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 7d59817fbb..4d9281adf1 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -277,6 +277,7 @@ public enum ResolvedUrl { case groupBotStart(peerId: PeerId, payload: String, adminRights: ResolvedBotAdminRights?) case channelMessage(peer: Peer, messageId: MessageId, timecode: Double?) case replyThreadMessage(replyThreadMessage: ChatReplyThreadMessage, messageId: MessageId) + case replyThread(messageId: MessageId) case stickerPack(name: String, type: StickerPackUrlType) case instantView(TelegramMediaWebpage, String?) case proxy(host: String, port: Int32, username: String?, password: String?, secret: Data?) @@ -737,7 +738,7 @@ public protocol SharedAccountContext: AnyObject { func makePrivacyAndSecurityController(context: AccountContext) -> ViewController func navigateToChatController(_ params: NavigateToChatControllerParams) func navigateToForumChannel(context: AccountContext, peerId: EnginePeer.Id, navigationController: NavigationController) - func navigateToForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?) -> Signal + func navigateToForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?, keepStack: NavigateToChatKeepStack) -> Signal func chatControllerForForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64) -> Signal func openStorageUsage(context: AccountContext) func openLocationScreen(context: AccountContext, messageId: MessageId, navigationController: NavigationController) diff --git a/submodules/AccountContext/Sources/ChatController.swift b/submodules/AccountContext/Sources/ChatController.swift index 32dc9402b9..b5d6e16c22 100644 --- a/submodules/AccountContext/Sources/ChatController.swift +++ b/submodules/AccountContext/Sources/ChatController.swift @@ -31,8 +31,9 @@ public final class ChatMessageItemAssociatedData: Equatable { public let isPremium: Bool public let forceInlineReactions: Bool public let accountPeer: EnginePeer? + public let topicAuthorId: EnginePeer.Id? - public init(automaticDownloadPeerType: MediaAutoDownloadPeerType, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, isRecentActions: Bool = false, subject: ChatControllerSubject? = nil, contactsPeerIds: Set = Set(), channelDiscussionGroup: ChannelDiscussionGroupStatus = .unknown, animatedEmojiStickers: [String: [StickerPackItem]] = [:], additionalAnimatedEmojiStickers: [String: [Int: StickerPackItem]] = [:], forcedResourceStatus: FileMediaResourceStatus? = nil, currentlyPlayingMessageId: EngineMessage.Index? = nil, isCopyProtectionEnabled: Bool = false, availableReactions: AvailableReactions?, defaultReaction: MessageReaction.Reaction?, isPremium: Bool, accountPeer: EnginePeer?, forceInlineReactions: Bool = false) { + public init(automaticDownloadPeerType: MediaAutoDownloadPeerType, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, isRecentActions: Bool = false, subject: ChatControllerSubject? = nil, contactsPeerIds: Set = Set(), channelDiscussionGroup: ChannelDiscussionGroupStatus = .unknown, animatedEmojiStickers: [String: [StickerPackItem]] = [:], additionalAnimatedEmojiStickers: [String: [Int: StickerPackItem]] = [:], forcedResourceStatus: FileMediaResourceStatus? = nil, currentlyPlayingMessageId: EngineMessage.Index? = nil, isCopyProtectionEnabled: Bool = false, availableReactions: AvailableReactions?, defaultReaction: MessageReaction.Reaction?, isPremium: Bool, accountPeer: EnginePeer?, forceInlineReactions: Bool = false, topicAuthorId: EnginePeer.Id? = nil) { self.automaticDownloadPeerType = automaticDownloadPeerType self.automaticDownloadNetworkType = automaticDownloadNetworkType self.isRecentActions = isRecentActions @@ -49,6 +50,7 @@ public final class ChatMessageItemAssociatedData: Equatable { self.isPremium = isPremium self.accountPeer = accountPeer self.forceInlineReactions = forceInlineReactions + self.topicAuthorId = topicAuthorId } public static func == (lhs: ChatMessageItemAssociatedData, rhs: ChatMessageItemAssociatedData) -> Bool { @@ -97,6 +99,9 @@ public final class ChatMessageItemAssociatedData: Equatable { if lhs.forceInlineReactions != rhs.forceInlineReactions { return false } + if lhs.topicAuthorId != rhs.topicAuthorId { + return false + } return true } } diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index ae990d0b58..54de766542 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1372,7 +1372,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController strongSelf.context.sharedContext.navigateToForumChannel(context: strongSelf.context, peerId: channel.id, navigationController: navigationController) } else { if let threadId = threadId { - let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, messageId: nil, navigationController: navigationController, activateInput: nil).start() + let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, messageId: nil, navigationController: navigationController, activateInput: nil, keepStack: .never).start() strongSelf.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true) } else { var navigationAnimationOptions: NavigationAnimationOptions = [] @@ -1473,7 +1473,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController navigationAnimationOptions = .removeOnMasterDetails } if let threadId = threadId { - let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, messageId: messageId, navigationController: navigationController, activateInput: nil).start() + let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, messageId: messageId, navigationController: navigationController, activateInput: nil, keepStack: .never).start() } else { strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: actualPeerId), subject: .message(id: .id(messageId), highlight: true, timecode: nil), purposefulAction: { if deactivateOnAction { @@ -1506,7 +1506,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController navigationAnimationOptions = .removeOnMasterDetails } if let threadId = threadId { - let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, messageId: nil, navigationController: navigationController, activateInput: nil).start() + let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, messageId: nil, navigationController: navigationController, activateInput: nil, keepStack: .never).start() } else { strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peer.id), purposefulAction: { [weak self] in self?.deactivateSearch(animated: false) @@ -1602,7 +1602,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconColor: ForumCreateTopicScreen.iconColors.randomElement()!, iconFileId: fileId) |> deliverOnMainQueue).start(next: { topicId in - let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: topicId, messageId: nil, navigationController: navigationController, activateInput: .text).start() + let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: topicId, messageId: nil, navigationController: navigationController, activateInput: .text, keepStack: .never).start() }, error: { _ in controller?.isInProgress = false }) @@ -2670,7 +2670,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconColor: ForumCreateTopicScreen.iconColors.randomElement()!, iconFileId: fileId) |> deliverOnMainQueue).start(next: { topicId in if let navigationController = (sourceController.navigationController as? NavigationController) { - let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: topicId, messageId: nil, navigationController: navigationController, activateInput: .text).start() + let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: topicId, messageId: nil, navigationController: navigationController, activateInput: .text, keepStack: .never).start() } }, error: { _ in controller?.isInProgress = false diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 2ecb6ae5b4..6153584090 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -2534,7 +2534,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { if let currentMutedIconImage = currentMutedIconImage { strongSelf.mutedIconNode.image = currentMutedIconImage strongSelf.mutedIconNode.isHidden = false - transition.updateFrame(node: strongSelf.mutedIconNode, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin - 5.0, y: floorToScreenPixels(titleFrame.midY - currentMutedIconImage.size.height / 2.0) - UIScreenPixel), size: currentMutedIconImage.size)) + transition.updateFrame(node: strongSelf.mutedIconNode, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin - 5.0, y: titleFrame.minY - 1.0 - UIScreenPixel), size: currentMutedIconImage.size)) nextTitleIconOrigin += currentMutedIconImage.size.width + 1.0 } else { strongSelf.mutedIconNode.image = nil diff --git a/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift index cbc3a78c20..2bef75b640 100644 --- a/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift +++ b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift @@ -196,6 +196,12 @@ public func combineLatest(queue: Queue? = nil, _ s1: Signal, _ s2: Signal, _ s3: Signal, _ s4: Signal, _ s5: Signal, _ s6: Signal, _ s7: Signal, _ s8: Signal, _ s9: Signal, _ s10: Signal, _ s11: Signal, _ s12: Signal, _ s13: Signal, _ s14: Signal, _ s15: Signal, _ s16: Signal, _ s17: Signal, _ s18: Signal, _ s19: Signal) -> Signal<(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19), E> { + return combineLatestAny([signalOfAny(s1), signalOfAny(s2), signalOfAny(s3), signalOfAny(s4), signalOfAny(s5), signalOfAny(s6), signalOfAny(s7), signalOfAny(s8), signalOfAny(s9), signalOfAny(s10), signalOfAny(s11), signalOfAny(s12), signalOfAny(s13), signalOfAny(s14), signalOfAny(s15), signalOfAny(s16), signalOfAny(s17), signalOfAny(s18), signalOfAny(s19)], combine: { values in + return (values[0] as! T1, values[1] as! T2, values[2] as! T3, values[3] as! T4, values[4] as! T5, values[5] as! T6, values[6] as! T7, values[7] as! T8, values[8] as! T9, values[9] as! T10, values[10] as! T11, values[11] as! T12, values[12] as! T13, values[13] as! T14, values[14] as! T15, values[15] as! T16, values[16] as! T17, values[17] as! T18, values[18] as! T19) + }, initialValues: [:], queue: queue) +} + public func combineLatest(queue: Queue? = nil, _ signals: [Signal]) -> Signal<[T], E> { if signals.count == 0 { return single([T](), E.self) diff --git a/submodules/TelegramCore/Sources/ForumChannels.swift b/submodules/TelegramCore/Sources/ForumChannels.swift index eca113aab6..3e9f5e2ae3 100644 --- a/submodules/TelegramCore/Sources/ForumChannels.swift +++ b/submodules/TelegramCore/Sources/ForumChannels.swift @@ -238,6 +238,32 @@ func _internal_createForumChannelTopic(account: Account, peerId: PeerId, title: } } +func _internal_fetchForumChannelTopic(account: Account, peerId: PeerId, threadId: Int64) -> Signal { + return account.postbox.transaction { transaction -> (info: EngineMessageHistoryThread.Info?, inputChannel: Api.InputChannel?) in + if let data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self) { + return (data.info, nil) + } else { + return (nil, transaction.getPeer(peerId).flatMap(apiInputChannel)) + } + } + |> mapToSignal { info, _ -> Signal in + if let info = info { + return .single(info) + } else { + return resolveForumThreads(postbox: account.postbox, network: account.network, ids: [MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId))]) + |> mapToSignal { _ -> Signal in + return account.postbox.transaction { transaction -> EngineMessageHistoryThread.Info? in + if let data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self) { + return data.info + } else { + return nil + } + } + } + } + } +} + public enum EditForumChannelTopicError { case generic } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift index 39c7794410..08698d3aeb 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift @@ -832,6 +832,10 @@ public extension TelegramEngine { return _internal_createForumChannelTopic(account: self.account, peerId: id, title: title, iconColor: iconColor, iconFileId: iconFileId) } + public func fetchForumChannelTopic(id: EnginePeer.Id, threadId: Int64) -> Signal { + return _internal_fetchForumChannelTopic(account: self.account, peerId: id, threadId: threadId) + } + public func editForumChannelTopic(id: EnginePeer.Id, threadId: Int64, title: String, iconFileId: Int64?) -> Signal { return _internal_editForumChannelTopic(account: self.account, peerId: id, threadId: threadId, title: title, iconFileId: iconFileId) } diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 61596d917d..b23fc547fe 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -314,7 +314,7 @@ private final class ChatHistoryTransactionOpaqueState { } } -private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHistoryView, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, animatedEmojiStickers: [String: [StickerPackItem]], additionalAnimatedEmojiStickers: [String: [Int: StickerPackItem]], subject: ChatControllerSubject?, currentlyPlayingMessageId: MessageIndex?, isCopyProtectionEnabled: Bool, availableReactions: AvailableReactions?, defaultReaction: MessageReaction.Reaction?, isPremium: Bool, accountPeer: EnginePeer?) -> ChatMessageItemAssociatedData { +private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHistoryView, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, animatedEmojiStickers: [String: [StickerPackItem]], additionalAnimatedEmojiStickers: [String: [Int: StickerPackItem]], subject: ChatControllerSubject?, currentlyPlayingMessageId: MessageIndex?, isCopyProtectionEnabled: Bool, availableReactions: AvailableReactions?, defaultReaction: MessageReaction.Reaction?, isPremium: Bool, accountPeer: EnginePeer?, topicAuthorId: EnginePeer.Id?) -> ChatMessageItemAssociatedData { var automaticMediaDownloadPeerType: MediaAutoDownloadPeerType = .channel var contactsPeerIds: Set = Set() var channelDiscussionGroup: ChatMessageItemAssociatedData.ChannelDiscussionGroupStatus = .unknown @@ -363,7 +363,7 @@ private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHist } } - return ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadNetworkType: automaticDownloadNetworkType, isRecentActions: false, subject: subject, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, currentlyPlayingMessageId: currentlyPlayingMessageId, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, defaultReaction: defaultReaction, isPremium: isPremium, accountPeer: accountPeer) + return ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadNetworkType: automaticDownloadNetworkType, isRecentActions: false, subject: subject, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, currentlyPlayingMessageId: currentlyPlayingMessageId, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, defaultReaction: defaultReaction, isPremium: isPremium, accountPeer: accountPeer, topicAuthorId: topicAuthorId) } private extension ChatHistoryLocationInput { @@ -1032,6 +1032,17 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } |> distinctUntilChanged + let topicAuthorId: Signal + if let peerId = chatLocation.peerId, let threadId = chatLocation.threadId { + topicAuthorId = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.ThreadData(id: peerId, threadId: threadId)) + |> map { data -> EnginePeer.Id? in + return data?.author + } + |> distinctUntilChanged + } else { + topicAuthorId = .single(nil) + } + let historyViewTransitionDisposable = combineLatest(queue: messageViewQueue, historyViewUpdate, self.chatPresentationDataPromise.get(), @@ -1050,8 +1061,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { adMessages, availableReactions, defaultReaction, - accountPeer - ).start(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, historyAppearsCleared, pendingUnpinnedAllMessages, pendingRemovedMessages, animatedEmojiStickers, additionalAnimatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, currentlyPlayingMessageIdAndType, scrollToMessageId, adMessages, availableReactions, defaultReaction, accountPeer in + accountPeer, + topicAuthorId + ).start(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, historyAppearsCleared, pendingUnpinnedAllMessages, pendingRemovedMessages, animatedEmojiStickers, additionalAnimatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, currentlyPlayingMessageIdAndType, scrollToMessageId, adMessages, availableReactions, defaultReaction, accountPeer, topicAuthorId in let currentlyPlayingMessageId = currentlyPlayingMessageIdAndType?.0 func applyHole() { @@ -1185,7 +1197,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { isPremium = true } - let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, subject: subject, currentlyPlayingMessageId: currentlyPlayingMessageId, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, defaultReaction: defaultReaction, isPremium: isPremium, accountPeer: accountPeer) + let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, subject: subject, currentlyPlayingMessageId: currentlyPlayingMessageId, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, defaultReaction: defaultReaction, isPremium: isPremium, accountPeer: accountPeer, topicAuthorId: topicAuthorId) let filteredEntries = chatHistoryEntriesForView( location: chatLocation, diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index a5783465c0..9e609da052 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -1392,6 +1392,13 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode } authorRank = attributes.rank } + + if authorRank == nil { + if let topicAuthorId = item.associatedData.topicAuthorId, topicAuthorId == message.author?.id { + //TODO:localize + authorRank = .custom("Topic Author") + } + } case .group: break } diff --git a/submodules/TelegramUI/Sources/ChatQrCodeScreen.swift b/submodules/TelegramUI/Sources/ChatQrCodeScreen.swift index a57b097912..9718d282c9 100644 --- a/submodules/TelegramUI/Sources/ChatQrCodeScreen.swift +++ b/submodules/TelegramUI/Sources/ChatQrCodeScreen.swift @@ -1571,7 +1571,7 @@ private class QrContentNode: ASDisplayNode, ContentNode { codeLink = "" } if let threadId = threadId { - codeLink += "?topic=\(threadId)" + codeLink += "/\(threadId)" } let codeReadyPromise = ValuePromise() diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift index 1a81c6ae52..29b47974fd 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift @@ -901,6 +901,10 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { if let navigationController = strongSelf.getNavigationController() { strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .replyThread(message: replyThreadMessage), subject: .message(id: .id(messageId), highlight: true, timecode: nil))) } + case let .replyThread(messageId): + if let navigationController = strongSelf.getNavigationController() { + let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: messageId.peerId, threadId: Int64(messageId.id), messageId: nil, navigationController: navigationController, activateInput: nil, keepStack: .always).start() + } case let .stickerPack(name, type): let _ = type let packReference: StickerPackReference = .name(name) diff --git a/submodules/TelegramUI/Sources/NavigateToChatController.swift b/submodules/TelegramUI/Sources/NavigateToChatController.swift index 25575e1a2c..91c3716724 100644 --- a/submodules/TelegramUI/Sources/NavigateToChatController.swift +++ b/submodules/TelegramUI/Sources/NavigateToChatController.swift @@ -227,7 +227,7 @@ public func isOverlayControllerForChatNotificationOverlayPresentation(_ controll return false } -public func navigateToForumThreadImpl(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?) -> Signal { +public func navigateToForumThreadImpl(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?, keepStack: NavigateToChatKeepStack) -> Signal { return fetchAndPreloadReplyThreadInfo(context: context, subject: .groupMessage(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId))), atMessageId: messageId, preload: false) |> deliverOnMainQueue |> beforeNext { [weak context, weak navigationController] result in @@ -248,7 +248,7 @@ public func navigateToForumThreadImpl(context: AccountContext, peerId: EnginePee chatLocationContextHolder: result.contextHolder, subject: messageId.flatMap { .message(id: .id($0), highlight: true, timecode: nil) }, activateInput: actualActivateInput, - keepStack: .never + keepStack: keepStack ) ) } diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index 233cc99a14..520f3623d1 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -163,6 +163,10 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur present(c, a) }, messageId: replyThreadMessage.messageId, isChannelPost: replyThreadMessage.isChannelPost, atMessage: messageId, displayModalProgress: true).start() } + case let .replyThread(messageId): + if let navigationController = navigationController { + let _ = context.sharedContext.navigateToForumThread(context: context, peerId: messageId.peerId, threadId: Int64(messageId.id), messageId: nil, navigationController: navigationController, activateInput: nil, keepStack: .always).start() + } case let .stickerPack(name, _): dismissInput() diff --git a/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenSwitchItem.swift b/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenSwitchItem.swift index 0f0d6dfe61..d62ca44354 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenSwitchItem.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenSwitchItem.swift @@ -1,19 +1,22 @@ import AsyncDisplayKit import Display import TelegramPresentationData +import AppBundle final class PeerInfoScreenSwitchItem: PeerInfoScreenItem { let id: AnyHashable let text: String let value: Bool let icon: UIImage? + let isLocked: Bool let toggled: ((Bool) -> Void)? - init(id: AnyHashable, text: String, value: Bool, icon: UIImage? = nil, toggled: ((Bool) -> Void)?) { + init(id: AnyHashable, text: String, value: Bool, icon: UIImage? = nil, isLocked: Bool = false, toggled: ((Bool) -> Void)?) { self.id = id self.text = text self.value = value self.icon = icon + self.isLocked = isLocked self.toggled = toggled } @@ -28,7 +31,9 @@ private final class PeerInfoScreenSwitchItemNode: PeerInfoScreenItemNode { private let iconNode: ASImageNode private let textNode: ImmediateTextNode private let switchNode: SwitchNode + private var lockedIconNode: ASImageNode? private let bottomSeparatorNode: ASDisplayNode + private var lockedButtonNode: HighlightableButtonNode? private let activateArea: AccessibilityAreaNode private var item: PeerInfoScreenSwitchItem? @@ -91,12 +96,51 @@ private final class PeerInfoScreenSwitchItemNode: PeerInfoScreenItemNode { let firstTime = self.item == nil + var updateLockedIconImage = false + if item.isLocked { + let lockedIconNode: ASImageNode + if let current = self.lockedIconNode { + lockedIconNode = current + } else { + updateLockedIconImage = true + lockedIconNode = ASImageNode() + self.lockedIconNode = lockedIconNode + self.insertSubnode(lockedIconNode, aboveSubnode: self.switchNode) + } + + } else if let lockedIconNode = self.lockedIconNode { + self.lockedIconNode = nil + lockedIconNode.removeFromSupernode() + } + + if item.isLocked { + self.switchNode.isUserInteractionEnabled = false + if self.lockedButtonNode == nil { + let lockedButtonNode = HighlightableButtonNode() + self.lockedButtonNode = lockedButtonNode + self.insertSubnode(lockedButtonNode, aboveSubnode: self.switchNode) + lockedButtonNode.addTarget(self, action: #selector(self.lockedButtonPressed), forControlEvents: .touchUpInside) + } + } else { + if let lockedButtonNode = self.lockedButtonNode { + self.lockedButtonNode = nil + lockedButtonNode.removeFromSupernode() + } + self.switchNode.isUserInteractionEnabled = true + } + if self.theme !== presentationData.theme { self.theme = presentationData.theme self.switchNode.frameColor = presentationData.theme.list.itemSwitchColors.frameColor self.switchNode.contentColor = presentationData.theme.list.itemSwitchColors.contentColor self.switchNode.handleColor = presentationData.theme.list.itemSwitchColors.handleColor + + updateLockedIconImage = true + } + + if updateLockedIconImage, let lockedIconNode = self.lockedIconNode, let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"), color: presentationData.theme.list.itemSecondaryTextColor) { + lockedIconNode.image = image } self.item = item @@ -143,10 +187,17 @@ private final class PeerInfoScreenSwitchItemNode: PeerInfoScreenItemNode { } let switchSize = switchView.bounds.size - self.switchNode.frame = CGRect(origin: CGPoint(x: width - switchSize.width - 15.0 - safeInsets.right, y: floor((height - switchSize.height) / 2.0)), size: switchSize) + let switchFrame = CGRect(origin: CGPoint(x: width - switchSize.width - 15.0 - safeInsets.right, y: floor((height - switchSize.height) / 2.0)), size: switchSize) + self.switchNode.frame = switchFrame if switchView.isOn != item.value { switchView.setOn(item.value, animated: !firstTime) } + + self.lockedButtonNode?.frame = switchFrame + + if let lockedIconNode = self.lockedIconNode, let icon = lockedIconNode.image { + lockedIconNode.frame = CGRect(origin: CGPoint(x: switchFrame.minX + 10.0 + UIScreenPixel, y: switchFrame.minY + 9.0), size: icon.size) + } } let hasCorners = hasCorners && (topItem == nil || bottomItem == nil) @@ -168,4 +219,8 @@ private final class PeerInfoScreenSwitchItemNode: PeerInfoScreenItemNode { return height } + + @objc private func lockedButtonPressed() { + self.item?.toggled?(self.switchNode.isOn) + } } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift index 72de6b8718..1a8157d1bf 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift @@ -188,6 +188,7 @@ final class PeerInfoScreenData { let requests: PeerInvitationImportersState? let requestsContext: PeerInvitationImportersContext? let threadData: MessageHistoryThreadData? + let appConfiguration: AppConfiguration? init( peer: Peer?, @@ -206,7 +207,8 @@ final class PeerInfoScreenData { invitations: PeerExportedInvitationsState?, requests: PeerInvitationImportersState?, requestsContext: PeerInvitationImportersContext?, - threadData: MessageHistoryThreadData? + threadData: MessageHistoryThreadData?, + appConfiguration: AppConfiguration? ) { self.peer = peer self.chatPeer = chatPeer @@ -225,6 +227,7 @@ final class PeerInfoScreenData { self.requests = requests self.requestsContext = requestsContext self.threadData = threadData + self.appConfiguration = appConfiguration } } @@ -435,7 +438,8 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id, }) var enableQRLogin = false - if let appConfiguration = accountPreferences.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self), let data = appConfiguration.data, let enableQR = data["qr_login_camera"] as? Bool, enableQR { + let appConfiguration = accountPreferences.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) + if let appConfiguration, let data = appConfiguration.data, let enableQR = data["qr_login_camera"] as? Bool, enableQR { enableQRLogin = true } @@ -477,7 +481,8 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id, invitations: nil, requests: nil, requestsContext: nil, - threadData: nil + threadData: nil, + appConfiguration: appConfiguration ) } } @@ -504,7 +509,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen invitations: nil, requests: nil, requestsContext: nil, - threadData: nil + threadData: nil, + appConfiguration: nil )) case let .user(userPeerId, secretChatId, kind): let groupsInCommon: GroupsInCommonContext? @@ -635,7 +641,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen invitations: nil, requests: nil, requestsContext: nil, - threadData: nil + threadData: nil, + appConfiguration: nil ) } case .channel: @@ -711,7 +718,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen invitations: invitations, requests: requests, requestsContext: currentRequestsContext, - threadData: nil + threadData: nil, + appConfiguration: nil ) } case let .group(groupId): @@ -840,9 +848,10 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen invitationsStatePromise.get(), requestsContextPromise.get(), requestsStatePromise.get(), - threadData + threadData, + context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) ) - |> map { peerView, availablePanes, globalNotificationSettings, status, membersData, currentInvitationsContext, invitations, currentRequestsContext, requests, threadData -> PeerInfoScreenData in + |> map { peerView, availablePanes, globalNotificationSettings, status, membersData, currentInvitationsContext, invitations, currentRequestsContext, requests, threadData, preferencesView -> PeerInfoScreenData in var discussionPeer: Peer? if case let .known(maybeLinkedDiscussionPeerId) = (peerView.cachedData as? CachedChannelData)?.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = peerView.peers[linkedDiscussionPeerId] { discussionPeer = peer @@ -889,6 +898,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen } else { notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings } + + let appConfiguration: AppConfiguration = preferencesView.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? .defaultValue return PeerInfoScreenData( peer: peerView.peers[groupId], @@ -907,7 +918,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen invitations: invitations, requests: requests, requestsContext: currentRequestsContext, - threadData: threadData + threadData: threadData, + appConfiguration: appConfiguration ) } } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 400e137ca2..0c8b705341 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -509,6 +509,7 @@ private final class PeerInfoInteraction { let editingOpenReactionsSetup: () -> Void let dismissInput: () -> Void let toggleForumTopics: (Bool) -> Void + let displayTopicsLimited: (Int) -> Void init( openUsername: @escaping (String) -> Void, @@ -553,7 +554,8 @@ private final class PeerInfoInteraction { openQrCode: @escaping () -> Void, editingOpenReactionsSetup: @escaping () -> Void, dismissInput: @escaping () -> Void, - toggleForumTopics: @escaping (Bool) -> Void + toggleForumTopics: @escaping (Bool) -> Void, + displayTopicsLimited: @escaping (Int) -> Void ) { self.openUsername = openUsername self.openPhone = openPhone @@ -598,6 +600,7 @@ private final class PeerInfoInteraction { self.editingOpenReactionsSetup = editingOpenReactionsSetup self.dismissInput = dismissInput self.toggleForumTopics = toggleForumTopics + self.displayTopicsLimited = displayTopicsLimited } } @@ -1094,7 +1097,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese threadId = Int64(message.messageId.id) } - let linkText = "https://t.me/\(mainUsername)?topic=\(threadId)" + let linkText = "https://t.me/\(mainUsername)/\(threadId)" items[.peerInfo]!.append( PeerInfoScreenLabeledValueItem( @@ -1611,12 +1614,33 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr })) } - if isCreator { - //TODO:localize - items[.peerDataSettings]!.append(PeerInfoScreenSwitchItem(id: ItemTopics, text: "Topics", value: channel.flags.contains(.isForum), icon: UIImage(bundleImageName: "Settings/Menu/ChatListFilters"), toggled: { value in - interaction.toggleForumTopics(value) - })) - items[.peerDataSettings]!.append(PeerInfoScreenCommentItem(id: ItemTopicsText, text: "The group chat will be divided into topics created by admins or users.")) + if isCreator, let appConfiguration = data.appConfiguration { + var minParticipants = 5 + if let data = appConfiguration.data, let value = data["forum_min_participants"] as? Int { + minParticipants = value + } + + var canSetupTopics = false + var areTopicsLocked = true + if channel.flags.contains(.isForum) { + canSetupTopics = true + areTopicsLocked = false + } else if let memberCount = cachedData.participantsSummary.memberCount { + canSetupTopics = true + areTopicsLocked = Int(memberCount) < minParticipants + } + + if canSetupTopics { + //TODO:localize + items[.peerDataSettings]!.append(PeerInfoScreenSwitchItem(id: ItemTopics, text: "Topics", value: channel.flags.contains(.isForum), icon: UIImage(bundleImageName: "Settings/Menu/ChatListFilters"), isLocked: areTopicsLocked, toggled: { value in + if areTopicsLocked { + interaction.displayTopicsLimited(minParticipants) + } else { + interaction.toggleForumTopics(value) + } + })) + items[.peerDataSettings]!.append(PeerInfoScreenCommentItem(id: ItemTopicsText, text: "The group chat will be divided into topics created by admins or users.")) + } } var canViewAdminsAndBanned = false @@ -2050,6 +2074,16 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate return } let _ = strongSelf.context.engine.peers.setChannelForumMode(id: strongSelf.peerId, isForum: value).start() + }, + displayTopicsLimited: { [weak self] minCount in + guard let self else { + return + } + + //TODO:localize + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + let text = "Only groups with more than **\(minCount) members** can have topics enabled." + self.controller?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_mute_for", scale: 0.066, colors: [:], title: nil, text: text, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) } ) @@ -6810,7 +6844,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate return } - var threadId: Int64 = 0 + var threadId: Int64? if case let .replyThread(message) = self.chatLocation { threadId = Int64(message.messageId.id) } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 286b1f878f..680c79e20d 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -1159,8 +1159,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { navigateToForumChannelImpl(context: context, peerId: peerId, navigationController: navigationController) } - public func navigateToForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?) -> Signal { - return navigateToForumThreadImpl(context: context, peerId: peerId, threadId: threadId, messageId: messageId, navigationController: navigationController, activateInput: activateInput) + public func navigateToForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?, keepStack: NavigateToChatKeepStack) -> Signal { + return navigateToForumThreadImpl(context: context, peerId: peerId, threadId: threadId, messageId: messageId, navigationController: navigationController, activateInput: activateInput, keepStack: keepStack) } public func chatControllerForForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64) -> Signal { diff --git a/submodules/TelegramUI/Sources/TextLinkHandling.swift b/submodules/TelegramUI/Sources/TextLinkHandling.swift index b27420b1b1..1c2293c8b0 100644 --- a/submodules/TelegramUI/Sources/TextLinkHandling.swift +++ b/submodules/TelegramUI/Sources/TextLinkHandling.swift @@ -70,6 +70,10 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: PeerId?, navigate controller?.present(c, in: .window(.root), with: a) }, messageId: replyThreadMessage.messageId, isChannelPost: replyThreadMessage.isChannelPost, atMessage: messageId, displayModalProgress: true).start() } + case let .replyThread(messageId): + if let navigationController = controller.navigationController as? NavigationController { + let _ = context.sharedContext.navigateToForumThread(context: context, peerId: messageId.peerId, threadId: Int64(messageId.id), messageId: nil, navigationController: navigationController, activateInput: nil, keepStack: .always).start() + } case let .stickerPack(name, _): let packReference: StickerPackReference = .name(name) controller.present(StickerPackScreen(context: context, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: controller.navigationController as? NavigationController), in: .window(.root)) diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index 5897291836..e2b315ed9c 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -275,7 +275,7 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? { return .invoice(component) } return .peer(.name(peerName), nil) - } else if pathComponents.count == 2 || pathComponents.count == 3 { + } else if pathComponents.count == 2 || pathComponents.count == 3 || pathComponents.count == 4 { if pathComponents[0] == "addstickers" { return .stickerPack(name: pathComponents[1], type: .stickers) } else if pathComponents[0] == "addemoji" { @@ -429,6 +429,24 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? { } else { return nil } + } else if pathComponents.count == 4 && pathComponents[0] == "c" { + if let channelId = Int64(pathComponents[1]), let threadId = Int32(pathComponents[2]), let messageId = Int32(pathComponents[3]) { + var timecode: Double? + if let queryItems = components.queryItems { + for queryItem in queryItems { + if let value = queryItem.value { + if queryItem.name == "t" { + if let doubleValue = Double(value) { + timecode = doubleValue + } + } + } + } + } + return .privateMessage(messageId: MessageId(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), namespace: Namespaces.Message.Cloud, id: messageId), threadId: threadId, timecode: timecode) + } else { + return nil + } } else if pathComponents.count == 2 && pathComponents[0] == "c" { if let channelId = Int64(pathComponents[1]) { var threadId: Int32? @@ -475,7 +493,10 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? { } } } - if let threadId = threadId { + + if pathComponents.count >= 3, let subMessageId = Int32(pathComponents[2]) { + return .peer(.name(peerName), .replyThread(value, subMessageId)) + } else if let threadId = threadId { return .peer(.name(peerName), .replyThread(threadId, value)) } else if let commentId = commentId { return .peer(.name(peerName), .replyThread(value, commentId)) @@ -569,19 +590,42 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) } } case let .channelMessage(id, timecode): - return .single(.channelMessage(peer: peer, messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id), timecode: timecode)) + if let channel = peer as? TelegramChannel, channel.flags.contains(.isForum) { + return context.engine.peers.fetchForumChannelTopic(id: channel.id, threadId: Int64(id)) + |> map { info -> ResolvedUrl? in + if let _ = info { + return .replyThread(messageId: MessageId(peerId: channel.id, namespace: Namespaces.Message.Cloud, id: id)) + } else { + return .peer(peer, .chat(textInputState: nil, subject: nil, peekData: nil)) + } + } + } else { + return .single(.channelMessage(peer: peer, messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id), timecode: timecode)) + } case let .replyThread(id, replyId): let replyThreadMessageId = MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id) - return context.engine.messages.fetchChannelReplyThreadMessage(messageId: replyThreadMessageId, atMessageId: nil) - |> map(Optional.init) - |> `catch` { _ -> Signal in - return .single(nil) - } - |> map { result -> ResolvedUrl? in - guard let result = result else { - return .channelMessage(peer: peer, messageId: replyThreadMessageId, timecode: nil) + + if let channel = peer as? TelegramChannel, channel.flags.contains(.isForum) { + return context.engine.peers.fetchForumChannelTopic(id: channel.id, threadId: Int64(replyThreadMessageId.id)) + |> map { info -> ResolvedUrl? in + if let _ = info { + return .replyThreadMessage(replyThreadMessage: ChatReplyThreadMessage(messageId: MessageId(peerId: channel.id, namespace: Namespaces.Message.Cloud, id: Int32(clamping: replyThreadMessageId.id)), channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false), messageId: MessageId(peerId: channel.id, namespace: Namespaces.Message.Cloud, id: replyId)) + } else { + return .peer(peer, .chat(textInputState: nil, subject: nil, peekData: nil)) + } + } + } else { + return context.engine.messages.fetchChannelReplyThreadMessage(messageId: replyThreadMessageId, atMessageId: nil) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> map { result -> ResolvedUrl? in + guard let result = result else { + return .channelMessage(peer: peer, messageId: replyThreadMessageId, timecode: nil) + } + return .replyThreadMessage(replyThreadMessage: result, messageId: MessageId(peerId: result.messageId.peerId, namespace: Namespaces.Message.Cloud, id: replyId)) } - return .replyThreadMessage(replyThreadMessage: result, messageId: MessageId(peerId: result.messageId.peerId, namespace: Namespaces.Message.Cloud, id: replyId)) } case let .voiceChat(invite): return .single(.joinVoiceChat(peer.id, invite)) @@ -614,7 +658,27 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) return foundPeer |> mapToSignal { foundPeer -> Signal in if let foundPeer = foundPeer { - if let threadId = threadId { + if case let .channel(channel) = foundPeer, channel.flags.contains(.isForum) { + if let threadId = threadId { + return context.engine.peers.fetchForumChannelTopic(id: channel.id, threadId: Int64(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 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)) + } + } + } + } else if let threadId = threadId { let replyThreadMessageId = MessageId(peerId: foundPeer.id, namespace: Namespaces.Message.Cloud, id: threadId) return context.engine.messages.fetchChannelReplyThreadMessage(messageId: replyThreadMessageId, atMessageId: nil) |> map(Optional.init)