mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
[WIP] Topics
This commit is contained in:
parent
d9ab563c94
commit
e0746fa0c2
@ -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)
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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>()
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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> {
|
||||||
|
@ -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))
|
||||||
|
@ -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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user