[WIP] Topics

This commit is contained in:
Ali 2022-10-22 22:15:33 +04:00
parent d9ab563c94
commit e0746fa0c2
19 changed files with 289 additions and 51 deletions

View File

@ -277,6 +277,7 @@ public enum ResolvedUrl {
case groupBotStart(peerId: PeerId, payload: String, adminRights: ResolvedBotAdminRights?) case groupBotStart(peerId: PeerId, payload: String, adminRights: ResolvedBotAdminRights?)
case channelMessage(peer: Peer, messageId: MessageId, timecode: Double?) case channelMessage(peer: Peer, messageId: MessageId, timecode: Double?)
case replyThreadMessage(replyThreadMessage: ChatReplyThreadMessage, messageId: MessageId) case replyThreadMessage(replyThreadMessage: ChatReplyThreadMessage, messageId: MessageId)
case replyThread(messageId: MessageId)
case stickerPack(name: String, type: StickerPackUrlType) case stickerPack(name: String, type: StickerPackUrlType)
case instantView(TelegramMediaWebpage, String?) case instantView(TelegramMediaWebpage, String?)
case proxy(host: String, port: Int32, username: String?, password: String?, secret: Data?) case proxy(host: String, port: Int32, username: String?, password: String?, secret: Data?)
@ -737,7 +738,7 @@ public protocol SharedAccountContext: AnyObject {
func makePrivacyAndSecurityController(context: AccountContext) -> ViewController func makePrivacyAndSecurityController(context: AccountContext) -> ViewController
func navigateToChatController(_ params: NavigateToChatControllerParams) func navigateToChatController(_ params: NavigateToChatControllerParams)
func navigateToForumChannel(context: AccountContext, peerId: EnginePeer.Id, navigationController: NavigationController) func navigateToForumChannel(context: AccountContext, peerId: EnginePeer.Id, navigationController: NavigationController)
func navigateToForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?) -> Signal<Never, NoError> func navigateToForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?, keepStack: NavigateToChatKeepStack) -> Signal<Never, NoError>
func chatControllerForForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64) -> Signal<ChatController, NoError> func chatControllerForForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64) -> Signal<ChatController, NoError>
func openStorageUsage(context: AccountContext) func openStorageUsage(context: AccountContext)
func openLocationScreen(context: AccountContext, messageId: MessageId, navigationController: NavigationController) func openLocationScreen(context: AccountContext, messageId: MessageId, navigationController: NavigationController)

View File

