diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index e3479c2d1d..d063538c3c 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -471,6 +471,7 @@ public enum PeerInfoControllerMode { case nearbyPeer(distance: Int32) case group(PeerId) case reaction(MessageId) + case forumTopic(thread: ChatReplyThreadMessage) } public enum ContactListActionItemInlineIconPosition { @@ -749,7 +750,7 @@ public protocol SharedAccountContext: AnyObject { func makeRecentSessionsController(context: AccountContext, activeSessionsContext: ActiveSessionsContext) -> ViewController & RecentSessionsController - func makeChatQrCodeScreen(context: AccountContext, peer: Peer) -> ViewController + func makeChatQrCodeScreen(context: AccountContext, peer: Peer, threadId: Int64?) -> ViewController func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource) -> ViewController diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index a8cad79be0..de67510cd6 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1543,8 +1543,11 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController let context = strongSelf.context let controller = ForumCreateTopicScreen(context: context, peerId: peerId, mode: .create) controller.navigationPresentation = .modal + controller.completion = { title, fileId in - let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconFileId: fileId) + let availableColors: [Int32] = [0x6FB9F0, 0xFFD67E, 0xCB86DB, 0x8EEE98, 0xFF93B2, 0xFB6F5F] + + let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconColor: availableColors.randomElement()!, iconFileId: fileId) |> deliverOnMainQueue).start(next: { topicId in let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: topicId, navigationController: navigationController, activateInput: .text).start() }) @@ -2528,7 +2531,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController let controller = ForumCreateTopicScreen(context: context, peerId: peerId, mode: .create) controller.navigationPresentation = .modal controller.completion = { title, fileId in - let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconFileId: fileId) + let availableColors: [Int32] = [0x6FB9F0, 0xFFD67E, 0xCB86DB, 0x8EEE98, 0xFF93B2, 0xFB6F5F] + + let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconColor: availableColors.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, navigationController: navigationController, activateInput: .text).start() diff --git a/submodules/ContactListUI/Sources/ContactsController.swift b/submodules/ContactListUI/Sources/ContactsController.swift index 2e1f0c103a..e3cbe7d661 100644 --- a/submodules/ContactListUI/Sources/ContactsController.swift +++ b/submodules/ContactListUI/Sources/ContactsController.swift @@ -487,7 +487,7 @@ public class ContactsController: ViewController { let _ = (strongSelf.context.account.postbox.loadedPeerWithId(strongSelf.context.account.peerId) |> deliverOnMainQueue).start(next: { [weak self, weak controller] peer in if let strongSelf = self, let controller = controller { - controller.present(strongSelf.context.sharedContext.makeChatQrCodeScreen(context: strongSelf.context, peer: peer), in: .window(.root)) + controller.present(strongSelf.context.sharedContext.makeChatQrCodeScreen(context: strongSelf.context, peer: peer, threadId: nil), in: .window(.root)) } }) } diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift index 2b85f99fd5..09c5bde38f 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift @@ -89,13 +89,13 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe return TelegramMediaAction(action: .giftPremium(currency: currency, amount: amount, months: months)) case let .messageActionTopicCreate(_, title, iconColor, iconEmojiId): return TelegramMediaAction(action: .topicCreated(title: title, iconColor: iconColor, iconFileId: iconEmojiId)) - case let .messageActionTopicEdit(_, title, iconEmojiId): + case let .messageActionTopicEdit(flags, title, iconEmojiId): var componenents: [TelegramMediaActionType.ForumTopicEditComponent] = [] //messageActionTopicEdit#b117a9f5 flags:# title:flags.0?string icon_emoji_id:flags.1?long = MessageAction; if let title { componenents.append(.title(title)) } - if let iconEmojiId { + if (flags & (1 << 1)) != 0 { componenents.append(.iconFileId(iconEmojiId == 0 ? nil : iconEmojiId)) } return TelegramMediaAction(action: .topicEdited(components: componenents)) diff --git a/submodules/TelegramCore/Sources/ForumChannels.swift b/submodules/TelegramCore/Sources/ForumChannels.swift index 3b725b6de3..58fb72daef 100644 --- a/submodules/TelegramCore/Sources/ForumChannels.swift +++ b/submodules/TelegramCore/Sources/ForumChannels.swift @@ -123,6 +123,55 @@ func _internal_createForumChannelTopic(account: Account, peerId: PeerId, title: } } +public enum EditForumChannelTopicError { + case generic +} + +func _internal_editForumChannelTopic(account: Account, peerId: PeerId, threadId: Int64, title: String, iconFileId: Int64?) -> Signal { + return account.postbox.transaction { transaction -> Api.InputChannel? in + return transaction.getPeer(peerId).flatMap(apiInputChannel) + } + |> castError(EditForumChannelTopicError.self) + |> mapToSignal { inputChannel -> Signal in + guard let inputChannel = inputChannel else { + return .fail(.generic) + } + var flags: Int32 = 0 + flags |= (1 << 0) + flags |= (1 << 1) + + return account.network.request(Api.functions.channels.editForumTopic( + flags: flags, + channel: inputChannel, + topicId: Int32(clamping: threadId), + title: title, + iconEmojiId: iconFileId ?? 0 + )) + |> mapError { _ -> EditForumChannelTopicError in + return .generic + } + |> mapToSignal { result -> Signal in + account.stateManager.addUpdates(result) + + return account.postbox.transaction { transaction -> Void in + if let initialData = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.get(MessageHistoryThreadData.self) { + var data = initialData + + data.info = EngineMessageHistoryThread.Info(title: title, icon: iconFileId, iconColor: data.info.iconColor) + + if data != initialData { + if let entry = CodableEntry(data) { + transaction.setMessageHistoryThreadInfo(peerId: peerId, threadId: threadId, info: entry) + } + } + } + } + |> castError(EditForumChannelTopicError.self) + |> ignoreValues + } + } +} + func _internal_setChannelForumMode(account: Account, peerId: PeerId, isForum: Bool) -> Signal { return account.postbox.transaction { transaction -> Api.InputChannel? in return transaction.getPeer(peerId).flatMap(apiInputChannel) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SparseMessageList.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SparseMessageList.swift index 643d238f1f..1453a50275 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SparseMessageList.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SparseMessageList.swift @@ -7,6 +7,7 @@ public final class SparseMessageList { private let queue: Queue private let account: Account private let peerId: PeerId + private let threadId: Int64? private let messageTag: MessageTags private struct TopSection: Equatable { @@ -91,76 +92,79 @@ public final class SparseMessageList { let statePromise = Promise() - init(queue: Queue, account: Account, peerId: PeerId, messageTag: MessageTags) { + init(queue: Queue, account: Account, peerId: PeerId, threadId: Int64?, messageTag: MessageTags) { self.queue = queue self.account = account self.peerId = peerId + self.threadId = threadId self.messageTag = messageTag self.resetTopSection() - self.sparseItemsDisposable = (self.account.postbox.transaction { transaction -> Api.InputPeer? in - return transaction.getPeer(peerId).flatMap(apiInputPeer) - } - |> mapToSignal { inputPeer -> Signal in - guard let inputPeer = inputPeer else { - return .single(SparseItems(items: [])) + if self.threadId == nil { + self.sparseItemsDisposable = (self.account.postbox.transaction { transaction -> Api.InputPeer? in + return transaction.getPeer(peerId).flatMap(apiInputPeer) } - guard let messageFilter = messageFilterForTagMask(messageTag) else { - return .single(SparseItems(items: [])) - } - return account.network.request(Api.functions.messages.getSearchResultsPositions(peer: inputPeer, filter: messageFilter, offsetId: 0, limit: 1000)) - |> map { result -> SparseItems in - switch result { - case let .searchResultsPositions(totalCount, positions): - struct Position: Equatable { - var id: Int32 - var date: Int32 - var offset: Int - } - var positions: [Position] = positions.map { position -> Position in - switch position { - case let .searchResultPosition(id, date, offset): - return Position(id: id, date: date, offset: Int(offset)) + |> mapToSignal { inputPeer -> Signal in + guard let inputPeer = inputPeer else { + return .single(SparseItems(items: [])) + } + guard let messageFilter = messageFilterForTagMask(messageTag) else { + return .single(SparseItems(items: [])) + } + return account.network.request(Api.functions.messages.getSearchResultsPositions(peer: inputPeer, filter: messageFilter, offsetId: 0, limit: 1000)) + |> map { result -> SparseItems in + switch result { + case let .searchResultsPositions(totalCount, positions): + struct Position: Equatable { + var id: Int32 + var date: Int32 + var offset: Int } - } - positions.sort(by: { lhs, rhs in - return lhs.id > rhs.id - }) - - var result = SparseItems(items: []) - for i in 0 ..< positions.count { - if i != 0 { - let deltaCount = positions[i].offset - 1 - positions[i - 1].offset - if deltaCount > 0 { - result.items.append(.range(count: deltaCount)) + var positions: [Position] = positions.map { position -> Position in + switch position { + case let .searchResultPosition(id, date, offset): + return Position(id: id, date: date, offset: Int(offset)) } } - result.items.append(.anchor(id: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: positions[i].id), timestamp: positions[i].date, message: nil)) - if i == positions.count - 1 { - let remainingCount = Int(totalCount) - 1 - positions[i].offset - if remainingCount > 0 { - result.items.append(.range(count: remainingCount)) + positions.sort(by: { lhs, rhs in + return lhs.id > rhs.id + }) + + var result = SparseItems(items: []) + for i in 0 ..< positions.count { + if i != 0 { + let deltaCount = positions[i].offset - 1 - positions[i - 1].offset + if deltaCount > 0 { + result.items.append(.range(count: deltaCount)) + } + } + result.items.append(.anchor(id: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: positions[i].id), timestamp: positions[i].date, message: nil)) + if i == positions.count - 1 { + let remainingCount = Int(totalCount) - 1 - positions[i].offset + if remainingCount > 0 { + result.items.append(.range(count: remainingCount)) + } } } + + return result } - - return result + } + |> `catch` { _ -> Signal in + return .single(SparseItems(items: [])) } } - |> `catch` { _ -> Signal in - return .single(SparseItems(items: [])) - } + |> deliverOn(self.queue)).start(next: { [weak self] sparseItems in + guard let strongSelf = self else { + return + } + strongSelf.sparseItems = sparseItems + if strongSelf.topSection != nil { + strongSelf.updateState() + } + }) } - |> deliverOn(self.queue)).start(next: { [weak self] sparseItems in - guard let strongSelf = self else { - return - } - strongSelf.sparseItems = sparseItems - if strongSelf.topSection != nil { - strongSelf.updateState() - } - }) self.deletedMessagesDisposable = (account.postbox.combinedView(keys: [.deletedMessages(peerId: peerId)]) |> deliverOn(self.queue)).start(next: { [weak self] views in @@ -183,12 +187,16 @@ public final class SparseMessageList { private func resetTopSection() { let count: Int - /*#if DEBUG - count = 20 - #else*/ count = 200 - //#endif - self.topItemsDisposable.set((self.account.postbox.aroundMessageHistoryViewForLocation(.peer(peerId: peerId), anchor: .upperBound, ignoreMessagesInTimestampRange: nil, count: count, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: Set(), tagMask: self.messageTag, appendMessagesFromTheSameGroup: false, namespaces: .not(Set(Namespaces.Message.allScheduled)), orderStatistics: []) + + let location: ChatLocationInput + if let threadId = self.threadId { + location = .thread(peerId: self.peerId, threadId: threadId, data: .single(MessageHistoryViewExternalInput(content: .thread(peerId: self.peerId, id: threadId, holes: [:]), maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil))) + } else { + location = .peer(peerId: self.peerId) + } + + self.topItemsDisposable.set((self.account.postbox.aroundMessageHistoryViewForLocation(location, anchor: .upperBound, ignoreMessagesInTimestampRange: nil, count: count, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: Set(), tagMask: self.messageTag, appendMessagesFromTheSameGroup: false, namespaces: .not(Set(Namespaces.Message.allScheduled)), orderStatistics: []) |> deliverOn(self.queue)).start(next: { [weak self] view, updateType, _ in guard let strongSelf = self else { return @@ -688,11 +696,11 @@ public final class SparseMessageList { } } - init(account: Account, peerId: PeerId, messageTag: MessageTags) { + init(account: Account, peerId: PeerId, threadId: Int64?, messageTag: MessageTags) { self.queue = Queue() let queue = self.queue self.impl = QueueLocalObject(queue: queue, generate: { - return Impl(queue: queue, account: account, peerId: peerId, messageTag: messageTag) + return Impl(queue: queue, account: account, peerId: peerId, threadId: threadId, messageTag: messageTag) }) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index e3cfc381d5..dfca752f6d 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -285,8 +285,8 @@ public extension TelegramEngine { } } - public func sparseMessageList(peerId: EnginePeer.Id, tag: EngineMessage.Tags) -> SparseMessageList { - return SparseMessageList(account: self.account, peerId: peerId, messageTag: tag) + public func sparseMessageList(peerId: EnginePeer.Id, threadId: Int64?, tag: EngineMessage.Tags) -> SparseMessageList { + return SparseMessageList(account: self.account, peerId: peerId, threadId: threadId, messageTag: tag) } public func sparseMessageCalendar(peerId: EnginePeer.Id, tag: EngineMessage.Tags) -> SparseMessageCalendar { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift index 63258f9f9e..dd54dde501 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift @@ -805,6 +805,10 @@ public extension TelegramEngine { public func createForumChannelTopic(id: EnginePeer.Id, title: String, iconColor: Int32, iconFileId: Int64?) -> Signal { return _internal_createForumChannelTopic(account: self.account, peerId: id, title: title, iconColor: iconColor, iconFileId: iconFileId) } + + 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/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift b/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift index 68a51a1012..7540a4f04d 100644 --- a/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift +++ b/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift @@ -225,8 +225,11 @@ public final class EmojiStatusComponent: Component { } case let .topic(title, colorIndex): func generateTopicIcon(backgroundColors: [UIColor], strokeColors: [UIColor]) -> UIImage? { - return generateImage(CGSize(width: 44.0, height: 44.0), rotatedContext: { size, context in - context.clear(CGRect(origin: .zero, size: size)) + return generateImage(CGSize(width: availableSize.width, height: availableSize.height), rotatedContext: { realSize, context in + context.clear(CGRect(origin: .zero, size: realSize)) + + let size = CGSize(width: 44.0, height: 44.0) + context.scaleBy(x: realSize.width / size.width, y: realSize.height / size.height) context.saveGState() diff --git a/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift b/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift index 26c6fd8477..29413d68db 100644 --- a/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift +++ b/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift @@ -357,9 +357,9 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent { case .create: self.title = "" self.fileId = 0 - case let .edit(topic): - self.title = topic.info.title - self.fileId = topic.info.icon ?? 0 + case let .edit(info): + self.title = info.title + self.fileId = info.icon ?? 0 } super.init() @@ -670,7 +670,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent { public class ForumCreateTopicScreen: ViewControllerComponentContainer { public enum Mode: Equatable { case create - case edit(topic: ForumChannelTopics.Item) + case edit(topic: EngineMessageHistoryThread.Info) } private var state: (String, Int64?) = ("", nil) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 28fd0abe1e..75ab8f8cb1 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -4809,14 +4809,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - if let threadInfo = messageAndTopic.threadInfo, let message = message { + if let threadInfo = messageAndTopic.threadInfo { strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, customTitle: threadInfo.title, onlineMemberCount: onlineMemberCount, isScheduledMessages: false) let avatarContent: EmojiStatusComponent.Content if let fileId = threadInfo.icon { avatarContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: strongSelf.presentationData.theme.list.mediaPlaceholderColor, themeColor: nil, loopMode: .count(2)) } else { - avatarContent = .topic(title: String(threadInfo.title.prefix(1)), colorIndex: Int(message.id.id)) + avatarContent = .topic(title: String(threadInfo.title.prefix(1)), colorIndex: Int(threadInfo.iconColor)) } (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.setStatus(context: strongSelf.context, content: avatarContent) } else { @@ -9979,7 +9979,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if case let .peer(peerId) = self.chatLocation { let context = self.context - self.keepPeerInfoScreenDataHotDisposable.set(keepPeerInfoScreenDataHot(context: context, peerId: peerId).start()) + self.keepPeerInfoScreenDataHotDisposable.set(keepPeerInfoScreenDataHot(context: context, peerId: peerId, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder).start()) if peerId.namespace == Namespaces.Peer.CloudUser { self.preloadAvatarDisposable.set((peerInfoProfilePhotosWithCache(context: context, peerId: peerId) @@ -11140,7 +11140,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } })) case .replyThread: - break + if let channel = self.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.flags.contains(.isForum), case let .replyThread(message) = self.chatLocation { + if let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: channel, mode: .forumTopic(thread: message), avatarInitiallyExpanded: false, fromChat: true, requestsContext: self.inviteRequestsContext) { + self.effectiveNavigationController?.pushViewController(infoController) + } + } case .feed: break } diff --git a/submodules/TelegramUI/Sources/ChatQrCodeScreen.swift b/submodules/TelegramUI/Sources/ChatQrCodeScreen.swift index fb9bf5ab34..afebcc3bab 100644 --- a/submodules/TelegramUI/Sources/ChatQrCodeScreen.swift +++ b/submodules/TelegramUI/Sources/ChatQrCodeScreen.swift @@ -564,19 +564,24 @@ final class ChatQrCodeScreen: ViewController { static let themeCrossfadeDelay: Double = 0.05 enum Subject { - case peer(Peer) + case peer(peer: Peer, threadId: Int64?) case messages([Message]) var fileName: String { switch self { - case let .peer(peer): + case let .peer(peer, threadId): + var result: String if let addressName = peer.addressName, !addressName.isEmpty { - return "t_me-\(peer.addressName ?? "")" + result = "t_me-\(peer.addressName ?? "")" } else if let peer = peer as? TelegramUser { - return "t_me-\(peer.phone ?? "")" + result = "t_me-\(peer.phone ?? "")" } else { - return "t_me-\(Int32.random(in: 0 ..< Int32.max))" + result = "t_me-\(Int32.random(in: 0 ..< Int32.max))" } + if let threadId = threadId { + result.append("-\(threadId)") + } + return result case let .messages(messages): if let message = messages.first, let chatPeer = message.peers[message.id.peerId] as? TelegramChannel, message.id.namespace == Namespaces.Message.Cloud, let addressName = chatPeer.addressName, !addressName.isEmpty { return "t_me-\(addressName)-\(message.id.id)" @@ -800,8 +805,8 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg self.wrappingScrollNode.view.canCancelContentTouches = true switch controller.subject { - case let .peer(peer): - self.contentNode = QrContentNode(context: context, peer: peer, isStatic: false) + case let .peer(peer, threadId): + self.contentNode = QrContentNode(context: context, peer: peer, threadId: threadId, isStatic: false) case let .messages(messages): self.contentNode = MessageContentNode(context: context, messages: messages) } @@ -994,7 +999,7 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg let initiallySelectedEmoticon: Signal let sharedData = self.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings]) |> take(1) - if case let .peer(peer) = controller.subject, peer.id != self.context.account.peerId { + if case let .peer(peer, _) = controller.subject, peer.id != self.context.account.peerId { let themeEmoticon = self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.ThemeEmoticon(id: peer.id)) initiallySelectedEmoticon = combineLatest(themeEmoticon, sharedData) |> map { themeEmoticon, sharedData -> String in @@ -1434,6 +1439,7 @@ private protocol ContentNode: ASDisplayNode { private class QrContentNode: ASDisplayNode, ContentNode { private let context: AccountContext private let peer: Peer + private let threadId: Int64? private let isStatic: Bool fileprivate let containerNode: ASDisplayNode @@ -1463,9 +1469,10 @@ private class QrContentNode: ASDisplayNode, ContentNode { return false } - init(context: AccountContext, peer: Peer, isStatic: Bool = false) { + init(context: AccountContext, peer: Peer, threadId: Int64?, isStatic: Bool = false) { self.context = context self.peer = peer + self.threadId = threadId self.isStatic = isStatic self.containerNode = ASDisplayNode() @@ -1506,12 +1513,15 @@ private class QrContentNode: ASDisplayNode, ContentNode { self.codeStaticIconNode = nil } - let codeText: String + var codeText: String if let addressName = peer.addressName, !addressName.isEmpty { codeText = "@\(peer.addressName ?? "")".uppercased() } else { codeText = peer.debugDisplayTitle.uppercased() } + if let threadId = self.threadId { + codeText += "/\(threadId)" + } self.codeTextNode = ImmediateTextNode() self.codeTextNode.displaysAsynchronously = false @@ -1550,14 +1560,19 @@ private class QrContentNode: ASDisplayNode, ContentNode { self.addSubnode(codeAnimatedIconNode) } - let codeLink: String + var codeLink: String if let addressName = peer.addressName, !addressName.isEmpty { codeLink = "https://t.me/\(peer.addressName ?? "")" } else if let peer = peer as? TelegramUser { codeLink = "https://t.me/+\(peer.phone ?? "")" + } else if let _ = peer as? TelegramChannel { + codeLink = "https://t.me/c/\(peer.id.id._internalGetInt64Value())" } else { codeLink = "" } + if let threadId = threadId { + codeLink += "?topic=\(threadId)" + } let codeReadyPromise = ValuePromise() self.codeImageNode.setSignal(qrCode(string: codeLink, color: .black, backgroundColor: nil, icon: .cutout, ecl: "Q") |> beforeNext { [weak self] size, _ in @@ -1591,7 +1606,7 @@ private class QrContentNode: ASDisplayNode, ContentNode { let size = CGSize(width: 390.0, height: 844.0) let scale: CGFloat = 3.0 - let copyNode = QrContentNode(context: self.context, peer: self.peer, isStatic: true) + let copyNode = QrContentNode(context: self.context, peer: self.peer, threadId: self.threadId, isStatic: true) func prepare(view: UIView, scale: CGFloat) { view.contentScaleFactor = scale diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index 663e033788..2f57f5bce9 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -1408,7 +1408,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { } } else if let channel = peer as? TelegramChannel, case .group = channel.info, channel.hasPermission(.canBeAnonymous) { placeholder = interfaceState.strings.Conversation_InputTextAnonymousPlaceholder - } else if case let .replyThread(replyThreadMessage) = interfaceState.chatLocation { + } else if case let .replyThread(replyThreadMessage) = interfaceState.chatLocation, !replyThreadMessage.isForumPost { if replyThreadMessage.isChannelPost { placeholder = interfaceState.strings.Conversation_InputTextPlaceholderComment } else { diff --git a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift index fb9e5e0534..b59833c101 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoListPaneNode.swift @@ -19,6 +19,8 @@ import ChatPresentationInterfaceState final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { private let context: AccountContext private let peerId: PeerId + private let chatLocation: ChatLocation + private let chatLocationContextHolder: Atomic private let chatControllerInteraction: ChatControllerInteraction weak var parentController: ViewController? @@ -61,16 +63,18 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { return 0.0 } - init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, chatControllerInteraction: ChatControllerInteraction, peerId: PeerId, tagMask: MessageTags) { + init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, chatControllerInteraction: ChatControllerInteraction, peerId: PeerId, chatLocation: ChatLocation, chatLocationContextHolder: Atomic, tagMask: MessageTags) { self.context = context self.peerId = peerId + self.chatLocation = chatLocation + self.chatLocationContextHolder = chatLocationContextHolder + self.chatControllerInteraction = chatControllerInteraction self.selectedMessages = chatControllerInteraction.selectionState.flatMap { $0.selectedIds } self.selectedMessagesPromise.set(.single(self.selectedMessages)) - let chatLocationContextHolder = Atomic(value: nil) - self.listNode = ChatHistoryListNode(context: context, updatedPresentationData: updatedPresentationData ?? (context.sharedContext.currentPresentationData.with({ $0 }), context.sharedContext.presentationData), chatLocation: .peer(id: peerId), chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, subject: nil, controllerInteraction: chatControllerInteraction, selectedMessages: self.selectedMessagesPromise.get(), mode: .list(search: false, reversed: false, displayHeaders: .allButLast, hintLinks: tagMask == .webPage, isGlobalSearch: false)) + self.listNode = ChatHistoryListNode(context: context, updatedPresentationData: updatedPresentationData ?? (context.sharedContext.currentPresentationData.with({ $0 }), context.sharedContext.presentationData), chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, tagMask: tagMask, subject: nil, controllerInteraction: chatControllerInteraction, selectedMessages: self.selectedMessagesPromise.get(), mode: .list(search: false, reversed: false, displayHeaders: .allButLast, hintLinks: tagMask == .webPage, isGlobalSearch: false)) self.listNode.clipsToBounds = true self.listNode.defaultToSynchronousTransactionWhileScrolling = true self.listNode.scroller.bounces = false diff --git a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift index 2d175e3795..56a39b7727 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift @@ -1594,6 +1594,8 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro private let context: AccountContext private let peerId: PeerId + private let chatLocation: ChatLocation + private let chatLocationContextHolder: Atomic private let chatControllerInteraction: ChatControllerInteraction private(set) var contentType: ContentType private var contentTypePromise: ValuePromise @@ -1657,9 +1659,11 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro private var presentationData: PresentationData private var presentationDataDisposable: Disposable? - init(context: AccountContext, chatControllerInteraction: ChatControllerInteraction, peerId: PeerId, contentType: ContentType, captureProtected: Bool) { + init(context: AccountContext, chatControllerInteraction: ChatControllerInteraction, peerId: PeerId, chatLocation: ChatLocation, chatLocationContextHolder: Atomic, contentType: ContentType, captureProtected: Bool) { self.context = context self.peerId = peerId + self.chatLocation = chatLocation + self.chatLocationContextHolder = chatLocationContextHolder self.chatControllerInteraction = chatControllerInteraction self.contentType = contentType self.contentTypePromise = ValuePromise(contentType) @@ -1719,12 +1723,21 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro directMediaImageCache: self.directMediaImageCache, captureProtected: captureProtected ) + + var threadId: Int64? + if case let .replyThread(message) = chatLocation { + threadId = Int64(message.messageId.id) + } - self.listSource = self.context.engine.messages.sparseMessageList(peerId: self.peerId, tag: tagMaskForType(self.contentType)) - switch contentType { - case .photoOrVideo, .photo, .video: - self.calendarSource = self.context.engine.messages.sparseMessageCalendar(peerId: self.peerId, tag: tagMaskForType(self.contentType)) - default: + self.listSource = self.context.engine.messages.sparseMessageList(peerId: self.peerId, threadId: threadId, tag: tagMaskForType(self.contentType)) + if threadId == nil { + switch contentType { + case .photoOrVideo, .photo, .video: + self.calendarSource = self.context.engine.messages.sparseMessageCalendar(peerId: self.peerId, tag: tagMaskForType(self.contentType)) + default: + self.calendarSource = nil + } + } else { self.calendarSource = nil } @@ -2172,8 +2185,13 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro self.contentTypePromise.set(contentType) self.itemGrid.hideScrollingArea() + + var threadId: Int64? + if case let .replyThread(message) = chatLocation { + threadId = Int64(message.messageId.id) + } - self.listSource = self.context.engine.messages.sparseMessageList(peerId: self.peerId, tag: tagMaskForType(self.contentType)) + self.listSource = self.context.engine.messages.sparseMessageList(peerId: self.peerId, threadId: threadId, tag: tagMaskForType(self.contentType)) self.isRequestingView = false self.requestHistoryAroundVisiblePosition(synchronous: true, reloadAtTop: true) } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift index d1b218dcce..46f26eb955 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift @@ -187,6 +187,7 @@ final class PeerInfoScreenData { let invitations: PeerExportedInvitationsState? let requests: PeerInvitationImportersState? let requestsContext: PeerInvitationImportersContext? + let threadInfo: EngineMessageHistoryThread.Info? init( peer: Peer?, @@ -204,7 +205,8 @@ final class PeerInfoScreenData { globalSettings: TelegramGlobalSettings?, invitations: PeerExportedInvitationsState?, requests: PeerInvitationImportersState?, - requestsContext: PeerInvitationImportersContext? + requestsContext: PeerInvitationImportersContext?, + threadInfo: EngineMessageHistoryThread.Info? ) { self.peer = peer self.chatPeer = chatPeer @@ -222,6 +224,7 @@ final class PeerInfoScreenData { self.invitations = invitations self.requests = requests self.requestsContext = requestsContext + self.threadInfo = threadInfo } } @@ -240,7 +243,7 @@ private enum PeerInfoScreenInputData: Equatable { case group(groupId: PeerId) } -private func peerInfoAvailableMediaPanes(context: AccountContext, peerId: PeerId) -> Signal<[PeerInfoPaneKey]?, NoError> { +private func peerInfoAvailableMediaPanes(context: AccountContext, peerId: PeerId, chatLocation: ChatLocation, chatLocationContextHolder: Atomic) -> Signal<[PeerInfoPaneKey]?, NoError> { let tags: [(MessageTags, PeerInfoPaneKey)] = [ (.photoOrVideo, .media), (.file, .files), @@ -257,7 +260,8 @@ private func peerInfoAvailableMediaPanes(context: AccountContext, peerId: PeerId let loadedOnce = Atomic(value: false) return combineLatest(queue: .mainQueue(), tags.map { tagAndKey -> Signal<(PeerInfoPaneKey, PaneState), NoError> in let (tag, key) = tagAndKey - return context.account.viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId: peerId), index: .upperBound, anchorIndex: .upperBound, count: 20, clipHoles: false, fixedCombinedReadStates: nil, tagMask: tag) + let location = context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder) + return context.account.viewTracker.aroundMessageHistoryViewForLocation(location, index: .upperBound, anchorIndex: .upperBound, count: 20, clipHoles: false, fixedCombinedReadStates: nil, tagMask: tag) |> map { (view, _, _) -> (PeerInfoPaneKey, PaneState) in if view.entries.isEmpty { if view.isLoading { @@ -353,7 +357,7 @@ private func peerInfoScreenInputData(context: AccountContext, peerId: EnginePeer |> distinctUntilChanged } -func keepPeerInfoScreenDataHot(context: AccountContext, peerId: PeerId) -> Signal { +func keepPeerInfoScreenDataHot(context: AccountContext, peerId: PeerId, chatLocation: ChatLocation, chatLocationContextHolder: Atomic) -> Signal { return peerInfoScreenInputData(context: context, peerId: peerId, isSettings: false) |> mapToSignal { inputData -> Signal in switch inputData { @@ -361,7 +365,7 @@ func keepPeerInfoScreenDataHot(context: AccountContext, peerId: PeerId) -> Signa return .complete() case .user, .channel, .group: return combineLatest( - context.peerChannelMemberCategoriesContextsManager.profileData(postbox: context.account.postbox, network: context.account.network, peerId: peerId, customData: peerInfoAvailableMediaPanes(context: context, peerId: peerId) |> ignoreValues), + context.peerChannelMemberCategoriesContextsManager.profileData(postbox: context.account.postbox, network: context.account.network, peerId: peerId, customData: peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder) |> ignoreValues), context.peerChannelMemberCategoriesContextsManager.profilePhotos(postbox: context.account.postbox, network: context.account.network, peerId: peerId, fetch: peerInfoProfilePhotos(context: context, peerId: peerId)) |> ignoreValues ) |> ignoreValues @@ -472,12 +476,13 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id, globalSettings: globalSettings, invitations: nil, requests: nil, - requestsContext: nil + requestsContext: nil, + threadInfo: nil ) } } -func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, isSettings: Bool, hintGroupInCommon: PeerId?, existingRequestsContext: PeerInvitationImportersContext?) -> Signal { +func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, isSettings: Bool, hintGroupInCommon: PeerId?, existingRequestsContext: PeerInvitationImportersContext?, chatLocation: ChatLocation, chatLocationContextHolder: Atomic) -> Signal { return peerInfoScreenInputData(context: context, peerId: peerId, isSettings: isSettings) |> mapToSignal { inputData -> Signal in switch inputData { @@ -498,7 +503,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen globalSettings: nil, invitations: nil, requests: nil, - requestsContext: nil + requestsContext: nil, + threadInfo: nil )) case let .user(userPeerId, secretChatId, kind): let groupsInCommon: GroupsInCommonContext? @@ -599,7 +605,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen return combineLatest( context.account.viewTracker.peerView(peerId, updateData: true), - peerInfoAvailableMediaPanes(context: context, peerId: peerId), + peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder), context.engine.data.subscribe(TelegramEngine.EngineData.Item.NotificationSettings.Global()), secretChatKeyFingerprint, status @@ -628,7 +634,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen globalSettings: nil, invitations: nil, requests: nil, - requestsContext: nil + requestsContext: nil, + threadInfo: nil ) } case .channel: @@ -653,7 +660,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen return combineLatest( context.account.viewTracker.peerView(peerId, updateData: true), - peerInfoAvailableMediaPanes(context: context, peerId: peerId), + peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder), context.engine.data.subscribe(TelegramEngine.EngineData.Item.NotificationSettings.Global()), status, invitationsContextPromise.get(), @@ -703,7 +710,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen globalSettings: nil, invitations: invitations, requests: requests, - requestsContext: currentRequestsContext + requestsContext: currentRequestsContext, + threadInfo: nil ) } case let .group(groupId): @@ -785,17 +793,21 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen } |> distinctUntilChanged - let membersContext = PeerInfoMembersContext(context: context, peerId: groupId) - - let membersData: Signal = combineLatest(membersContext.state, context.account.viewTracker.peerView(groupId, updateData: false)) - |> map { state, view -> PeerInfoMembersData? in - if state.members.count > 5 { - return .longList(membersContext) - } else { - return .shortList(membersContext: membersContext, members: state.members) + let membersData: Signal + if case .peer = chatLocation { + let membersContext = PeerInfoMembersContext(context: context, peerId: groupId) + membersData = combineLatest(membersContext.state, context.account.viewTracker.peerView(groupId, updateData: false)) + |> map { state, view -> PeerInfoMembersData? in + if state.members.count > 5 { + return .longList(membersContext) + } else { + return .shortList(membersContext: membersContext, members: state.members) + } } + |> distinctUntilChanged + } else { + membersData = .single(nil) } - |> distinctUntilChanged let invitationsContextPromise = Promise(nil) let invitationsStatePromise = Promise(nil) @@ -803,27 +815,40 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen let requestsContextPromise = Promise(nil) let requestsStatePromise = Promise(nil) + let threadInfo: Signal + if case let .replyThread(message) = chatLocation { + let threadId = Int64(message.messageId.id) + let viewKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: peerId, threadId: threadId) + threadInfo = context.account.postbox.combinedView(keys: [viewKey]) + |> map { views -> EngineMessageHistoryThread.Info? in + guard let view = views.views[viewKey] as? MessageHistoryThreadInfoView else { + return nil + } + return view.info?.get(MessageHistoryThreadData.self)?.info + } + } else { + threadInfo = .single(nil) + } + return combineLatest(queue: .mainQueue(), context.account.viewTracker.peerView(groupId, updateData: true), - peerInfoAvailableMediaPanes(context: context, peerId: groupId), + peerInfoAvailableMediaPanes(context: context, peerId: groupId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder), context.engine.data.subscribe(TelegramEngine.EngineData.Item.NotificationSettings.Global()), status, membersData, invitationsContextPromise.get(), invitationsStatePromise.get(), requestsContextPromise.get(), - requestsStatePromise.get() + requestsStatePromise.get(), + threadInfo ) - |> map { peerView, availablePanes, globalNotificationSettings, status, membersData, currentInvitationsContext, invitations, currentRequestsContext, requests -> PeerInfoScreenData in + |> map { peerView, availablePanes, globalNotificationSettings, status, membersData, currentInvitationsContext, invitations, currentRequestsContext, requests, threadInfo -> 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 } var availablePanes = availablePanes - if let channel = peerView.peers[peerView.peerId] as? TelegramChannel, channel.flags.contains(.isForum) { - availablePanes?.removeAll() - } if let membersData = membersData, case .longList = membersData { if availablePanes != nil { availablePanes?.insert(.members, at: 0) @@ -874,7 +899,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen globalSettings: nil, invitations: invitations, requests: requests, - requestsContext: currentRequestsContext + requestsContext: currentRequestsContext, + threadInfo: threadInfo ) } } @@ -1013,7 +1039,7 @@ func peerInfoHeaderButtonIsHiddenWhileExpanded(buttonKey: PeerInfoHeaderButtonKe return hiddenWhileExpanded } -func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFromChat: Bool, isExpanded: Bool, videoCallsEnabled: Bool, isSecretChat: Bool, isContact: Bool) -> [PeerInfoHeaderButtonKey] { +func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFromChat: Bool, isExpanded: Bool, videoCallsEnabled: Bool, isSecretChat: Bool, isContact: Bool, threadInfo: EngineMessageHistoryThread.Info?) -> [PeerInfoHeaderButtonKey] { var result: [PeerInfoHeaderButtonKey] = [] if let user = peer as? TelegramUser { if !isOpenedFromChat { @@ -1050,69 +1076,74 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro result.append(.more) } } else if let channel = peer as? TelegramChannel { - let hasVoiceChat = channel.flags.contains(.hasVoiceChat) - let canStartVoiceChat = !hasVoiceChat && (channel.flags.contains(.isCreator) || channel.hasPermission(.manageCalls)) - let canManage = channel.flags.contains(.isCreator) || channel.adminRights != nil + if let _ = threadInfo { + result.append(.mute) + result.append(.search) + } else { + let hasVoiceChat = channel.flags.contains(.hasVoiceChat) + let canStartVoiceChat = !hasVoiceChat && (channel.flags.contains(.isCreator) || channel.hasPermission(.manageCalls)) + let canManage = channel.flags.contains(.isCreator) || channel.adminRights != nil - let hasDiscussion: Bool - switch channel.info { + let hasDiscussion: Bool + switch channel.info { case let .broadcast(info): hasDiscussion = info.flags.contains(.hasDiscussionGroup) case .group: hasDiscussion = false - } - - let canLeave: Bool - switch channel.participationStatus { - case .member: - canLeave = true - default: - canLeave = false - } - - let canViewStats: Bool - if let cachedChannelData = cachedData as? CachedChannelData { - canViewStats = cachedChannelData.flags.contains(.canViewStats) - } else { - canViewStats = false - } - - if hasVoiceChat || canStartVoiceChat { - result.append(.voiceChat) - } - result.append(.mute) - if hasDiscussion { - result.append(.discussion) - } - result.append(.search) - if canLeave { - result.append(.leave) - } - - var canReport = true - if channel.isVerified || channel.adminRights != nil || channel.flags.contains(.isCreator) { - canReport = false - } - - var hasMore = false - if canReport || canViewStats { - hasMore = true - result.append(.more) - } - - if hasDiscussion && isExpanded && result.count >= 5 { - result.removeAll(where: { $0 == .search }) - if !hasMore { + } + + let canLeave: Bool + switch channel.participationStatus { + case .member: + canLeave = true + default: + canLeave = false + } + + let canViewStats: Bool + if let cachedChannelData = cachedData as? CachedChannelData { + canViewStats = cachedChannelData.flags.contains(.canViewStats) + } else { + canViewStats = false + } + + if hasVoiceChat || canStartVoiceChat { + result.append(.voiceChat) + } + result.append(.mute) + if hasDiscussion { + result.append(.discussion) + } + result.append(.search) + if canLeave { + result.append(.leave) + } + + var canReport = true + if channel.isVerified || channel.adminRights != nil || channel.flags.contains(.isCreator) { + canReport = false + } + + var hasMore = false + if canReport || canViewStats { hasMore = true result.append(.more) } - } - - if canLeave && isExpanded && (canManage || result.count >= 5) { - result.removeAll(where: { $0 == .leave }) - if !hasMore { - hasMore = true - result.append(.more) + + if hasDiscussion && isExpanded && result.count >= 5 { + result.removeAll(where: { $0 == .search }) + if !hasMore { + hasMore = true + result.append(.more) + } + } + + if canLeave && isExpanded && (canManage || result.count >= 5) { + result.removeAll(where: { $0 == .leave }) + if !hasMore { + hasMore = true + result.append(.more) + } } } } else if let group = peer as? TelegramGroup { diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index 24eae0d90b..2679e995d7 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -308,6 +308,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { let avatarNode: AvatarNode fileprivate var videoNode: UniversalVideoNode? + fileprivate var iconView: ComponentView? private var videoContent: NativeVideoContent? private var videoStartTimestamp: Double? @@ -380,7 +381,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { } var removedPhotoResourceIds = Set() - func update(peer: Peer?, item: PeerInfoAvatarListItem?, theme: PresentationTheme, avatarSize: CGFloat, isExpanded: Bool, isSettings: Bool) { + func update(peer: Peer?, threadInfo: EngineMessageHistoryThread.Info?, item: PeerInfoAvatarListItem?, theme: PresentationTheme, avatarSize: CGFloat, isExpanded: Bool, isSettings: Bool) { if let peer = peer { let previousItem = self.item var item = item @@ -410,6 +411,43 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { self.avatarNode.setPeer(context: self.context, theme: theme, peer: EnginePeer(peer), overrideImage: overrideImage, clipStyle: .none, synchronousLoad: self.isFirstAvatarLoading, displayDimensions: CGSize(width: avatarSize, height: avatarSize), storeUnrounded: true) + if let threadInfo = threadInfo { + self.avatarNode.isHidden = true + + let iconView: ComponentView + if let current = self.iconView { + iconView = current + } else { + iconView = ComponentView() + self.iconView = iconView + } + let content: EmojiStatusComponent.Content + if let iconFileId = threadInfo.icon { + content = .animation(content: .customEmoji(fileId: iconFileId), size: CGSize(width: avatarSize, height: avatarSize), placeholderColor: theme.list.mediaPlaceholderColor, themeColor: nil, loopMode: .forever) + } else { + content = .topic(title: String(threadInfo.title.prefix(1)), colorIndex: Int(threadInfo.iconColor)) + } + let _ = iconView.update( + transition: .immediate, + component: AnyComponent(EmojiStatusComponent( + context: self.context, + animationCache: self.context.animationCache, + animationRenderer: self.context.animationRenderer, + content: content, + isVisibleForAnimations: true, + action: nil + )), + environment: {}, + containerSize: CGSize(width: avatarSize, height: avatarSize) + ) + if let iconComponentView = iconView.view { + if iconComponentView.superview == nil { + self.avatarNode.view.superview?.addSubview(iconComponentView) + } + iconComponentView.frame = CGRect(origin: CGPoint(), size: CGSize(width: avatarSize, height: avatarSize)) + } + } + let avatarCornerRadius: CGFloat if let channel = peer as? TelegramChannel, channel.flags.contains(.isForum) { avatarCornerRadius = floor(avatarSize * 0.25) @@ -825,7 +863,7 @@ final class PeerInfoAvatarListNode: ASDisplayNode { let isReady = Promise() - var arguments: (Peer?, PresentationTheme, CGFloat, Bool)? + var arguments: (Peer?, EngineMessageHistoryThread.Info?, PresentationTheme, CGFloat, Bool)? var item: PeerInfoAvatarListItem? var itemsUpdated: (([PeerInfoAvatarListItem]) -> Void)? @@ -884,14 +922,14 @@ final class PeerInfoAvatarListNode: ASDisplayNode { if let strongSelf = self { strongSelf.item = items.first strongSelf.itemsUpdated?(items) - if let (peer, theme, avatarSize, isExpanded) = strongSelf.arguments { - strongSelf.avatarContainerNode.update(peer: peer, item: strongSelf.item, theme: theme, avatarSize: avatarSize, isExpanded: isExpanded, isSettings: strongSelf.isSettings) + if let (peer, threadInfo, theme, avatarSize, isExpanded) = strongSelf.arguments { + strongSelf.avatarContainerNode.update(peer: peer, threadInfo: threadInfo, item: strongSelf.item, theme: theme, avatarSize: avatarSize, isExpanded: isExpanded, isSettings: strongSelf.isSettings) } } } self.pinchSourceNode.activate = { [weak self] sourceNode in - guard let strongSelf = self, let (_, _, _, isExpanded) = strongSelf.arguments, isExpanded else { + guard let strongSelf = self, let (_, _, _, _, isExpanded) = strongSelf.arguments, isExpanded else { return } let pinchController = PinchController(sourceNode: sourceNode, getContentAreaInScreenSpace: { @@ -910,11 +948,11 @@ final class PeerInfoAvatarListNode: ASDisplayNode { } } - func update(size: CGSize, avatarSize: CGFloat, isExpanded: Bool, peer: Peer?, theme: PresentationTheme, transition: ContainedViewLayoutTransition) { - self.arguments = (peer, theme, avatarSize, isExpanded) + func update(size: CGSize, avatarSize: CGFloat, isExpanded: Bool, peer: Peer?, threadInfo: EngineMessageHistoryThread.Info?, theme: PresentationTheme, transition: ContainedViewLayoutTransition) { + self.arguments = (peer, threadInfo, theme, avatarSize, isExpanded) self.pinchSourceNode.update(size: size, transition: transition) self.pinchSourceNode.frame = CGRect(origin: CGPoint(), size: size) - self.avatarContainerNode.update(peer: peer, item: self.item, theme: theme, avatarSize: avatarSize, isExpanded: isExpanded, isSettings: self.isSettings) + self.avatarContainerNode.update(peer: peer, threadInfo: threadInfo, item: self.item, theme: theme, avatarSize: avatarSize, isExpanded: isExpanded, isSettings: self.isSettings) } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { @@ -1996,6 +2034,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { private let isOpenedFromChat: Bool private let isSettings: Bool private let videoCallsEnabled: Bool + private let forumTopicThreadId: Int64? private(set) var isAvatarExpanded: Bool var skipCollapseCompletion = false @@ -2056,12 +2095,13 @@ final class PeerInfoHeaderNode: ASDisplayNode { var emojiStatusPackDisposable = MetaDisposable() var emojiStatusFileAndPackTitle = Promise<(TelegramMediaFile, LoadedStickerPack)?>() - init(context: AccountContext, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, isMediaOnly: Bool, isSettings: Bool) { + init(context: AccountContext, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, isMediaOnly: Bool, isSettings: Bool, forumTopicThreadId: Int64?) { self.context = context self.isAvatarExpanded = avatarInitiallyExpanded self.isOpenedFromChat = isOpenedFromChat self.isSettings = isSettings self.videoCallsEnabled = true + self.forumTopicThreadId = forumTopicThreadId self.avatarListNode = PeerInfoAvatarListNode(context: context, readyWhenGalleryLoads: avatarInitiallyExpanded, isSettings: isSettings) @@ -2304,7 +2344,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { private var currentCredibilityIcon: CredibilityIcon? private var currentPanelStatusData: PeerInfoStatusData? - func update(width: CGFloat, containerHeight: CGFloat, containerInset: CGFloat, statusBarHeight: CGFloat, navigationHeight: CGFloat, isModalOverlay: Bool, isMediaOnly: Bool, contentOffset: CGFloat, paneContainerY: CGFloat, presentationData: PresentationData, peer: Peer?, cachedData: CachedPeerData?, notificationSettings: TelegramPeerNotificationSettings?, statusData: PeerInfoStatusData?, panelStatusData: (PeerInfoStatusData?, PeerInfoStatusData?, CGFloat?), isSecretChat: Bool, isContact: Bool, isSettings: Bool, state: PeerInfoState, metrics: LayoutMetrics, transition: ContainedViewLayoutTransition, additive: Bool) -> CGFloat { + func update(width: CGFloat, containerHeight: CGFloat, containerInset: CGFloat, statusBarHeight: CGFloat, navigationHeight: CGFloat, isModalOverlay: Bool, isMediaOnly: Bool, contentOffset: CGFloat, paneContainerY: CGFloat, presentationData: PresentationData, peer: Peer?, cachedData: CachedPeerData?, threadInfo: EngineMessageHistoryThread.Info?, notificationSettings: TelegramPeerNotificationSettings?, statusData: PeerInfoStatusData?, panelStatusData: (PeerInfoStatusData?, PeerInfoStatusData?, CGFloat?), isSecretChat: Bool, isContact: Bool, isSettings: Bool, state: PeerInfoState, metrics: LayoutMetrics, transition: ContainedViewLayoutTransition, additive: Bool) -> CGFloat { self.state = state self.peer = peer self.avatarListNode.listContainerNode.peer = peer @@ -2527,7 +2567,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { let expandedAvatarListHeight = min(width, containerHeight - expandedAvatarControlsHeight) let expandedAvatarListSize = CGSize(width: width, height: expandedAvatarListHeight) - let buttonKeys: [PeerInfoHeaderButtonKey] = self.isSettings ? [] : peerInfoHeaderButtons(peer: peer, cachedData: cachedData, isOpenedFromChat: self.isOpenedFromChat, isExpanded: true, videoCallsEnabled: width > 320.0 && self.videoCallsEnabled, isSecretChat: isSecretChat, isContact: isContact) + let buttonKeys: [PeerInfoHeaderButtonKey] = self.isSettings ? [] : peerInfoHeaderButtons(peer: peer, cachedData: cachedData, isOpenedFromChat: self.isOpenedFromChat, isExpanded: true, videoCallsEnabled: width > 320.0 && self.videoCallsEnabled, isSecretChat: isSecretChat, isContact: isContact, threadInfo: threadInfo) var isPremium = false var isVerified = false @@ -2551,6 +2591,8 @@ final class PeerInfoHeaderNode: ASDisplayNode { title = presentationData.strings.Conversation_SavedMessages } else if peer.id == self.context.account.peerId && !self.isSettings { title = presentationData.strings.DialogList_Replies + } else if let threadInfo = threadInfo { + title = threadInfo.title } else { title = EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) } @@ -2577,6 +2619,34 @@ final class PeerInfoHeaderNode: ASDisplayNode { smallSubtitleString = NSAttributedString(string: subtitle, font: Font.regular(15.0), textColor: UIColor(rgb: 0xffffff, alpha: 0.7)) subtitleString = NSAttributedString(string: subtitle, font: Font.regular(17.0), textColor: presentationData.theme.list.itemSecondaryTextColor) usernameString = NSAttributedString(string: "", font: Font.regular(15.0), textColor: presentationData.theme.list.itemSecondaryTextColor) + } else if let _ = threadInfo { + let subtitleColor: UIColor = presentationData.theme.list.itemSecondaryTextColor + + //TODO:localize + var statusText = "in " + if let addressName = peer.addressName { + statusText += "@\(addressName)" + } else { + statusText += peer.debugDisplayTitle + } + + smallSubtitleString = NSAttributedString(string: statusText, font: Font.regular(15.0), textColor: UIColor(rgb: 0xffffff, alpha: 0.7)) + subtitleString = NSAttributedString(string: statusText, font: Font.regular(17.0), textColor: subtitleColor) + usernameString = NSAttributedString(string: "", font: Font.regular(15.0), textColor: presentationData.theme.list.itemSecondaryTextColor) + + let (maybePanelStatusData, maybeNextPanelStatusData, _) = panelStatusData + if let panelStatusData = maybePanelStatusData { + let subtitleColor: UIColor + if panelStatusData.isActivity { + subtitleColor = presentationData.theme.list.itemAccentColor + } else { + subtitleColor = presentationData.theme.list.itemSecondaryTextColor + } + panelSubtitleString = NSAttributedString(string: panelStatusData.text, font: Font.regular(17.0), textColor: subtitleColor) + } + if let nextPanelStatusData = maybeNextPanelStatusData { + nextPanelSubtitleString = NSAttributedString(string: nextPanelStatusData.text, font: Font.regular(17.0), textColor: presentationData.theme.list.itemSecondaryTextColor) + } } else if let statusData = statusData { let subtitleColor: UIColor if statusData.isActivity { @@ -2839,7 +2909,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { }) } - self.avatarListNode.update(size: CGSize(), avatarSize: avatarSize, isExpanded: self.isAvatarExpanded, peer: peer, theme: presentationData.theme, transition: transition) + self.avatarListNode.update(size: CGSize(), avatarSize: avatarSize, isExpanded: self.isAvatarExpanded, peer: peer, threadInfo: threadInfo, theme: presentationData.theme, transition: transition) self.editingContentNode.avatarNode.update(peer: peer, item: self.avatarListNode.item, updatingAvatar: state.updatingAvatar, uploadProgress: state.avatarUploadProgress, theme: presentationData.theme, avatarSize: avatarSize, isEditing: state.isEditing) self.avatarOverlayNode.update(peer: peer, item: self.avatarListNode.item, updatingAvatar: state.updatingAvatar, uploadProgress: state.avatarUploadProgress, theme: presentationData.theme, avatarSize: avatarSize, isEditing: state.isEditing) if additive { diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoPaneContainerNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoPaneContainerNode.swift index 3ad85bb14e..ed2435ca9a 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoPaneContainerNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoPaneContainerNode.swift @@ -386,6 +386,8 @@ private final class PeerInfoPendingPane { openAddMemberAction: @escaping () -> Void, requestPerformPeerMemberAction: @escaping (PeerInfoMember, PeerMembersListAction) -> Void, peerId: PeerId, + chatLocation: ChatLocation, + chatLocationContextHolder: Atomic, key: PeerInfoPaneKey, hasBecomeReady: @escaping (PeerInfoPaneKey) -> Void, parentController: ViewController?, @@ -396,7 +398,7 @@ private final class PeerInfoPendingPane { let paneNode: PeerInfoPaneNode switch key { case .media: - let visualPaneNode = PeerInfoVisualMediaPaneNode(context: context, chatControllerInteraction: chatControllerInteraction, peerId: peerId, contentType: .photoOrVideo, captureProtected: captureProtected) + let visualPaneNode = PeerInfoVisualMediaPaneNode(context: context, chatControllerInteraction: chatControllerInteraction, peerId: peerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, contentType: .photoOrVideo, captureProtected: captureProtected) paneNode = visualPaneNode visualPaneNode.openCurrentDate = { openMediaCalendar() @@ -405,21 +407,19 @@ private final class PeerInfoPendingPane { paneDidScroll() } case .files: - let visualPaneNode = PeerInfoVisualMediaPaneNode(context: context, chatControllerInteraction: chatControllerInteraction, peerId: peerId, contentType: .files, captureProtected: captureProtected) + let visualPaneNode = PeerInfoVisualMediaPaneNode(context: context, chatControllerInteraction: chatControllerInteraction, peerId: peerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, contentType: .files, captureProtected: captureProtected) paneNode = visualPaneNode //paneNode = PeerInfoListPaneNode(context: context, updatedPresentationData: updatedPresentationData, chatControllerInteraction: chatControllerInteraction, peerId: peerId, tagMask: .file) case .links: - paneNode = PeerInfoListPaneNode(context: context, updatedPresentationData: updatedPresentationData, chatControllerInteraction: chatControllerInteraction, peerId: peerId, tagMask: .webPage) + paneNode = PeerInfoListPaneNode(context: context, updatedPresentationData: updatedPresentationData, chatControllerInteraction: chatControllerInteraction, peerId: peerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, tagMask: .webPage) case .voice: - let visualPaneNode = PeerInfoVisualMediaPaneNode(context: context, chatControllerInteraction: chatControllerInteraction, peerId: peerId, contentType: .voiceAndVideoMessages, captureProtected: captureProtected) + let visualPaneNode = PeerInfoVisualMediaPaneNode(context: context, chatControllerInteraction: chatControllerInteraction, peerId: peerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, contentType: .voiceAndVideoMessages, captureProtected: captureProtected) paneNode = visualPaneNode - //paneNode = PeerInfoListPaneNode(context: context, updatedPresentationData: updatedPresentationData, chatControllerInteraction: chatControllerInteraction, peerId: peerId, tagMask: .voiceOrInstantVideo) case .music: - let visualPaneNode = PeerInfoVisualMediaPaneNode(context: context, chatControllerInteraction: chatControllerInteraction, peerId: peerId, contentType: .music, captureProtected: captureProtected) + let visualPaneNode = PeerInfoVisualMediaPaneNode(context: context, chatControllerInteraction: chatControllerInteraction, peerId: peerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, contentType: .music, captureProtected: captureProtected) paneNode = visualPaneNode - //paneNode = PeerInfoListPaneNode(context: context, updatedPresentationData: updatedPresentationData, chatControllerInteraction: chatControllerInteraction, peerId: peerId, tagMask: .music) case .gifs: - let visualPaneNode = PeerInfoGifPaneNode(context: context, chatControllerInteraction: chatControllerInteraction, peerId: peerId, contentType: .gifs) + let visualPaneNode = PeerInfoGifPaneNode(context: context, chatControllerInteraction: chatControllerInteraction, peerId: peerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, contentType: .gifs) paneNode = visualPaneNode case .groupsInCommon: paneNode = PeerInfoGroupsInCommonPaneNode(context: context, peerId: peerId, chatControllerInteraction: chatControllerInteraction, openPeerContextAction: openPeerContextAction, groupsInCommonContext: data.groupsInCommon!) @@ -452,6 +452,8 @@ private final class PeerInfoPendingPane { final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { private let context: AccountContext private let peerId: PeerId + private let chatLocation: ChatLocation + private let chatLocationContextHolder: Atomic private let isMediaOnly: Bool weak var parentController: ViewController? @@ -509,10 +511,12 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat private var currentAvailablePanes: [PeerInfoPaneKey]? private let updatedPresentationData: (initial: PresentationData, signal: Signal)? - init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, peerId: PeerId, isMediaOnly: Bool) { + init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, peerId: PeerId, chatLocation: ChatLocation, chatLocationContextHolder: Atomic, isMediaOnly: Bool) { self.context = context self.updatedPresentationData = updatedPresentationData self.peerId = peerId + self.chatLocation = chatLocation + self.chatLocationContextHolder = chatLocationContextHolder self.isMediaOnly = isMediaOnly self.additionalBackgroundNode = ASDisplayNode() @@ -807,6 +811,8 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat self?.requestPerformPeerMemberAction?(member, action) }, peerId: self.peerId, + chatLocation: self.chatLocation, + chatLocationContextHolder: self.chatLocationContextHolder, key: key, hasBecomeReady: { [weak self] key in let apply: () -> Void = { diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 99e753f888..50f18bb84d 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -77,6 +77,7 @@ import AvatarNode import ComponentFlow import EmojiStatusComponent import ChatTitleView +import ForumCreateTopicScreen protocol PeerInfoScreenItem: AnyObject { var id: AnyHashable { get } @@ -423,7 +424,7 @@ private enum PeerInfoMemberAction { private enum PeerInfoContextSubject { case bio case phone(String) - case link + case link(customLink: String?) } private enum PeerInfoSettingsSection { @@ -915,7 +916,7 @@ private func settingsEditingItems(data: PeerInfoScreenData?, state: PeerInfoStat return result } -private func infoItems(data: PeerInfoScreenData?, context: AccountContext, presentationData: PresentationData, interaction: PeerInfoInteraction, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message]) -> [(AnyHashable, [PeerInfoScreenItem])] { +private func infoItems(data: PeerInfoScreenData?, context: AccountContext, presentationData: PresentationData, interaction: PeerInfoInteraction, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message], chatLocation: ChatLocation) -> [(AnyHashable, [PeerInfoScreenItem])] { guard let data = data else { return [] } @@ -973,7 +974,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese action: { _ in interaction.openUsername(username) }, longTapAction: { sourceNode in - interaction.openPeerInfoContextMenu(.link, sourceNode) + interaction.openPeerInfoContextMenu(.link(customLink: nil), sourceNode) }, linkItemAction: { type, item in if case .tap = type { if case let .mention(username) = item { @@ -1077,42 +1078,32 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese let ItemMembers = 6 let ItemMemberRequests = 7 - if let location = (data.cachedData as? CachedChannelData)?.peerGeoLocation { - items[.groupLocation]!.append(PeerInfoScreenHeaderItem(id: ItemLocationHeader, text: presentationData.strings.GroupInfo_Location.uppercased())) - - let imageSignal = chatMapSnapshotImage(engine: context.engine, resource: MapSnapshotMediaResource(latitude: location.latitude, longitude: location.longitude, width: 90, height: 90)) - items[.groupLocation]!.append(PeerInfoScreenAddressItem( - id: ItemLocation, - label: "", - text: location.address.replacingOccurrences(of: ", ", with: "\n"), - imageSignal: imageSignal, - action: { - interaction.openLocation() - } - )) - } - - if let username = channel.username { - var additionalUsernames: String? - let mainUsername = channel.usernames.first?.username ?? username - - let usernames = channel.usernames.filter { $0.flags.contains(.isActive) && $0.username != mainUsername } - if !usernames.isEmpty { - additionalUsernames = presentationData.strings.Profile_AdditionalUsernames(String(usernames.map { "@\($0.username)" }.joined(separator: ", "))).string + if let _ = data.threadInfo { + let mainUsername: String + if let addressName = channel.addressName { + mainUsername = addressName + } else { + mainUsername = "c/\(channel.id.id._internalGetInt64Value())" } + var threadId: Int64 = 0 + if case let .replyThread(message) = chatLocation { + threadId = Int64(message.messageId.id) + } + + let linkText = "https://t.me/\(mainUsername)?topic=\(threadId)" + items[.peerInfo]!.append( PeerInfoScreenLabeledValueItem( id: ItemUsername, label: presentationData.strings.Channel_LinkItem, - text: "https://t.me/\(mainUsername)", - additionalText: additionalUsernames, + text: linkText, textColor: .accent, icon: .qrCode, action: { _ in - interaction.openUsername(username) + interaction.openUsername(linkText) }, longTapAction: { sourceNode in - interaction.openPeerInfoContextMenu(.link, sourceNode) + interaction.openPeerInfoContextMenu(.link(customLink: linkText), sourceNode) }, linkItemAction: { type, item in if case .tap = type { if case let .mention(username) = item { @@ -1126,58 +1117,109 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese } ) ) - } - if let cachedData = data.cachedData as? CachedChannelData { - let aboutText: String? - if channel.isFake { - if case .broadcast = channel.info { - aboutText = presentationData.strings.ChannelInfo_FakeChannelWarning - } else { - aboutText = presentationData.strings.GroupInfo_FakeGroupWarning - } - } else if channel.isScam { - if case .broadcast = channel.info { - aboutText = presentationData.strings.ChannelInfo_ScamChannelWarning - } else { - aboutText = presentationData.strings.GroupInfo_ScamGroupWarning - } - } else if let about = cachedData.about, !about.isEmpty { - aboutText = about - } else { - aboutText = nil + } else { + if let location = (data.cachedData as? CachedChannelData)?.peerGeoLocation { + items[.groupLocation]!.append(PeerInfoScreenHeaderItem(id: ItemLocationHeader, text: presentationData.strings.GroupInfo_Location.uppercased())) + + let imageSignal = chatMapSnapshotImage(engine: context.engine, resource: MapSnapshotMediaResource(latitude: location.latitude, longitude: location.longitude, width: 90, height: 90)) + items[.groupLocation]!.append(PeerInfoScreenAddressItem( + id: ItemLocation, + label: "", + text: location.address.replacingOccurrences(of: ", ", with: "\n"), + imageSignal: imageSignal, + action: { + interaction.openLocation() + } + )) } - if let aboutText = aboutText { - var enabledEntities = enabledPublicBioEntities - if case .group = channel.info { - enabledEntities = enabledPrivateBioEntities + if let username = channel.username { + var additionalUsernames: String? + let mainUsername = channel.usernames.first?.username ?? username + + let usernames = channel.usernames.filter { $0.flags.contains(.isActive) && $0.username != mainUsername } + if !usernames.isEmpty { + additionalUsernames = presentationData.strings.Profile_AdditionalUsernames(String(usernames.map { "@\($0.username)" }.joined(separator: ", "))).string } - items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: ItemAbout, label: presentationData.strings.Channel_Info_Description, text: aboutText, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledEntities), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction, requestLayout: { - interaction.requestLayout(true) - })) + + items[.peerInfo]!.append( + PeerInfoScreenLabeledValueItem( + id: ItemUsername, + label: presentationData.strings.Channel_LinkItem, + text: "https://t.me/\(mainUsername)", + additionalText: additionalUsernames, + textColor: .accent, + icon: .qrCode, + action: { _ in + interaction.openUsername(username) + }, longTapAction: { sourceNode in + interaction.openPeerInfoContextMenu(.link(customLink: nil), sourceNode) + }, linkItemAction: { type, item in + if case .tap = type { + if case let .mention(username) = item { + interaction.openUsername(String(username.suffix(from: username.index(username.startIndex, offsetBy: 1)))) + } + } + }, iconAction: { + interaction.openQrCode() + }, requestLayout: { + interaction.requestLayout(false) + } + ) + ) } - - if case .broadcast = channel.info { - var canEditMembers = false - if channel.hasPermission(.banMembers) { - canEditMembers = true + if let cachedData = data.cachedData as? CachedChannelData { + let aboutText: String? + if channel.isFake { + if case .broadcast = channel.info { + aboutText = presentationData.strings.ChannelInfo_FakeChannelWarning + } else { + aboutText = presentationData.strings.GroupInfo_FakeGroupWarning + } + } else if channel.isScam { + if case .broadcast = channel.info { + aboutText = presentationData.strings.ChannelInfo_ScamChannelWarning + } else { + aboutText = presentationData.strings.GroupInfo_ScamGroupWarning + } + } else if let about = cachedData.about, !about.isEmpty { + aboutText = about + } else { + aboutText = nil } - if canEditMembers { - if channel.adminRights != nil || channel.flags.contains(.isCreator) { - let adminCount = cachedData.participantsSummary.adminCount ?? 0 - let memberCount = cachedData.participantsSummary.memberCount ?? 0 - - items[.peerMembers]!.append(PeerInfoScreenDisclosureItem(id: ItemAdmins, label: .text("\(adminCount == 0 ? "" : "\(presentationStringsFormattedNumber(adminCount, presentationData.dateTimeFormat.groupingSeparator))")"), text: presentationData.strings.GroupInfo_Administrators, icon: UIImage(bundleImageName: "Chat/Info/GroupAdminsIcon"), action: { - interaction.openParticipantsSection(.admins) - })) - items[.peerMembers]!.append(PeerInfoScreenDisclosureItem(id: ItemMembers, label: .text("\(memberCount == 0 ? "" : "\(presentationStringsFormattedNumber(memberCount, presentationData.dateTimeFormat.groupingSeparator))")"), text: presentationData.strings.Channel_Info_Subscribers, icon: UIImage(bundleImageName: "Chat/Info/GroupMembersIcon"), action: { - interaction.openParticipantsSection(.members) - })) - - if let count = data.requests?.count, count > 0 { - items[.peerMembers]!.append(PeerInfoScreenDisclosureItem(id: ItemMemberRequests, label: .badge(presentationStringsFormattedNumber(count, presentationData.dateTimeFormat.groupingSeparator), presentationData.theme.list.itemAccentColor), text: presentationData.strings.GroupInfo_MemberRequests, icon: UIImage(bundleImageName: "Chat/Info/GroupRequestsIcon"), action: { - interaction.openParticipantsSection(.memberRequests) + + if let aboutText = aboutText { + var enabledEntities = enabledPublicBioEntities + if case .group = channel.info { + enabledEntities = enabledPrivateBioEntities + } + items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: ItemAbout, label: presentationData.strings.Channel_Info_Description, text: aboutText, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledEntities), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction, requestLayout: { + interaction.requestLayout(true) + })) + } + + if case .broadcast = channel.info { + var canEditMembers = false + if channel.hasPermission(.banMembers) { + canEditMembers = true + } + if canEditMembers { + if channel.adminRights != nil || channel.flags.contains(.isCreator) { + let adminCount = cachedData.participantsSummary.adminCount ?? 0 + let memberCount = cachedData.participantsSummary.memberCount ?? 0 + + items[.peerMembers]!.append(PeerInfoScreenDisclosureItem(id: ItemAdmins, label: .text("\(adminCount == 0 ? "" : "\(presentationStringsFormattedNumber(adminCount, presentationData.dateTimeFormat.groupingSeparator))")"), text: presentationData.strings.GroupInfo_Administrators, icon: UIImage(bundleImageName: "Chat/Info/GroupAdminsIcon"), action: { + interaction.openParticipantsSection(.admins) })) + items[.peerMembers]!.append(PeerInfoScreenDisclosureItem(id: ItemMembers, label: .text("\(memberCount == 0 ? "" : "\(presentationStringsFormattedNumber(memberCount, presentationData.dateTimeFormat.groupingSeparator))")"), text: presentationData.strings.Channel_Info_Subscribers, icon: UIImage(bundleImageName: "Chat/Info/GroupMembersIcon"), action: { + interaction.openParticipantsSection(.members) + })) + + if let count = data.requests?.count, count > 0 { + items[.peerMembers]!.append(PeerInfoScreenDisclosureItem(id: ItemMemberRequests, label: .badge(presentationStringsFormattedNumber(count, presentationData.dateTimeFormat.groupingSeparator), presentationData.theme.list.itemAccentColor), text: presentationData.strings.GroupInfo_MemberRequests, icon: UIImage(bundleImageName: "Chat/Info/GroupRequestsIcon"), action: { + interaction.openParticipantsSection(.memberRequests) + })) + } } } } @@ -1740,6 +1782,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate private let isOpenedFromChat: Bool private let videoCallsEnabled: Bool private let callMessages: [Message] + private let chatLocation: ChatLocation + private let chatLocationContextHolder: Atomic let isSettings: Bool private let isMediaOnly: Bool @@ -1834,7 +1878,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate } private var didSetReady = false - init(controller: PeerInfoScreenImpl, context: AccountContext, peerId: PeerId, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message], isSettings: Bool, hintGroupInCommon: PeerId?, requestsContext: PeerInvitationImportersContext?) { + init(controller: PeerInfoScreenImpl, context: AccountContext, peerId: PeerId, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message], isSettings: Bool, hintGroupInCommon: PeerId?, requestsContext: PeerInvitationImportersContext?, chatLocation: ChatLocation, chatLocationContextHolder: Atomic) { self.controller = controller self.context = context self.peerId = peerId @@ -1845,14 +1889,20 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate self.reactionSourceMessageId = reactionSourceMessageId self.callMessages = callMessages self.isSettings = isSettings + self.chatLocation = chatLocation + self.chatLocationContextHolder = chatLocationContextHolder self.isMediaOnly = context.account.peerId == peerId && !isSettings self.scrollNode = ASScrollNode() self.scrollNode.view.delaysContentTouches = false self.scrollNode.canCancelAllTouchesInViews = true - self.headerNode = PeerInfoHeaderNode(context: context, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, isMediaOnly: self.isMediaOnly, isSettings: isSettings) - self.paneContainerNode = PeerInfoPaneContainerNode(context: context, updatedPresentationData: controller.updatedPresentationData, peerId: peerId, isMediaOnly: self.isMediaOnly) + var forumTopicThreadId: Int64? + if case let .replyThread(message) = chatLocation { + forumTopicThreadId = Int64(message.messageId.id) + } + self.headerNode = PeerInfoHeaderNode(context: context, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, isMediaOnly: self.isMediaOnly, isSettings: isSettings, forumTopicThreadId: forumTopicThreadId) + self.paneContainerNode = PeerInfoPaneContainerNode(context: context, updatedPresentationData: controller.updatedPresentationData, peerId: peerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, isMediaOnly: self.isMediaOnly) super.init() @@ -2765,36 +2815,52 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate } switch key { case .edit: - (strongSelf.controller?.parent as? TabBarController)?.updateIsTabBarHidden(true, transition: .animated(duration: 0.3, curve: .linear)) - strongSelf.state = strongSelf.state.withIsEditing(true) - var updateOnCompletion = false - if strongSelf.headerNode.isAvatarExpanded { - updateOnCompletion = true - strongSelf.headerNode.skipCollapseCompletion = true - strongSelf.headerNode.avatarListNode.avatarContainerNode.canAttachVideo = false - strongSelf.headerNode.editingContentNode.avatarNode.canAttachVideo = false - strongSelf.headerNode.avatarListNode.listContainerNode.isCollapsing = true - strongSelf.headerNode.updateIsAvatarExpanded(false, transition: .immediate) - strongSelf.updateNavigationExpansionPresentation(isExpanded: false, animated: true) - } - if let (layout, navigationHeight) = strongSelf.validLayout { - strongSelf.scrollNode.view.setContentOffset(CGPoint(), animated: false) - strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) - } - UIView.transition(with: strongSelf.view, duration: 0.3, options: [.transitionCrossDissolve], animations: { - }, completion: { _ in - if updateOnCompletion { - strongSelf.headerNode.skipCollapseCompletion = false - strongSelf.headerNode.avatarListNode.listContainerNode.isCollapsing = false - strongSelf.headerNode.avatarListNode.avatarContainerNode.canAttachVideo = true - strongSelf.headerNode.editingContentNode.avatarNode.canAttachVideo = true - strongSelf.headerNode.editingContentNode.avatarNode.reset() - if let (layout, navigationHeight) = strongSelf.validLayout { - strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) + if case let .replyThread(message) = strongSelf.chatLocation { + let threadId = Int64(message.messageId.id) + if let threadInfo = strongSelf.data?.threadInfo { + let controller = ForumCreateTopicScreen(context: strongSelf.context, peerId: strongSelf.peerId, mode: .edit(topic: threadInfo)) + controller.navigationPresentation = .modal + let context = strongSelf.context + controller.completion = { [weak controller] title, fileId in + let _ = (context.engine.peers.editForumChannelTopic(id: peerId, threadId: threadId, title: title, iconFileId: fileId) + |> deliverOnMainQueue).start(completed: { + controller?.dismiss() + }) } + strongSelf.controller?.push(controller) } - }) - strongSelf.controller?.navigationItem.setLeftBarButton(UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, style: .plain, target: strongSelf, action: #selector(strongSelf.editingCancelPressed)), animated: true) + } else { + (strongSelf.controller?.parent as? TabBarController)?.updateIsTabBarHidden(true, transition: .animated(duration: 0.3, curve: .linear)) + strongSelf.state = strongSelf.state.withIsEditing(true) + var updateOnCompletion = false + if strongSelf.headerNode.isAvatarExpanded { + updateOnCompletion = true + strongSelf.headerNode.skipCollapseCompletion = true + strongSelf.headerNode.avatarListNode.avatarContainerNode.canAttachVideo = false + strongSelf.headerNode.editingContentNode.avatarNode.canAttachVideo = false + strongSelf.headerNode.avatarListNode.listContainerNode.isCollapsing = true + strongSelf.headerNode.updateIsAvatarExpanded(false, transition: .immediate) + strongSelf.updateNavigationExpansionPresentation(isExpanded: false, animated: true) + } + if let (layout, navigationHeight) = strongSelf.validLayout { + strongSelf.scrollNode.view.setContentOffset(CGPoint(), animated: false) + strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) + } + UIView.transition(with: strongSelf.view, duration: 0.3, options: [.transitionCrossDissolve], animations: { + }, completion: { _ in + if updateOnCompletion { + strongSelf.headerNode.skipCollapseCompletion = false + strongSelf.headerNode.avatarListNode.listContainerNode.isCollapsing = false + strongSelf.headerNode.avatarListNode.avatarContainerNode.canAttachVideo = true + strongSelf.headerNode.editingContentNode.avatarNode.canAttachVideo = true + strongSelf.headerNode.editingContentNode.avatarNode.reset() + if let (layout, navigationHeight) = strongSelf.validLayout { + strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) + } + } + }) + strongSelf.controller?.navigationItem.setLeftBarButton(UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, style: .plain, target: strongSelf, action: #selector(strongSelf.editingCancelPressed)), animated: true) + } case .done, .cancel: strongSelf.view.endEditing(true) if case .done = key { @@ -3207,7 +3273,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate strongSelf.controller?.present(emojiStatusSelectionController, in: .window(.root)) } } else { - screenData = peerInfoScreenData(context: context, peerId: peerId, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, isSettings: self.isSettings, hintGroupInCommon: hintGroupInCommon, existingRequestsContext: requestsContext) + screenData = peerInfoScreenData(context: context, peerId: peerId, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, isSettings: self.isSettings, hintGroupInCommon: hintGroupInCommon, existingRequestsContext: requestsContext, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder) self.headerNode.displayPremiumIntro = { [weak self] sourceView, peerStatus, emojiStatusFileAndPack, white in guard let strongSelf = self else { @@ -4085,8 +4151,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate return .single(items) } - let allHeaderButtons = Set(peerInfoHeaderButtons(peer: peer, cachedData: data.cachedData, isOpenedFromChat: strongSelf.isOpenedFromChat, isExpanded: false, videoCallsEnabled: strongSelf.videoCallsEnabled, isSecretChat: strongSelf.peerId.namespace == Namespaces.Peer.SecretChat, isContact: strongSelf.data?.isContact ?? false)) - let headerButtons = Set(peerInfoHeaderButtons(peer: peer, cachedData: data.cachedData, isOpenedFromChat: strongSelf.isOpenedFromChat, isExpanded: true, videoCallsEnabled: strongSelf.videoCallsEnabled, isSecretChat: strongSelf.peerId.namespace == Namespaces.Peer.SecretChat, isContact: strongSelf.data?.isContact ?? false)) + let allHeaderButtons = Set(peerInfoHeaderButtons(peer: peer, cachedData: data.cachedData, isOpenedFromChat: strongSelf.isOpenedFromChat, isExpanded: false, videoCallsEnabled: strongSelf.videoCallsEnabled, isSecretChat: strongSelf.peerId.namespace == Namespaces.Peer.SecretChat, isContact: strongSelf.data?.isContact ?? false, threadInfo: data.threadInfo)) + let headerButtons = Set(peerInfoHeaderButtons(peer: peer, cachedData: data.cachedData, isOpenedFromChat: strongSelf.isOpenedFromChat, isExpanded: true, videoCallsEnabled: strongSelf.videoCallsEnabled, isSecretChat: strongSelf.peerId.namespace == Namespaces.Peer.SecretChat, isContact: strongSelf.data?.isContact ?? false, threadInfo: strongSelf.data?.threadInfo)) let filteredButtons = allHeaderButtons.subtracting(headerButtons) @@ -4538,18 +4604,32 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate private func openChatWithMessageSearch() { if let navigationController = (self.controller?.navigationController as? NavigationController) { - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(id: self.peerId), keepStack: self.nearbyPeerDistance != nil ? .always : .default, activateMessageSearch: (.everything, ""), peerNearbyData: self.nearbyPeerDistance.flatMap({ ChatPeerNearbyData(distance: $0) }), completion: { [weak self] _ in - if let strongSelf = self, strongSelf.nearbyPeerDistance != nil { - var viewControllers = navigationController.viewControllers - viewControllers = viewControllers.filter { controller in - if controller is PeerInfoScreen { - return false - } - return true - } - navigationController.setViewControllers(viewControllers, animated: false) + if case let .replyThread(currentMessage) = self.chatLocation, let current = navigationController.viewControllers.first(where: { controller in + if let controller = controller as? ChatControllerImpl, case let .replyThread(message) = controller.chatLocation, message.messageId == currentMessage.messageId { + return true } - })) + return false + }) as? ChatControllerImpl { + var viewControllers = navigationController.viewControllers + if let index = viewControllers.firstIndex(of: current) { + viewControllers.removeSubrange(index + 1 ..< viewControllers.count) + } + navigationController.setViewControllers(viewControllers, animated: true) + current.activateSearch() + } else { + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(id: self.peerId), keepStack: self.nearbyPeerDistance != nil ? .always : .default, activateMessageSearch: (.everything, ""), peerNearbyData: self.nearbyPeerDistance.flatMap({ ChatPeerNearbyData(distance: $0) }), completion: { [weak self] _ in + if let strongSelf = self, strongSelf.nearbyPeerDistance != nil { + var viewControllers = navigationController.viewControllers + viewControllers = viewControllers.filter { controller in + if controller is PeerInfoScreen { + return false + } + return true + } + navigationController.setViewControllers(viewControllers, animated: false) + } + })) + } } } @@ -4779,7 +4859,14 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate } private func openUsername(value: String) { - let shareController = ShareController(context: self.context, subject: .url("https://t.me/\(value)"), updatedPresentationData: self.controller?.updatedPresentationData) + let url: String + if value.hasPrefix("https://") { + url = value + } else { + url = "https://t.me/\(value)" + } + + let shareController = ShareController(context: self.context, subject: .url(url), updatedPresentationData: self.controller?.updatedPresentationData) shareController.completed = { [weak self] peerIds in guard let strongSelf = self else { return @@ -5940,10 +6027,13 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate return nil } })) - case .link: - if let addressName = peer.addressName { - let text: String - let content: UndoOverlayContent + case let .link(customLink): + let text: String + let content: UndoOverlayContent + if let customLink = customLink { + text = customLink + content = .linkCopied(text: self.presentationData.strings.Conversation_LinkCopied) + } else if let addressName = peer.addressName { if peer is TelegramChannel { text = "https://t.me/\(addressName)" content = .linkCopied(text: self.presentationData.strings.Conversation_LinkCopied) @@ -5951,20 +6041,24 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate text = "@" + addressName content = .copy(text: self.presentationData.strings.Conversation_UsernameCopied) } - let contextMenuController = ContextMenuController(actions: [ContextMenuAction(content: .text(title: self.presentationData.strings.Conversation_ContextMenuCopy, accessibilityLabel: self.presentationData.strings.Conversation_ContextMenuCopy), action: { [weak self] in - UIPasteboard.general.string = text - - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - self?.controller?.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) - })]) - controller.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self, weak sourceNode] in - if let controller = self?.controller, let sourceNode = sourceNode { - return (sourceNode, sourceNode.bounds.insetBy(dx: 0.0, dy: 2.0), controller.displayNode, controller.view.bounds) - } else { - return nil - } - })) + } else { + text = "https://t.me/@id\(peer.id.id._internalGetInt64Value())" + content = .linkCopied(text: self.presentationData.strings.Conversation_LinkCopied) } + + let contextMenuController = ContextMenuController(actions: [ContextMenuAction(content: .text(title: self.presentationData.strings.Conversation_ContextMenuCopy, accessibilityLabel: self.presentationData.strings.Conversation_ContextMenuCopy), action: { [weak self] in + UIPasteboard.general.string = text + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + self?.controller?.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + })]) + controller.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self, weak sourceNode] in + if let controller = self?.controller, let sourceNode = sourceNode { + return (sourceNode, sourceNode.bounds.insetBy(dx: 0.0, dy: 2.0), controller.displayNode, controller.view.bounds) + } else { + return nil + } + })) } } @@ -6459,8 +6553,13 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate guard let data = self.data, let peer = data.peer, let controller = self.controller else { return } + + var threadId: Int64 = 0 + if case let .replyThread(message) = self.chatLocation { + threadId = Int64(message.messageId.id) + } - controller.present(ChatQrCodeScreen(context: self.context, subject: .peer(peer)), in: .window(.root)) + controller.present(ChatQrCodeScreen(context: self.context, subject: .peer(peer: peer, threadId: threadId)), in: .window(.root)) } fileprivate func openSettings(section: PeerInfoSettingsSection) { @@ -7428,7 +7527,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate } let headerInset = sectionInset - var headerHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : self.scrollNode.view.contentOffset.y, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.peer, cachedData: self.data?.cachedData, notificationSettings: self.data?.notificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, metrics: layout.metrics, transition: transition, additive: additive) + var headerHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : self.scrollNode.view.contentOffset.y, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.peer, cachedData: self.data?.cachedData, threadInfo: self.data?.threadInfo, notificationSettings: self.data?.notificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, metrics: layout.metrics, transition: transition, additive: additive) if !self.isSettings && !self.state.isEditing { headerHeight += 71.0 } @@ -7448,7 +7547,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate insets.left += sectionInset insets.right += sectionInset - let items = self.isSettings ? settingsItems(data: self.data, context: self.context, presentationData: self.presentationData, interaction: self.interaction, isExpanded: self.headerNode.isAvatarExpanded) : infoItems(data: self.data, context: self.context, presentationData: self.presentationData, interaction: self.interaction, nearbyPeerDistance: self.nearbyPeerDistance, reactionSourceMessageId: self.reactionSourceMessageId, callMessages: self.callMessages) + let items = self.isSettings ? settingsItems(data: self.data, context: self.context, presentationData: self.presentationData, interaction: self.interaction, isExpanded: self.headerNode.isAvatarExpanded) : infoItems(data: self.data, context: self.context, presentationData: self.presentationData, interaction: self.interaction, nearbyPeerDistance: self.nearbyPeerDistance, reactionSourceMessageId: self.reactionSourceMessageId, callMessages: self.callMessages, chatLocation: self.chatLocation) contentHeight += headerHeight if !(self.isSettings && self.state.isEditing) { @@ -7788,7 +7887,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate } let headerInset = sectionInset - let _ = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : offsetY, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.peer, cachedData: self.data?.cachedData, notificationSettings: self.data?.notificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, metrics: layout.metrics, transition: transition, additive: additive) + let _ = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : offsetY, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.peer, cachedData: self.data?.cachedData, threadInfo: self.data?.threadInfo, notificationSettings: self.data?.notificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, metrics: layout.metrics, transition: transition, additive: additive) } let paneAreaExpansionDistance: CGFloat = 32.0 @@ -8068,6 +8167,8 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc private let isSettings: Bool private let hintGroupInCommon: PeerId? private weak var requestsContext: PeerInvitationImportersContext? + private let chatLocation: ChatLocation + private let chatLocationContextHolder = Atomic(value: nil) fileprivate var presentationData: PresentationData private var presentationDataDisposable: Disposable? @@ -8102,7 +8203,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc private var validLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)? - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, peerId: PeerId, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message], isSettings: Bool = false, hintGroupInCommon: PeerId? = nil, requestsContext: PeerInvitationImportersContext? = nil) { + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, peerId: PeerId, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message], isSettings: Bool = false, hintGroupInCommon: PeerId? = nil, requestsContext: PeerInvitationImportersContext? = nil, forumTopicThread: ChatReplyThreadMessage? = nil) { self.context = context self.updatedPresentationData = updatedPresentationData self.peerId = peerId @@ -8115,6 +8216,12 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc self.hintGroupInCommon = hintGroupInCommon self.requestsContext = requestsContext + if let forumTopicThread = forumTopicThread { + self.chatLocation = .replyThread(message: forumTopicThread) + } else { + self.chatLocation = .peer(id: peerId) + } + self.presentationData = updatedPresentationData?.0 ?? context.sharedContext.currentPresentationData.with { $0 } let baseNavigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData) @@ -8298,33 +8405,35 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) } - - self.navigationBar?.makeCustomTransitionNode = { [weak self] other, isInteractive in - guard let strongSelf = self else { + + if case .peer = self.chatLocation { + self.navigationBar?.makeCustomTransitionNode = { [weak self] other, isInteractive in + guard let strongSelf = self else { + return nil + } + if strongSelf.navigationItem.leftBarButtonItem != nil { + return nil + } + if other.item?.leftBarButtonItem != nil { + return nil + } + if strongSelf.controllerNode.scrollNode.view.contentOffset.y > .ulpOfOne { + return nil + } + if isInteractive && strongSelf.controllerNode.headerNode.isAvatarExpanded { + return nil + } + if other.contentNode != nil { + return nil + } + if let allowsCustomTransition = other.allowsCustomTransition, !allowsCustomTransition() { + return nil + } + if let tag = other.userInfo as? PeerInfoNavigationSourceTag, tag.peerId == peerId { + return PeerInfoNavigationTransitionNode(screenNode: strongSelf.controllerNode, presentationData: strongSelf.presentationData, headerNode: strongSelf.controllerNode.headerNode) + } return nil } - if strongSelf.navigationItem.leftBarButtonItem != nil { - return nil - } - if other.item?.leftBarButtonItem != nil { - return nil - } - if strongSelf.controllerNode.scrollNode.view.contentOffset.y > .ulpOfOne { - return nil - } - if isInteractive && strongSelf.controllerNode.headerNode.isAvatarExpanded { - return nil - } - if other.contentNode != nil { - return nil - } - if let allowsCustomTransition = other.allowsCustomTransition, !allowsCustomTransition() { - return nil - } - if let tag = other.userInfo as? PeerInfoNavigationSourceTag, tag.peerId == peerId { - return PeerInfoNavigationTransitionNode(screenNode: strongSelf.controllerNode, presentationData: strongSelf.presentationData, headerNode: strongSelf.controllerNode.headerNode) - } - return nil } self.setStatusBarStyle(avatarInitiallyExpanded ? .White : self.presentationData.theme.rootController.statusBarStyle.style, animated: false) @@ -8396,7 +8505,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc } override public func loadDisplayNode() { - self.displayNode = PeerInfoScreenNode(controller: self, context: self.context, peerId: self.peerId, avatarInitiallyExpanded: self.avatarInitiallyExpanded, isOpenedFromChat: self.isOpenedFromChat, nearbyPeerDistance: self.nearbyPeerDistance, reactionSourceMessageId: self.reactionSourceMessageId, callMessages: self.callMessages, isSettings: self.isSettings, hintGroupInCommon: self.hintGroupInCommon, requestsContext: requestsContext) + self.displayNode = PeerInfoScreenNode(controller: self, context: self.context, peerId: self.peerId, avatarInitiallyExpanded: self.avatarInitiallyExpanded, isOpenedFromChat: self.isOpenedFromChat, nearbyPeerDistance: self.nearbyPeerDistance, reactionSourceMessageId: self.reactionSourceMessageId, callMessages: self.callMessages, isSettings: self.isSettings, hintGroupInCommon: self.hintGroupInCommon, requestsContext: self.requestsContext, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder) self.controllerNode.accountsAndPeers.set(self.accountsAndPeers.get() |> map { $0.1 }) self.controllerNode.activeSessionsContextAndCount.set(self.activeSessionsContextAndCount.get()) self.cachedDataPromise.set(self.controllerNode.cachedDataPromise.get()) @@ -8795,7 +8904,7 @@ private final class PeerInfoNavigationTransitionNode: ASDisplayNode, CustomNavig } let headerInset = sectionInset - topHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: topNavigationBar.bounds.height, isModalOverlay: layout.isModalOverlay, isMediaOnly: false, contentOffset: 0.0, paneContainerY: 0.0, presentationData: self.presentationData, peer: self.screenNode.data?.peer, cachedData: self.screenNode.data?.cachedData, notificationSettings: self.screenNode.data?.notificationSettings, statusData: self.screenNode.data?.status, panelStatusData: (nil, nil, nil), isSecretChat: self.screenNode.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.screenNode.data?.isContact ?? false, isSettings: self.screenNode.isSettings, state: self.screenNode.state, metrics: layout.metrics, transition: transition, additive: false) + topHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: topNavigationBar.bounds.height, isModalOverlay: layout.isModalOverlay, isMediaOnly: false, contentOffset: 0.0, paneContainerY: 0.0, presentationData: self.presentationData, peer: self.screenNode.data?.peer, cachedData: self.screenNode.data?.cachedData, threadInfo: self.screenNode.data?.threadInfo, notificationSettings: self.screenNode.data?.notificationSettings, statusData: self.screenNode.data?.status, panelStatusData: (nil, nil, nil), isSecretChat: self.screenNode.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.screenNode.data?.isContact ?? false, isSettings: self.screenNode.isSettings, state: self.screenNode.state, metrics: layout.metrics, transition: transition, additive: false) } let titleScale = (fraction * previousTitleNode.view.bounds.height + (1.0 - fraction) * self.headerNode.titleNodeRawContainer.bounds.height) / previousTitleNode.view.bounds.height diff --git a/submodules/TelegramUI/Sources/PeerInfoGifPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfoGifPaneNode.swift index e2d4f5c3f7..6006332083 100644 --- a/submodules/TelegramUI/Sources/PeerInfoGifPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfoGifPaneNode.swift @@ -761,6 +761,8 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDe private let context: AccountContext private let peerId: PeerId + private let chatLocation: ChatLocation + private let chatLocationContextHolder: Atomic private let chatControllerInteraction: ChatControllerInteraction private let contentType: ContentType @@ -809,9 +811,11 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDe return 0.0 } - init(context: AccountContext, chatControllerInteraction: ChatControllerInteraction, peerId: PeerId, contentType: ContentType) { + init(context: AccountContext, chatControllerInteraction: ChatControllerInteraction, peerId: PeerId, chatLocation: ChatLocation, chatLocationContextHolder: Atomic, contentType: ContentType) { self.context = context self.peerId = peerId + self.chatLocation = chatLocation + self.chatLocationContextHolder = chatLocationContextHolder self.chatControllerInteraction = chatControllerInteraction self.contentType = contentType diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 772723cd6c..1e9b1d2ba6 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -1468,8 +1468,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { return recentSessionsController(context: context, activeSessionsContext: activeSessionsContext, webSessionsContext: context.engine.privacy.webSessions(), websitesOnly: false) } - public func makeChatQrCodeScreen(context: AccountContext, peer: Peer) -> ViewController { - return ChatQrCodeScreen(context: context, subject: .peer(peer)) + public func makeChatQrCodeScreen(context: AccountContext, peer: Peer, threadId: Int64?) -> ViewController { + return ChatQrCodeScreen(context: context, subject: .peer(peer: peer, threadId: threadId)) } public func makePrivacyAndSecurityController(context: AccountContext) -> ViewController { @@ -1532,7 +1532,14 @@ private func peerInfoControllerImpl(context: AccountContext, updatedPresentation if let _ = peer as? TelegramGroup { return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nil, reactionSourceMessageId: nil, callMessages: []) } else if let _ = peer as? TelegramChannel { - return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nil, reactionSourceMessageId: nil, callMessages: []) + var forumTopicThread: ChatReplyThreadMessage? + switch mode { + case let .forumTopic(thread): + forumTopicThread = thread + default: + break + } + return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nil, reactionSourceMessageId: nil, callMessages: [], forumTopicThread: forumTopicThread) } else if peer is TelegramUser { var nearbyPeerDistance: Int32? var reactionSourceMessageId: MessageId? @@ -1549,6 +1556,8 @@ private func peerInfoControllerImpl(context: AccountContext, updatedPresentation hintGroupInCommon = id case let .reaction(messageId): reactionSourceMessageId = messageId + case .forumTopic: + break } return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nearbyPeerDistance, reactionSourceMessageId: reactionSourceMessageId, callMessages: callMessages, hintGroupInCommon: hintGroupInCommon) } else if peer is TelegramSecretChat { diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index 5c3cfa3552..5897291836 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -73,7 +73,12 @@ public enum ParsedInternalPeerUrlParameter { } public enum ParsedInternalUrl { - case peerName(String, ParsedInternalPeerUrlParameter?) + public enum UrlPeerReference { + case name(String) + case id(PeerId) + } + + case peer(UrlPeerReference, ParsedInternalPeerUrlParameter?) case peerId(PeerId) case privateMessage(messageId: MessageId, threadId: Int32?, timecode: Double?) case stickerPack(name: String, type: StickerPackUrlType) @@ -193,9 +198,9 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? { break } } - return .peerName(peerName, .attachBotStart(value, startAttach)) + return .peer(.name(peerName), .attachBotStart(value, startAttach)) } else if queryItem.name == "start" { - return .peerName(peerName, .botStart(value)) + return .peer(.name(peerName), .botStart(value)) } else if queryItem.name == "startgroup" { var botAdminRights: ResolvedBotAdminRights? for queryItem in queryItems { @@ -204,11 +209,11 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? { break } } - return .peerName(peerName, .groupBotStart(value, botAdminRights)) + return .peer(.name(peerName), .groupBotStart(value, botAdminRights)) } else if queryItem.name == "game" { return nil } else if ["voicechat", "videochat", "livestream"].contains(queryItem.name) { - return .peerName(peerName, .voiceChat(value)) + return .peer(.name(peerName), .voiceChat(value)) } else if queryItem.name == "startattach" { var choose: String? for queryItem in queryItems { @@ -220,7 +225,7 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? { return .startAttach(peerName, value, choose) } } else if ["voicechat", "videochat", "livestream"].contains(queryItem.name) { - return .peerName(peerName, .voiceChat(nil)) + return .peer(.name(peerName), .voiceChat(nil)) } else if queryItem.name == "startattach" { var choose: String? for queryItem in queryItems { @@ -238,7 +243,7 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? { break } } - return .peerName(peerName, .groupBotStart("", botAdminRights)) + return .peer(.name(peerName), .groupBotStart("", botAdminRights)) } } } @@ -269,7 +274,7 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? { let component = pathComponents[0].replacingOccurrences(of: "%24", with: "$") return .invoice(component) } - return .peerName(peerName, nil) + return .peer(.name(peerName), nil) } else if pathComponents.count == 2 || pathComponents.count == 3 { if pathComponents[0] == "addstickers" { return .stickerPack(name: pathComponents[1], type: .stickers) @@ -408,7 +413,7 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? { if let queryItems = components.queryItems { for queryItem in queryItems { if let value = queryItem.value { - if queryItem.name == "thread" { + if queryItem.name == "thread" || queryItem.name == "topic" { if let intValue = Int32(value) { threadId = intValue } @@ -424,6 +429,29 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? { } else { return nil } + } else if pathComponents.count == 2 && pathComponents[0] == "c" { + if let channelId = Int64(pathComponents[1]) { + var threadId: Int32? + if let queryItems = components.queryItems { + for queryItem in queryItems { + if let value = queryItem.value { + if queryItem.name == "thread" || queryItem.name == "topic" { + if let intValue = Int32(value) { + threadId = intValue + } + } + } + } + } + + if let threadId = threadId { + return .peer(.id(PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId))), .replyThread(threadId, threadId)) + } else { + return nil + } + } else { + return nil + } } else if let value = Int32(pathComponents[1]) { var threadId: Int32? var commentId: Int32? @@ -431,7 +459,7 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? { if let queryItems = components.queryItems { for queryItem in queryItems { if let value = queryItem.value { - if queryItem.name == "thread" { + if queryItem.name == "thread" || queryItem.name == "topic" { if let intValue = Int32(value) { threadId = intValue } @@ -448,11 +476,11 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? { } } if let threadId = threadId { - return .peerName(peerName, .replyThread(threadId, value)) + return .peer(.name(peerName), .replyThread(threadId, value)) } else if let commentId = commentId { - return .peerName(peerName, .replyThread(value, commentId)) + return .peer(.name(peerName), .replyThread(value, commentId)) } else { - return .peerName(peerName, .channelMessage(value, timecode)) + return .peer(.name(peerName), .channelMessage(value, timecode)) } } else { return nil @@ -489,12 +517,36 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) return .single(.peer(nil, .info)) } } - case let .peerName(name, parameter): - return context.engine.peers.resolvePeerByName(name: name) - |> take(1) - |> mapToSignal { peer -> Signal in - return .single(peer?._asPeer()) + case let .peer(reference, parameter): + let resolvedPeer: Signal + switch reference { + case let .name(name): + resolvedPeer = context.engine.peers.resolvePeerByName(name: name) + |> take(1) + |> mapToSignal { peer -> Signal in + return .single(peer?._asPeer()) + } + case let .id(id): + if id.namespace == Namespaces.Peer.CloudChannel { + resolvedPeer = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: id)) + |> mapToSignal { peer -> Signal in + let foundPeer: Signal + if let peer = peer { + foundPeer = .single(peer._asPeer()) + } else { + foundPeer = TelegramEngine(account: context.account).peers.findChannelById(channelId: id.id._internalGetInt64Value()) + |> map { peer -> Peer? in + return peer?._asPeer() + } + } + return foundPeer + } + } else { + resolvedPeer = .single(nil) + } } + + return resolvedPeer |> mapToSignal { peer -> Signal in if let peer = peer { if let parameter = parameter {