@ -31,8 +31,9 @@ public final class ChatMessageItemAssociatedData: Equatable {
public let isPremium: Bool public let isPremium: Bool
public let forceInlineReactions: Bool public let forceInlineReactions: Bool
public let accountPeer: EnginePeer? public let accountPeer: EnginePeer?
public let topicAuthorId: EnginePeer.Id?
public init(automaticDownloadPeerType: MediaAutoDownloadPeerType, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, isRecentActions: Bool = false, subject: ChatControllerSubject? = nil, contactsPeerIds: Set<EnginePeer.Id> = Set(), channelDiscussionGroup: ChannelDiscussionGroupStatus = .unknown, animatedEmojiStickers: [String: [StickerPackItem]] = [:], additionalAnimatedEmojiStickers: [String: [Int: StickerPackItem]] = [:], forcedResourceStatus: FileMediaResourceStatus? = nil, currentlyPlayingMessageId: EngineMessage.Index? = nil, isCopyProtectionEnabled: Bool = false, availableReactions: AvailableReactions?, defaultReaction: MessageReaction.Reaction?, isPremium: Bool, accountPeer: EnginePeer?, forceInlineReactions: Bool = false) { public init(automaticDownloadPeerType: MediaAutoDownloadPeerType, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, isRecentActions: Bool = false, subject: ChatControllerSubject? = nil, contactsPeerIds: Set<EnginePeer.Id> = Set(), channelDiscussionGroup: ChannelDiscussionGroupStatus = .unknown, animatedEmojiStickers: [String: [StickerPackItem]] = [:], additionalAnimatedEmojiStickers: [String: [Int: StickerPackItem]] = [:], forcedResourceStatus: FileMediaResourceStatus? = nil, currentlyPlayingMessageId: EngineMessage.Index? = nil, isCopyProtectionEnabled: Bool = false, availableReactions: AvailableReactions?, defaultReaction: MessageReaction.Reaction?, isPremium: Bool, accountPeer: EnginePeer?, forceInlineReactions: Bool = false, topicAuthorId: EnginePeer.Id? = nil) {
self.automaticDownloadPeerType = automaticDownloadPeerType self.automaticDownloadPeerType = automaticDownloadPeerType
self.automaticDownloadNetworkType = automaticDownloadNetworkType self.automaticDownloadNetworkType = automaticDownloadNetworkType
self.isRecentActions = isRecentActions self.isRecentActions = isRecentActions
@ -49,6 +50,7 @@ public final class ChatMessageItemAssociatedData: Equatable {
self.isPremium = isPremium self.isPremium = isPremium
self.accountPeer = accountPeer self.accountPeer = accountPeer
self.forceInlineReactions = forceInlineReactions self.forceInlineReactions = forceInlineReactions
self.topicAuthorId = topicAuthorId
} }
public static func == (lhs: ChatMessageItemAssociatedData, rhs: ChatMessageItemAssociatedData) -> Bool { public static func == (lhs: ChatMessageItemAssociatedData, rhs: ChatMessageItemAssociatedData) -> Bool {
@ -97,6 +99,9 @@ public final class ChatMessageItemAssociatedData: Equatable {
if lhs.forceInlineReactions != rhs.forceInlineReactions { if lhs.forceInlineReactions != rhs.forceInlineReactions {
return false return false
} }
if lhs.topicAuthorId != rhs.topicAuthorId {
return false
}
return true return true
} }
} }

View File

@ -1372,7 +1372,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
strongSelf.context.sharedContext.navigateToForumChannel(context: strongSelf.context, peerId: channel.id, navigationController: navigationController) strongSelf.context.sharedContext.navigateToForumChannel(context: strongSelf.context, peerId: channel.id, navigationController: navigationController)
} else { } else {
if let threadId = threadId { if let threadId = threadId {
let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, messageId: nil, navigationController: navigationController, activateInput: nil).start() let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, messageId: nil, navigationController: navigationController, activateInput: nil, keepStack: .never).start()
strongSelf.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true) strongSelf.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true)
} else { } else {
var navigationAnimationOptions: NavigationAnimationOptions = [] var navigationAnimationOptions: NavigationAnimationOptions = []
@ -1473,7 +1473,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
navigationAnimationOptions = .removeOnMasterDetails navigationAnimationOptions = .removeOnMasterDetails
} }
if let threadId = threadId { if let threadId = threadId {
let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, messageId: messageId, navigationController: navigationController, activateInput: nil).start() let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, messageId: messageId, navigationController: navigationController, activateInput: nil, keepStack: .never).start()
} else { } else {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: actualPeerId), subject: .message(id: .id(messageId), highlight: true, timecode: nil), purposefulAction: { strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: actualPeerId), subject: .message(id: .id(messageId), highlight: true, timecode: nil), purposefulAction: {
if deactivateOnAction { if deactivateOnAction {
@ -1506,7 +1506,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
navigationAnimationOptions = .removeOnMasterDetails navigationAnimationOptions = .removeOnMasterDetails
} }
if let threadId = threadId { if let threadId = threadId {
let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, messageId: nil, navigationController: navigationController, activateInput: nil).start() let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, messageId: nil, navigationController: navigationController, activateInput: nil, keepStack: .never).start()
} else { } else {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peer.id), purposefulAction: { [weak self] in strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peer.id), purposefulAction: { [weak self] in
self?.deactivateSearch(animated: false) self?.deactivateSearch(animated: false)
@ -1602,7 +1602,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconColor: ForumCreateTopicScreen.iconColors.randomElement()!, iconFileId: fileId) let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconColor: ForumCreateTopicScreen.iconColors.randomElement()!, iconFileId: fileId)
|> deliverOnMainQueue).start(next: { topicId in |> deliverOnMainQueue).start(next: { topicId in
let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: topicId, messageId: nil, navigationController: navigationController, activateInput: .text).start() let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: topicId, messageId: nil, navigationController: navigationController, activateInput: .text, keepStack: .never).start()
}, error: { _ in }, error: { _ in
controller?.isInProgress = false controller?.isInProgress = false
}) })
@ -2670,7 +2670,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconColor: ForumCreateTopicScreen.iconColors.randomElement()!, iconFileId: fileId) let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconColor: ForumCreateTopicScreen.iconColors.randomElement()!, iconFileId: fileId)
|> deliverOnMainQueue).start(next: { topicId in |> deliverOnMainQueue).start(next: { topicId in
if let navigationController = (sourceController.navigationController as? NavigationController) { if let navigationController = (sourceController.navigationController as? NavigationController) {
let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: topicId, messageId: nil, navigationController: navigationController, activateInput: .text).start() let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: topicId, messageId: nil, navigationController: navigationController, activateInput: .text, keepStack: .never).start()
} }
}, error: { _ in }, error: { _ in
controller?.isInProgress = false controller?.isInProgress = false

View File

@ -2534,7 +2534,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
if let currentMutedIconImage = currentMutedIconImage { if let currentMutedIconImage = currentMutedIconImage {
strongSelf.mutedIconNode.image = currentMutedIconImage strongSelf.mutedIconNode.image = currentMutedIconImage
strongSelf.mutedIconNode.isHidden = false strongSelf.mutedIconNode.isHidden = false
transition.updateFrame(node: strongSelf.mutedIconNode, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin - 5.0, y: floorToScreenPixels(titleFrame.midY - currentMutedIconImage.size.height / 2.0) - UIScreenPixel), size: currentMutedIconImage.size)) transition.updateFrame(node: strongSelf.mutedIconNode, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin - 5.0, y: titleFrame.minY - 1.0 - UIScreenPixel), size: currentMutedIconImage.size))
nextTitleIconOrigin += currentMutedIconImage.size.width + 1.0 nextTitleIconOrigin += currentMutedIconImage.size.width + 1.0
} else { } else {
strongSelf.mutedIconNode.image = nil strongSelf.mutedIconNode.image = nil

View File

@ -196,6 +196,12 @@ public func combineLatest<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13
}, initialValues: [:], queue: queue) }, initialValues: [:], queue: queue)
} }
public func combineLatest<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, E>(queue: Queue? = nil, _ s1: Signal<T1, E>, _ s2: Signal<T2, E>, _ s3: Signal<T3, E>, _ s4: Signal<T4, E>, _ s5: Signal<T5, E>, _ s6: Signal<T6, E>, _ s7: Signal<T7, E>, _ s8: Signal<T8, E>, _ s9: Signal<T9, E>, _ s10: Signal<T10, E>, _ s11: Signal<T11, E>, _ s12: Signal<T12, E>, _ s13: Signal<T13, E>, _ s14: Signal<T14, E>, _ s15: Signal<T15, E>, _ s16: Signal<T16, E>, _ s17: Signal<T17, E>, _ s18: Signal<T18, E>, _ s19: Signal<T19, E>) -> Signal<(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19), E> {
return combineLatestAny([signalOfAny(s1), signalOfAny(s2), signalOfAny(s3), signalOfAny(s4), signalOfAny(s5), signalOfAny(s6), signalOfAny(s7), signalOfAny(s8), signalOfAny(s9), signalOfAny(s10), signalOfAny(s11), signalOfAny(s12), signalOfAny(s13), signalOfAny(s14), signalOfAny(s15), signalOfAny(s16), signalOfAny(s17), signalOfAny(s18), signalOfAny(s19)], combine: { values in
return (values[0] as! T1, values[1] as! T2, values[2] as! T3, values[3] as! T4, values[4] as! T5, values[5] as! T6, values[6] as! T7, values[7] as! T8, values[8] as! T9, values[9] as! T10, values[10] as! T11, values[11] as! T12, values[12] as! T13, values[13] as! T14, values[14] as! T15, values[15] as! T16, values[16] as! T17, values[17] as! T18, values[18] as! T19)
}, initialValues: [:], queue: queue)
}
public func combineLatest<T, E>(queue: Queue? = nil, _ signals: [Signal<T, E>]) -> Signal<[T], E> { public func combineLatest<T, E>(queue: Queue? = nil, _ signals: [Signal<T, E>]) -> Signal<[T], E> {
if signals.count == 0 { if signals.count == 0 {
return single([T](), E.self) return single([T](), E.self)

View File

@ -238,6 +238,32 @@ func _internal_createForumChannelTopic(account: Account, peerId: PeerId, title:
} }
} }
func _internal_fetchForumChannelTopic(account: Account, peerId: PeerId, threadId: Int64) -> Signal<EngineMessageHistoryThread.Info?, NoError> {
return account.postbox.transaction { transaction -> (info: EngineMessageHistoryThread.Info?, inputChannel: Api.InputChannel?) in
if let data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self) {
return (data.info, nil)
} else {
return (nil, transaction.getPeer(peerId).flatMap(apiInputChannel))
}
}
|> mapToSignal { info, _ -> Signal<EngineMessageHistoryThread.Info?, NoError> in
if let info = info {
return .single(info)
} else {
return resolveForumThreads(postbox: account.postbox, network: account.network, ids: [MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId))])
|> mapToSignal { _ -> Signal<EngineMessageHistoryThread.Info?, NoError> in
return account.postbox.transaction { transaction -> EngineMessageHistoryThread.Info? in
if let data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self) {
return data.info
} else {
return nil
}
}
}
}
}
}
public enum EditForumChannelTopicError { public enum EditForumChannelTopicError {
case generic case generic
} }

View File

@ -832,6 +832,10 @@ public extension TelegramEngine {
return _internal_createForumChannelTopic(account: self.account, peerId: id, title: title, iconColor: iconColor, iconFileId: iconFileId) return _internal_createForumChannelTopic(account: self.account, peerId: id, title: title, iconColor: iconColor, iconFileId: iconFileId)
} }
public func fetchForumChannelTopic(id: EnginePeer.Id, threadId: Int64) -> Signal<EngineMessageHistoryThread.Info?, NoError> {
return _internal_fetchForumChannelTopic(account: self.account, peerId: id, threadId: threadId)
}
public func editForumChannelTopic(id: EnginePeer.Id, threadId: Int64, title: String, iconFileId: Int64?) -> Signal<Never, EditForumChannelTopicError> { public func editForumChannelTopic(id: EnginePeer.Id, threadId: Int64, title: String, iconFileId: Int64?) -> Signal<Never, EditForumChannelTopicError> {
return _internal_editForumChannelTopic(account: self.account, peerId: id, threadId: threadId, title: title, iconFileId: iconFileId) return _internal_editForumChannelTopic(account: self.account, peerId: id, threadId: threadId, title: title, iconFileId: iconFileId)
} }

View File

@ -314,7 +314,7 @@ private final class ChatHistoryTransactionOpaqueState {
} }
} }
private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHistoryView, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, animatedEmojiStickers: [String: [StickerPackItem]], additionalAnimatedEmojiStickers: [String: [Int: StickerPackItem]], subject: ChatControllerSubject?, currentlyPlayingMessageId: MessageIndex?, isCopyProtectionEnabled: Bool, availableReactions: AvailableReactions?, defaultReaction: MessageReaction.Reaction?, isPremium: Bool, accountPeer: EnginePeer?) -> ChatMessageItemAssociatedData { private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHistoryView, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, animatedEmojiStickers: [String: [StickerPackItem]], additionalAnimatedEmojiStickers: [String: [Int: StickerPackItem]], subject: ChatControllerSubject?, currentlyPlayingMessageId: MessageIndex?, isCopyProtectionEnabled: Bool, availableReactions: AvailableReactions?, defaultReaction: MessageReaction.Reaction?, isPremium: Bool, accountPeer: EnginePeer?, topicAuthorId: EnginePeer.Id?) -> ChatMessageItemAssociatedData {
var automaticMediaDownloadPeerType: MediaAutoDownloadPeerType = .channel var automaticMediaDownloadPeerType: MediaAutoDownloadPeerType = .channel
var contactsPeerIds: Set<PeerId> = Set() var contactsPeerIds: Set<PeerId> = Set()
var channelDiscussionGroup: ChatMessageItemAssociatedData.ChannelDiscussionGroupStatus = .unknown var channelDiscussionGroup: ChatMessageItemAssociatedData.ChannelDiscussionGroupStatus = .unknown
@ -363,7 +363,7 @@ private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHist
} }
} }
return ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadNetworkType: automaticDownloadNetworkType, isRecentActions: false, subject: subject, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, currentlyPlayingMessageId: currentlyPlayingMessageId, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, defaultReaction: defaultReaction, isPremium: isPremium, accountPeer: accountPeer) return ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadNetworkType: automaticDownloadNetworkType, isRecentActions: false, subject: subject, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, currentlyPlayingMessageId: currentlyPlayingMessageId, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, defaultReaction: defaultReaction, isPremium: isPremium, accountPeer: accountPeer, topicAuthorId: topicAuthorId)
} }
private extension ChatHistoryLocationInput { private extension ChatHistoryLocationInput {
@ -1032,6 +1032,17 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
} }
|> distinctUntilChanged |> distinctUntilChanged
let topicAuthorId: Signal<EnginePeer.Id?, NoError>
if let peerId = chatLocation.peerId, let threadId = chatLocation.threadId {
topicAuthorId = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.ThreadData(id: peerId, threadId: threadId))
|> map { data -> EnginePeer.Id? in
return data?.author
}
|> distinctUntilChanged
} else {
topicAuthorId = .single(nil)
}
let historyViewTransitionDisposable = combineLatest(queue: messageViewQueue, let historyViewTransitionDisposable = combineLatest(queue: messageViewQueue,
historyViewUpdate, historyViewUpdate,
self.chatPresentationDataPromise.get(), self.chatPresentationDataPromise.get(),
@ -1050,8 +1061,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
adMessages, adMessages,
availableReactions, availableReactions,
defaultReaction, defaultReaction,
accountPeer accountPeer,
).start(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, historyAppearsCleared, pendingUnpinnedAllMessages, pendingRemovedMessages, animatedEmojiStickers, additionalAnimatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, currentlyPlayingMessageIdAndType, scrollToMessageId, adMessages, availableReactions, defaultReaction, accountPeer in topicAuthorId
).start(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, historyAppearsCleared, pendingUnpinnedAllMessages, pendingRemovedMessages, animatedEmojiStickers, additionalAnimatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, currentlyPlayingMessageIdAndType, scrollToMessageId, adMessages, availableReactions, defaultReaction, accountPeer, topicAuthorId in
let currentlyPlayingMessageId = currentlyPlayingMessageIdAndType?.0 let currentlyPlayingMessageId = currentlyPlayingMessageIdAndType?.0
func applyHole() { func applyHole() {
@ -1185,7 +1197,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
isPremium = true isPremium = true
} }
let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, subject: subject, currentlyPlayingMessageId: currentlyPlayingMessageId, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, defaultReaction: defaultReaction, isPremium: isPremium, accountPeer: accountPeer) let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, subject: subject, currentlyPlayingMessageId: currentlyPlayingMessageId, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, defaultReaction: defaultReaction, isPremium: isPremium, accountPeer: accountPeer, topicAuthorId: topicAuthorId)
let filteredEntries = chatHistoryEntriesForView( let filteredEntries = chatHistoryEntriesForView(
location: chatLocation, location: chatLocation,

View File

@ -1392,6 +1392,13 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
} }
authorRank = attributes.rank authorRank = attributes.rank
} }
if authorRank == nil {
if let topicAuthorId = item.associatedData.topicAuthorId, topicAuthorId == message.author?.id {
//TODO:localize
authorRank = .custom("Topic Author")
}
}
case .group: case .group:
break break
} }

View File

@ -1571,7 +1571,7 @@ private class QrContentNode: ASDisplayNode, ContentNode {
codeLink = "" codeLink = ""
} }
if let threadId = threadId { if let threadId = threadId {
codeLink += "?topic=\(threadId)" codeLink += "/\(threadId)"
} }
let codeReadyPromise = ValuePromise<Bool>() let codeReadyPromise = ValuePromise<Bool>()

View File

@ -901,6 +901,10 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
if let navigationController = strongSelf.getNavigationController() { if let navigationController = strongSelf.getNavigationController() {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .replyThread(message: replyThreadMessage), subject: .message(id: .id(messageId), highlight: true, timecode: nil))) strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .replyThread(message: replyThreadMessage), subject: .message(id: .id(messageId), highlight: true, timecode: nil)))
} }
case let .replyThread(messageId):
if let navigationController = strongSelf.getNavigationController() {
let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: messageId.peerId, threadId: Int64(messageId.id), messageId: nil, navigationController: navigationController, activateInput: nil, keepStack: .always).start()
}
case let .stickerPack(name, type): case let .stickerPack(name, type):
let _ = type let _ = type
let packReference: StickerPackReference = .name(name) let packReference: StickerPackReference = .name(name)

View File

@ -227,7 +227,7 @@ public func isOverlayControllerForChatNotificationOverlayPresentation(_ controll
return false return false
} }
public func navigateToForumThreadImpl(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?) -> Signal<Never, NoError> { public func navigateToForumThreadImpl(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?, keepStack: NavigateToChatKeepStack) -> Signal<Never, NoError> {
return fetchAndPreloadReplyThreadInfo(context: context, subject: .groupMessage(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId))), atMessageId: messageId, preload: false) return fetchAndPreloadReplyThreadInfo(context: context, subject: .groupMessage(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId))), atMessageId: messageId, preload: false)
|> deliverOnMainQueue |> deliverOnMainQueue
|> beforeNext { [weak context, weak navigationController] result in |> beforeNext { [weak context, weak navigationController] result in
@ -248,7 +248,7 @@ public func navigateToForumThreadImpl(context: AccountContext, peerId: EnginePee
chatLocationContextHolder: result.contextHolder, chatLocationContextHolder: result.contextHolder,
subject: messageId.flatMap { .message(id: .id($0), highlight: true, timecode: nil) }, subject: messageId.flatMap { .message(id: .id($0), highlight: true, timecode: nil) },
activateInput: actualActivateInput, activateInput: actualActivateInput,
keepStack: .never keepStack: keepStack
) )
) )
} }

View File

@ -163,6 +163,10 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
present(c, a) present(c, a)
}, messageId: replyThreadMessage.messageId, isChannelPost: replyThreadMessage.isChannelPost, atMessage: messageId, displayModalProgress: true).start() }, messageId: replyThreadMessage.messageId, isChannelPost: replyThreadMessage.isChannelPost, atMessage: messageId, displayModalProgress: true).start()
} }
case let .replyThread(messageId):
if let navigationController = navigationController {
let _ = context.sharedContext.navigateToForumThread(context: context, peerId: messageId.peerId, threadId: Int64(messageId.id), messageId: nil, navigationController: navigationController, activateInput: nil, keepStack: .always).start()
}
case let .stickerPack(name, _): case let .stickerPack(name, _):
dismissInput() dismissInput()

View File

@ -1,19 +1,22 @@
import AsyncDisplayKit import AsyncDisplayKit
import Display import Display
import TelegramPresentationData import TelegramPresentationData
import AppBundle
final class PeerInfoScreenSwitchItem: PeerInfoScreenItem { final class PeerInfoScreenSwitchItem: PeerInfoScreenItem {
let id: AnyHashable let id: AnyHashable
let text: String let text: String
let value: Bool let value: Bool
let icon: UIImage? let icon: UIImage?
let isLocked: Bool
let toggled: ((Bool) -> Void)? let toggled: ((Bool) -> Void)?
init(id: AnyHashable, text: String, value: Bool, icon: UIImage? = nil, toggled: ((Bool) -> Void)?) { init(id: AnyHashable, text: String, value: Bool, icon: UIImage? = nil, isLocked: Bool = false, toggled: ((Bool) -> Void)?) {
self.id = id self.id = id
self.text = text self.text = text
self.value = value self.value = value
self.icon = icon self.icon = icon
self.isLocked = isLocked
self.toggled = toggled self.toggled = toggled
} }
@ -28,7 +31,9 @@ private final class PeerInfoScreenSwitchItemNode: PeerInfoScreenItemNode {
private let iconNode: ASImageNode private let iconNode: ASImageNode
private let textNode: ImmediateTextNode private let textNode: ImmediateTextNode
private let switchNode: SwitchNode private let switchNode: SwitchNode
private var lockedIconNode: ASImageNode?
private let bottomSeparatorNode: ASDisplayNode private let bottomSeparatorNode: ASDisplayNode
private var lockedButtonNode: HighlightableButtonNode?
private let activateArea: AccessibilityAreaNode private let activateArea: AccessibilityAreaNode
private var item: PeerInfoScreenSwitchItem? private var item: PeerInfoScreenSwitchItem?
@ -91,12 +96,51 @@ private final class PeerInfoScreenSwitchItemNode: PeerInfoScreenItemNode {
let firstTime = self.item == nil let firstTime = self.item == nil
var updateLockedIconImage = false
if item.isLocked {
let lockedIconNode: ASImageNode
if let current = self.lockedIconNode {
lockedIconNode = current
} else {
updateLockedIconImage = true
lockedIconNode = ASImageNode()
self.lockedIconNode = lockedIconNode
self.insertSubnode(lockedIconNode, aboveSubnode: self.switchNode)
}
} else if let lockedIconNode = self.lockedIconNode {
self.lockedIconNode = nil
lockedIconNode.removeFromSupernode()
}
if item.isLocked {
self.switchNode.isUserInteractionEnabled = false
if self.lockedButtonNode == nil {
let lockedButtonNode = HighlightableButtonNode()
self.lockedButtonNode = lockedButtonNode
self.insertSubnode(lockedButtonNode, aboveSubnode: self.switchNode)
lockedButtonNode.addTarget(self, action: #selector(self.lockedButtonPressed), forControlEvents: .touchUpInside)
}
} else {
if let lockedButtonNode = self.lockedButtonNode {
self.lockedButtonNode = nil
lockedButtonNode.removeFromSupernode()
}
self.switchNode.isUserInteractionEnabled = true
}
if self.theme !== presentationData.theme { if self.theme !== presentationData.theme {
self.theme = presentationData.theme self.theme = presentationData.theme
self.switchNode.frameColor = presentationData.theme.list.itemSwitchColors.frameColor self.switchNode.frameColor = presentationData.theme.list.itemSwitchColors.frameColor
self.switchNode.contentColor = presentationData.theme.list.itemSwitchColors.contentColor self.switchNode.contentColor = presentationData.theme.list.itemSwitchColors.contentColor
self.switchNode.handleColor = presentationData.theme.list.itemSwitchColors.handleColor self.switchNode.handleColor = presentationData.theme.list.itemSwitchColors.handleColor
updateLockedIconImage = true
}
if updateLockedIconImage, let lockedIconNode = self.lockedIconNode, let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"), color: presentationData.theme.list.itemSecondaryTextColor) {
lockedIconNode.image = image
} }
self.item = item self.item = item
@ -143,10 +187,17 @@ private final class PeerInfoScreenSwitchItemNode: PeerInfoScreenItemNode {
} }
let switchSize = switchView.bounds.size let switchSize = switchView.bounds.size
self.switchNode.frame = CGRect(origin: CGPoint(x: width - switchSize.width - 15.0 - safeInsets.right, y: floor((height - switchSize.height) / 2.0)), size: switchSize) let switchFrame = CGRect(origin: CGPoint(x: width - switchSize.width - 15.0 - safeInsets.right, y: floor((height - switchSize.height) / 2.0)), size: switchSize)
self.switchNode.frame = switchFrame
if switchView.isOn != item.value { if switchView.isOn != item.value {
switchView.setOn(item.value, animated: !firstTime) switchView.setOn(item.value, animated: !firstTime)
} }
self.lockedButtonNode?.frame = switchFrame
if let lockedIconNode = self.lockedIconNode, let icon = lockedIconNode.image {
lockedIconNode.frame = CGRect(origin: CGPoint(x: switchFrame.minX + 10.0 + UIScreenPixel, y: switchFrame.minY + 9.0), size: icon.size)
}
} }
let hasCorners = hasCorners && (topItem == nil || bottomItem == nil) let hasCorners = hasCorners && (topItem == nil || bottomItem == nil)
@ -168,4 +219,8 @@ private final class PeerInfoScreenSwitchItemNode: PeerInfoScreenItemNode {
return height return height
} }
@objc private func lockedButtonPressed() {
self.item?.toggled?(self.switchNode.isOn)
}
} }

View File

@ -188,6 +188,7 @@ final class PeerInfoScreenData {
let requests: PeerInvitationImportersState? let requests: PeerInvitationImportersState?
let requestsContext: PeerInvitationImportersContext? let requestsContext: PeerInvitationImportersContext?
let threadData: MessageHistoryThreadData? let threadData: MessageHistoryThreadData?
let appConfiguration: AppConfiguration?
init( init(
peer: Peer?, peer: Peer?,
@ -206,7 +207,8 @@ final class PeerInfoScreenData {
invitations: PeerExportedInvitationsState?, invitations: PeerExportedInvitationsState?,
requests: PeerInvitationImportersState?, requests: PeerInvitationImportersState?,
requestsContext: PeerInvitationImportersContext?, requestsContext: PeerInvitationImportersContext?,
threadData: MessageHistoryThreadData? threadData: MessageHistoryThreadData?,
appConfiguration: AppConfiguration?
) { ) {
self.peer = peer self.peer = peer
self.chatPeer = chatPeer self.chatPeer = chatPeer
@ -225,6 +227,7 @@ final class PeerInfoScreenData {
self.requests = requests self.requests = requests
self.requestsContext = requestsContext self.requestsContext = requestsContext
self.threadData = threadData self.threadData = threadData
self.appConfiguration = appConfiguration
} }
} }
@ -435,7 +438,8 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id,
}) })
var enableQRLogin = false var enableQRLogin = false
if let appConfiguration = accountPreferences.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self), let data = appConfiguration.data, let enableQR = data["qr_login_camera"] as? Bool, enableQR { let appConfiguration = accountPreferences.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self)
if let appConfiguration, let data = appConfiguration.data, let enableQR = data["qr_login_camera"] as? Bool, enableQR {
enableQRLogin = true enableQRLogin = true
} }
@ -477,7 +481,8 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id,
invitations: nil, invitations: nil,
requests: nil, requests: nil,
requestsContext: nil, requestsContext: nil,
threadData: nil threadData: nil,
appConfiguration: appConfiguration
) )
} }
} }
@ -504,7 +509,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
invitations: nil, invitations: nil,
requests: nil, requests: nil,
requestsContext: nil, requestsContext: nil,
threadData: nil threadData: nil,
appConfiguration: nil
)) ))
case let .user(userPeerId, secretChatId, kind): case let .user(userPeerId, secretChatId, kind):
let groupsInCommon: GroupsInCommonContext? let groupsInCommon: GroupsInCommonContext?
@ -635,7 +641,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
invitations: nil, invitations: nil,
requests: nil, requests: nil,
requestsContext: nil, requestsContext: nil,
threadData: nil threadData: nil,
appConfiguration: nil
) )
} }
case .channel: case .channel:
@ -711,7 +718,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
invitations: invitations, invitations: invitations,
requests: requests, requests: requests,
requestsContext: currentRequestsContext, requestsContext: currentRequestsContext,
threadData: nil threadData: nil,
appConfiguration: nil
) )
} }
case let .group(groupId): case let .group(groupId):
@ -840,9 +848,10 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
invitationsStatePromise.get(), invitationsStatePromise.get(),
requestsContextPromise.get(), requestsContextPromise.get(),
requestsStatePromise.get(), requestsStatePromise.get(),
threadData threadData,
context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
) )
|> map { peerView, availablePanes, globalNotificationSettings, status, membersData, currentInvitationsContext, invitations, currentRequestsContext, requests, threadData -> PeerInfoScreenData in |> map { peerView, availablePanes, globalNotificationSettings, status, membersData, currentInvitationsContext, invitations, currentRequestsContext, requests, threadData, preferencesView -> PeerInfoScreenData in
var discussionPeer: Peer? var discussionPeer: Peer?
if case let .known(maybeLinkedDiscussionPeerId) = (peerView.cachedData as? CachedChannelData)?.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = peerView.peers[linkedDiscussionPeerId] { if case let .known(maybeLinkedDiscussionPeerId) = (peerView.cachedData as? CachedChannelData)?.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = peerView.peers[linkedDiscussionPeerId] {
discussionPeer = peer discussionPeer = peer
@ -889,6 +898,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
} else { } else {
notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings
} }
let appConfiguration: AppConfiguration = preferencesView.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? .defaultValue
return PeerInfoScreenData( return PeerInfoScreenData(
peer: peerView.peers[groupId], peer: peerView.peers[groupId],
@ -907,7 +918,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
invitations: invitations, invitations: invitations,
requests: requests, requests: requests,
requestsContext: currentRequestsContext, requestsContext: currentRequestsContext,
threadData: threadData threadData: threadData,
appConfiguration: appConfiguration
) )
} }
} }

View File

@ -509,6 +509,7 @@ private final class PeerInfoInteraction {
let editingOpenReactionsSetup: () -> Void let editingOpenReactionsSetup: () -> Void
let dismissInput: () -> Void let dismissInput: () -> Void
let toggleForumTopics: (Bool) -> Void let toggleForumTopics: (Bool) -> Void
let displayTopicsLimited: (Int) -> Void
init( init(
openUsername: @escaping (String) -> Void, openUsername: @escaping (String) -> Void,
@ -553,7 +554,8 @@ private final class PeerInfoInteraction {
openQrCode: @escaping () -> Void, openQrCode: @escaping () -> Void,
editingOpenReactionsSetup: @escaping () -> Void, editingOpenReactionsSetup: @escaping () -> Void,
dismissInput: @escaping () -> Void, dismissInput: @escaping () -> Void,
toggleForumTopics: @escaping (Bool) -> Void toggleForumTopics: @escaping (Bool) -> Void,
displayTopicsLimited: @escaping (Int) -> Void
) { ) {
self.openUsername = openUsername self.openUsername = openUsername
self.openPhone = openPhone self.openPhone = openPhone
@ -598,6 +600,7 @@ private final class PeerInfoInteraction {
self.editingOpenReactionsSetup = editingOpenReactionsSetup self.editingOpenReactionsSetup = editingOpenReactionsSetup
self.dismissInput = dismissInput self.dismissInput = dismissInput
self.toggleForumTopics = toggleForumTopics self.toggleForumTopics = toggleForumTopics
self.displayTopicsLimited = displayTopicsLimited
} }
} }
@ -1094,7 +1097,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
threadId = Int64(message.messageId.id) threadId = Int64(message.messageId.id)
} }
let linkText = "https://t.me/\(mainUsername)?topic=\(threadId)" let linkText = "https://t.me/\(mainUsername)/\(threadId)"
items[.peerInfo]!.append( items[.peerInfo]!.append(
PeerInfoScreenLabeledValueItem( PeerInfoScreenLabeledValueItem(
@ -1611,12 +1614,33 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
})) }))
} }
if isCreator { if isCreator, let appConfiguration = data.appConfiguration {
//TODO:localize var minParticipants = 5
items[.peerDataSettings]!.append(PeerInfoScreenSwitchItem(id: ItemTopics, text: "Topics", value: channel.flags.contains(.isForum), icon: UIImage(bundleImageName: "Settings/Menu/ChatListFilters"), toggled: { value in if let data = appConfiguration.data, let value = data["forum_min_participants"] as? Int {
interaction.toggleForumTopics(value) minParticipants = value
})) }
items[.peerDataSettings]!.append(PeerInfoScreenCommentItem(id: ItemTopicsText, text: "The group chat will be divided into topics created by admins or users."))
var canSetupTopics = false
var areTopicsLocked = true
if channel.flags.contains(.isForum) {
canSetupTopics = true
areTopicsLocked = false
} else if let memberCount = cachedData.participantsSummary.memberCount {
canSetupTopics = true
areTopicsLocked = Int(memberCount) < minParticipants
}
if canSetupTopics {
//TODO:localize
items[.peerDataSettings]!.append(PeerInfoScreenSwitchItem(id: ItemTopics, text: "Topics", value: channel.flags.contains(.isForum), icon: UIImage(bundleImageName: "Settings/Menu/ChatListFilters"), isLocked: areTopicsLocked, toggled: { value in
if areTopicsLocked {
interaction.displayTopicsLimited(minParticipants)
} else {
interaction.toggleForumTopics(value)
}
}))
items[.peerDataSettings]!.append(PeerInfoScreenCommentItem(id: ItemTopicsText, text: "The group chat will be divided into topics created by admins or users."))
}
} }
var canViewAdminsAndBanned = false var canViewAdminsAndBanned = false
@ -2050,6 +2074,16 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
return return
} }
let _ = strongSelf.context.engine.peers.setChannelForumMode(id: strongSelf.peerId, isForum: value).start() let _ = strongSelf.context.engine.peers.setChannelForumMode(id: strongSelf.peerId, isForum: value).start()
},
displayTopicsLimited: { [weak self] minCount in
guard let self else {
return
}
//TODO:localize
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let text = "Only groups with more than **\(minCount) members** can have topics enabled."
self.controller?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_mute_for", scale: 0.066, colors: [:], title: nil, text: text, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
} }
) )
@ -6810,7 +6844,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
return return
} }
var threadId: Int64 = 0 var threadId: Int64?
if case let .replyThread(message) = self.chatLocation { if case let .replyThread(message) = self.chatLocation {
threadId = Int64(message.messageId.id) threadId = Int64(message.messageId.id)
} }

View File

@ -1159,8 +1159,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
navigateToForumChannelImpl(context: context, peerId: peerId, navigationController: navigationController) navigateToForumChannelImpl(context: context, peerId: peerId, navigationController: navigationController)
} }
public func navigateToForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?) -> Signal<Never, NoError> { public func navigateToForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?, keepStack: NavigateToChatKeepStack) -> Signal<Never, NoError> {
return navigateToForumThreadImpl(context: context, peerId: peerId, threadId: threadId, messageId: messageId, navigationController: navigationController, activateInput: activateInput) return navigateToForumThreadImpl(context: context, peerId: peerId, threadId: threadId, messageId: messageId, navigationController: navigationController, activateInput: activateInput, keepStack: keepStack)
} }
public func chatControllerForForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64) -> Signal<ChatController, NoError> { public func chatControllerForForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64) -> Signal<ChatController, NoError> {

View File

@ -70,6 +70,10 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: PeerId?, navigate
controller?.present(c, in: .window(.root), with: a) controller?.present(c, in: .window(.root), with: a)
}, messageId: replyThreadMessage.messageId, isChannelPost: replyThreadMessage.isChannelPost, atMessage: messageId, displayModalProgress: true).start() }, messageId: replyThreadMessage.messageId, isChannelPost: replyThreadMessage.isChannelPost, atMessage: messageId, displayModalProgress: true).start()
} }
case let .replyThread(messageId):
if let navigationController = controller.navigationController as? NavigationController {
let _ = context.sharedContext.navigateToForumThread(context: context, peerId: messageId.peerId, threadId: Int64(messageId.id), messageId: nil, navigationController: navigationController, activateInput: nil, keepStack: .always).start()
}
case let .stickerPack(name, _): case let .stickerPack(name, _):
let packReference: StickerPackReference = .name(name) let packReference: StickerPackReference = .name(name)
controller.present(StickerPackScreen(context: context, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: controller.navigationController as? NavigationController), in: .window(.root)) controller.present(StickerPackScreen(context: context, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: controller.navigationController as? NavigationController), in: .window(.root))

View File

@ -275,7 +275,7 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? {
return .invoice(component) return .invoice(component)
} }
return .peer(.name(peerName), nil) return .peer(.name(peerName), nil)
} else if pathComponents.count == 2 || pathComponents.count == 3 { } else if pathComponents.count == 2 || pathComponents.count == 3 || pathComponents.count == 4 {
if pathComponents[0] == "addstickers" { if pathComponents[0] == "addstickers" {
return .stickerPack(name: pathComponents[1], type: .stickers) return .stickerPack(name: pathComponents[1], type: .stickers)
} else if pathComponents[0] == "addemoji" { } else if pathComponents[0] == "addemoji" {
@ -429,6 +429,24 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? {
} else { } else {
return nil return nil
} }
} else if pathComponents.count == 4 && pathComponents[0] == "c" {
if let channelId = Int64(pathComponents[1]), let threadId = Int32(pathComponents[2]), let messageId = Int32(pathComponents[3]) {
var timecode: Double?
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "t" {
if let doubleValue = Double(value) {
timecode = doubleValue
}
}
}
}
}
return .privateMessage(messageId: MessageId(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), namespace: Namespaces.Message.Cloud, id: messageId), threadId: threadId, timecode: timecode)
} else {
return nil
}
} else if pathComponents.count == 2 && pathComponents[0] == "c" { } else if pathComponents.count == 2 && pathComponents[0] == "c" {
if let channelId = Int64(pathComponents[1]) { if let channelId = Int64(pathComponents[1]) {
var threadId: Int32? var threadId: Int32?
@ -475,7 +493,10 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? {
} }
} }
} }
if let threadId = threadId {
if pathComponents.count >= 3, let subMessageId = Int32(pathComponents[2]) {
return .peer(.name(peerName), .replyThread(value, subMessageId))
} else if let threadId = threadId {
return .peer(.name(peerName), .replyThread(threadId, value)) return .peer(.name(peerName), .replyThread(threadId, value))
} else if let commentId = commentId { } else if let commentId = commentId {
return .peer(.name(peerName), .replyThread(value, commentId)) return .peer(.name(peerName), .replyThread(value, commentId))
@ -569,19 +590,42 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
} }
} }
case let .channelMessage(id, timecode): case let .channelMessage(id, timecode):
return .single(.channelMessage(peer: peer, messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id), timecode: timecode)) if let channel = peer as? TelegramChannel, channel.flags.contains(.isForum) {
return context.engine.peers.fetchForumChannelTopic(id: channel.id, threadId: Int64(id))
|> map { info -> ResolvedUrl? in
if let _ = info {
return .replyThread(messageId: MessageId(peerId: channel.id, namespace: Namespaces.Message.Cloud, id: id))
} else {
return .peer(peer, .chat(textInputState: nil, subject: nil, peekData: nil))
}
}
} else {
return .single(.channelMessage(peer: peer, messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id), timecode: timecode))
}
case let .replyThread(id, replyId): case let .replyThread(id, replyId):
let replyThreadMessageId = MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id) let replyThreadMessageId = MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id)
return context.engine.messages.fetchChannelReplyThreadMessage(messageId: replyThreadMessageId, atMessageId: nil)
|> map(Optional.init) if let channel = peer as? TelegramChannel, channel.flags.contains(.isForum) {
|> `catch` { _ -> Signal<ChatReplyThreadMessage?, NoError> in return context.engine.peers.fetchForumChannelTopic(id: channel.id, threadId: Int64(replyThreadMessageId.id))
return .single(nil) |> map { info -> ResolvedUrl? in
} if let _ = info {
|> map { result -> ResolvedUrl? in return .replyThreadMessage(replyThreadMessage: ChatReplyThreadMessage(messageId: MessageId(peerId: channel.id, namespace: Namespaces.Message.Cloud, id: Int32(clamping: replyThreadMessageId.id)), channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false), messageId: MessageId(peerId: channel.id, namespace: Namespaces.Message.Cloud, id: replyId))
guard let result = result else { } else {
return .channelMessage(peer: peer, messageId: replyThreadMessageId, timecode: nil) return .peer(peer, .chat(textInputState: nil, subject: nil, peekData: nil))
}
}
} else {
return context.engine.messages.fetchChannelReplyThreadMessage(messageId: replyThreadMessageId, atMessageId: nil)
|> map(Optional.init)
|> `catch` { _ -> Signal<ChatReplyThreadMessage?, NoError> in
return .single(nil)
}
|> map { result -> ResolvedUrl? in
guard let result = result else {
return .channelMessage(peer: peer, messageId: replyThreadMessageId, timecode: nil)
}
return .replyThreadMessage(replyThreadMessage: result, messageId: MessageId(peerId: result.messageId.peerId, namespace: Namespaces.Message.Cloud, id: replyId))
} }
return .replyThreadMessage(replyThreadMessage: result, messageId: MessageId(peerId: result.messageId.peerId, namespace: Namespaces.Message.Cloud, id: replyId))
} }
case let .voiceChat(invite): case let .voiceChat(invite):
return .single(.joinVoiceChat(peer.id, invite)) return .single(.joinVoiceChat(peer.id, invite))
@ -614,7 +658,27 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
return foundPeer return foundPeer
|> mapToSignal { foundPeer -> Signal<ResolvedUrl?, NoError> in |> mapToSignal { foundPeer -> Signal<ResolvedUrl?, NoError> in
if let foundPeer = foundPeer { if let foundPeer = foundPeer {
if let threadId = threadId { if case let .channel(channel) = foundPeer, channel.flags.contains(.isForum) {
if let threadId = threadId {
return context.engine.peers.fetchForumChannelTopic(id: channel.id, threadId: Int64(threadId))
|> map { info -> ResolvedUrl? in
if let _ = info {
return .replyThreadMessage(replyThreadMessage: ChatReplyThreadMessage(messageId: MessageId(peerId: channel.id, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId)), channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false), messageId: messageId)
} else {
return .peer(peer?._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil))
}
}
} else {
return context.engine.peers.fetchForumChannelTopic(id: channel.id, threadId: Int64(messageId.id))
|> map { info -> ResolvedUrl? in
if let _ = info {
return .replyThread(messageId: messageId)
} else {
return .peer(foundPeer._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil))
}
}
}
} else if let threadId = threadId {
let replyThreadMessageId = MessageId(peerId: foundPeer.id, namespace: Namespaces.Message.Cloud, id: threadId) let replyThreadMessageId = MessageId(peerId: foundPeer.id, namespace: Namespaces.Message.Cloud, id: threadId)
return context.engine.messages.fetchChannelReplyThreadMessage(messageId: replyThreadMessageId, atMessageId: nil) return context.engine.messages.fetchChannelReplyThreadMessage(messageId: replyThreadMessageId, atMessageId: nil)
|> map(Optional.init) |> map(Optional.init)