diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index aa9cdfd572..e3479c2d1d 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -271,11 +271,11 @@ public enum StickerPackUrlType { public enum ResolvedUrl { case externalUrl(String) case urlAuth(String) - case peer(PeerId?, ChatControllerInteractionNavigateToPeer) + case peer(Peer?, ChatControllerInteractionNavigateToPeer) case inaccessiblePeer - case botStart(peerId: PeerId, payload: String) + case botStart(peer: Peer, payload: String) case groupBotStart(peerId: PeerId, payload: String, adminRights: ResolvedBotAdminRights?) - case channelMessage(peerId: PeerId, messageId: MessageId, timecode: Double?) + case channelMessage(peer: Peer, messageId: MessageId, timecode: Double?) case replyThreadMessage(replyThreadMessage: ChatReplyThreadMessage, messageId: MessageId) case stickerPack(name: String, type: StickerPackUrlType) case instantView(TelegramMediaWebpage, String?) @@ -718,7 +718,7 @@ public protocol SharedAccountContext: AnyObject { func makeDeviceContactInfoController(context: AccountContext, subject: DeviceContactInfoSubject, completed: (() -> Void)?, cancelled: (() -> Void)?) -> ViewController func makePeersNearbyController(context: AccountContext) -> ViewController func makeComposeController(context: AccountContext) -> ViewController - func makeChatListController(context: AccountContext, groupId: PeerGroupId, controlsHistoryPreload: Bool, hideNetworkActivityStatus: Bool, previewing: Bool, enableDebugActions: Bool) -> ChatListController + func makeChatListController(context: AccountContext, location: ChatListControllerLocation, controlsHistoryPreload: Bool, hideNetworkActivityStatus: Bool, previewing: Bool, enableDebugActions: Bool) -> ChatListController func makeChatController(context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject?, botStart: ChatControllerInitialBotStart?, mode: ChatControllerPresentationMode) -> ChatController func makeChatMessagePreviewItem(context: AccountContext, messages: [Message], theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder, forcedResourceStatus: FileMediaResourceStatus?, tapMessage: ((Message) -> Void)?, clickThroughMessage: (() -> Void)?, backgroundNode: ASDisplayNode?, availableReactions: AvailableReactions?, isCentered: Bool) -> ListViewItem func makeChatMessageDateHeaderItem(context: AccountContext, timestamp: Int32, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder) -> ListViewItemHeader @@ -733,14 +733,14 @@ public protocol SharedAccountContext: AnyObject { func makePrivacyAndSecurityController(context: AccountContext) -> ViewController func navigateToChatController(_ params: NavigateToChatControllerParams) func navigateToForumChannel(context: AccountContext, peerId: EnginePeer.Id, navigationController: NavigationController) - func navigateToForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, navigationController: NavigationController) -> Signal + func navigateToForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, navigationController: NavigationController, activateInput: ChatControllerActivateInput?) -> Signal func openStorageUsage(context: AccountContext) func openLocationScreen(context: AccountContext, messageId: MessageId, navigationController: NavigationController) func openExternalUrl(context: AccountContext, urlContext: OpenURLContext, url: String, forceExternal: Bool, presentationData: PresentationData, navigationController: NavigationController?, dismissInput: @escaping () -> Void) func chatAvailableMessageActions(engine: TelegramEngine, accountPeerId: EnginePeer.Id, messageIds: Set) -> Signal func chatAvailableMessageActions(engine: TelegramEngine, accountPeerId: EnginePeer.Id, messageIds: Set, messages: [EngineMessage.Id: EngineMessage], peers: [EnginePeer.Id: EnginePeer]) -> Signal func resolveUrl(context: AccountContext, peerId: PeerId?, url: String, skipUrlAuth: Bool) -> Signal - func openResolvedUrl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, forceExternal: Bool, openPeer: @escaping (PeerId, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?, requestMessageActionUrlAuth: ((MessageActionUrlSubject) -> Void)?, joinVoiceChat: ((PeerId, String?, CachedChannelData.ActiveCall) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?) + func openResolvedUrl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, forceExternal: Bool, openPeer: @escaping (EnginePeer, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?, requestMessageActionUrlAuth: ((MessageActionUrlSubject) -> Void)?, joinVoiceChat: ((PeerId, String?, CachedChannelData.ActiveCall) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?) func openAddContact(context: AccountContext, firstName: String, lastName: String, phoneNumber: String, label: String, present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, completed: @escaping () -> Void) func openAddPersonContact(context: AccountContext, peerId: PeerId, pushController: @escaping (ViewController) -> Void, present: @escaping (ViewController, Any?) -> Void) func presentContactsWarningSuppression(context: AccountContext, present: (ViewController, Any?) -> Void) diff --git a/submodules/AccountContext/Sources/GalleryController.swift b/submodules/AccountContext/Sources/GalleryController.swift index fbfab81a96..80515336b0 100644 --- a/submodules/AccountContext/Sources/GalleryController.swift +++ b/submodules/AccountContext/Sources/GalleryController.swift @@ -14,14 +14,14 @@ public final class GalleryControllerActionInteraction { public let openUrl: (String, Bool) -> Void public let openUrlIn: (String) -> Void public let openPeerMention: (String) -> Void - public let openPeer: (PeerId) -> Void + public let openPeer: (EnginePeer) -> Void public let openHashtag: (String?, String) -> Void public let openBotCommand: (String) -> Void public let addContact: (String) -> Void public let storeMediaPlaybackState: (MessageId, Double?, Double) -> Void public let editMedia: (MessageId, [UIView], @escaping () -> Void) -> Void - public init(openUrl: @escaping (String, Bool) -> Void, openUrlIn: @escaping (String) -> Void, openPeerMention: @escaping (String) -> Void, openPeer: @escaping (PeerId) -> Void, openHashtag: @escaping (String?, String) -> Void, openBotCommand: @escaping (String) -> Void, addContact: @escaping (String) -> Void, storeMediaPlaybackState: @escaping (MessageId, Double?, Double) -> Void, editMedia: @escaping (MessageId, [UIView], @escaping () -> Void) -> Void) { + public init(openUrl: @escaping (String, Bool) -> Void, openUrlIn: @escaping (String) -> Void, openPeerMention: @escaping (String) -> Void, openPeer: @escaping (EnginePeer) -> Void, openHashtag: @escaping (String?, String) -> Void, openBotCommand: @escaping (String) -> Void, addContact: @escaping (String) -> Void, storeMediaPlaybackState: @escaping (MessageId, Double?, Double) -> Void, editMedia: @escaping (MessageId, [UIView], @escaping () -> Void) -> Void) { self.openUrl = openUrl self.openUrlIn = openUrlIn self.openPeerMention = openPeerMention diff --git a/submodules/AvatarNode/Sources/AvatarNode.swift b/submodules/AvatarNode/Sources/AvatarNode.swift index d970e61eaa..d934d8afb5 100644 --- a/submodules/AvatarNode/Sources/AvatarNode.swift +++ b/submodules/AvatarNode/Sources/AvatarNode.swift @@ -70,7 +70,7 @@ public enum AvatarNodeExplicitIcon { private enum AvatarNodeState: Equatable { case empty - case peerAvatar(EnginePeer.Id, [String], TelegramMediaImageRepresentation?) + case peerAvatar(EnginePeer.Id, [String], TelegramMediaImageRepresentation?, AvatarNodeClipStyle) case custom(letter: [String], explicitColorIndex: Int?, explicitIcon: AvatarNodeExplicitIcon?) } @@ -78,8 +78,8 @@ private func ==(lhs: AvatarNodeState, rhs: AvatarNodeState) -> Bool { switch (lhs, rhs) { case (.empty, .empty): return true - case let (.peerAvatar(lhsPeerId, lhsLetters, lhsPhotoRepresentations), .peerAvatar(rhsPeerId, rhsLetters, rhsPhotoRepresentations)): - return lhsPeerId == rhsPeerId && lhsLetters == rhsLetters && lhsPhotoRepresentations == rhsPhotoRepresentations + case let (.peerAvatar(lhsPeerId, lhsLetters, lhsPhotoRepresentations, lhsClipStyle), .peerAvatar(rhsPeerId, rhsLetters, rhsPhotoRepresentations, rhsClipStyle)): + return lhsPeerId == rhsPeerId && lhsLetters == rhsLetters && lhsPhotoRepresentations == rhsPhotoRepresentations && lhsClipStyle == rhsClipStyle case let (.custom(lhsLetters, lhsIndex, lhsIcon), .custom(rhsLetters, rhsIndex, rhsIcon)): return lhsLetters == rhsLetters && lhsIndex == rhsIndex && lhsIcon == rhsIcon default: @@ -321,7 +321,7 @@ public final class AvatarNode: ASDisplayNode { } else if peer?.restrictionText(platform: "ios", contentSettings: genericContext.currentContentSettings.with { $0 }) == nil { representation = peer?.smallProfileImage } - let updatedState: AvatarNodeState = .peerAvatar(peer?.id ?? EnginePeer.Id(0), peer?.displayLetters ?? [], representation) + let updatedState: AvatarNodeState = .peerAvatar(peer?.id ?? EnginePeer.Id(0), peer?.displayLetters ?? [], representation, clipStyle) if updatedState != self.state || overrideImage != self.overrideImage || theme !== self.theme { self.state = updatedState self.overrideImage = overrideImage @@ -588,9 +588,8 @@ public final class AvatarNode: ASDisplayNode { let currentState = node?.state let createNode = node == nil return { [weak node] context, peer, font in - let state: AvatarNodeState = .peerAvatar(peer.id, peer.displayLetters, peer.smallProfileImage) + let state: AvatarNodeState = .peerAvatar(peer.id, peer.displayLetters, peer.smallProfileImage, .round) if currentState != state { - } var createdNode: AvatarNode? if createNode { diff --git a/submodules/ChatListUI/BUILD b/submodules/ChatListUI/BUILD index 49af1bbc83..458ac01c5a 100644 --- a/submodules/ChatListUI/BUILD +++ b/submodules/ChatListUI/BUILD @@ -79,6 +79,7 @@ swift_library( "//submodules/TelegramUI/Components/EmojiStatusSelectionComponent", "//submodules/TelegramUI/Components/EntityKeyboard", "//submodules/TelegramUI/Components/ForumCreateTopicScreen:ForumCreateTopicScreen", + "//submodules/TelegramUI/Components/ChatTitleView", "//submodules/AnimationUI:AnimationUI", ], visibility = [ diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 883c71a2c8..948aa43a30 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -38,6 +38,7 @@ import EntityKeyboard import TelegramStringFormatting import ForumCreateTopicScreen import AnimationUI +import ChatTitleView private func fixListNodeScrolling(_ listNode: ListView, searchNode: NavigationBarSearchContentNode) -> Bool { if listNode.scroller.isDragging { @@ -110,22 +111,22 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent } } -private final class MoreHeaderButton: HighlightableButtonNode { - enum Content { +public final class MoreHeaderButton: HighlightableButtonNode { + public enum Content { case image(UIImage?) case more(UIImage?) } - let referenceNode: ContextReferenceContentNode - let containerNode: ContextControllerSourceNode + public let referenceNode: ContextReferenceContentNode + public let containerNode: ContextControllerSourceNode private let iconNode: ASImageNode private var animationNode: AnimationNode? - var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? + public var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? private var color: UIColor - init(color: UIColor) { + public init(color: UIColor) { self.color = color self.referenceNode = ContextReferenceContentNode() @@ -158,7 +159,7 @@ private final class MoreHeaderButton: HighlightableButtonNode { self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 26.0, height: 44.0)) self.referenceNode.frame = self.containerNode.bounds - self.iconNode.image = optionsCircleImage(color: color) + self.iconNode.image = MoreHeaderButton.optionsCircleImage(color: color) if let image = self.iconNode.image { self.iconNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - image.size.width) / 2.0), y: floor((self.containerNode.bounds.height - image.size.height) / 2.0)), size: image.size) } @@ -167,7 +168,7 @@ private final class MoreHeaderButton: HighlightableButtonNode { } private var content: Content? - func setContent(_ content: Content, animated: Bool = false) { + public func setContent(_ content: Content, animated: Bool = false) { if case .more = content, self.animationNode == nil { let iconColor = self.color let animationNode = AnimationNode(animation: "anim_profilemore", colors: ["Point 2.Group 1.Fill 1": iconColor, @@ -236,33 +237,33 @@ private final class MoreHeaderButton: HighlightableButtonNode { } } - override func didLoad() { + override public func didLoad() { super.didLoad() self.view.isOpaque = false } - override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { + override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { return CGSize(width: 22.0, height: 44.0) } - func onLayout() { + public func onLayout() { } - func play() { + public func play() { self.animationNode?.playOnce() } -} + + public static func optionsCircleImage(color: UIColor) -> UIImage? { + return generateImage(CGSize(width: 22.0, height: 22.0), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) -private func optionsCircleImage(color: UIColor) -> UIImage? { - return generateImage(CGSize(width: 22.0, height: 22.0), contextGenerator: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) + context.setStrokeColor(color.cgColor) + let lineWidth: CGFloat = 1.3 + context.setLineWidth(lineWidth) - context.setStrokeColor(color.cgColor) - let lineWidth: CGFloat = 1.3 - context.setLineWidth(lineWidth) - - context.strokeEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: lineWidth, dy: lineWidth)) - }) + context.strokeEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: lineWidth, dy: lineWidth)) + }) + } } public class ChatListControllerImpl: TelegramBaseController, ChatListController { @@ -284,11 +285,15 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController return super.displayNode as! ChatListControllerNode } - private let titleView: ChatListTitleView + private var titleView: ChatListTitleView? + private var chatTitleView: ChatTitleView? + private let infoReady = Promise() + private var proxyUnavailableTooltipController: TooltipController? private var didShowProxyUnavailableTooltipController = false private var titleDisposable: Disposable? + private var chatTitleDisposable: Disposable? private var badgeDisposable: Disposable? private var badgeIconDisposable: Disposable? @@ -358,17 +363,22 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.animationCache = context.animationCache self.animationRenderer = context.animationRenderer - self.titleView = ChatListTitleView( - context: context, - theme: self.presentationData.theme, - strings: self.presentationData.strings, - animationCache: self.animationCache, - animationRenderer: self.animationRenderer - ) + switch self.location { + case .chatList: + self.titleView = ChatListTitleView( + context: context, + theme: self.presentationData.theme, + strings: self.presentationData.strings, + animationCache: self.animationCache, + animationRenderer: self.animationRenderer + ) + case .forum: + self.chatTitleView = ChatTitleView(context: self.context, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, animationCache: self.context.animationCache, animationRenderer: self.context.animationRenderer) + } self.moreBarButton = MoreHeaderButton(color: self.presentationData.theme.rootController.navigationBar.buttonColor) self.moreBarButton.isUserInteractionEnabled = true - self.moreBarButton.setContent(.more(optionsCircleImage(color: self.presentationData.theme.rootController.navigationBar.buttonColor))) + self.moreBarButton.setContent(.more(MoreHeaderButton.optionsCircleImage(color: self.presentationData.theme.rootController.navigationBar.buttonColor))) self.moreBarButtonItem = UIBarButtonItem(customDisplayNode: self.moreBarButton)! @@ -401,16 +411,96 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController guard case let .forum(peerId) = self.location else { return } - ChatListControllerImpl.openMoreMenu(context: self.context, peerId: peerId, sourceController: self, sourceView: sourceNode.view, gesture: gesture) + ChatListControllerImpl.openMoreMenu(context: self.context, peerId: peerId, sourceController: self, isViewingAsTopics: true, sourceView: sourceNode.view, gesture: gesture) } self.moreBarButton.addTarget(self, action: #selector(self.moreButtonPressed), forControlEvents: .touchUpInside) } - self.titleView.title = NetworkStatusTitle(text: title, activity: false, hasProxy: false, connectsViaProxy: false, isPasscodeSet: false, isManuallyLocked: false, peerStatus: nil) - self.navigationItem.titleView = self.titleView - - self.titleView.openStatusSetup = { [weak self] sourceView in - self?.openStatusSetup(sourceView: sourceView) + switch self.location { + case .chatList: + if let titleView = self.titleView { + titleView.title = NetworkStatusTitle(text: title, activity: false, hasProxy: false, connectsViaProxy: false, isPasscodeSet: false, isManuallyLocked: false, peerStatus: nil) + self.navigationItem.titleView = titleView + + titleView.openStatusSetup = { [weak self] sourceView in + self?.openStatusSetup(sourceView: sourceView) + } + } + self.infoReady.set(.single(true)) + case let .forum(peerId): + if let chatTitleView = self.chatTitleView { + self.navigationItem.titleView = chatTitleView + + chatTitleView.pressed = { [weak self] in + guard let self = self else { + return + } + let _ = (self.context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) + ) + |> deliverOnMainQueue).start(next: { [weak self] peer in + guard let self = self, let peer = peer, let controller = context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else { + return + } + (self.navigationController as? NavigationController)?.pushViewController(controller) + }) + } + + let peerView = Promise() + peerView.set(context.account.viewTracker.peerView(peerId)) + + var onlineMemberCount: Signal = .single(nil) + + let recentOnlineSignal: Signal = peerView.get() + |> map { view -> Bool? in + if let cachedData = view.cachedData as? CachedChannelData, let peer = peerViewMainPeer(view) as? TelegramChannel { + if case .broadcast = peer.info { + return nil + } else if let memberCount = cachedData.participantsSummary.memberCount, memberCount > 50 { + return true + } else { + return false + } + } else { + return false + } + } + |> distinctUntilChanged + |> mapToSignal { isLarge -> Signal in + if let isLarge = isLarge { + if isLarge { + return context.peerChannelMemberCategoriesContextsManager.recentOnline(account: context.account, accountPeerId: context.account.peerId, peerId: peerId) + |> map(Optional.init) + } else { + return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId) + |> map(Optional.init) + } + } else { + return .single(nil) + } + } + onlineMemberCount = recentOnlineSignal + + self.chatTitleDisposable = (combineLatest(queue: Queue.mainQueue(), + peerView.get(), + onlineMemberCount + ) + |> deliverOnMainQueue).start(next: { [weak self] peerView, onlineMemberCount in + guard let strongSelf = self, let chatTitleView = strongSelf.chatTitleView else { + return + } + + chatTitleView.titleContent = .peer(peerView: peerView, customTitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: false) + strongSelf.infoReady.set(.single(true)) + + if let channel = peerView.peers[peerView.peerId] as? TelegramChannel, !channel.flags.contains(.isForum) { + if let navigationController = strongSelf.navigationController as? NavigationController { + let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: peerId), subject: nil, botStart: nil, mode: .standard(previewing: false)) + navigationController.replaceController(strongSelf, with: chatController, animated: true) + } + } + }) + } } if !previewing { @@ -581,7 +671,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController animated = true } } - strongSelf.titleView.setTitle(NetworkStatusTitle(text: title, activity: false, hasProxy: false, connectsViaProxy: false, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus), animated: animated) + strongSelf.titleView?.setTitle(NetworkStatusTitle(text: title, activity: false, hasProxy: false, connectsViaProxy: false, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus), animated: animated) } else if isReorderingTabs { if case .chatList(.root) = strongSelf.location { strongSelf.navigationItem.setRightBarButton(nil, animated: true) @@ -592,17 +682,17 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController let (_, connectsViaProxy) = proxy switch networkState { case .waitingForNetwork: - strongSelf.titleView.title = NetworkStatusTitle(text: strongSelf.presentationData.strings.State_WaitingForNetwork, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus) + strongSelf.titleView?.title = NetworkStatusTitle(text: strongSelf.presentationData.strings.State_WaitingForNetwork, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus) case let .connecting(proxy): var text = strongSelf.presentationData.strings.State_Connecting if let layout = strongSelf.validLayout, proxy != nil && layout.metrics.widthClass != .regular && layout.size.width > 320.0 { text = strongSelf.presentationData.strings.State_ConnectingToProxy } - strongSelf.titleView.title = NetworkStatusTitle(text: text, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus) + strongSelf.titleView?.title = NetworkStatusTitle(text: text, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus) case .updating: - strongSelf.titleView.title = NetworkStatusTitle(text: strongSelf.presentationData.strings.State_Updating, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus) + strongSelf.titleView?.title = NetworkStatusTitle(text: strongSelf.presentationData.strings.State_Updating, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus) case .online: - strongSelf.titleView.title = NetworkStatusTitle(text: defaultTitle, activity: false, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus) + strongSelf.titleView?.title = NetworkStatusTitle(text: defaultTitle, activity: false, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus) } } else { var isRoot = false @@ -654,7 +744,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController var checkProxy = false switch networkState { case .waitingForNetwork: - strongSelf.titleView.title = NetworkStatusTitle(text: strongSelf.presentationData.strings.State_WaitingForNetwork, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked, peerStatus: peerStatus) + strongSelf.titleView?.title = NetworkStatusTitle(text: strongSelf.presentationData.strings.State_WaitingForNetwork, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked, peerStatus: peerStatus) case let .connecting(proxy): var text = strongSelf.presentationData.strings.State_Connecting if let layout = strongSelf.validLayout, proxy != nil && layout.metrics.widthClass != .regular && layout.size.width > 320.0 { @@ -663,11 +753,11 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController if let proxy = proxy, proxy.hasConnectionIssues { checkProxy = true } - strongSelf.titleView.title = NetworkStatusTitle(text: text, activity: true, hasProxy: isRoot && hasProxy, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked, peerStatus: peerStatus) + strongSelf.titleView?.title = NetworkStatusTitle(text: text, activity: true, hasProxy: isRoot && hasProxy, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked, peerStatus: peerStatus) case .updating: - strongSelf.titleView.title = NetworkStatusTitle(text: strongSelf.presentationData.strings.State_Updating, activity: true, hasProxy: isRoot && hasProxy, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked, peerStatus: peerStatus) + strongSelf.titleView?.title = NetworkStatusTitle(text: strongSelf.presentationData.strings.State_Updating, activity: true, hasProxy: isRoot && hasProxy, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked, peerStatus: peerStatus) case .online: - strongSelf.titleView.setTitle(NetworkStatusTitle(text: defaultTitle, activity: false, hasProxy: isRoot && hasProxy, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked, peerStatus: peerStatus), animated: (previousEditingAndNetworkState?.0 ?? false) != stateAndFilterId.state.editing) + strongSelf.titleView?.setTitle(NetworkStatusTitle(text: defaultTitle, activity: false, hasProxy: isRoot && hasProxy, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked, peerStatus: peerStatus), animated: (previousEditingAndNetworkState?.0 ?? false) != stateAndFilterId.state.editing) } if case .chatList(.root) = location, checkProxy { if strongSelf.proxyUnavailableTooltipController == nil && !strongSelf.didShowProxyUnavailableTooltipController && strongSelf.isNodeLoaded && strongSelf.displayNode.view.window != nil && strongSelf.navigationController?.topViewController === self { @@ -680,8 +770,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } } strongSelf.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceViewAndRect: { - if let strongSelf = self, let rect = strongSelf.titleView.proxyButtonFrame { - return (strongSelf.titleView, rect.insetBy(dx: 0.0, dy: -4.0)) + if let strongSelf = self, let titleView = strongSelf.titleView, let rect = titleView.proxyButtonFrame { + return (titleView, rect.insetBy(dx: 0.0, dy: -4.0)) } return nil })) @@ -708,13 +798,13 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } }) - self.titleView.toggleIsLocked = { [weak self] in + self.titleView?.toggleIsLocked = { [weak self] in if let strongSelf = self { strongSelf.context.sharedContext.appLockContext.lock() } } - self.titleView.openProxySettings = { [weak self] in + self.titleView?.openProxySettings = { [weak self] in if let strongSelf = self { (strongSelf.navigationController as? NavigationController)?.pushViewController(context.sharedContext.makeProxySettingsController(context: context)) } @@ -1044,6 +1134,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController deinit { self.openMessageFromSearchDisposable.dispose() self.titleDisposable?.dispose() + self.chatTitleDisposable?.dispose() self.badgeDisposable?.dispose() self.badgeIconDisposable?.dispose() self.passcodeLockTooltipDisposable.dispose() @@ -1064,7 +1155,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController var selectedItems = Set() var topStatusTitle = self.presentationData.strings.PeerStatusSetup_NoTimerTitle var currentSelection: Int64? - if let peerStatus = self.titleView.title.peerStatus, case let .emoji(emojiStatus) = peerStatus { + if let peerStatus = self.titleView?.title.peerStatus, case let .emoji(emojiStatus) = peerStatus { selectedItems.insert(MediaId(namespace: Namespaces.Media.CloudFile, id: emojiStatus.fileId)) currentSelection = emojiStatus.fileId @@ -1141,8 +1232,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.navigationItem.rightBarButtonItem = editItem } - self.titleView.theme = self.presentationData.theme - self.titleView.strings = self.presentationData.strings + self.titleView?.theme = self.presentationData.theme + self.titleView?.strings = self.presentationData.strings + + self.chatTitleView?.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings, hasEmbeddedTitleContent: false) self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) @@ -1221,7 +1314,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController strongSelf.context.sharedContext.navigateToForumChannel(context: strongSelf.context, peerId: channel.id, navigationController: navigationController) } else { if let threadId = threadId { - let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, navigationController: navigationController).start() + let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, navigationController: navigationController, activateInput: nil).start() strongSelf.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true) } else { var navigationAnimationOptions: NavigationAnimationOptions = [] @@ -1440,9 +1533,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController controller.completion = { title, fileId in let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconFileId: fileId) |> deliverOnMainQueue).start(next: { topicId in - let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: topicId, navigationController: navigationController).start() - -// let _ = enqueueMessages(account: context.account, peerId: peerId, messages: [.message(text: "First Message", attributes: [], inlineStickers: [:], mediaReference: nil, replyToMessageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(topicId)), localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]).start() + let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: topicId, navigationController: navigationController, activateInput: .text).start() }) } strongSelf.push(controller) @@ -1840,7 +1931,14 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController if case .chatList(.root) = self.location { self.ready.set(.never()) } else { - self.ready.set(self.chatListDisplayNode.containerNode.ready) + self.ready.set(combineLatest([ + self.chatListDisplayNode.containerNode.ready, + self.infoReady.get() + ]) + |> map { values -> Bool in + return !values.contains(where: { !$0 }) + } + |> filter { $0 }) } self.displayNodeDidLoad() @@ -1913,7 +2011,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController }) #endif - if let lockViewFrame = self.titleView.lockViewFrame, !self.didShowPasscodeLockTooltipController { + if let lockViewFrame = self.titleView?.lockViewFrame, !self.didShowPasscodeLockTooltipController { self.passcodeLockTooltipDisposable.set(combineLatest(queue: .mainQueue(), ApplicationSpecificNotice.getPasscodeLockTips(accountManager: self.context.sharedContext.accountManager), self.context.sharedContext.accountManager.accessChallengeData() |> take(1)).start(next: { [weak self] tooltipValue, passcodeView in if let strongSelf = self { if !tooltipValue { @@ -1923,8 +2021,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController let tooltipController = TooltipController(content: .text(strongSelf.presentationData.strings.DialogList_PasscodeLockHelp), baseFontSize: strongSelf.presentationData.listsFontSize.baseDisplaySize, dismissByTapOutside: true) strongSelf.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceViewAndRect: { [weak self] in - if let strongSelf = self { - return (strongSelf.titleView, lockViewFrame.offsetBy(dx: 4.0, dy: 14.0)) + if let strongSelf = self, let titleView = strongSelf.titleView { + return (titleView, lockViewFrame.offsetBy(dx: 4.0, dy: 14.0)) } return nil })) @@ -2337,9 +2435,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.moreBarButton.contextAction?(self.moreBarButton.containerNode, nil) } - public static func openMoreMenu(context: AccountContext, peerId: EnginePeer.Id, sourceController: ViewController, sourceView: UIView, gesture: ContextGesture?) { - let isViewingAsTopics: Bool = true - + public static func openMoreMenu(context: AccountContext, peerId: EnginePeer.Id, sourceController: ViewController, isViewingAsTopics: Bool, sourceView: UIView, gesture: ContextGesture?) { var items: [ContextMenuItem] = [] items.append(.action(ContextMenuActionItem(text: "View as Topics", icon: { theme in @@ -2347,8 +2443,15 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController return nil } return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) - }, action: { _, a in + }, action: { [weak sourceController] _, a in a(.default) + + guard let sourceController = sourceController, let navigationController = sourceController.navigationController as? NavigationController else { + return + } + + let chatController = context.sharedContext.makeChatListController(context: context, location: .forum(peerId: peerId), controlsHistoryPreload: false, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: false) + navigationController.replaceController(sourceController, with: chatController, animated: false) }))) items.append(.action(ContextMenuActionItem(text: "View as Messages", icon: { theme in if isViewingAsTopics { @@ -2414,7 +2517,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController controller.completion = { title, fileId in let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconFileId: fileId) |> deliverOnMainQueue).start(next: { topicId in - let _ = enqueueMessages(account: context.account, peerId: peerId, messages: [.message(text: "First Message", attributes: [], inlineStickers: [:], mediaReference: nil, replyToMessageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(topicId)), localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]).start() + if let navigationController = (sourceController.navigationController as? NavigationController) { + let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: topicId, navigationController: navigationController, activateInput: .text).start() + } }) } sourceController.push(controller) @@ -3693,8 +3798,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } public var lockViewFrame: CGRect? { - if let lockViewFrame = self.titleView.lockViewFrame { - return self.titleView.convert(lockViewFrame, to: self.view) + if let titleView = self.titleView, let lockViewFrame = titleView.lockViewFrame { + return titleView.convert(lockViewFrame, to: self.view) } else { return nil } diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 355f99e2ac..12996f06d1 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -29,6 +29,16 @@ import ComponentFlow import EmojiStatusComponent public enum ChatListItemContent { + public struct ThreadInfo: Equatable { + public var id: Int64 + public var info: EngineMessageHistoryThread.Info + + public init(id: Int64, info: EngineMessageHistoryThread.Info) { + self.id = id + self.info = info + } + } + public final class DraftState: Equatable { let text: String let entities: [MessageTextEntity] @@ -49,7 +59,7 @@ public enum ChatListItemContent { } } - case peer(messages: [EngineMessage], peer: EngineRenderedPeer, threadInfo: EngineMessageHistoryThread.Info?, combinedReadState: EnginePeerReadCounters?, isRemovedFromTotalUnreadCount: Bool, presence: EnginePeer.Presence?, hasUnseenMentions: Bool, hasUnseenReactions: Bool, draftState: DraftState?, inputActivities: [(EnginePeer, PeerInputActivity)]?, promoInfo: ChatListNodeEntryPromoInfo?, ignoreUnreadBadge: Bool, displayAsMessage: Bool, hasFailedMessages: Bool, forumThreadTitle: String?) + case peer(messages: [EngineMessage], peer: EngineRenderedPeer, threadInfo: ThreadInfo?, combinedReadState: EnginePeerReadCounters?, isRemovedFromTotalUnreadCount: Bool, presence: EnginePeer.Presence?, hasUnseenMentions: Bool, hasUnseenReactions: Bool, draftState: DraftState?, inputActivities: [(EnginePeer, PeerInputActivity)]?, promoInfo: ChatListNodeEntryPromoInfo?, ignoreUnreadBadge: Bool, displayAsMessage: Bool, hasFailedMessages: Bool, forumThreadTitle: String?) case groupReference(groupId: EngineChatList.Group, peers: [EngineChatList.GroupItem.Item], message: EngineMessage?, unreadCount: Int, hiddenByDefault: Bool) public var chatLocation: ChatLocation? { @@ -1011,7 +1021,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let promoInfo: ChatListNodeEntryPromoInfo? let displayAsMessage: Bool let hasFailedMessages: Bool - var threadInfo: EngineMessageHistoryThread.Info? + var threadInfo: ChatListItemContent.ThreadInfo? var forumThreadTitle: String? var groupHiddenByDefault = false @@ -1150,7 +1160,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let leftInset: CGFloat = params.leftInset + avatarLeftInset enum ContentData { - case chat(itemPeer: EngineRenderedPeer, threadInfo: EngineMessageHistoryThread.Info?, peer: EnginePeer?, hideAuthor: Bool, messageText: String, spoilers: [NSRange]?, customEmojiRanges: [(NSRange, ChatTextInputTextCustomEmojiAttribute)]?) + case chat(itemPeer: EngineRenderedPeer, threadInfo: ChatListItemContent.ThreadInfo?, peer: EnginePeer?, hideAuthor: Bool, messageText: String, spoilers: [NSRange]?, customEmojiRanges: [(NSRange, ChatTextInputTextCustomEmojiAttribute)]?) case group(peers: [EngineChatList.GroupItem.Item]) } @@ -1238,8 +1248,13 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } } - if let forumThreadTitle = forumThreadTitle, let peerTextValue = peerText { - peerText = "\(peerTextValue) → \(forumThreadTitle)" + if let peerTextValue = peerText, case let .channel(channel) = itemPeer.chatMainPeer, channel.flags.contains(.isForum), threadInfo == nil { + if let forumThreadTitle = forumThreadTitle { + peerText = "\(peerTextValue) → \(forumThreadTitle)" + } else { + //TODO:localize + peerText = "\(peerTextValue) → General" + } } let messageText: String @@ -1432,7 +1447,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { switch contentData { case let .chat(itemPeer, threadInfo, _, _, _, _, _): if let threadInfo = threadInfo { - titleAttributedString = NSAttributedString(string: threadInfo.title, font: titleFont, textColor: theme.titleColor) + titleAttributedString = NSAttributedString(string: threadInfo.info.title, font: titleFont, textColor: theme.titleColor) } else if let message = messages.last, case let .user(author) = message.author, displayAsMessage { titleAttributedString = NSAttributedString(string: author.id == account.peerId ? item.presentationData.strings.DialogList_You : EnginePeer.user(author).displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder), font: titleFont, textColor: theme.titleColor) } else if isPeerGroup { @@ -1687,9 +1702,17 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { textCutout = TextNodeCutout(topLeft: CGSize(width: textLeftCutout, height: 10.0), topRight: nil, bottomRight: nil) } let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(attributedString: textAttributedString, backgroundColor: nil, maximumNumberOfLines: authorAttributedString == nil ? 2 : 1, truncationType: .end, constrainedSize: CGSize(width: rawContentWidth - badgeSize, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: textCutout, insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0))) + + let maxTitleLines: Int + switch item.index { + case .forum: + maxTitleLines = 2 + case .chatList: + maxTitleLines = 1 + } let titleRectWidth = rawContentWidth - dateLayout.size.width - 10.0 - statusWidth - titleIconsWidth - let (titleLayout, titleApply) = titleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: titleRectWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (titleLayout, titleApply) = titleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: maxTitleLines, truncationType: .end, constrainedSize: CGSize(width: titleRectWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) var inputActivitiesSize: CGSize? var inputActivitiesApply: (() -> Void)? @@ -1777,6 +1800,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let titleSpacing: CGFloat = -1.0 let authorSpacing: CGFloat = -3.0 var itemHeight: CGFloat = 8.0 * 2.0 + 1.0 + itemHeight -= 21.0 + itemHeight += titleLayout.size.height itemHeight += measureLayout.size.height * 3.0 itemHeight += titleSpacing itemHeight += authorSpacing @@ -1892,7 +1917,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { transition.updateFrame(node: strongSelf.avatarNode, frame: avatarFrame) strongSelf.updateVideoVisibility() - if let iconFileId = threadInfo?.icon { + if let threadInfo = threadInfo { let avatarIconView: ComponentHostView if let current = strongSelf.avatarIconView { avatarIconView = current @@ -1902,11 +1927,18 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { strongSelf.contextContainer.view.addSubview(avatarIconView) } + let avatarIconContent: EmojiStatusComponent.Content + if let fileId = threadInfo.info.icon { + avatarIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 40.0, height: 40.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: nil, loopMode: .forever) + } else { + avatarIconContent = .topic(title: String(threadInfo.info.title.prefix(1)), colorIndex: Int(clamping: abs(threadInfo.id))) + } + let avatarIconComponent = EmojiStatusComponent( context: item.context, animationCache: item.interaction.animationCache, animationRenderer: item.interaction.animationRenderer, - content: .animation(content: .customEmoji(fileId: iconFileId), size: CGSize(width: 40.0, height: 40.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: nil, loopMode: .forever), + content: avatarIconContent, isVisibleForAnimations: strongSelf.visibilityStatus, action: nil ) diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift index 60590c69d8..1b9c9cfad1 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift @@ -55,7 +55,7 @@ enum ChatListNodeEntry: Comparable, Identifiable { isRemovedFromTotalUnreadCount: Bool, draftState: ChatListItemContent.DraftState?, peer: EngineRenderedPeer, - threadInfo: EngineMessageHistoryThread.Info?, + threadInfo: ChatListItemContent.ThreadInfo?, presence: EnginePeer.Presence?, hasUnseenMentions: Bool, hasUnseenReactions: Bool, @@ -377,8 +377,16 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState if let peerId { inputActivities = state.peerInputActivities?.activities[peerId] } + + var threadId: Int64 = 0 + switch entry.index { + case let .forum(_, threadIdValue, _, _): + threadId = threadIdValue + default: + break + } - result.append(.PeerEntry(index: offsetPinnedIndex(entry.index, offset: pinnedIndexOffset), presentationData: state.presentationData, messages: updatedMessages, readState: updatedCombinedReadState, isRemovedFromTotalUnreadCount: entry.isMuted, draftState: draftState, peer: entry.renderedPeer, threadInfo: entry.threadInfo, presence: entry.presence, hasUnseenMentions: entry.hasUnseenMentions, hasUnseenReactions: entry.hasUnseenReactions, editing: state.editing, hasActiveRevealControls: hasActiveRevealControls, selected: isSelected, inputActivities: inputActivities, promoInfo: nil, hasFailedMessages: entry.hasFailed, isContact: entry.isContact, forumThreadTitle: entry.forumTopicTitle)) + result.append(.PeerEntry(index: offsetPinnedIndex(entry.index, offset: pinnedIndexOffset), presentationData: state.presentationData, messages: updatedMessages, readState: updatedCombinedReadState, isRemovedFromTotalUnreadCount: entry.isMuted, draftState: draftState, peer: entry.renderedPeer, threadInfo: entry.threadInfo.flatMap { ChatListItemContent.ThreadInfo(id: threadId, info: $0) }, presence: entry.presence, hasUnseenMentions: entry.hasUnseenMentions, hasUnseenReactions: entry.hasUnseenReactions, editing: state.editing, hasActiveRevealControls: hasActiveRevealControls, selected: isSelected, inputActivities: inputActivities, promoInfo: nil, hasFailedMessages: entry.hasFailed, isContact: entry.isContact, forumThreadTitle: entry.forumTopicTitle)) } if !view.hasLater { var pinningIndex: UInt16 = UInt16(pinnedIndexOffset == 0 ? 0 : (pinnedIndexOffset - 1)) @@ -440,6 +448,14 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState let peerId = index.messageIndex.id.peerId let isSelected = state.selectedPeerIds.contains(peerId) + var threadId: Int64 = 0 + switch item.item.index { + case let .forum(_, threadIdValue, _, _): + threadId = threadIdValue + default: + break + } + result.append(.PeerEntry( index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: pinningIndex, messageIndex: index.messageIndex)), presentationData: state.presentationData, @@ -448,7 +464,7 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState isRemovedFromTotalUnreadCount: item.item.isMuted, draftState: draftState, peer: item.item.renderedPeer, - threadInfo: item.item.threadInfo, + threadInfo: item.item.threadInfo.flatMap { ChatListItemContent.ThreadInfo(id: threadId, info: $0) }, presence: item.item.presence, hasUnseenMentions: item.item.hasUnseenMentions, hasUnseenReactions: item.item.hasUnseenReactions, diff --git a/submodules/Components/ReactionListContextMenuContent/Sources/ReactionListContextMenuContent.swift b/submodules/Components/ReactionListContextMenuContent/Sources/ReactionListContextMenuContent.swift index f27503f8ad..9a52a5229c 100644 --- a/submodules/Components/ReactionListContextMenuContent/Sources/ReactionListContextMenuContent.swift +++ b/submodules/Components/ReactionListContextMenuContent/Sources/ReactionListContextMenuContent.swift @@ -667,7 +667,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent let reaction: MessageReaction.Reaction? private let requestUpdate: (ReactionsTabNode, ContainedViewLayoutTransition) -> Void private let requestUpdateApparentHeight: (ReactionsTabNode, ContainedViewLayoutTransition) -> Void - private let openPeer: (PeerId) -> Void + private let openPeer: (EnginePeer) -> Void private var hasMore: Bool = false @@ -699,7 +699,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent readStats: MessageReadStats?, requestUpdate: @escaping (ReactionsTabNode, ContainedViewLayoutTransition) -> Void, requestUpdateApparentHeight: @escaping (ReactionsTabNode, ContainedViewLayoutTransition) -> Void, - openPeer: @escaping (PeerId) -> Void + openPeer: @escaping (EnginePeer) -> Void ) { self.context = context self.availableReactions = availableReactions @@ -800,9 +800,9 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent itemNode = current } else { let openPeer = self.openPeer - let peerId = item.peer.id + let peer = item.peer itemNode = ItemNode(context: self.context, availableReactions: self.availableReactions, animationCache: self.animationCache, animationRenderer: self.animationRenderer, action: { - openPeer(peerId) + openPeer(peer) }) self.itemNodes[index] = itemNode self.scrollNode.addSubnode(itemNode) @@ -972,7 +972,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent } private var interactiveTransitionState: InteractiveTransitionState? - private let openPeer: (PeerId) -> Void + private let openPeer: (EnginePeer) -> Void private(set) var apparentHeight: CGFloat = 0.0 @@ -987,7 +987,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent requestUpdate: @escaping (ContainedViewLayoutTransition) -> Void, requestUpdateApparentHeight: @escaping (ContainedViewLayoutTransition) -> Void, back: (() -> Void)?, - openPeer: @escaping (PeerId) -> Void + openPeer: @escaping (EnginePeer) -> Void ) { self.context = context self.availableReactions = availableReactions @@ -1334,7 +1334,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent let reaction: MessageReaction.Reaction? let readStats: MessageReadStats? let back: (() -> Void)? - let openPeer: (PeerId) -> Void + let openPeer: (EnginePeer) -> Void public init( context: AccountContext, @@ -1345,7 +1345,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent reaction: MessageReaction.Reaction?, readStats: MessageReadStats?, back: (() -> Void)?, - openPeer: @escaping (PeerId) -> Void + openPeer: @escaping (EnginePeer) -> Void ) { self.context = context self.availableReactions = availableReactions diff --git a/submodules/GalleryUI/Sources/GalleryController.swift b/submodules/GalleryUI/Sources/GalleryController.swift index f727cb38ac..12283c5772 100644 --- a/submodules/GalleryUI/Sources/GalleryController.swift +++ b/submodules/GalleryUI/Sources/GalleryController.swift @@ -669,7 +669,12 @@ public class GalleryController: ViewController, StandalonePresentableController, case let .textMention(mention): strongSelf.actionInteraction?.openPeerMention(mention) case let .peerMention(peerId, _): - strongSelf.actionInteraction?.openPeer(peerId) + let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> deliverOnMainQueue).start(next: { peer in + if let strongSelf = self, let peer = peer { + strongSelf.actionInteraction?.openPeer(peer) + } + }) case let .botCommand(command): strongSelf.actionInteraction?.openBotCommand(command) case let .hashtag(peerName, hashtag): @@ -775,7 +780,13 @@ public class GalleryController: ViewController, StandalonePresentableController, actionSheet?.dismissAnimated() if let strongSelf = self { strongSelf.dismiss(forceAway: false) - strongSelf.actionInteraction?.openPeer(peerId) + + let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> deliverOnMainQueue).start(next: { peer in + if let strongSelf = self, let peer = peer { + strongSelf.actionInteraction?.openPeer(peer) + } + }) } })) if !mention.isEmpty { diff --git a/submodules/InstantPageUI/Sources/InstantPageAnchorItem.swift b/submodules/InstantPageUI/Sources/InstantPageAnchorItem.swift index 44bbcd1fc4..5356646d0d 100644 --- a/submodules/InstantPageUI/Sources/InstantPageAnchorItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageAnchorItem.swift @@ -28,7 +28,7 @@ final class InstantPageAnchorItem: InstantPageItem { func drawInTile(context: CGContext) { } - func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? { + func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? { return nil } diff --git a/submodules/InstantPageUI/Sources/InstantPageArticleItem.swift b/submodules/InstantPageUI/Sources/InstantPageArticleItem.swift index 5e261f10e0..f83b6ab86f 100644 --- a/submodules/InstantPageUI/Sources/InstantPageArticleItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageArticleItem.swift @@ -35,7 +35,7 @@ final class InstantPageArticleItem: InstantPageItem { self.hasRTL = hasRTL } - func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? { + func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? { return InstantPageArticleNode(context: context, item: self, webPage: self.webPage, strings: strings, theme: theme, contentItems: self.contentItems, contentSize: self.contentSize, cover: self.cover, url: self.url, webpageId: self.webpageId, openUrl: openUrl) } diff --git a/submodules/InstantPageUI/Sources/InstantPageAudioItem.swift b/submodules/InstantPageUI/Sources/InstantPageAudioItem.swift index f153258914..21fded36a4 100644 --- a/submodules/InstantPageUI/Sources/InstantPageAudioItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageAudioItem.swift @@ -24,7 +24,7 @@ final class InstantPageAudioItem: InstantPageItem { self.medias = [media] } - func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? { + func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? { return InstantPageAudioNode(context: context, strings: strings, theme: theme, webPage: self.webpage, media: self.media, openMedia: openMedia) } diff --git a/submodules/InstantPageUI/Sources/InstantPageContentNode.swift b/submodules/InstantPageUI/Sources/InstantPageContentNode.swift index ca5bf723df..5d342c96a7 100644 --- a/submodules/InstantPageUI/Sources/InstantPageContentNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageContentNode.swift @@ -18,7 +18,7 @@ final class InstantPageContentNode : ASDisplayNode { private let openMedia: (InstantPageMedia) -> Void private let longPressMedia: (InstantPageMedia) -> Void - private let openPeer: (PeerId) -> Void + private let openPeer: (EnginePeer) -> Void private let openUrl: (InstantPageUrlItem) -> Void var currentLayoutTiles: [InstantPageTile] = [] @@ -40,7 +40,7 @@ final class InstantPageContentNode : ASDisplayNode { private var previousVisibleBounds: CGRect? - init(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, sourcePeerType: MediaAutoDownloadPeerType, theme: InstantPageTheme, items: [InstantPageItem], contentSize: CGSize, inOverlayPanel: Bool = false, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void) { + init(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, sourcePeerType: MediaAutoDownloadPeerType, theme: InstantPageTheme, items: [InstantPageItem], contentSize: CGSize, inOverlayPanel: Bool = false, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void) { self.context = context self.strings = strings self.nameDisplayOrder = nameDisplayOrder diff --git a/submodules/InstantPageUI/Sources/InstantPageController.swift b/submodules/InstantPageUI/Sources/InstantPageController.swift index d0d77e26bf..9843215f74 100644 --- a/submodules/InstantPageUI/Sources/InstantPageController.swift +++ b/submodules/InstantPageUI/Sources/InstantPageController.swift @@ -151,9 +151,9 @@ public final class InstantPageController: ViewController { self?.present(c, in: .window(.root), with: a, blockInteraction: true) }, pushController: { [weak self] c in (self?.navigationController as? NavigationController)?.pushViewController(c) - }, openPeer: { [weak self] peerId in + }, openPeer: { [weak self] peer in if let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController { - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId), animated: true)) + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peer.id), animated: true)) } }, navigateBack: { [weak self] in if let strongSelf = self, let controllers = strongSelf.navigationController?.viewControllers.reversed() { diff --git a/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift b/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift index d3e37e1c43..ac66805218 100644 --- a/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift @@ -34,7 +34,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { private let getNavigationController: () -> NavigationController? private let present: (ViewController, Any?) -> Void private let pushController: (ViewController) -> Void - private let openPeer: (PeerId) -> Void + private let openPeer: (EnginePeer) -> Void private var webPage: TelegramMediaWebpage? private var initialAnchor: String? @@ -92,7 +92,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { return InstantPageStoredState(contentOffset: Double(self.scrollNode.view.contentOffset.y), details: details) } - init(controller: InstantPageController, context: AccountContext, settings: InstantPagePresentationSettings?, themeSettings: PresentationThemeSettings?, presentationTheme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, autoNightModeTriggered: Bool, statusBar: StatusBar, sourcePeerType: MediaAutoDownloadPeerType, getNavigationController: @escaping () -> NavigationController?, present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, openPeer: @escaping (PeerId) -> Void, navigateBack: @escaping () -> Void) { + init(controller: InstantPageController, context: AccountContext, settings: InstantPagePresentationSettings?, themeSettings: PresentationThemeSettings?, presentationTheme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, autoNightModeTriggered: Bool, statusBar: StatusBar, sourcePeerType: MediaAutoDownloadPeerType, getNavigationController: @escaping () -> NavigationController?, present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, openPeer: @escaping (EnginePeer) -> Void, navigateBack: @escaping () -> Void) { self.controller = controller self.context = context self.presentationTheme = presentationTheme @@ -1326,22 +1326,22 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { } default: strongSelf.loadProgress.set(1.0) - strongSelf.context.sharedContext.openResolvedUrl(result, context: strongSelf.context, urlContext: .generic, navigationController: strongSelf.getNavigationController(), forceExternal: false, openPeer: { peerId, navigation in + strongSelf.context.sharedContext.openResolvedUrl(result, context: strongSelf.context, urlContext: .generic, navigationController: strongSelf.getNavigationController(), forceExternal: false, openPeer: { peer, navigation in switch navigation { case let .chat(_, subject, peekData): if let navigationController = strongSelf.getNavigationController() { - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId), subject: subject, peekData: peekData)) + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peer.id), subject: subject, peekData: peekData)) } case let .withBotStartPayload(botStart): if let navigationController = strongSelf.getNavigationController() { - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId), botStart: botStart, keepStack: .always)) + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peer.id), botStart: botStart, keepStack: .always)) } case let .withAttachBot(attachBotStart): if let navigationController = strongSelf.getNavigationController() { - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId), attachBotStart: attachBotStart)) + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peer.id), attachBotStart: attachBotStart)) } case .info: - let _ = (strongSelf.context.account.postbox.loadedPeerWithId(peerId) + let _ = (strongSelf.context.account.postbox.loadedPeerWithId(peer.id) |> deliverOnMainQueue).start(next: { peer in if let strongSelf = self { if let controller = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { diff --git a/submodules/InstantPageUI/Sources/InstantPageDetailsItem.swift b/submodules/InstantPageUI/Sources/InstantPageDetailsItem.swift index a6f7c8e0b6..9d610105b7 100644 --- a/submodules/InstantPageUI/Sources/InstantPageDetailsItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageDetailsItem.swift @@ -40,7 +40,7 @@ final class InstantPageDetailsItem: InstantPageItem { self.index = index } - func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? { + func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? { var expanded: Bool? if let expandedDetails = currentExpandedDetails, let currentlyExpanded = expandedDetails[self.index] { expanded = currentlyExpanded diff --git a/submodules/InstantPageUI/Sources/InstantPageDetailsNode.swift b/submodules/InstantPageUI/Sources/InstantPageDetailsNode.swift index c7b3589573..a65e0dc7b4 100644 --- a/submodules/InstantPageUI/Sources/InstantPageDetailsNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageDetailsNode.swift @@ -35,7 +35,7 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode { var requestLayoutUpdate: ((Bool) -> Void)? - init(context: AccountContext, sourcePeerType: MediaAutoDownloadPeerType, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, item: InstantPageDetailsItem, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, currentlyExpanded: Bool?, updateDetailsExpanded: @escaping (Bool) -> Void) { + init(context: AccountContext, sourcePeerType: MediaAutoDownloadPeerType, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, item: InstantPageDetailsItem, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, currentlyExpanded: Bool?, updateDetailsExpanded: @escaping (Bool) -> Void) { self.context = context self.strings = strings self.nameDisplayOrder = nameDisplayOrder diff --git a/submodules/InstantPageUI/Sources/InstantPageFeedbackItem.swift b/submodules/InstantPageUI/Sources/InstantPageFeedbackItem.swift index 14cfe67406..122c8e0702 100644 --- a/submodules/InstantPageUI/Sources/InstantPageFeedbackItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageFeedbackItem.swift @@ -21,7 +21,7 @@ final class InstantPageFeedbackItem: InstantPageItem { self.webPage = webPage } - func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? { + func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? { return InstantPageFeedbackNode(context: context, strings: strings, theme: theme, webPage: self.webPage, openUrl: openUrl) } diff --git a/submodules/InstantPageUI/Sources/InstantPageImageItem.swift b/submodules/InstantPageUI/Sources/InstantPageImageItem.swift index ac9f99c4fa..05aae91fd3 100644 --- a/submodules/InstantPageUI/Sources/InstantPageImageItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageImageItem.swift @@ -45,7 +45,7 @@ final class InstantPageImageItem: InstantPageItem { self.fit = fit } - func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? { + func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? { return InstantPageImageNode(context: context, sourcePeerType: sourcePeerType, theme: theme, webPage: self.webPage, media: self.media, attributes: self.attributes, interactive: self.interactive, roundCorners: self.roundCorners, fit: self.fit, openMedia: openMedia, longPressMedia: longPressMedia, activatePinchPreview: activatePinchPreview, pinchPreviewFinished: pinchPreviewFinished) } diff --git a/submodules/InstantPageUI/Sources/InstantPageItem.swift b/submodules/InstantPageUI/Sources/InstantPageItem.swift index 9bf8e57fc9..0ef9bc2226 100644 --- a/submodules/InstantPageUI/Sources/InstantPageItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageItem.swift @@ -16,7 +16,7 @@ protocol InstantPageItem { func matchesAnchor(_ anchor: String) -> Bool func drawInTile(context: CGContext) - func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? + func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? func matchesNode(_ node: InstantPageNode) -> Bool func linkSelectionRects(at point: CGPoint) -> [CGRect] diff --git a/submodules/InstantPageUI/Sources/InstantPagePeerReferenceItem.swift b/submodules/InstantPageUI/Sources/InstantPagePeerReferenceItem.swift index 1337b368c4..3c2a91b33d 100644 --- a/submodules/InstantPageUI/Sources/InstantPagePeerReferenceItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPagePeerReferenceItem.swift @@ -27,7 +27,7 @@ final class InstantPagePeerReferenceItem: InstantPageItem { self.rtl = rtl } - func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? { + func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? { return InstantPagePeerReferenceNode(context: context, strings: strings, nameDisplayOrder: nameDisplayOrder, theme: theme, initialPeer: self.initialPeer, safeInset: self.safeInset, transparent: self.transparent, rtl: self.rtl, openPeer: openPeer) } diff --git a/submodules/InstantPageUI/Sources/InstantPagePeerReferenceNode.swift b/submodules/InstantPageUI/Sources/InstantPagePeerReferenceNode.swift index d69acf174f..1fb24ddb06 100644 --- a/submodules/InstantPageUI/Sources/InstantPagePeerReferenceNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPagePeerReferenceNode.swift @@ -55,7 +55,7 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode { private var strings: PresentationStrings private var nameDisplayOrder: PresentationPersonNameOrder private var theme: InstantPageTheme - private let openPeer: (PeerId) -> Void + private let openPeer: (EnginePeer) -> Void private let highlightedBackgroundNode: ASDisplayNode private let buttonNode: HighlightableButtonNode @@ -70,7 +70,7 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode { private let joinDisposable = MetaDisposable() private var joinState: JoinState = .none - init(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, initialPeer: Peer, safeInset: CGFloat, transparent: Bool, rtl: Bool, openPeer: @escaping (PeerId) -> Void) { + init(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, initialPeer: Peer, safeInset: CGFloat, transparent: Bool, rtl: Bool, openPeer: @escaping (EnginePeer) -> Void) { self.context = context self.strings = strings self.nameDisplayOrder = nameDisplayOrder @@ -300,7 +300,7 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode { @objc func buttonPressed() { if let peer = self.peer { - self.openPeer(peer.id) + self.openPeer(EnginePeer(peer)) } } diff --git a/submodules/InstantPageUI/Sources/InstantPagePlayableVideoItem.swift b/submodules/InstantPageUI/Sources/InstantPagePlayableVideoItem.swift index 1058f5b778..a68ae01c39 100644 --- a/submodules/InstantPageUI/Sources/InstantPagePlayableVideoItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPagePlayableVideoItem.swift @@ -29,7 +29,7 @@ final class InstantPagePlayableVideoItem: InstantPageItem { self.interactive = interactive } - func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? { + func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? { return InstantPagePlayableVideoNode(context: context, webPage: self.webPage, theme: theme, media: self.media, interactive: self.interactive, openMedia: openMedia) } diff --git a/submodules/InstantPageUI/Sources/InstantPageShapeItem.swift b/submodules/InstantPageUI/Sources/InstantPageShapeItem.swift index f4508b3269..a1703ed3ff 100644 --- a/submodules/InstantPageUI/Sources/InstantPageShapeItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageShapeItem.swift @@ -62,7 +62,7 @@ final class InstantPageShapeItem: InstantPageItem { return false } - func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? { + func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? { return nil } diff --git a/submodules/InstantPageUI/Sources/InstantPageSlideshowItem.swift b/submodules/InstantPageUI/Sources/InstantPageSlideshowItem.swift index 358134ecc5..2d19757b57 100644 --- a/submodules/InstantPageUI/Sources/InstantPageSlideshowItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageSlideshowItem.swift @@ -21,7 +21,7 @@ final class InstantPageSlideshowItem: InstantPageItem { self.medias = medias } - func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? { + func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? { return InstantPageSlideshowNode(context: context, sourcePeerType: sourcePeerType, theme: theme, webPage: webPage, medias: self.medias, openMedia: openMedia, longPressMedia: longPressMedia) } diff --git a/submodules/InstantPageUI/Sources/InstantPageSubContentNode.swift b/submodules/InstantPageUI/Sources/InstantPageSubContentNode.swift index 278a7d6875..89b4ab0e93 100644 --- a/submodules/InstantPageUI/Sources/InstantPageSubContentNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageSubContentNode.swift @@ -18,7 +18,7 @@ final class InstantPageSubContentNode : ASDisplayNode { private let openMedia: (InstantPageMedia) -> Void private let longPressMedia: (InstantPageMedia) -> Void - private let openPeer: (PeerId) -> Void + private let openPeer: (EnginePeer) -> Void private let openUrl: (InstantPageUrlItem) -> Void var currentLayoutTiles: [InstantPageTile] = [] @@ -40,7 +40,7 @@ final class InstantPageSubContentNode : ASDisplayNode { private var previousVisibleBounds: CGRect? - init(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, sourcePeerType: MediaAutoDownloadPeerType, theme: InstantPageTheme, items: [InstantPageItem], contentSize: CGSize, inOverlayPanel: Bool = false, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void) { + init(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, sourcePeerType: MediaAutoDownloadPeerType, theme: InstantPageTheme, items: [InstantPageItem], contentSize: CGSize, inOverlayPanel: Bool = false, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void) { self.context = context self.strings = strings self.nameDisplayOrder = nameDisplayOrder diff --git a/submodules/InstantPageUI/Sources/InstantPageTableItem.swift b/submodules/InstantPageUI/Sources/InstantPageTableItem.swift index 3600c0ca44..4188955b66 100644 --- a/submodules/InstantPageUI/Sources/InstantPageTableItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageTableItem.swift @@ -200,7 +200,7 @@ final class InstantPageTableItem: InstantPageScrollableItem { return false } - func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? { + func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? { var additionalNodes: [InstantPageNode] = [] for cell in self.cells { for item in cell.additionalItems { diff --git a/submodules/InstantPageUI/Sources/InstantPageTextItem.swift b/submodules/InstantPageUI/Sources/InstantPageTextItem.swift index dc87cd83ce..d1c9287ecd 100644 --- a/submodules/InstantPageUI/Sources/InstantPageTextItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageTextItem.swift @@ -436,7 +436,7 @@ final class InstantPageTextItem: InstantPageItem { return false } - func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? { + func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? { return nil } @@ -485,7 +485,7 @@ final class InstantPageScrollableTextItem: InstantPageScrollableItem { context.restoreGState() } - func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? { + func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? { var additionalNodes: [InstantPageNode] = [] for item in additionalItems { if item.wantsNode { diff --git a/submodules/InstantPageUI/Sources/InstantPageWebEmbedItem.swift b/submodules/InstantPageUI/Sources/InstantPageWebEmbedItem.swift index 304e0311b5..71d24dc6d8 100644 --- a/submodules/InstantPageUI/Sources/InstantPageWebEmbedItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageWebEmbedItem.swift @@ -25,7 +25,7 @@ final class InstantPageWebEmbedItem: InstantPageItem { self.enableScrolling = enableScrolling } - func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? { + func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourcePeerType: MediaAutoDownloadPeerType, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? { return InstantPageWebEmbedNode(frame: self.frame, url: self.url, html: self.html, enableScrolling: self.enableScrolling, updateWebEmbedHeight: updateWebEmbedHeight) } diff --git a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewController.swift b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewController.swift index d13f3fec18..4bd4dcd33f 100644 --- a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewController.swift +++ b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewController.swift @@ -22,14 +22,14 @@ public final class JoinLinkPreviewController: ViewController { private let link: String private var isRequest = false private var isGroup = false - private let navigateToPeer: (EnginePeer.Id, ChatPeekTimeout?) -> Void + private let navigateToPeer: (EnginePeer, ChatPeekTimeout?) -> Void private let parentNavigationController: NavigationController? private var resolvedState: ExternalJoiningChatState? private var presentationData: PresentationData private let disposable = MetaDisposable() - public init(context: AccountContext, link: String, navigateToPeer: @escaping (EnginePeer.Id, ChatPeekTimeout?) -> Void, parentNavigationController: NavigationController?, resolvedState: ExternalJoiningChatState? = nil) { + public init(context: AccountContext, link: String, navigateToPeer: @escaping (EnginePeer, ChatPeekTimeout?) -> Void, parentNavigationController: NavigationController?, resolvedState: ExternalJoiningChatState? = nil) { self.context = context self.link = link self.navigateToPeer = navigateToPeer @@ -87,11 +87,11 @@ public final class JoinLinkPreviewController: ViewController { let data = JoinLinkPreviewData(isGroup: invite.participants != nil, isJoined: false) strongSelf.controllerNode.setInvitePeer(image: invite.photoRepresentation, title: invite.title, memberCount: invite.participantsCount, members: invite.participants?.map({ $0 }) ?? [], data: data) } - case let .alreadyJoined(peerId): - strongSelf.navigateToPeer(peerId, nil) + case let .alreadyJoined(peer): + strongSelf.navigateToPeer(peer, nil) strongSelf.dismiss() - case let .peek(peerId, deadline): - strongSelf.navigateToPeer(peerId, ChatPeekTimeout(deadline: deadline, linkData: strongSelf.link)) + case let .peek(peer, deadline): + strongSelf.navigateToPeer(peer, ChatPeekTimeout(deadline: deadline, linkData: strongSelf.link)) strongSelf.dismiss() case .invalidHash: let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } @@ -139,13 +139,13 @@ public final class JoinLinkPreviewController: ViewController { } private func join() { - self.disposable.set((self.context.engine.peers.joinChatInteractively(with: self.link) |> deliverOnMainQueue).start(next: { [weak self] peerId in + self.disposable.set((self.context.engine.peers.joinChatInteractively(with: self.link) |> deliverOnMainQueue).start(next: { [weak self] peer in if let strongSelf = self { if strongSelf.isRequest { strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .inviteRequestSent(title: strongSelf.presentationData.strings.MemberRequests_RequestToJoinSent, text: strongSelf.isGroup ? strongSelf.presentationData.strings.MemberRequests_RequestToJoinSentDescriptionGroup : strongSelf.presentationData.strings.MemberRequests_RequestToJoinSentDescriptionChannel ), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) } else { - if let peerId = peerId { - strongSelf.navigateToPeer(peerId, nil) + if let peer = peer { + strongSelf.navigateToPeer(peer, nil) } } strongSelf.dismiss() diff --git a/submodules/QrCodeUI/Sources/QrCodeScanScreen.swift b/submodules/QrCodeUI/Sources/QrCodeScanScreen.swift index 899a758337..34cbfd3363 100644 --- a/submodules/QrCodeUI/Sources/QrCodeScanScreen.swift +++ b/submodules/QrCodeUI/Sources/QrCodeScanScreen.swift @@ -692,11 +692,11 @@ private final class QrCodeScanScreenNode: ViewControllerTracingNode, UIScrollVie guard let navigationController = self.controller?.navigationController as? NavigationController else { return false } - self.context.sharedContext.openResolvedUrl(result, context: self.context, urlContext: .generic, navigationController: navigationController, forceExternal: false, openPeer: { [weak self] peerId, navigation in + self.context.sharedContext.openResolvedUrl(result, context: self.context, urlContext: .generic, navigationController: navigationController, forceExternal: false, openPeer: { [weak self] peer, navigation in guard let strongSelf = self else { return } - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId), subject: nil, keepStack: .always, peekData: nil, completion: { [weak navigationController] _ in + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peer.id), subject: nil, keepStack: .always, peekData: nil, completion: { [weak navigationController] _ in if let navigationController = navigationController { var viewControllers = navigationController.viewControllers viewControllers = viewControllers.filter { controller in diff --git a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift index cd1f8d11c5..74254c9291 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift @@ -18,9 +18,9 @@ private final class SelectivePrivacyPeersControllerArguments { let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void let removePeer: (PeerId) -> Void let addPeer: () -> Void - let openPeer: (PeerId) -> Void + let openPeer: (EnginePeer) -> Void - init(context: AccountContext, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, removePeer: @escaping (PeerId) -> Void, addPeer: @escaping () -> Void, openPeer: @escaping (PeerId) -> Void) { + init(context: AccountContext, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, removePeer: @escaping (PeerId) -> Void, addPeer: @escaping () -> Void, openPeer: @escaping (EnginePeer) -> Void) { self.context = context self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions self.removePeer = removePeer @@ -141,7 +141,7 @@ private enum SelectivePrivacyPeersEntry: ItemListNodeEntry { } } return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(peer.peer), presence: nil, text: text, label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: { - arguments.openPeer(peer.peer.id) + arguments.openPeer(EnginePeer(peer.peer)) }, setPeerIdWithRevealedOptions: { previousId, id in arguments.setPeerIdWithRevealedOptions(previousId, id) }, removePeer: { peerId in @@ -323,14 +323,11 @@ public func selectivePrivacyPeersController(context: AccountContext, title: Stri controller?.dismiss() })) presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) - }, openPeer: { peerId in - let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) - |> deliverOnMainQueue).start(next: { peer in - guard let peer = peer, let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else { - return - } - pushControllerImpl?(controller) - }) + }, openPeer: { peer in + guard let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else { + return + } + pushControllerImpl?(controller) }) var previousPeers: [SelectivePrivacyPeer]? diff --git a/submodules/SettingsUI/Sources/UsernameSetupController.swift b/submodules/SettingsUI/Sources/UsernameSetupController.swift index 90ce0726c1..d20e49df53 100644 --- a/submodules/SettingsUI/Sources/UsernameSetupController.swift +++ b/submodules/SettingsUI/Sources/UsernameSetupController.swift @@ -572,6 +572,10 @@ public func usernameSetupController(context: AccountContext) -> ViewController { }) }) + controller.beganInteractiveDragging = { + dismissInputImpl?() + } + dismissImpl = { [weak controller] in controller?.view.endEditing(true) controller?.dismiss() diff --git a/submodules/StatisticsUI/Sources/GroupStatsController.swift b/submodules/StatisticsUI/Sources/GroupStatsController.swift index 2bac63f92e..dbb709b20c 100644 --- a/submodules/StatisticsUI/Sources/GroupStatsController.swift +++ b/submodules/StatisticsUI/Sources/GroupStatsController.swift @@ -22,7 +22,7 @@ private let maxUsersDisplayedHighLimit: Int32 = 12 private final class GroupStatsControllerArguments { let context: AccountContext let loadDetailedGraph: (StatsGraph, Int64) -> Signal - let openPeer: (PeerId) -> Void + let openPeer: (EnginePeer) -> Void let openPeerHistory: (PeerId) -> Void let openPeerAdminActions: (PeerId) -> Void let promotePeer: (PeerId) -> Void @@ -33,7 +33,7 @@ private final class GroupStatsControllerArguments { let setAdminsPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void let setInvitersPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void - init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal, openPeer: @escaping (PeerId) -> Void, openPeerHistory: @escaping (PeerId) -> Void, openPeerAdminActions: @escaping (PeerId) -> Void, promotePeer: @escaping (PeerId) -> Void, expandTopPosters: @escaping () -> Void, expandTopAdmins: @escaping () -> Void, expandTopInviters: @escaping () -> Void, setPostersPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setAdminsPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setInvitersPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void) { + init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal, openPeer: @escaping (EnginePeer) -> Void, openPeerHistory: @escaping (PeerId) -> Void, openPeerAdminActions: @escaping (PeerId) -> Void, promotePeer: @escaping (PeerId) -> Void, expandTopPosters: @escaping () -> Void, expandTopAdmins: @escaping () -> Void, expandTopInviters: @escaping () -> Void, setPostersPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setAdminsPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setInvitersPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void) { self.context = context self.loadDetailedGraph = loadDetailedGraph self.openPeer = openPeer @@ -412,7 +412,7 @@ private enum StatsEntry: ItemListNodeEntry { } } return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: EnginePeer(peer), height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", "), .secondary), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: arguments.context.account.peerId != peer.id, sectionId: self.section, action: { - arguments.openPeer(peer.id) + arguments.openPeer(EnginePeer(peer)) }, setPeerIdWithRevealedOptions: { peerId, fromPeerId in arguments.setPostersPeerIdWithRevealedOptions(peerId, fromPeerId) }, removePeer: { _ in }) @@ -443,7 +443,7 @@ private enum StatsEntry: ItemListNodeEntry { } } return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: EnginePeer(peer), height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", "), .secondary), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: arguments.context.account.peerId != peer.id, sectionId: self.section, action: { - arguments.openPeer(peer.id) + arguments.openPeer(EnginePeer(peer)) }, setPeerIdWithRevealedOptions: { peerId, fromPeerId in arguments.setAdminsPeerIdWithRevealedOptions(peerId, fromPeerId) }, removePeer: { _ in }) @@ -466,7 +466,7 @@ private enum StatsEntry: ItemListNodeEntry { } } return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: EnginePeer(peer), height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", "), .secondary), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: arguments.context.account.peerId != peer.id, sectionId: self.section, action: { - arguments.openPeer(peer.id) + arguments.openPeer(EnginePeer(peer)) }, setPeerIdWithRevealedOptions: { peerId, fromPeerId in arguments.setInvitersPeerIdWithRevealedOptions(peerId, fromPeerId) }, removePeer: { _ in }) @@ -721,7 +721,7 @@ public func groupStatsController(context: AccountContext, updatedPresentationDat let datacenterId: Int32 = statsDatacenterId ?? 0 - var openPeerImpl: ((PeerId) -> Void)? + var openPeerImpl: ((EnginePeer) -> Void)? var openPeerHistoryImpl: ((PeerId) -> Void)? var openPeerAdminActionsImpl: ((PeerId) -> Void)? var promotePeerImpl: ((PeerId) -> Void)? @@ -779,8 +779,8 @@ public func groupStatsController(context: AccountContext, updatedPresentationDat let arguments = GroupStatsControllerArguments(context: context, loadDetailedGraph: { graph, x -> Signal in return statsContext.loadDetailedGraph(graph, x: x) - }, openPeer: { peerId in - openPeerImpl?(peerId) + }, openPeer: { peer in + openPeerImpl?(peer) }, openPeerHistory: { peerId in openPeerHistoryImpl?(peerId) }, openPeerAdminActions: { peerId in @@ -864,9 +864,9 @@ public func groupStatsController(context: AccountContext, updatedPresentationDat controller.didDisappear = { [weak controller] _ in controller?.clearItemNodesHighlight(animated: true) } - openPeerImpl = { [weak controller] peerId in + openPeerImpl = { [weak controller] peer in if let navigationController = controller?.navigationController as? NavigationController { - let _ = (context.account.postbox.loadedPeerWithId(peerId) + let _ = (context.account.postbox.loadedPeerWithId(peer.id) |> take(1) |> deliverOnMainQueue).start(next: { peer in if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 8fcce13189..8a3f3f04d6 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -4,7 +4,6 @@ public enum Api { public enum auth {} public enum channels {} public enum contacts {} - public enum feed {} public enum help {} public enum messages {} public enum payments {} @@ -22,7 +21,6 @@ public enum Api { public enum bots {} public enum channels {} public enum contacts {} - public enum feed {} public enum folders {} public enum help {} public enum langpack {} @@ -221,11 +219,10 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[179611673] = { return Api.ExportedChatInvite.parse_chatInviteExported($0) } dict[-317687113] = { return Api.ExportedChatInvite.parse_chatInvitePublicJoinRequests($0) } dict[1571494644] = { return Api.ExportedMessageLink.parse_exportedMessageLink($0) } - dict[1348066419] = { return Api.FeedPosition.parse_feedPosition($0) } dict[-207944868] = { return Api.FileHash.parse_fileHash($0) } dict[-11252123] = { return Api.Folder.parse_folder($0) } dict[-373643672] = { return Api.FolderPeer.parse_folderPeer($0) } - dict[1885902651] = { return Api.ForumTopic.parse_forumTopic($0) } + dict[792599046] = { return Api.ForumTopic.parse_forumTopic($0) } dict[-1107729093] = { return Api.Game.parse_game($0) } dict[-1297942941] = { return Api.GeoPoint.parse_geoPoint($0) } dict[286776671] = { return Api.GeoPoint.parse_geoPointEmpty($0) } @@ -772,7 +769,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-761649164] = { return Api.Update.parse_updateChannelMessageForwards($0) } dict[-232346616] = { return Api.Update.parse_updateChannelMessageViews($0) } dict[-1738720581] = { return Api.Update.parse_updateChannelParticipant($0) } - dict[1153291573] = { return Api.Update.parse_updateChannelReadMessagesContents($0) } + dict[-366410403] = { return Api.Update.parse_updateChannelReadMessagesContents($0) } dict[277713951] = { return Api.Update.parse_updateChannelTooLong($0) } dict[-1937192669] = { return Api.Update.parse_updateChannelUserTyping($0) } dict[791390623] = { return Api.Update.parse_updateChannelWebPage($0) } @@ -841,7 +838,6 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1218471511] = { return Api.Update.parse_updateReadChannelOutbox($0) } dict[-78886548] = { return Api.Update.parse_updateReadFeaturedEmojiStickers($0) } dict[1461528386] = { return Api.Update.parse_updateReadFeaturedStickers($0) } - dict[1951948721] = { return Api.Update.parse_updateReadFeed($0) } dict[-1667805217] = { return Api.Update.parse_updateReadHistoryInbox($0) } dict[791617983] = { return Api.Update.parse_updateReadHistoryOutbox($0) } dict[1757493555] = { return Api.Update.parse_updateReadMessagesContents($0) } @@ -961,8 +957,6 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1891070632] = { return Api.contacts.TopPeers.parse_topPeers($0) } dict[-1255369827] = { return Api.contacts.TopPeers.parse_topPeersDisabled($0) } dict[-567906571] = { return Api.contacts.TopPeers.parse_topPeersNotModified($0) } - dict[-587770695] = { return Api.feed.FeedMessages.parse_feedMessages($0) } - dict[-619039485] = { return Api.feed.FeedMessages.parse_feedMessagesNotModified($0) } dict[-860107216] = { return Api.help.AppUpdate.parse_appUpdate($0) } dict[-1000708810] = { return Api.help.AppUpdate.parse_noAppUpdate($0) } dict[-2016381538] = { return Api.help.CountriesList.parse_countriesList($0) } @@ -1287,8 +1281,6 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.ExportedMessageLink: _1.serialize(buffer, boxed) - case let _1 as Api.FeedPosition: - _1.serialize(buffer, boxed) case let _1 as Api.FileHash: _1.serialize(buffer, boxed) case let _1 as Api.Folder: @@ -1727,8 +1719,6 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.contacts.TopPeers: _1.serialize(buffer, boxed) - case let _1 as Api.feed.FeedMessages: - _1.serialize(buffer, boxed) case let _1 as Api.help.AppUpdate: _1.serialize(buffer, boxed) case let _1 as Api.help.CountriesList: diff --git a/submodules/TelegramApi/Sources/Api20.swift b/submodules/TelegramApi/Sources/Api20.swift index 1c4824fd64..7ea180663b 100644 --- a/submodules/TelegramApi/Sources/Api20.swift +++ b/submodules/TelegramApi/Sources/Api20.swift @@ -671,7 +671,7 @@ public extension Api { case updateChannelMessageForwards(channelId: Int64, id: Int32, forwards: Int32) case updateChannelMessageViews(channelId: Int64, id: Int32, views: Int32) case updateChannelParticipant(flags: Int32, channelId: Int64, date: Int32, actorId: Int64, userId: Int64, prevParticipant: Api.ChannelParticipant?, newParticipant: Api.ChannelParticipant?, invite: Api.ExportedChatInvite?, qts: Int32) - case updateChannelReadMessagesContents(channelId: Int64, messages: [Int32]) + case updateChannelReadMessagesContents(flags: Int32, channelId: Int64, topMsgId: Int32?, messages: [Int32]) case updateChannelTooLong(flags: Int32, channelId: Int64, pts: Int32?) case updateChannelUserTyping(flags: Int32, channelId: Int64, topMsgId: Int32?, fromId: Api.Peer, action: Api.SendMessageAction) case updateChannelWebPage(channelId: Int64, webpage: Api.WebPage, pts: Int32, ptsCount: Int32) @@ -740,7 +740,6 @@ public extension Api { case updateReadChannelOutbox(channelId: Int64, maxId: Int32) case updateReadFeaturedEmojiStickers case updateReadFeaturedStickers - case updateReadFeed(flags: Int32, filterId: Int32, maxPosition: Api.FeedPosition, unreadCount: Int32?, unreadMutedCount: Int32?) case updateReadHistoryInbox(flags: Int32, folderId: Int32?, peer: Api.Peer, maxId: Int32, stillUnreadCount: Int32, pts: Int32, ptsCount: Int32) case updateReadHistoryOutbox(peer: Api.Peer, maxId: Int32, pts: Int32, ptsCount: Int32) case updateReadMessagesContents(messages: [Int32], pts: Int32, ptsCount: Int32) @@ -925,11 +924,13 @@ public extension Api { if Int(flags) & Int(1 << 2) != 0 {invite!.serialize(buffer, true)} serializeInt32(qts, buffer: buffer, boxed: false) break - case .updateChannelReadMessagesContents(let channelId, let messages): + case .updateChannelReadMessagesContents(let flags, let channelId, let topMsgId, let messages): if boxed { - buffer.appendInt32(1153291573) + buffer.appendInt32(-366410403) } + serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(channelId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} buffer.appendInt32(481674261) buffer.appendInt32(Int32(messages.count)) for item in messages { @@ -1510,16 +1511,6 @@ public extension Api { buffer.appendInt32(1461528386) } - break - case .updateReadFeed(let flags, let filterId, let maxPosition, let unreadCount, let unreadMutedCount): - if boxed { - buffer.appendInt32(1951948721) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(filterId, buffer: buffer, boxed: false) - maxPosition.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(unreadCount!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 0) != 0 {serializeInt32(unreadMutedCount!, buffer: buffer, boxed: false)} break case .updateReadHistoryInbox(let flags, let folderId, let peer, let maxId, let stillUnreadCount, let pts, let ptsCount): if boxed { @@ -1731,8 +1722,8 @@ public extension Api { return ("updateChannelMessageViews", [("channelId", String(describing: channelId)), ("id", String(describing: id)), ("views", String(describing: views))]) case .updateChannelParticipant(let flags, let channelId, let date, let actorId, let userId, let prevParticipant, let newParticipant, let invite, let qts): return ("updateChannelParticipant", [("flags", String(describing: flags)), ("channelId", String(describing: channelId)), ("date", String(describing: date)), ("actorId", String(describing: actorId)), ("userId", String(describing: userId)), ("prevParticipant", String(describing: prevParticipant)), ("newParticipant", String(describing: newParticipant)), ("invite", String(describing: invite)), ("qts", String(describing: qts))]) - case .updateChannelReadMessagesContents(let channelId, let messages): - return ("updateChannelReadMessagesContents", [("channelId", String(describing: channelId)), ("messages", String(describing: messages))]) + case .updateChannelReadMessagesContents(let flags, let channelId, let topMsgId, let messages): + return ("updateChannelReadMessagesContents", [("flags", String(describing: flags)), ("channelId", String(describing: channelId)), ("topMsgId", String(describing: topMsgId)), ("messages", String(describing: messages))]) case .updateChannelTooLong(let flags, let channelId, let pts): return ("updateChannelTooLong", [("flags", String(describing: flags)), ("channelId", String(describing: channelId)), ("pts", String(describing: pts))]) case .updateChannelUserTyping(let flags, let channelId, let topMsgId, let fromId, let action): @@ -1869,8 +1860,6 @@ public extension Api { return ("updateReadFeaturedEmojiStickers", []) case .updateReadFeaturedStickers: return ("updateReadFeaturedStickers", []) - case .updateReadFeed(let flags, let filterId, let maxPosition, let unreadCount, let unreadMutedCount): - return ("updateReadFeed", [("flags", String(describing: flags)), ("filterId", String(describing: filterId)), ("maxPosition", String(describing: maxPosition)), ("unreadCount", String(describing: unreadCount)), ("unreadMutedCount", String(describing: unreadMutedCount))]) case .updateReadHistoryInbox(let flags, let folderId, let peer, let maxId, let stillUnreadCount, let pts, let ptsCount): return ("updateReadHistoryInbox", [("flags", String(describing: flags)), ("folderId", String(describing: folderId)), ("peer", String(describing: peer)), ("maxId", String(describing: maxId)), ("stillUnreadCount", String(describing: stillUnreadCount)), ("pts", String(describing: pts)), ("ptsCount", String(describing: ptsCount))]) case .updateReadHistoryOutbox(let peer, let maxId, let pts, let ptsCount): @@ -2294,16 +2283,22 @@ public extension Api { } } public static func parse_updateChannelReadMessagesContents(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: [Int32]? + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } + var _4: [Int32]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + _4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateChannelReadMessagesContents(channelId: _1!, messages: _2!) + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.Update.updateChannelReadMessagesContents(flags: _1!, channelId: _2!, topMsgId: _3, messages: _4!) } else { return nil @@ -3452,31 +3447,6 @@ public extension Api { public static func parse_updateReadFeaturedStickers(_ reader: BufferReader) -> Update? { return Api.Update.updateReadFeaturedStickers } - public static func parse_updateReadFeed(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Api.FeedPosition? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.FeedPosition - } - var _4: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_4 = reader.readInt32() } - var _5: Int32? - if Int(_1!) & Int(1 << 0) != 0 {_5 = reader.readInt32() } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updateReadFeed(flags: _1!, filterId: _2!, maxPosition: _3!, unreadCount: _4, unreadMutedCount: _5) - } - else { - return nil - } - } public static func parse_updateReadHistoryInbox(_ reader: BufferReader) -> Update? { var _1: Int32? _1 = reader.readInt32() diff --git a/submodules/TelegramApi/Sources/Api24.swift b/submodules/TelegramApi/Sources/Api24.swift index f32b951004..f3e8113f14 100644 --- a/submodules/TelegramApi/Sources/Api24.swift +++ b/submodules/TelegramApi/Sources/Api24.swift @@ -1,99 +1,3 @@ -public extension Api.feed { - enum FeedMessages: TypeConstructorDescription { - case feedMessages(flags: Int32, maxPosition: Api.FeedPosition?, minPosition: Api.FeedPosition?, readMaxPosition: Api.FeedPosition?, messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) - case feedMessagesNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .feedMessages(let flags, let maxPosition, let minPosition, let readMaxPosition, let messages, let chats, let users): - if boxed { - buffer.appendInt32(-587770695) - } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {maxPosition!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {minPosition!.serialize(buffer, true)} - if Int(flags) & Int(1 << 2) != 0 {readMaxPosition!.serialize(buffer, true)} - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(messages.count)) - for item in messages { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - case .feedMessagesNotModified: - if boxed { - buffer.appendInt32(-619039485) - } - - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .feedMessages(let flags, let maxPosition, let minPosition, let readMaxPosition, let messages, let chats, let users): - return ("feedMessages", [("flags", String(describing: flags)), ("maxPosition", String(describing: maxPosition)), ("minPosition", String(describing: minPosition)), ("readMaxPosition", String(describing: readMaxPosition)), ("messages", String(describing: messages)), ("chats", String(describing: chats)), ("users", String(describing: users))]) - case .feedMessagesNotModified: - return ("feedMessagesNotModified", []) - } - } - - public static func parse_feedMessages(_ reader: BufferReader) -> FeedMessages? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.FeedPosition? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.FeedPosition - } } - var _3: Api.FeedPosition? - if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.FeedPosition - } } - var _4: Api.FeedPosition? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.FeedPosition - } } - var _5: [Api.Message]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) - } - var _6: [Api.Chat]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _7: [Api.User]? - if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.feed.FeedMessages.feedMessages(flags: _1!, maxPosition: _2, minPosition: _3, readMaxPosition: _4, messages: _5!, chats: _6!, users: _7!) - } - else { - return nil - } - } - public static func parse_feedMessagesNotModified(_ reader: BufferReader) -> FeedMessages? { - return Api.feed.FeedMessages.feedMessagesNotModified - } - - } -} public extension Api.help { enum AppUpdate: TypeConstructorDescription { case appUpdate(flags: Int32, id: Int32, version: String, text: String, entities: [Api.MessageEntity], document: Api.Document?, url: String?, sticker: Api.Document?) @@ -1316,3 +1220,125 @@ public extension Api.messages { } } +public extension Api.messages { + enum BotCallbackAnswer: TypeConstructorDescription { + case botCallbackAnswer(flags: Int32, message: String?, url: String?, cacheTime: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .botCallbackAnswer(let flags, let message, let url, let cacheTime): + if boxed { + buffer.appendInt32(911761060) + } + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(message!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {serializeString(url!, buffer: buffer, boxed: false)} + serializeInt32(cacheTime, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .botCallbackAnswer(let flags, let message, let url, let cacheTime): + return ("botCallbackAnswer", [("flags", String(describing: flags)), ("message", String(describing: message)), ("url", String(describing: url)), ("cacheTime", String(describing: cacheTime))]) + } + } + + public static func parse_botCallbackAnswer(_ reader: BufferReader) -> BotCallbackAnswer? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + if Int(_1!) & Int(1 << 0) != 0 {_2 = parseString(reader) } + var _3: String? + if Int(_1!) & Int(1 << 2) != 0 {_3 = parseString(reader) } + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.messages.BotCallbackAnswer.botCallbackAnswer(flags: _1!, message: _2, url: _3, cacheTime: _4!) + } + else { + return nil + } + } + + } +} +public extension Api.messages { + enum BotResults: TypeConstructorDescription { + case botResults(flags: Int32, queryId: Int64, nextOffset: String?, switchPm: Api.InlineBotSwitchPM?, results: [Api.BotInlineResult], cacheTime: Int32, users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .botResults(let flags, let queryId, let nextOffset, let switchPm, let results, let cacheTime, let users): + if boxed { + buffer.appendInt32(-1803769784) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(queryId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {switchPm!.serialize(buffer, true)} + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(results.count)) + for item in results { + item.serialize(buffer, true) + } + serializeInt32(cacheTime, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .botResults(let flags, let queryId, let nextOffset, let switchPm, let results, let cacheTime, let users): + return ("botResults", [("flags", String(describing: flags)), ("queryId", String(describing: queryId)), ("nextOffset", String(describing: nextOffset)), ("switchPm", String(describing: switchPm)), ("results", String(describing: results)), ("cacheTime", String(describing: cacheTime)), ("users", String(describing: users))]) + } + } + + public static func parse_botResults(_ reader: BufferReader) -> BotResults? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + if Int(_1!) & Int(1 << 1) != 0 {_3 = parseString(reader) } + var _4: Api.InlineBotSwitchPM? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.InlineBotSwitchPM + } } + var _5: [Api.BotInlineResult]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotInlineResult.self) + } + var _6: Int32? + _6 = reader.readInt32() + var _7: [Api.User]? + if let _ = reader.readInt32() { + _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.messages.BotResults.botResults(flags: _1!, queryId: _2!, nextOffset: _3, switchPm: _4, results: _5!, cacheTime: _6!, users: _7!) + } + else { + return nil + } + } + + } +} diff --git a/submodules/TelegramApi/Sources/Api25.swift b/submodules/TelegramApi/Sources/Api25.swift index 8cb241bd5b..e3b58abe33 100644 --- a/submodules/TelegramApi/Sources/Api25.swift +++ b/submodules/TelegramApi/Sources/Api25.swift @@ -1,125 +1,3 @@ -public extension Api.messages { - enum BotCallbackAnswer: TypeConstructorDescription { - case botCallbackAnswer(flags: Int32, message: String?, url: String?, cacheTime: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .botCallbackAnswer(let flags, let message, let url, let cacheTime): - if boxed { - buffer.appendInt32(911761060) - } - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(message!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {serializeString(url!, buffer: buffer, boxed: false)} - serializeInt32(cacheTime, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .botCallbackAnswer(let flags, let message, let url, let cacheTime): - return ("botCallbackAnswer", [("flags", String(describing: flags)), ("message", String(describing: message)), ("url", String(describing: url)), ("cacheTime", String(describing: cacheTime))]) - } - } - - public static func parse_botCallbackAnswer(_ reader: BufferReader) -> BotCallbackAnswer? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - if Int(_1!) & Int(1 << 0) != 0 {_2 = parseString(reader) } - var _3: String? - if Int(_1!) & Int(1 << 2) != 0 {_3 = parseString(reader) } - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.messages.BotCallbackAnswer.botCallbackAnswer(flags: _1!, message: _2, url: _3, cacheTime: _4!) - } - else { - return nil - } - } - - } -} -public extension Api.messages { - enum BotResults: TypeConstructorDescription { - case botResults(flags: Int32, queryId: Int64, nextOffset: String?, switchPm: Api.InlineBotSwitchPM?, results: [Api.BotInlineResult], cacheTime: Int32, users: [Api.User]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .botResults(let flags, let queryId, let nextOffset, let switchPm, let results, let cacheTime, let users): - if boxed { - buffer.appendInt32(-1803769784) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(queryId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {switchPm!.serialize(buffer, true)} - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(results.count)) - for item in results { - item.serialize(buffer, true) - } - serializeInt32(cacheTime, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .botResults(let flags, let queryId, let nextOffset, let switchPm, let results, let cacheTime, let users): - return ("botResults", [("flags", String(describing: flags)), ("queryId", String(describing: queryId)), ("nextOffset", String(describing: nextOffset)), ("switchPm", String(describing: switchPm)), ("results", String(describing: results)), ("cacheTime", String(describing: cacheTime)), ("users", String(describing: users))]) - } - } - - public static func parse_botResults(_ reader: BufferReader) -> BotResults? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - if Int(_1!) & Int(1 << 1) != 0 {_3 = parseString(reader) } - var _4: Api.InlineBotSwitchPM? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.InlineBotSwitchPM - } } - var _5: [Api.BotInlineResult]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotInlineResult.self) - } - var _6: Int32? - _6 = reader.readInt32() - var _7: [Api.User]? - if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.messages.BotResults.botResults(flags: _1!, queryId: _2!, nextOffset: _3, switchPm: _4, results: _5!, cacheTime: _6!, users: _7!) - } - else { - return nil - } - } - - } -} public extension Api.messages { enum ChatAdminsWithInvites: TypeConstructorDescription { case chatAdminsWithInvites(admins: [Api.ChatAdminWithInvites], users: [Api.User]) diff --git a/submodules/TelegramApi/Sources/Api29.swift b/submodules/TelegramApi/Sources/Api29.swift index b74cf74da5..d3ad69554b 100644 --- a/submodules/TelegramApi/Sources/Api29.swift +++ b/submodules/TelegramApi/Sources/Api29.swift @@ -2983,44 +2983,6 @@ public extension Api.functions.contacts { }) } } -public extension Api.functions.feed { - static func getFeed(flags: Int32, filterId: Int32, offsetPosition: Api.FeedPosition?, addOffset: Int32, limit: Int32, maxPosition: Api.FeedPosition?, minPosition: Api.FeedPosition?, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2121717715) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(filterId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {offsetPosition!.serialize(buffer, true)} - serializeInt32(addOffset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 {maxPosition!.serialize(buffer, true)} - if Int(flags) & Int(1 << 2) != 0 {minPosition!.serialize(buffer, true)} - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "feed.getFeed", parameters: [("flags", String(describing: flags)), ("filterId", String(describing: filterId)), ("offsetPosition", String(describing: offsetPosition)), ("addOffset", String(describing: addOffset)), ("limit", String(describing: limit)), ("maxPosition", String(describing: maxPosition)), ("minPosition", String(describing: minPosition)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.feed.FeedMessages? in - let reader = BufferReader(buffer) - var result: Api.feed.FeedMessages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.feed.FeedMessages - } - return result - }) - } -} -public extension Api.functions.feed { - static func readFeed(filterId: Int32, maxPosition: Api.FeedPosition) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1271479809) - serializeInt32(filterId, buffer: buffer, boxed: false) - maxPosition.serialize(buffer, true) - return (FunctionDescription(name: "feed.readFeed", parameters: [("filterId", String(describing: filterId)), ("maxPosition", String(describing: maxPosition))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} public extension Api.functions.folders { static func deleteFolder(folderId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -5155,16 +5117,18 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func getUnreadMentions(peer: Api.InputPeer, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func getUnreadMentions(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(1180140658) + buffer.appendInt32(-251140208) + serializeInt32(flags, buffer: buffer, boxed: false) peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} serializeInt32(offsetId, buffer: buffer, boxed: false) serializeInt32(addOffset, buffer: buffer, boxed: false) serializeInt32(limit, buffer: buffer, boxed: false) serializeInt32(maxId, buffer: buffer, boxed: false) serializeInt32(minId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getUnreadMentions", parameters: [("peer", String(describing: peer)), ("offsetId", String(describing: offsetId)), ("addOffset", String(describing: addOffset)), ("limit", String(describing: limit)), ("maxId", String(describing: maxId)), ("minId", String(describing: minId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + return (FunctionDescription(name: "messages.getUnreadMentions", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("topMsgId", String(describing: topMsgId)), ("offsetId", String(describing: offsetId)), ("addOffset", String(describing: addOffset)), ("limit", String(describing: limit)), ("maxId", String(describing: maxId)), ("minId", String(describing: minId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in let reader = BufferReader(buffer) var result: Api.messages.Messages? if let signature = reader.readInt32() { @@ -5175,16 +5139,18 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func getUnreadReactions(peer: Api.InputPeer, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func getUnreadReactions(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(-396644838) + buffer.appendInt32(841173339) + serializeInt32(flags, buffer: buffer, boxed: false) peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} serializeInt32(offsetId, buffer: buffer, boxed: false) serializeInt32(addOffset, buffer: buffer, boxed: false) serializeInt32(limit, buffer: buffer, boxed: false) serializeInt32(maxId, buffer: buffer, boxed: false) serializeInt32(minId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getUnreadReactions", parameters: [("peer", String(describing: peer)), ("offsetId", String(describing: offsetId)), ("addOffset", String(describing: addOffset)), ("limit", String(describing: limit)), ("maxId", String(describing: maxId)), ("minId", String(describing: minId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + return (FunctionDescription(name: "messages.getUnreadReactions", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("topMsgId", String(describing: topMsgId)), ("offsetId", String(describing: offsetId)), ("addOffset", String(describing: addOffset)), ("limit", String(describing: limit)), ("maxId", String(describing: maxId)), ("minId", String(describing: minId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in let reader = BufferReader(buffer) var result: Api.messages.Messages? if let signature = reader.readInt32() { @@ -5466,11 +5432,13 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func readMentions(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func readMentions(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(251759059) + buffer.appendInt32(921026381) + serializeInt32(flags, buffer: buffer, boxed: false) peer.serialize(buffer, true) - return (FunctionDescription(name: "messages.readMentions", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)} + return (FunctionDescription(name: "messages.readMentions", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("topMsgId", String(describing: topMsgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in let reader = BufferReader(buffer) var result: Api.messages.AffectedHistory? if let signature = reader.readInt32() { diff --git a/submodules/TelegramApi/Sources/Api5.swift b/submodules/TelegramApi/Sources/Api5.swift index ad3c60b58d..ab3f199352 100644 --- a/submodules/TelegramApi/Sources/Api5.swift +++ b/submodules/TelegramApi/Sources/Api5.swift @@ -956,52 +956,6 @@ public extension Api { } } -public extension Api { - enum FeedPosition: TypeConstructorDescription { - case feedPosition(date: Int32, peer: Api.Peer, id: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .feedPosition(let date, let peer, let id): - if boxed { - buffer.appendInt32(1348066419) - } - serializeInt32(date, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .feedPosition(let date, let peer, let id): - return ("feedPosition", [("date", String(describing: date)), ("peer", String(describing: peer)), ("id", String(describing: id))]) - } - } - - public static func parse_feedPosition(_ reader: BufferReader) -> FeedPosition? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.FeedPosition.feedPosition(date: _1!, peer: _2!, id: _3!) - } - else { - return nil - } - } - - } -} public extension Api { enum FileHash: TypeConstructorDescription { case fileHash(offset: Int64, limit: Int32, hash: Buffer) @@ -1138,3 +1092,79 @@ public extension Api { } } +public extension Api { + enum ForumTopic: TypeConstructorDescription { + case forumTopic(flags: Int32, id: Int32, date: Int32, title: String, iconEmojiId: Int64?, topMessage: Int32, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, unreadMentionsCount: Int32, unreadReactionsCount: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .forumTopic(let flags, let id, let date, let title, let iconEmojiId, let topMessage, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let unreadMentionsCount, let unreadReactionsCount): + if boxed { + buffer.appendInt32(792599046) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(id, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt64(iconEmojiId!, buffer: buffer, boxed: false)} + serializeInt32(topMessage, buffer: buffer, boxed: false) + serializeInt32(readInboxMaxId, buffer: buffer, boxed: false) + serializeInt32(readOutboxMaxId, buffer: buffer, boxed: false) + serializeInt32(unreadCount, buffer: buffer, boxed: false) + serializeInt32(unreadMentionsCount, buffer: buffer, boxed: false) + serializeInt32(unreadReactionsCount, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .forumTopic(let flags, let id, let date, let title, let iconEmojiId, let topMessage, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let unreadMentionsCount, let unreadReactionsCount): + return ("forumTopic", [("flags", String(describing: flags)), ("id", String(describing: id)), ("date", String(describing: date)), ("title", String(describing: title)), ("iconEmojiId", String(describing: iconEmojiId)), ("topMessage", String(describing: topMessage)), ("readInboxMaxId", String(describing: readInboxMaxId)), ("readOutboxMaxId", String(describing: readOutboxMaxId)), ("unreadCount", String(describing: unreadCount)), ("unreadMentionsCount", String(describing: unreadMentionsCount)), ("unreadReactionsCount", String(describing: unreadReactionsCount))]) + } + } + + public static func parse_forumTopic(_ reader: BufferReader) -> ForumTopic? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: String? + _4 = parseString(reader) + var _5: Int64? + if Int(_1!) & Int(1 << 0) != 0 {_5 = reader.readInt64() } + var _6: Int32? + _6 = reader.readInt32() + var _7: Int32? + _7 = reader.readInt32() + var _8: Int32? + _8 = reader.readInt32() + var _9: Int32? + _9 = reader.readInt32() + var _10: Int32? + _10 = reader.readInt32() + var _11: Int32? + _11 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + let _c10 = _10 != nil + let _c11 = _11 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { + return Api.ForumTopic.forumTopic(flags: _1!, id: _2!, date: _3!, title: _4!, iconEmojiId: _5, topMessage: _6!, readInboxMaxId: _7!, readOutboxMaxId: _8!, unreadCount: _9!, unreadMentionsCount: _10!, unreadReactionsCount: _11!) + } + else { + return nil + } + } + + } +} diff --git a/submodules/TelegramApi/Sources/Api6.swift b/submodules/TelegramApi/Sources/Api6.swift index 0276d0e26e..7177c5d984 100644 --- a/submodules/TelegramApi/Sources/Api6.swift +++ b/submodules/TelegramApi/Sources/Api6.swift @@ -1,71 +1,3 @@ -public extension Api { - enum ForumTopic: TypeConstructorDescription { - case forumTopic(flags: Int32, id: Int32, date: Int32, title: String, iconEmojiId: Int64?, topMessage: Int32, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .forumTopic(let flags, let id, let date, let title, let iconEmojiId, let topMessage, let readInboxMaxId, let readOutboxMaxId, let unreadCount): - if boxed { - buffer.appendInt32(1885902651) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(id, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - serializeString(title, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeInt64(iconEmojiId!, buffer: buffer, boxed: false)} - serializeInt32(topMessage, buffer: buffer, boxed: false) - serializeInt32(readInboxMaxId, buffer: buffer, boxed: false) - serializeInt32(readOutboxMaxId, buffer: buffer, boxed: false) - serializeInt32(unreadCount, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .forumTopic(let flags, let id, let date, let title, let iconEmojiId, let topMessage, let readInboxMaxId, let readOutboxMaxId, let unreadCount): - return ("forumTopic", [("flags", String(describing: flags)), ("id", String(describing: id)), ("date", String(describing: date)), ("title", String(describing: title)), ("iconEmojiId", String(describing: iconEmojiId)), ("topMessage", String(describing: topMessage)), ("readInboxMaxId", String(describing: readInboxMaxId)), ("readOutboxMaxId", String(describing: readOutboxMaxId)), ("unreadCount", String(describing: unreadCount))]) - } - } - - public static func parse_forumTopic(_ reader: BufferReader) -> ForumTopic? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: String? - _4 = parseString(reader) - var _5: Int64? - if Int(_1!) & Int(1 << 0) != 0 {_5 = reader.readInt64() } - var _6: Int32? - _6 = reader.readInt32() - var _7: Int32? - _7 = reader.readInt32() - var _8: Int32? - _8 = reader.readInt32() - var _9: Int32? - _9 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.ForumTopic.forumTopic(flags: _1!, id: _2!, date: _3!, title: _4!, iconEmojiId: _5, topMessage: _6!, readInboxMaxId: _7!, readOutboxMaxId: _8!, unreadCount: _9!) - } - else { - return nil - } - } - - } -} public extension Api { enum Game: TypeConstructorDescription { case game(flags: Int32, id: Int64, accessHash: Int64, shortName: String, title: String, description: String, photo: Api.Photo, document: Api.Document?) diff --git a/submodules/TelegramCore/Sources/ForumChannels.swift b/submodules/TelegramCore/Sources/ForumChannels.swift index e70d9c748c..796270697a 100644 --- a/submodules/TelegramCore/Sources/ForumChannels.swift +++ b/submodules/TelegramCore/Sources/ForumChannels.swift @@ -193,7 +193,7 @@ func _internal_loadMessageHistoryThreads(account: Account, peerId: PeerId) -> Si for topic in topics { switch topic { - case let .forumTopic(_, id, _, title, iconEmojiId, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount): + case let .forumTopic(_, id, _, title, iconEmojiId, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, _, _): let data = MessageHistoryThreadData( info: EngineMessageHistoryThread.Info( title: title, diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index ce5047a018..20ac73dd48 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -1374,7 +1374,8 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo } case let .updateReadMessagesContents(messages, _, _): updatedState.addReadMessagesContents((nil, messages)) - case let .updateChannelReadMessagesContents(channelId, messages): + case let .updateChannelReadMessagesContents(_, channelId, topMsgId, messages): + let _ = topMsgId updatedState.addReadMessagesContents((PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), messages)) case let .updateChannelMessageViews(channelId, id, views): updatedState.addUpdateMessageImpressionCount(id: MessageId(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), namespace: Namespaces.Message.Cloud, id: id), count: views) @@ -1693,7 +1694,7 @@ func resolveForumThreads(postbox: Postbox, network: Network, state: AccountMutab for topic in topics { switch topic { - case let .forumTopic(_, id, _, title, iconEmojiId, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount): + case let .forumTopic(_, id, _, title, iconEmojiId, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, _, _): state.operations.append(.ResetForumTopic(topicId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: id), data: MessageHistoryThreadData( info: EngineMessageHistoryThread.Info( title: title, @@ -1785,7 +1786,7 @@ func resolveForumThreads(postbox: Postbox, network: Network, fetchedChatList: Fe for topic in topics { switch topic { - case let .forumTopic(_, id, _, title, iconEmojiId, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount): + case let .forumTopic(_, id, _, title, iconEmojiId, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, _, _): fetchedChatList.threadInfos[MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: id)] = MessageHistoryThreadData( info: EngineMessageHistoryThread.Info( title: title, @@ -2429,7 +2430,8 @@ private func pollChannel(postbox: Postbox, network: Network, peer: Peer, state: updatedState.updateMessagesPinned(ids: messages.map { id in MessageId(peerId: channelPeerId, namespace: Namespaces.Message.Cloud, id: id) }, pinned: (flags & (1 << 0)) != 0) - case let .updateChannelReadMessagesContents(_, messages): + case let .updateChannelReadMessagesContents(_, _, topMsgId, messages): + let _ = topMsgId updatedState.addReadMessagesContents((peer.id, messages)) case let .updateChannelMessageViews(_, id, views): updatedState.addUpdateMessageImpressionCount(id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id), count: views) diff --git a/submodules/TelegramCore/Sources/State/HistoryViewStateValidation.swift b/submodules/TelegramCore/Sources/State/HistoryViewStateValidation.swift index d23bf17830..69c9dfffa5 100644 --- a/submodules/TelegramCore/Sources/State/HistoryViewStateValidation.swift +++ b/submodules/TelegramCore/Sources/State/HistoryViewStateValidation.swift @@ -493,9 +493,9 @@ private func validateChannelMessagesBatch(postbox: Postbox, network: Network, ac let requestSignal: Signal if let tag = tag { if tag == MessageTags.unseenPersonalMessage { - requestSignal = network.request(Api.functions.messages.getUnreadMentions(peer: inputPeer, offsetId: messageIds[messageIds.count - 1].id + 1, addOffset: 0, limit: Int32(messageIds.count), maxId: messageIds[messageIds.count - 1].id + 1, minId: messageIds[0].id - 1)) + requestSignal = network.request(Api.functions.messages.getUnreadMentions(flags: 0, peer: inputPeer, topMsgId: nil, offsetId: messageIds[messageIds.count - 1].id + 1, addOffset: 0, limit: Int32(messageIds.count), maxId: messageIds[messageIds.count - 1].id + 1, minId: messageIds[0].id - 1)) } else if tag == MessageTags.unseenReaction { - requestSignal = network.request(Api.functions.messages.getUnreadReactions(peer: inputPeer, offsetId: messageIds[messageIds.count - 1].id + 1, addOffset: 0, limit: Int32(messageIds.count), maxId: messageIds[messageIds.count - 1].id + 1, minId: messageIds[0].id - 1)) + requestSignal = network.request(Api.functions.messages.getUnreadReactions(flags: 0, peer: inputPeer, topMsgId: nil, offsetId: messageIds[messageIds.count - 1].id + 1, addOffset: 0, limit: Int32(messageIds.count), maxId: messageIds[messageIds.count - 1].id + 1, minId: messageIds[0].id - 1)) } else if let filter = messageFilterForTagMask(tag) { requestSignal = network.request(Api.functions.messages.search(flags: 0, peer: inputPeer, q: "", fromId: nil, topMsgId: nil, filter: filter, minDate: 0, maxDate: 0, offsetId: messageIds[messageIds.count - 1].id + 1, addOffset: 0, limit: Int32(messageIds.count), maxId: messageIds[messageIds.count - 1].id + 1, minId: messageIds[0].id - 1, hash: hash)) } else { diff --git a/submodules/TelegramCore/Sources/State/Holes.swift b/submodules/TelegramCore/Sources/State/Holes.swift index e13da94172..8e53cd75fc 100644 --- a/submodules/TelegramCore/Sources/State/Holes.swift +++ b/submodules/TelegramCore/Sources/State/Holes.swift @@ -406,7 +406,7 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH minMaxRange = 1 ... Int32.max - 1 } - request = source.request(Api.functions.messages.getUnreadMentions(peer: inputPeer, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId)) + request = source.request(Api.functions.messages.getUnreadMentions(flags: 0, peer: inputPeer, topMsgId: nil, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId)) } else if tag == .unseenReaction { let offsetId: Int32 let addOffset: Int32 @@ -454,7 +454,7 @@ func fetchMessageHistoryHole(accountPeerId: PeerId, source: FetchMessageHistoryH minMaxRange = 1 ... Int32.max - 1 } - request = source.request(Api.functions.messages.getUnreadReactions(peer: inputPeer, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId)) + request = source.request(Api.functions.messages.getUnreadReactions(flags: 0, peer: inputPeer, topMsgId: nil, offsetId: offsetId, addOffset: addOffset, limit: Int32(selectedLimit), maxId: maxId, minId: minId)) } else if tag == .liveLocation { let selectedLimit = count diff --git a/submodules/TelegramCore/Sources/State/ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift b/submodules/TelegramCore/Sources/State/ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift index de44b60363..f040cf89f7 100644 --- a/submodules/TelegramCore/Sources/State/ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift +++ b/submodules/TelegramCore/Sources/State/ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift @@ -123,7 +123,7 @@ private func synchronizeMarkAllUnseen(transaction: Transaction, postbox: Postbox let inputChannel = transaction.getPeer(peerId).flatMap(apiInputChannel) let limit: Int32 = 100 let oneOperation: (Int32) -> Signal = { maxId in - return network.request(Api.functions.messages.getUnreadMentions(peer: inputPeer, offsetId: maxId, addOffset: maxId == 0 ? 0 : -1, limit: limit, maxId: maxId == 0 ? 0 : (maxId + 1), minId: 1)) + return network.request(Api.functions.messages.getUnreadMentions(flags: 0, peer: inputPeer, topMsgId: nil, offsetId: maxId, addOffset: maxId == 0 ? 0 : -1, limit: limit, maxId: maxId == 0 ? 0 : (maxId + 1), minId: 1)) |> mapToSignal { result -> Signal<[MessageId], MTRpcError> in switch result { case let .messages(messages, _, _): diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift index 1f67657668..223aecd05d 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift @@ -7,6 +7,7 @@ private struct DiscussionMessage { var messageId: MessageId var channelMessageId: MessageId? var isChannelPost: Bool + var isForumPost: Bool var maxMessage: MessageId? var maxReadIncomingMessageId: MessageId? var maxReadOutgoingMessageId: MessageId? @@ -242,10 +243,16 @@ private class ReplyThreadHistoryContextImpl { }) } + var isForumPost = false + if let channel = transaction.getPeer(parsedIndex.id.peerId) as? TelegramChannel, channel.flags.contains(.isForum) { + isForumPost = true + } + return .single(DiscussionMessage( messageId: parsedIndex.id, channelMessageId: channelMessageId, isChannelPost: isChannelPost, + isForumPost: isForumPost, maxMessage: resolvedMaxMessage, maxReadIncomingMessageId: maxReadIncomingMessageId, maxReadOutgoingMessageId: readOutboxMaxId.flatMap { readMaxId in @@ -554,6 +561,7 @@ public struct ChatReplyThreadMessage: Equatable { public var messageId: MessageId public var channelMessageId: MessageId? public var isChannelPost: Bool + public var isForumPost: Bool public var maxMessage: MessageId? public var maxReadIncomingMessageId: MessageId? public var maxReadOutgoingMessageId: MessageId? @@ -562,10 +570,11 @@ public struct ChatReplyThreadMessage: Equatable { public var initialAnchor: Anchor public var isNotAvailable: Bool - fileprivate init(messageId: MessageId, channelMessageId: MessageId?, isChannelPost: Bool, maxMessage: MessageId?, maxReadIncomingMessageId: MessageId?, maxReadOutgoingMessageId: MessageId?, unreadCount: Int, initialFilledHoles: IndexSet, initialAnchor: Anchor, isNotAvailable: Bool) { + fileprivate init(messageId: MessageId, channelMessageId: MessageId?, isChannelPost: Bool, isForumPost: Bool, maxMessage: MessageId?, maxReadIncomingMessageId: MessageId?, maxReadOutgoingMessageId: MessageId?, unreadCount: Int, initialFilledHoles: IndexSet, initialAnchor: Anchor, isNotAvailable: Bool) { self.messageId = messageId self.channelMessageId = channelMessageId self.isChannelPost = isChannelPost + self.isForumPost = isForumPost self.maxMessage = maxMessage self.maxReadIncomingMessageId = maxReadIncomingMessageId self.maxReadOutgoingMessageId = maxReadOutgoingMessageId @@ -664,10 +673,16 @@ func _internal_fetchChannelReplyThreadMessage(account: Account, messageId: Messa } } + var isForumPost = false + if let channel = transaction.getPeer(parsedIndex.id.peerId) as? TelegramChannel, channel.flags.contains(.isForum) { + isForumPost = true + } + return DiscussionMessage( messageId: parsedIndex.id, channelMessageId: channelMessageId, isChannelPost: isChannelPost, + isForumPost: isForumPost, maxMessage: resolvedMaxMessage, maxReadIncomingMessageId: readInboxMaxId.flatMap { readMaxId in MessageId(peerId: parsedIndex.id.peerId, namespace: Namespaces.Message.Cloud, id: readMaxId) @@ -691,6 +706,7 @@ func _internal_fetchChannelReplyThreadMessage(account: Account, messageId: Messa messageId: messageId, channelMessageId: nil, isChannelPost: false, + isForumPost: true, maxMessage: MessageId(peerId: messageId.peerId, namespace: messageId.namespace, id: threadData.maxKnownMessageId), maxReadIncomingMessageId: MessageId(peerId: messageId.peerId, namespace: messageId.namespace, id: threadData.maxIncomingReadId), maxReadOutgoingMessageId: MessageId(peerId: messageId.peerId, namespace: messageId.namespace, id: threadData.maxOutgoingReadId), @@ -929,6 +945,7 @@ func _internal_fetchChannelReplyThreadMessage(account: Account, messageId: Messa messageId: discussionMessage.messageId, channelMessageId: discussionMessage.channelMessageId, isChannelPost: discussionMessage.isChannelPost, + isForumPost: discussionMessage.isForumPost, maxMessage: discussionMessage.maxMessage, maxReadIncomingMessageId: discussionMessage.maxReadIncomingMessageId, maxReadOutgoingMessageId: discussionMessage.maxReadOutgoingMessageId, diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinLink.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinLink.swift index fdb833623e..2b9d7a39a6 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinLink.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinLink.swift @@ -47,9 +47,9 @@ public enum ExternalJoiningChatState { } case invite(Invite) - case alreadyJoined(PeerId) + case alreadyJoined(EnginePeer) case invalidHash - case peek(PeerId, Int32) + case peek(EnginePeer, Int32) } func _internal_joinChatInteractively(with hash: String, account: Account) -> Signal { @@ -112,7 +112,7 @@ func _internal_joinLinkInformation(_ hash: String, account: Account) -> Signal castError(JoinLinkInfoError.self) } @@ -124,7 +124,7 @@ func _internal_joinLinkInformation(_ hash: String, account: Account) -> Signal castError(JoinLinkInfoError.self) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift index 88a0fac4d2..9ff693db20 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift @@ -524,8 +524,18 @@ public extension TelegramEngine { } } - public func joinChatInteractively(with hash: String) -> Signal { + public func joinChatInteractively(with hash: String) -> Signal { + let account = self.account return _internal_joinChatInteractively(with: hash, account: self.account) + |> mapToSignal { id -> Signal in + guard let id = id else { + return .single(nil) + } + return account.postbox.transaction { transaction -> EnginePeer? in + return transaction.getPeer(id).flatMap(EnginePeer.init) + } + |> castError(JoinLinkError.self) + } } public func joinLinkInformation(_ hash: String) -> Signal { diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index 43ac6f9139..f12933e6d2 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -299,6 +299,7 @@ swift_library( "//submodules/Components/LottieAnimationComponent:LottieAnimationComponent", "//submodules/TelegramUI/Components/ForumTopicListScreen:ForumTopicListScreen", "//submodules/TelegramUI/Components/ForumCreateTopicScreen:ForumCreateTopicScreen", + "//submodules/TelegramUI/Components/ChatTitleView", ] + select({ "@build_bazel_rules_apple//apple:ios_armv7": [], "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, diff --git a/submodules/TelegramUI/Components/ChatTitleView/BUILD b/submodules/TelegramUI/Components/ChatTitleView/BUILD new file mode 100644 index 0000000000..3ef632edef --- /dev/null +++ b/submodules/TelegramUI/Components/ChatTitleView/BUILD @@ -0,0 +1,37 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatTitleView", + module_name = "ChatTitleView", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/LegacyComponents:LegacyComponents", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/TelegramUIPreferences:TelegramUIPreferences", + "//submodules/ActivityIndicator:ActivityIndicator", + "//submodules/TelegramStringFormatting:TelegramStringFormatting", + "//submodules/PeerPresenceStatusManager:PeerPresenceStatusManager", + "//submodules/ChatTitleActivityNode", + "//submodules/LocalizedPeerData", + "//submodules/PhoneNumberFormat", + "//submodules/AccountContext", + "//submodules/ComponentFlow", + "//submodules/AnimatedCountLabelNode", + "//submodules/TelegramUI/Components/EmojiStatusComponent", + "//submodules/TelegramUI/Components/AnimationCache:AnimationCache", + "//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift new file mode 100644 index 0000000000..6261360241 --- /dev/null +++ b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift @@ -0,0 +1,848 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import Postbox +import TelegramCore +import SwiftSignalKit +import LegacyComponents +import TelegramPresentationData +import TelegramUIPreferences +import ActivityIndicator +import TelegramStringFormatting +import PeerPresenceStatusManager +import ChatTitleActivityNode +import LocalizedPeerData +import PhoneNumberFormat +import ChatTitleActivityNode +import AnimatedCountLabelNode +import AccountContext +import ComponentFlow +import EmojiStatusComponent +import AnimationCache +import MultiAnimationRenderer + +private let titleFont = Font.with(size: 17.0, design: .regular, weight: .semibold, traits: [.monospacedNumbers]) +private let subtitleFont = Font.regular(13.0) + +public enum ChatTitleContent { + public enum ReplyThreadType { + case comments + case replies + } + + case peer(peerView: PeerView, customTitle: String?, onlineMemberCount: Int32?, isScheduledMessages: Bool) + case replyThread(type: ReplyThreadType, count: Int) + case custom(String, String?, Bool) +} + +private enum ChatTitleIcon { + case none + case lock + case mute +} + +private enum ChatTitleCredibilityIcon: Equatable { + case none + case fake + case scam + case verified + case premium + case emojiStatus(PeerEmojiStatus) +} + +public final class ChatTitleView: UIView, NavigationBarTitleView { + private let context: AccountContext + + private var theme: PresentationTheme + private var hasEmbeddedTitleContent: Bool = false + private var strings: PresentationStrings + private var dateTimeFormat: PresentationDateTimeFormat + private var nameDisplayOrder: PresentationPersonNameOrder + private let animationCache: AnimationCache + private let animationRenderer: MultiAnimationRenderer + + private let contentContainer: ASDisplayNode + public let titleContainerView: PortalSourceView + public let titleTextNode: ImmediateAnimatedCountLabelNode + public let titleLeftIconNode: ASImageNode + public let titleRightIconNode: ASImageNode + public let titleCredibilityIconView: ComponentHostView + public let activityNode: ChatTitleActivityNode + + private let button: HighlightTrackingButtonNode + + private var validLayout: (CGSize, CGRect)? + + private var titleLeftIcon: ChatTitleIcon = .none + private var titleRightIcon: ChatTitleIcon = .none + private var titleCredibilityIcon: ChatTitleCredibilityIcon = .none + + private var presenceManager: PeerPresenceStatusManager? + + private var pointerInteraction: PointerInteraction? + + public var inputActivities: (PeerId, [(Peer, PeerInputActivity)])? { + didSet { + let _ = self.updateStatus() + } + } + + private func updateNetworkStatusNode(networkState: AccountNetworkState, layout: ContainerViewLayout?) { + self.setNeedsLayout() + } + + public var networkState: AccountNetworkState = .online(proxy: nil) { + didSet { + if self.networkState != oldValue { + updateNetworkStatusNode(networkState: self.networkState, layout: self.layout) + let _ = self.updateStatus() + } + } + } + + public var layout: ContainerViewLayout? { + didSet { + if self.layout != oldValue { + updateNetworkStatusNode(networkState: self.networkState, layout: self.layout) + } + } + } + + public var pressed: (() -> Void)? + public var longPressed: (() -> Void)? + + public var titleContent: ChatTitleContent? { + didSet { + if let titleContent = self.titleContent { + let titleTheme = self.hasEmbeddedTitleContent ? defaultDarkPresentationTheme : self.theme + + var segments: [AnimatedCountLabelNode.Segment] = [] + var titleLeftIcon: ChatTitleIcon = .none + var titleRightIcon: ChatTitleIcon = .none + var titleCredibilityIcon: ChatTitleCredibilityIcon = .none + var isEnabled = true + switch titleContent { + case let .peer(peerView, customTitle, _, isScheduledMessages): + if peerView.peerId.isReplies { + let typeText: String = self.strings.DialogList_Replies + segments = [.text(0, NSAttributedString(string: typeText, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))] + isEnabled = false + } else if isScheduledMessages { + if peerView.peerId == self.context.account.peerId { + segments = [.text(0, NSAttributedString(string: self.strings.ScheduledMessages_RemindersTitle, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))] + } else { + segments = [.text(0, NSAttributedString(string: self.strings.ScheduledMessages_Title, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))] + } + isEnabled = false + } else { + if let peer = peerViewMainPeer(peerView) { + if let customTitle = customTitle { + segments = [.text(0, NSAttributedString(string: customTitle, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))] + } else if peerView.peerId == self.context.account.peerId { + segments = [.text(0, NSAttributedString(string: self.strings.Conversation_SavedMessages, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))] + } else { + if !peerView.peerIsContact, let user = peer as? TelegramUser, !user.flags.contains(.isSupport), user.botInfo == nil, let phone = user.phone, !phone.isEmpty { + segments = [.text(0, NSAttributedString(string: formatPhoneNumber(phone), font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))] + } else { + segments = [.text(0, NSAttributedString(string: EnginePeer(peer).displayTitle(strings: self.strings, displayOrder: self.nameDisplayOrder), font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))] + } + } + if peer.id != self.context.account.peerId { + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 }) + if peer.isFake { + titleCredibilityIcon = .fake + } else if peer.isScam { + titleCredibilityIcon = .scam + } else if let user = peer as? TelegramUser, let emojiStatus = user.emojiStatus, !premiumConfiguration.isPremiumDisabled { + titleCredibilityIcon = .emojiStatus(emojiStatus) + } else if peer.isVerified { + titleCredibilityIcon = .verified + } else if peer.isPremium && !premiumConfiguration.isPremiumDisabled { + titleCredibilityIcon = .premium + } + } + } + if peerView.peerId.namespace == Namespaces.Peer.SecretChat { + titleLeftIcon = .lock + } + if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings { + if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) { + if titleCredibilityIcon != .verified { + titleRightIcon = .mute + } + } + } + } + case let .replyThread(type, count): + let textFont = titleFont + let textColor = titleTheme.rootController.navigationBar.primaryTextColor + + if count > 0 { + var commentsPart: String + switch type { + case .comments: + commentsPart = self.strings.Conversation_TitleComments(Int32(count)) + case .replies: + commentsPart = self.strings.Conversation_TitleReplies(Int32(count)) + } + + if commentsPart.contains("[") && commentsPart.contains("]") { + if let startIndex = commentsPart.firstIndex(of: "["), let endIndex = commentsPart.firstIndex(of: "]") { + commentsPart.removeSubrange(startIndex ... endIndex) + } + } else { + commentsPart = commentsPart.trimmingCharacters(in: CharacterSet(charactersIn: "0123456789-,.")) + } + + let rawTextAndRanges: PresentationStrings.FormattedString + switch type { + case .comments: + rawTextAndRanges = self.strings.Conversation_TitleCommentsFormat("\(count)", commentsPart) + case .replies: + rawTextAndRanges = self.strings.Conversation_TitleRepliesFormat("\(count)", commentsPart) + } + + let rawText = rawTextAndRanges.string + + var textIndex = 0 + var latestIndex = 0 + for indexAndRange in rawTextAndRanges.ranges { + let index = indexAndRange.index + let range = indexAndRange.range + + var lowerSegmentIndex = range.lowerBound + if index != 0 { + lowerSegmentIndex = min(lowerSegmentIndex, latestIndex) + } else { + if latestIndex < range.lowerBound { + let part = String(rawText[rawText.index(rawText.startIndex, offsetBy: latestIndex) ..< rawText.index(rawText.startIndex, offsetBy: range.lowerBound)]) + segments.append(.text(textIndex, NSAttributedString(string: part, font: textFont, textColor: textColor))) + textIndex += 1 + } + } + latestIndex = range.upperBound + + let part = String(rawText[rawText.index(rawText.startIndex, offsetBy: lowerSegmentIndex) ..< rawText.index(rawText.startIndex, offsetBy: min(rawText.count, range.upperBound))]) + if index == 0 { + segments.append(.number(count, NSAttributedString(string: part, font: textFont, textColor: textColor))) + } else { + segments.append(.text(textIndex, NSAttributedString(string: part, font: textFont, textColor: textColor))) + textIndex += 1 + } + } + if latestIndex < rawText.count { + let part = String(rawText[rawText.index(rawText.startIndex, offsetBy: latestIndex)...]) + segments.append(.text(textIndex, NSAttributedString(string: part, font: textFont, textColor: textColor))) + textIndex += 1 + } + } else { + switch type { + case .comments: + segments = [.text(0, NSAttributedString(string: strings.Conversation_TitleCommentsEmpty, font: textFont, textColor: textColor))] + case .replies: + segments = [.text(0, NSAttributedString(string: strings.Conversation_TitleRepliesEmpty, font: textFont, textColor: textColor))] + } + } + + isEnabled = false + case let .custom(text, _, enabled): + segments = [.text(0, NSAttributedString(string: text, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))] + isEnabled = enabled + } + + var updated = false + + if self.titleTextNode.segments != segments { + self.titleTextNode.segments = segments + updated = true + } + + if titleLeftIcon != self.titleLeftIcon { + self.titleLeftIcon = titleLeftIcon + switch titleLeftIcon { + case .lock: + self.titleLeftIconNode.image = PresentationResourcesChat.chatTitleLockIcon(titleTheme) + default: + self.titleLeftIconNode.image = nil + } + updated = true + } + + if titleCredibilityIcon != self.titleCredibilityIcon { + self.titleCredibilityIcon = titleCredibilityIcon + /*switch titleCredibilityIcon { + case .none: + self.titleCredibilityIconNode.image = nil + case .fake: + self.titleCredibilityIconNode.image = PresentationResourcesChatList.fakeIcon(titleTheme, strings: self.strings, type: .regular) + case .scam: + self.titleCredibilityIconNode.image = PresentationResourcesChatList.scamIcon(titleTheme, strings: self.strings, type: .regular) + case .verified: + self.titleCredibilityIconNode.image = PresentationResourcesChatList.verifiedIcon(titleTheme) + case .premium: + self.titleCredibilityIconNode.image = PresentationResourcesChatList.premiumIcon(titleTheme) + }*/ + updated = true + } + + if titleRightIcon != self.titleRightIcon { + self.titleRightIcon = titleRightIcon + switch titleRightIcon { + case .mute: + self.titleRightIconNode.image = PresentationResourcesChat.chatTitleMuteIcon(titleTheme) + default: + self.titleRightIconNode.image = nil + } + updated = true + } + self.isUserInteractionEnabled = isEnabled + self.button.isUserInteractionEnabled = isEnabled + if !self.updateStatus() { + if updated { + if let (size, clearBounds) = self.validLayout { + self.updateLayout(size: size, clearBounds: clearBounds, transition: .animated(duration: 0.2, curve: .easeInOut)) + } + } + } + } + } + } + + private func updateStatus() -> Bool { + var inputActivitiesAllowed = true + if let titleContent = self.titleContent { + switch titleContent { + case let .peer(peerView, _, _, isScheduledMessages): + if let peer = peerViewMainPeer(peerView) { + if peer.id == self.context.account.peerId || isScheduledMessages || peer.id.isReplies { + inputActivitiesAllowed = false + } + } + case .replyThread: + inputActivitiesAllowed = true + default: + inputActivitiesAllowed = false + } + } + + let titleTheme = self.hasEmbeddedTitleContent ? defaultDarkPresentationTheme : self.theme + + var state = ChatTitleActivityNodeState.none + switch self.networkState { + case .waitingForNetwork, .connecting, .updating: + var infoText: String + switch self.networkState { + case .waitingForNetwork: + infoText = self.strings.ChatState_WaitingForNetwork + case .connecting: + infoText = self.strings.ChatState_Connecting + case .updating: + infoText = self.strings.ChatState_Updating + case .online: + infoText = "" + } + state = .info(NSAttributedString(string: infoText, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor), .generic) + case .online: + if let (peerId, inputActivities) = self.inputActivities, !inputActivities.isEmpty, inputActivitiesAllowed { + var stringValue = "" + var mergedActivity = inputActivities[0].1 + for (_, activity) in inputActivities { + if activity != mergedActivity { + mergedActivity = .typingText + break + } + } + if peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.SecretChat { + switch mergedActivity { + case .typingText: + stringValue = strings.Conversation_typing + case .uploadingFile: + stringValue = strings.Activity_UploadingDocument + case .recordingVoice: + stringValue = strings.Activity_RecordingAudio + case .uploadingPhoto: + stringValue = strings.Activity_UploadingPhoto + case .uploadingVideo: + stringValue = strings.Activity_UploadingVideo + case .playingGame: + stringValue = strings.Activity_PlayingGame + case .recordingInstantVideo: + stringValue = strings.Activity_RecordingVideoMessage + case .uploadingInstantVideo: + stringValue = strings.Activity_UploadingVideoMessage + case .choosingSticker: + stringValue = strings.Activity_ChoosingSticker + case let .seeingEmojiInteraction(emoticon): + stringValue = strings.Activity_EnjoyingAnimations(emoticon).string + case .speakingInGroupCall, .interactingWithEmoji: + stringValue = "" + } + } else { + if inputActivities.count > 1 { + let peerTitle = EnginePeer(inputActivities[0].0).compactDisplayTitle + if inputActivities.count == 2 { + let secondPeerTitle = EnginePeer(inputActivities[1].0).compactDisplayTitle + stringValue = strings.Chat_MultipleTypingPair(peerTitle, secondPeerTitle).string + } else { + stringValue = strings.Chat_MultipleTypingMore(peerTitle, String(inputActivities.count - 1)).string + } + } else if let (peer, _) = inputActivities.first { + stringValue = EnginePeer(peer).compactDisplayTitle + } + } + let color = titleTheme.rootController.navigationBar.accentTextColor + let string = NSAttributedString(string: stringValue, font: subtitleFont, textColor: color) + switch mergedActivity { + case .typingText: + state = .typingText(string, color) + case .recordingVoice: + state = .recordingVoice(string, color) + case .recordingInstantVideo: + state = .recordingVideo(string, color) + case .uploadingFile, .uploadingInstantVideo, .uploadingPhoto, .uploadingVideo: + state = .uploading(string, color) + case .playingGame: + state = .playingGame(string, color) + case .speakingInGroupCall, .interactingWithEmoji: + state = .typingText(string, color) + case .choosingSticker: + state = .choosingSticker(string, color) + case .seeingEmojiInteraction: + state = .choosingSticker(string, color) + } + } else { + if let titleContent = self.titleContent { + switch titleContent { + case let .peer(peerView, _, onlineMemberCount, isScheduledMessages): + if let peer = peerViewMainPeer(peerView) { + let servicePeer = isServicePeer(peer) + if peer.id == self.context.account.peerId || isScheduledMessages || peer.id.isReplies { + let string = NSAttributedString(string: "", font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor) + state = .info(string, .generic) + } else if let user = peer as? TelegramUser { + if user.isDeleted { + state = .none + } else if servicePeer { + let string = NSAttributedString(string: "", font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor) + state = .info(string, .generic) + } else if user.flags.contains(.isSupport) { + let statusText = self.strings.Bot_GenericSupportStatus + + let string = NSAttributedString(string: statusText, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor) + state = .info(string, .generic) + } else if let _ = user.botInfo { + let statusText = self.strings.Bot_GenericBotStatus + + let string = NSAttributedString(string: statusText, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor) + state = .info(string, .generic) + } else if let peer = peerViewMainPeer(peerView) { + let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + let userPresence: TelegramUserPresence + if let presence = peerView.peerPresences[peer.id] as? TelegramUserPresence { + userPresence = presence + self.presenceManager?.reset(presence: EnginePeer.Presence(presence)) + } else { + userPresence = TelegramUserPresence(status: .none, lastActivity: 0) + } + let (string, activity) = stringAndActivityForUserPresence(strings: self.strings, dateTimeFormat: self.dateTimeFormat, presence: EnginePeer.Presence(userPresence), relativeTo: Int32(timestamp)) + let attributedString = NSAttributedString(string: string, font: subtitleFont, textColor: activity ? titleTheme.rootController.navigationBar.accentTextColor : titleTheme.rootController.navigationBar.secondaryTextColor) + state = .info(attributedString, activity ? .online : .lastSeenTime) + } else { + let string = NSAttributedString(string: "", font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor) + state = .info(string, .generic) + } + } else if let group = peer as? TelegramGroup { + var onlineCount = 0 + if let cachedGroupData = peerView.cachedData as? CachedGroupData, let participants = cachedGroupData.participants { + let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + for participant in participants.participants { + if let presence = peerView.peerPresences[participant.peerId] as? TelegramUserPresence { + let relativeStatus = relativeUserPresenceStatus(EnginePeer.Presence(presence), relativeTo: Int32(timestamp)) + switch relativeStatus { + case .online: + onlineCount += 1 + default: + break + } + } + } + } + if onlineCount > 1 { + let string = NSMutableAttributedString() + + string.append(NSAttributedString(string: "\(strings.Conversation_StatusMembers(Int32(group.participantCount))), ", font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)) + string.append(NSAttributedString(string: strings.Conversation_StatusOnline(Int32(onlineCount)), font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)) + state = .info(string, .generic) + } else { + let string = NSAttributedString(string: strings.Conversation_StatusMembers(Int32(group.participantCount)), font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor) + state = .info(string, .generic) + } + } else if let channel = peer as? TelegramChannel { + if let cachedChannelData = peerView.cachedData as? CachedChannelData, let memberCount = cachedChannelData.participantsSummary.memberCount { + if memberCount == 0 { + let string: NSAttributedString + if case .group = channel.info { + string = NSAttributedString(string: strings.Group_Status, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor) + } else { + string = NSAttributedString(string: strings.Channel_Status, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor) + } + state = .info(string, .generic) + } else { + if case .group = channel.info, let onlineMemberCount = onlineMemberCount, onlineMemberCount > 1 { + let string = NSMutableAttributedString() + + string.append(NSAttributedString(string: "\(strings.Conversation_StatusMembers(Int32(memberCount))), ", font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)) + string.append(NSAttributedString(string: strings.Conversation_StatusOnline(Int32(onlineMemberCount)), font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)) + state = .info(string, .generic) + } else { + let membersString: String + if case .group = channel.info { + membersString = strings.Conversation_StatusMembers(memberCount) + } else { + membersString = strings.Conversation_StatusSubscribers(memberCount) + } + let string = NSAttributedString(string: membersString, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor) + state = .info(string, .generic) + } + } + } else { + switch channel.info { + case .group: + let string = NSAttributedString(string: strings.Group_Status, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor) + state = .info(string, .generic) + case .broadcast: + let string = NSAttributedString(string: strings.Channel_Status, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor) + state = .info(string, .generic) + } + } + } + } + case let .custom(_, subtitle?, _): + let string = NSAttributedString(string: subtitle, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor) + state = .info(string, .generic) + default: + break + } + + var accessibilityText = "" + for segment in self.titleTextNode.segments { + switch segment { + case let .number(_, string): + accessibilityText.append(string.string) + case let .text(_, string): + accessibilityText.append(string.string) + } + } + + self.accessibilityLabel = accessibilityText + self.accessibilityValue = state.string + } else { + self.accessibilityLabel = nil + } + } + } + + if self.activityNode.transitionToState(state, animation: .slide) { + if let (size, clearBounds) = self.validLayout { + self.updateLayout(size: size, clearBounds: clearBounds, transition: .animated(duration: 0.3, curve: .spring)) + } + return true + } else { + return false + } + } + + public init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer) { + self.context = context + self.theme = theme + self.strings = strings + self.dateTimeFormat = dateTimeFormat + self.nameDisplayOrder = nameDisplayOrder + self.animationCache = animationCache + self.animationRenderer = animationRenderer + + self.contentContainer = ASDisplayNode() + + self.titleContainerView = PortalSourceView() + self.titleTextNode = ImmediateAnimatedCountLabelNode() + + self.titleLeftIconNode = ASImageNode() + self.titleLeftIconNode.isLayerBacked = true + self.titleLeftIconNode.displayWithoutProcessing = true + self.titleLeftIconNode.displaysAsynchronously = false + + self.titleRightIconNode = ASImageNode() + self.titleRightIconNode.isLayerBacked = true + self.titleRightIconNode.displayWithoutProcessing = true + self.titleRightIconNode.displaysAsynchronously = false + + self.titleCredibilityIconView = ComponentHostView() + self.titleCredibilityIconView.isUserInteractionEnabled = false + + self.activityNode = ChatTitleActivityNode() + self.button = HighlightTrackingButtonNode() + + super.init(frame: CGRect()) + + self.isAccessibilityElement = true + self.accessibilityTraits = .header + + self.addSubnode(self.contentContainer) + self.titleContainerView.addSubnode(self.titleTextNode) + self.contentContainer.view.addSubview(self.titleContainerView) + self.contentContainer.addSubnode(self.activityNode) + self.addSubnode(self.button) + + self.presenceManager = PeerPresenceStatusManager(update: { [weak self] in + let _ = self?.updateStatus() + }) + + self.button.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: [.touchUpInside]) + self.button.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.titleTextNode.layer.removeAnimation(forKey: "opacity") + strongSelf.activityNode.layer.removeAnimation(forKey: "opacity") + strongSelf.titleCredibilityIconView.layer.removeAnimation(forKey: "opacity") + strongSelf.titleTextNode.alpha = 0.4 + strongSelf.activityNode.alpha = 0.4 + strongSelf.titleCredibilityIconView.alpha = 0.4 + } else { + strongSelf.titleTextNode.alpha = 1.0 + strongSelf.activityNode.alpha = 1.0 + strongSelf.titleCredibilityIconView.alpha = 1.0 + strongSelf.titleTextNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + strongSelf.activityNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } + self.button.view.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(self.longPressGesture(_:)))) + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override public func layoutSubviews() { + super.layoutSubviews() + + if let (size, clearBounds) = self.validLayout { + self.updateLayout(size: size, clearBounds: clearBounds, transition: .immediate) + } + } + + public func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings, hasEmbeddedTitleContent: Bool) { + self.theme = theme + self.hasEmbeddedTitleContent = hasEmbeddedTitleContent + self.strings = strings + + let titleContent = self.titleContent + self.titleCredibilityIcon = .none + self.titleContent = titleContent + let _ = self.updateStatus() + + if let (size, clearBounds) = self.validLayout { + self.updateLayout(size: size, clearBounds: clearBounds, transition: .immediate) + } + } + + public func updateLayout(size: CGSize, clearBounds: CGRect, transition: ContainedViewLayoutTransition) { + self.validLayout = (size, clearBounds) + + self.button.frame = clearBounds + self.contentContainer.frame = clearBounds + + var leftIconWidth: CGFloat = 0.0 + var rightIconWidth: CGFloat = 0.0 + var credibilityIconWidth: CGFloat = 0.0 + + if let image = self.titleLeftIconNode.image { + if self.titleLeftIconNode.supernode == nil { + self.titleTextNode.addSubnode(self.titleLeftIconNode) + } + leftIconWidth = image.size.width + 6.0 + } else if self.titleLeftIconNode.supernode != nil { + self.titleLeftIconNode.removeFromSupernode() + } + + let titleCredibilityContent: EmojiStatusComponent.Content + switch self.titleCredibilityIcon { + case .none: + titleCredibilityContent = .none + case .premium: + titleCredibilityContent = .premium(color: self.theme.list.itemAccentColor) + case .verified: + titleCredibilityContent = .verified(fillColor: self.theme.list.itemCheckColors.fillColor, foregroundColor: self.theme.list.itemCheckColors.foregroundColor, sizeType: .large) + case .fake: + titleCredibilityContent = .text(color: self.theme.chat.message.incoming.scamColor, string: self.strings.Message_FakeAccount.uppercased()) + case .scam: + titleCredibilityContent = .text(color: self.theme.chat.message.incoming.scamColor, string: self.strings.Message_ScamAccount.uppercased()) + case let .emojiStatus(emojiStatus): + titleCredibilityContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: self.theme.list.mediaPlaceholderColor, themeColor: self.theme.list.itemAccentColor, loopMode: .count(2)) + } + + let titleCredibilitySize = self.titleCredibilityIconView.update( + transition: .immediate, + component: AnyComponent(EmojiStatusComponent( + context: self.context, + animationCache: self.animationCache, + animationRenderer: self.animationRenderer, + content: titleCredibilityContent, + isVisibleForAnimations: true, + action: nil + )), + environment: {}, + containerSize: CGSize(width: 20.0, height: 20.0) + ) + + if self.titleCredibilityIcon != .none { + self.titleTextNode.view.addSubview(self.titleCredibilityIconView) + credibilityIconWidth = titleCredibilitySize.width + 3.0 + } else { + if self.titleCredibilityIconView.superview != nil { + self.titleCredibilityIconView.removeFromSuperview() + } + } + + if let image = self.titleRightIconNode.image { + if self.titleRightIconNode.supernode == nil { + self.titleTextNode.addSubnode(self.titleRightIconNode) + } + rightIconWidth = image.size.width + 3.0 + } else if self.titleRightIconNode.supernode != nil { + self.titleRightIconNode.removeFromSupernode() + } + + var titleTransition = transition + if self.titleContainerView.bounds.width.isZero { + titleTransition = .immediate + } + + let titleSideInset: CGFloat = 3.0 + var titleFrame: CGRect + if size.height > 40.0 { + var titleSize = self.titleTextNode.updateLayout(size: CGSize(width: clearBounds.width - leftIconWidth - credibilityIconWidth - rightIconWidth - titleSideInset * 2.0, height: size.height), animated: titleTransition.isAnimated) + titleSize.width += credibilityIconWidth + let activitySize = self.activityNode.updateLayout(clearBounds.size, alignment: .center) + let titleInfoSpacing: CGFloat = 0.0 + + if activitySize.height.isZero { + titleFrame = CGRect(origin: CGPoint(x: floor((clearBounds.width - titleSize.width) / 2.0), y: floor((size.height - titleSize.height) / 2.0)), size: titleSize) + if titleFrame.size.width < size.width { + titleFrame.origin.x = -clearBounds.minX + floor((size.width - titleFrame.width) / 2.0) + } + titleTransition.updateFrameAdditive(view: self.titleContainerView, frame: titleFrame) + titleTransition.updateFrameAdditive(node: self.titleTextNode, frame: CGRect(origin: CGPoint(), size: titleFrame.size)) + } else { + let combinedHeight = titleSize.height + activitySize.height + titleInfoSpacing + + titleFrame = CGRect(origin: CGPoint(x: floor((clearBounds.width - titleSize.width) / 2.0), y: floor((size.height - combinedHeight) / 2.0)), size: titleSize) + if titleFrame.size.width < size.width { + titleFrame.origin.x = -clearBounds.minX + floor((size.width - titleFrame.width) / 2.0) + } + titleFrame.origin.x = max(titleFrame.origin.x, clearBounds.minX + leftIconWidth) + titleTransition.updateFrameAdditive(view: self.titleContainerView, frame: titleFrame) + titleTransition.updateFrameAdditive(node: self.titleTextNode, frame: CGRect(origin: CGPoint(), size: titleFrame.size)) + + var activityFrame = CGRect(origin: CGPoint(x: floor((clearBounds.width - activitySize.width) / 2.0), y: floor((size.height - combinedHeight) / 2.0) + titleSize.height + titleInfoSpacing), size: activitySize) + if activitySize.width < size.width { + activityFrame.origin.x = -clearBounds.minX + floor((size.width - activityFrame.width) / 2.0) + } + self.activityNode.frame = activityFrame + } + + if let image = self.titleLeftIconNode.image { + self.titleLeftIconNode.frame = CGRect(origin: CGPoint(x: -image.size.width - 3.0 - UIScreenPixel, y: 4.0), size: image.size) + } + + self.titleCredibilityIconView.frame = CGRect(origin: CGPoint(x: titleFrame.width - titleCredibilitySize.width, y: floor((titleFrame.height - titleCredibilitySize.height) / 2.0)), size: titleCredibilitySize) + + if let image = self.titleRightIconNode.image { + self.titleRightIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.width + 3.0 + UIScreenPixel, y: 6.0), size: image.size) + } + } else { + let titleSize = self.titleTextNode.updateLayout(size: CGSize(width: floor(clearBounds.width / 2.0 - leftIconWidth - credibilityIconWidth - rightIconWidth - titleSideInset * 2.0), height: size.height), animated: titleTransition.isAnimated) + let activitySize = self.activityNode.updateLayout(CGSize(width: floor(clearBounds.width / 2.0), height: size.height), alignment: .center) + + let titleInfoSpacing: CGFloat = 8.0 + let combinedWidth = titleSize.width + leftIconWidth + credibilityIconWidth + rightIconWidth + activitySize.width + titleInfoSpacing + + titleFrame = CGRect(origin: CGPoint(x: leftIconWidth + floor((clearBounds.width - combinedWidth) / 2.0), y: floor((size.height - titleSize.height) / 2.0)), size: titleSize) + + titleTransition.updateFrameAdditiveToCenter(view: self.titleContainerView, frame: titleFrame) + titleTransition.updateFrameAdditiveToCenter(node: self.titleTextNode, frame: CGRect(origin: CGPoint(), size: titleFrame.size)) + + self.activityNode.frame = CGRect(origin: CGPoint(x: floor((clearBounds.width - combinedWidth) / 2.0 + titleSize.width + leftIconWidth + credibilityIconWidth + rightIconWidth + titleInfoSpacing), y: floor((size.height - activitySize.height) / 2.0)), size: activitySize) + + if let image = self.titleLeftIconNode.image { + self.titleLeftIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.minY + 4.0), size: image.size) + } + + self.titleCredibilityIconView.frame = CGRect(origin: CGPoint(x: titleFrame.maxX - titleCredibilitySize.width, y: floor((titleFrame.height - titleCredibilitySize.height) / 2.0)), size: titleCredibilitySize) + + if let image = self.titleRightIconNode.image { + self.titleRightIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.maxX - image.size.width, y: titleFrame.minY + 6.0), size: image.size) + } + } + + self.pointerInteraction = PointerInteraction(view: self, style: .rectangle(CGSize(width: titleFrame.width + 16.0, height: 40.0))) + } + + @objc private func buttonPressed() { + self.pressed?() + } + + @objc private func longPressGesture(_ gesture: UILongPressGestureRecognizer) { + switch gesture.state { + case .began: + self.longPressed?() + default: + break + } + } + + public func animateLayoutTransition() { + UIView.transition(with: self, duration: 0.25, options: [.transitionCrossDissolve], animations: { + }, completion: nil) + } + + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if !self.isUserInteractionEnabled { + return nil + } + if self.button.frame.contains(point) { + return self.button.view + } + return super.hitTest(point, with: event) + } + + public final class SnapshotState { + fileprivate let snapshotView: UIView + + fileprivate init(snapshotView: UIView) { + self.snapshotView = snapshotView + } + } + + public func prepareSnapshotState() -> SnapshotState { + let snapshotView = self.snapshotView(afterScreenUpdates: false)! + return SnapshotState( + snapshotView: snapshotView + ) + } + + public func animateFromSnapshot(_ snapshotState: SnapshotState) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + self.layer.animatePosition(from: CGPoint(x: 0.0, y: 20.0), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: true, additive: true) + + snapshotState.snapshotView.frame = self.frame + self.superview?.insertSubview(snapshotState.snapshotView, belowSubview: self) + + let snapshotView = snapshotState.snapshotView + snapshotState.snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + snapshotView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -20.0), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true) + } +} diff --git a/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift b/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift index 18b9dbb76f..68a51a1012 100644 --- a/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift +++ b/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift @@ -48,7 +48,7 @@ public final class EmojiStatusComponent: Component { case verified(fillColor: UIColor, foregroundColor: UIColor, sizeType: SizeType) case text(color: UIColor, string: String) case animation(content: AnimationContent, size: CGSize, placeholderColor: UIColor, themeColor: UIColor?, loopMode: LoopMode) - case topic(title: String) + case topic(title: String, colorIndex: Int) } public let context: AccountContext @@ -223,7 +223,7 @@ public final class EmojiStatusComponent: Component { } else { iconImage = nil } - case let .topic(title): + case let .topic(title, colorIndex): func generateTopicIcon(backgroundColors: [UIColor], strokeColors: [UIColor]) -> UIImage? { return generateImage(CGSize(width: 44.0, height: 44.0), rotatedContext: { size, context in context.clear(CGRect(origin: .zero, size: size)) @@ -279,7 +279,18 @@ public final class EmojiStatusComponent: Component { }) } - if let image = generateTopicIcon(backgroundColors: [UIColor(rgb: 0x6FB9F0), UIColor(rgb: 0x0261E4)], strokeColors: [UIColor(rgb: 0x026CB5), UIColor(rgb: 0x064BB7)]) { + let topicColors: [([UInt32], [UInt32])] = [ + ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7]), + ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7]), + ([0xFFD67E, 0xFC8601], [0xDA9400, 0xFA5F00]), + ([0xCB86DB, 0x9338AF], [0x812E98, 0x6F2B87]), + ([0x8EEE98, 0x02B504], [0x02A01B, 0x009716]), + ([0xFF93B2, 0xE23264], [0xFC447A, 0xC80C46]), + ([0xFB6F5F, 0xD72615], [0xDC1908, 0xB61506]) + ] + let clippedIndex = colorIndex % topicColors.count + + if let image = generateTopicIcon(backgroundColors: topicColors[clippedIndex].0.map(UIColor.init(rgb:)), strokeColors: topicColors[clippedIndex].1.map(UIColor.init(rgb:))) { iconImage = image } else { iconImage = nil diff --git a/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift b/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift index 0a5cbe9c90..26c6fd8477 100644 --- a/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift +++ b/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift @@ -106,7 +106,7 @@ private final class TitleFieldComponent: Component { let iconContent: EmojiStatusComponent.Content if component.fileId == 0 { - iconContent = .topic(title: String(component.text.prefix(1))) + iconContent = .topic(title: String(component.text.prefix(1)), colorIndex: 0) } else { iconContent = .animation(content: .customEmoji(fileId: component.fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: component.placeholderColor, themeColor: component.accentColor, loopMode: .count(2)) } @@ -731,7 +731,7 @@ public class ForumCreateTopicScreen: ViewControllerComponentContainer { } @objc private func createPressed() { - self.dismiss() +// self.dismiss() self.completion(self.state.0, self.state.1) } diff --git a/submodules/TelegramUI/Sources/ChatAvatarNavigationNode.swift b/submodules/TelegramUI/Sources/ChatAvatarNavigationNode.swift index 90277dc65a..20dc58de6a 100644 --- a/submodules/TelegramUI/Sources/ChatAvatarNavigationNode.swift +++ b/submodules/TelegramUI/Sources/ChatAvatarNavigationNode.swift @@ -13,6 +13,8 @@ import UniversalMediaPlayer import GalleryUI import HierarchyTrackingLayer import AccountContext +import ComponentFlow +import EmojiStatusComponent private let normalFont = avatarPlaceholderFont(size: 16.0) private let smallFont = avatarPlaceholderFont(size: 12.0) @@ -26,6 +28,8 @@ final class ChatAvatarNavigationNode: ASDisplayNode { let avatarNode: AvatarNode private var videoNode: UniversalVideoNode? + private let statusView: ComponentView + private var videoContent: NativeVideoContent? private let playbackStartDisposable = MetaDisposable() private var cachedDataDisposable = MetaDisposable() @@ -54,6 +58,7 @@ final class ChatAvatarNavigationNode: ASDisplayNode { override init() { self.containerNode = ContextControllerSourceNode() self.avatarNode = AvatarNode(font: normalFont) + self.statusView = ComponentView() super.init() @@ -81,6 +86,31 @@ final class ChatAvatarNavigationNode: ASDisplayNode { self.view.isOpaque = false } + public func setStatus(context: AccountContext, content: EmojiStatusComponent.Content) { + let statusSize = self.statusView.update( + transition: .immediate, + component: AnyComponent(EmojiStatusComponent( + context: context, + animationCache: context.animationCache, + animationRenderer: context.animationRenderer, + content: content, + isVisibleForAnimations: true, + action: nil + )), + environment: {}, + containerSize: CGSize(width: 32.0, height: 32.0) + ) + if let statusComponentView = self.statusView.view { + if statusComponentView.superview == nil { + self.containerNode.view.addSubview(statusComponentView) + } + + statusComponentView.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - statusSize.width) / 2.0), y: floor((self.containerNode.bounds.height - statusSize.height) / 2.0)), size: statusSize) + } + + self.avatarNode.isHidden = true + } + public func setPeer(context: AccountContext, theme: PresentationTheme, peer: EnginePeer?, authorOfMessage: MessageReference? = nil, overrideImage: AvatarNodeImageOverride? = nil, emptyColor: UIColor? = nil, clipStyle: AvatarNodeClipStyle = .round, synchronousLoad: Bool = false, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), storeUnrounded: Bool = false) { self.context = context self.avatarNode.setPeer(context: context, theme: theme, peer: peer, authorOfMessage: authorOfMessage, overrideImage: overrideImage, emptyColor: emptyColor, clipStyle: clipStyle, synchronousLoad: synchronousLoad, displayDimensions: displayDimensions, storeUnrounded: storeUnrounded) diff --git a/submodules/TelegramUI/Sources/ChatBotInfoItem.swift b/submodules/TelegramUI/Sources/ChatBotInfoItem.swift index 52aad3bd1a..a7850d9fce 100644 --- a/submodules/TelegramUI/Sources/ChatBotInfoItem.swift +++ b/submodules/TelegramUI/Sources/ChatBotInfoItem.swift @@ -453,7 +453,14 @@ final class ChatBotInfoItemNode: ListViewItemNode { case let .url(url, concealed): self.item?.controllerInteraction.openUrl(url, concealed, nil, nil) case let .peerMention(peerId, _): - self.item?.controllerInteraction.openPeer(peerId, .chat(textInputState: nil, subject: nil, peekData: nil), nil, false, nil) + if let item = self.item { + let _ = (item.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> deliverOnMainQueue).start(next: { [weak self] peer in + if let peer = peer { + self?.item?.controllerInteraction.openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: nil), nil, false) + } + }) + } case let .textMention(name): self.item?.controllerInteraction.openPeerMention(name) case let .botCommand(command): diff --git a/submodules/TelegramUI/Sources/ChatButtonKeyboardInputNode.swift b/submodules/TelegramUI/Sources/ChatButtonKeyboardInputNode.swift index 753e2f949b..7023f872d3 100644 --- a/submodules/TelegramUI/Sources/ChatButtonKeyboardInputNode.swift +++ b/submodules/TelegramUI/Sources/ChatButtonKeyboardInputNode.swift @@ -242,12 +242,14 @@ final class ChatButtonKeyboardInputNode: ChatInputNode { botPeer = message.author } - var peerId: PeerId? + var peer: Peer? if samePeer { - peerId = message.id.peerId + peer = message.peers[message.id.peerId] + } else { + peer = botPeer } - if let botPeer = botPeer, let addressName = botPeer.addressName { - self.controllerInteraction.openPeer(peerId, .chat(textInputState: ChatTextInputState(inputText: NSAttributedString(string: "@\(addressName) \(query)")), subject: nil, peekData: nil), nil, false, nil) + if let peer = peer, let botPeer = botPeer, let addressName = botPeer.addressName { + self.controllerInteraction.openPeer(EnginePeer(peer), .chat(textInputState: ChatTextInputState(inputText: NSAttributedString(string: "@\(addressName) \(query)")), subject: nil, peekData: nil), nil, false) } } case .payment: @@ -259,7 +261,13 @@ final class ChatButtonKeyboardInputNode: ChatInputNode { case let .setupPoll(isQuiz): self.controllerInteraction.openPollCreation(isQuiz) case let .openUserProfile(peerId): - self.controllerInteraction.openPeer(peerId, .info, nil, false, nil) + let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> deliverOnMainQueue).start(next: { [weak self] peer in + guard let self, let peer else { + return + } + self.controllerInteraction.openPeer(peer, .info, nil, false) + }) case let .openWebView(url, simple): self.controllerInteraction.openWebView(markupButton.title, url, simple, false) } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index d2def218ad..f845185898 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -81,6 +81,8 @@ import ImageTransparency import StickerPackPreviewUI import TextNodeWithEntities import EntityKeyboard +import ChatTitleView +import EmojiStatusComponent #if DEBUG import os.signpost @@ -258,6 +260,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G private var rightNavigationButton: ChatNavigationButton? private var chatInfoNavigationButton: ChatNavigationButton? + private var moreBarButton: MoreHeaderButton + private var moreInfoNavigationButton: ChatNavigationButton? + private var peerView: PeerView? private var historyStateDisposable: Disposable? @@ -592,6 +597,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G default: navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData, hideBackground: self.context.sharedContext.immediateExperimentalUISettings.playerEmbedding ? true : false, hideBadge: false) } + + self.moreBarButton = MoreHeaderButton(color: self.presentationData.theme.rootController.navigationBar.buttonColor) + self.moreBarButton.isUserInteractionEnabled = true + super.init(context: context, navigationBarPresentationData: navigationBarPresentationData, mediaAccessoryPanelVisibility: mediaAccessoryPanelVisibility, locationBroadcastPanelSource: locationBroadcastPanelSource, groupCallPanelSource: groupCallPanelSource) self.automaticallyControlPresentationContextLayout = false @@ -623,6 +632,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if strongSelf.presentVoiceMessageDiscardAlert(action: action, performAction: false) { return false } + + if strongSelf.presentTopicDiscardAlert(action: action, performAction: false) { + return false + } + return true } @@ -833,7 +847,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }, openUrl: { url in self?.openUrl(url, concealed: false, skipConcealedAlert: isLocation, message: nil) }, openPeer: { peer, navigation in - self?.openPeer(peerId: peer.id, navigation: navigation, fromMessage: nil) + self?.openPeer(peer: EnginePeer(peer), navigation: navigation, fromMessage: nil) }, callPeer: { peerId, isVideo in self?.controllerInteraction?.callPeer(peerId, isVideo) }, enqueueMessage: { message in @@ -894,9 +908,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let strongSelf = self { strongSelf.controllerInteraction?.openPeerMention(mention) } - }, openPeer: { [weak self] peerId in + }, openPeer: { [weak self] peer in if let strongSelf = self { - strongSelf.controllerInteraction?.openPeer(peerId, .default, nil, false, nil) + strongSelf.controllerInteraction?.openPeer(peer, .default, nil, false) } }, openHashtag: { [weak self] peerName, hashtag in if let strongSelf = self { @@ -965,8 +979,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }) }))) - }, openPeer: { [weak self] id, navigation, fromMessage, isReaction, _ in - self?.openPeer(peerId: id, navigation: navigation, fromMessage: fromMessage, fromReactionMessageId: isReaction ? fromMessage?.id : nil) + }, openPeer: { [weak self] peer, navigation, fromMessage, isReaction in + self?.openPeer(peer: peer, navigation: navigation, fromMessage: fromMessage, fromReactionMessageId: isReaction ? fromMessage?.id : nil) }, openPeerMention: { [weak self] name in self?.openPeerMention(name) }, openMessageContextMenu: { [weak self] message, selectAll, node, frame, anyRecognizer, location in @@ -1408,13 +1422,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G availableReactions: availableReactions, animationCache: strongSelf.controllerInteraction!.presentationContext.animationCache, animationRenderer: strongSelf.controllerInteraction!.presentationContext.animationRenderer, - message: EngineMessage(message), reaction: value, readStats: nil, back: nil, openPeer: { id in + message: EngineMessage(message), reaction: value, readStats: nil, back: nil, openPeer: { peer in dismissController?({ guard let strongSelf = self else { return } - strongSelf.openPeer(peerId: id, navigation: .default, fromMessage: MessageReference(message), fromReactionMessageId: message.id) + strongSelf.openPeer(peer: peer, navigation: .default, fromMessage: MessageReference(message), fromReactionMessageId: message.id) }) }))) @@ -2320,9 +2334,21 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } if let botStart = strongSelf.botStart, case let .automatic(returnToPeerId, scheduled) = botStart.behavior { - strongSelf.openPeer(peerId: returnToPeerId, navigation: .chat(textInputState: ChatTextInputState(inputText: NSAttributedString(string: inputString)), subject: scheduled ? .scheduledMessages : nil, peekData: nil), fromMessage: nil) + let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: returnToPeerId)) + |> deliverOnMainQueue).start(next: { peer in + if let strongSelf = self, let peer = peer { + strongSelf.openPeer(peer: peer, navigation: .chat(textInputState: ChatTextInputState(inputText: NSAttributedString(string: inputString)), subject: scheduled ? .scheduledMessages : nil, peekData: nil), fromMessage: nil) + } + }) } else { - strongSelf.openPeer(peerId: peerId, navigation: .chat(textInputState: ChatTextInputState(inputText: NSAttributedString(string: inputString)), subject: nil, peekData: nil), fromMessage: nil) + if let peerId = peerId { + let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> deliverOnMainQueue).start(next: { peer in + if let strongSelf = self, let peer = peer { + strongSelf.openPeer(peer: peer, navigation: .chat(textInputState: ChatTextInputState(inputText: NSAttributedString(string: inputString)), subject: nil, peekData: nil), fromMessage: nil) + } + }) + } } }, openUrl: { [weak self] url, concealed, _, message in if let strongSelf = self { @@ -2663,7 +2689,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_LinkDialogOpen, color: .accent, action: { [weak actionSheet] in actionSheet?.dismissAnimated() if let strongSelf = self { - strongSelf.openPeer(peerId: peerId, navigation: .chat(textInputState: nil, subject: nil, peekData: nil), fromMessage: nil) + let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> deliverOnMainQueue).start(next: { peer in + if let strongSelf = self, let peer = peer { + strongSelf.openPeer(peer: peer, navigation: .chat(textInputState: nil, subject: nil, peekData: nil), fromMessage: nil) + } + }) } })) if !mention.isEmpty { @@ -3580,14 +3611,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/User"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in f(.dismissWithoutContent) - self?.openPeer(peerId: peer.id, navigation: .info, fromMessage: nil) + self?.openPeer(peer: peer, navigation: .info, fromMessage: nil) })) ] items.append(.action(ContextMenuActionItem(text: isChannel ? strongSelf.presentationData.strings.Conversation_ContextMenuOpenChannel : strongSelf.presentationData.strings.Conversation_ContextMenuSendMessage, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: isChannel ? "Chat/Context Menu/Channels" : "Chat/Context Menu/Message"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in f(.dismissWithoutContent) - self?.openPeer(peerId: peer.id, navigation: .chat(textInputState: nil, subject: nil, peekData: nil), fromMessage: nil) + self?.openPeer(peer: peer, navigation: .chat(textInputState: nil, subject: nil, peekData: nil), fromMessage: nil) }))) if !isChannel && canSendMessagesToChat(strongSelf.presentationInterfaceState) { items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_ContextMenuMention, icon: { theme in @@ -4116,6 +4147,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G chatInfoButtonItem.action = #selector(self.rightNavigationButtonAction) self.chatInfoNavigationButton = ChatNavigationButton(action: .openChatInfo(expandAvatar: true), buttonItem: chatInfoButtonItem) + self.moreBarButton.setContent(.more(MoreHeaderButton.optionsCircleImage(color: self.presentationData.theme.rootController.navigationBar.buttonColor))) + self.moreInfoNavigationButton = ChatNavigationButton(action: .toggleInfoPanel, buttonItem: UIBarButtonItem(customDisplayNode: self.moreBarButton)!) + self.moreBarButton.contextAction = { [weak self] sourceNode, gesture in + guard let self = self else { + return + } + guard case let .peer(peerId) = self.chatLocation else { + return + } + ChatListControllerImpl.openMoreMenu(context: self.context, peerId: peerId, sourceController: self, isViewingAsTopics: false, sourceView: sourceNode.view, gesture: gesture) + } + self.moreBarButton.addTarget(self, action: #selector(self.moreButtonPressed), forControlEvents: .touchUpInside) + self.navigationItem.titleView = self.chatTitleView self.chatTitleView?.pressed = { [weak self] in self?.navigationButtonAction(.openChatInfo(expandAvatar: false)) @@ -4340,7 +4384,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if case .pinnedMessages = presentationInterfaceState.subject { strongSelf.chatTitleView?.titleContent = .custom(presentationInterfaceState.strings.Chat_TitlePinnedMessages(Int32(displayedCount ?? 1)), nil, false) } else { - strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages) + strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, customTitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages) let imageOverride: AvatarNodeImageOverride? if strongSelf.context.account.peerId == peer.id { imageOverride = .savedMessagesIcon @@ -4371,9 +4415,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.hasScheduledMessages = hasScheduledMessages var upgradedToPeerId: PeerId? + var movedToForumTopics = false if let previous = strongSelf.peerView, let group = previous.peers[previous.peerId] as? TelegramGroup, group.migrationReference == nil, let updatedGroup = peerView.peers[peerView.peerId] as? TelegramGroup, let migrationReference = updatedGroup.migrationReference { upgradedToPeerId = migrationReference.peerId } + if let previous = strongSelf.peerView, let channel = previous.peers[previous.peerId] as? TelegramChannel, !channel.flags.contains(.isForum), let updatedChannel = peerView.peers[peerView.peerId] as? TelegramChannel, updatedChannel.flags.contains(.isForum) { + movedToForumTopics = true + } var shouldDismiss = false if let previous = strongSelf.peerView, let group = previous.peers[previous.peerId] as? TelegramGroup, group.membership != .Removed, let updatedGroup = peerView.peers[peerView.peerId] as? TelegramGroup, updatedGroup.membership == .Removed { @@ -4663,6 +4711,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G navigationController.setViewControllers(viewControllers, animated: false) } } + } else if movedToForumTopics { + if let navigationController = strongSelf.effectiveNavigationController { + let chatListController = strongSelf.context.sharedContext.makeChatListController(context: strongSelf.context, location: .forum(peerId: peerView.peerId), controlsHistoryPreload: false, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: false) + navigationController.replaceController(strongSelf, with: chatListController, animated: true) + } } else if shouldDismiss { strongSelf.dismiss() } @@ -4687,13 +4740,65 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let peerView = context.account.viewTracker.peerView(peerId) + let messageAndTopic = messagePromise.get() + |> mapToSignal { message -> Signal<(message: Message?, threadInfo: EngineMessageHistoryThread.Info?), NoError> in + guard let message = message else { + return .single((nil, nil)) + } + + let viewKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: peerId, threadId: Int64(message.id.id)) + return context.account.postbox.combinedView(keys: [viewKey]) + |> map { views -> (message: Message?, threadInfo: EngineMessageHistoryThread.Info?) in + guard let view = views.views[viewKey] as? MessageHistoryThreadInfoView else { + return (message, nil) + } + return (message, view.info?.get(MessageHistoryThreadData.self)?.info) + } + } + + var onlineMemberCount: Signal = .single(nil) + if peerId.namespace == Namespaces.Peer.CloudChannel { + let recentOnlineSignal: Signal = peerView + |> map { view -> Bool? in + if let cachedData = view.cachedData as? CachedChannelData, let peer = peerViewMainPeer(view) as? TelegramChannel { + if case .broadcast = peer.info { + return nil + } else if let memberCount = cachedData.participantsSummary.memberCount, memberCount > 50 { + return true + } else { + return false + } + } else { + return false + } + } + |> distinctUntilChanged + |> mapToSignal { isLarge -> Signal in + if let isLarge = isLarge { + if isLarge { + return context.peerChannelMemberCategoriesContextsManager.recentOnline(account: context.account, accountPeerId: context.account.peerId, peerId: peerId) + |> map(Optional.init) + } else { + return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId) + |> map(Optional.init) + } + } else { + return .single(nil) + } + } + onlineMemberCount = recentOnlineSignal + } + self.titleDisposable.set(nil) self.peerDisposable.set((combineLatest(queue: Queue.mainQueue(), peerView, - messagePromise.get() + messageAndTopic, + onlineMemberCount ) - |> deliverOnMainQueue).start(next: { [weak self] peerView, message in + |> deliverOnMainQueue).start(next: { [weak self] peerView, messageAndTopic, onlineMemberCount in if let strongSelf = self { + let message = messageAndTopic.message + var count = 0 if let message = message { for attribute in message.attributes { @@ -4704,7 +4809,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - strongSelf.chatTitleView?.titleContent = .replyThread(type: replyThreadType, count: count) + if let threadInfo = messageAndTopic.threadInfo, let message = message { + strongSelf.chatTitleView?.titleContent = .peer(peerView: peerView, customTitle: threadInfo.title, onlineMemberCount: onlineMemberCount, isScheduledMessages: false) + + let avatarContent: EmojiStatusComponent.Content + if let fileId = threadInfo.icon { + avatarContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: strongSelf.presentationData.theme.list.mediaPlaceholderColor, themeColor: nil, loopMode: .count(2)) + } else { + avatarContent = .topic(title: String(threadInfo.title.prefix(1)), colorIndex: Int(message.id.id)) + } + (strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.setStatus(context: strongSelf.context, content: avatarContent) + } else { + strongSelf.chatTitleView?.titleContent = .replyThread(type: replyThreadType, count: count) + } let firstTime = strongSelf.peerView == nil strongSelf.peerView = peerView @@ -5868,7 +5985,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } if case let .replyThread(replyThreadMessageId) = strongSelf.chatLocation { - pinnedMessageId = replyThreadMessageId.effectiveTopId + if let channel = combinedInitialData.initialData?.peer as? TelegramChannel, channel.flags.contains(.isForum) { + pinnedMessageId = nil + } else { + pinnedMessageId = replyThreadMessageId.effectiveTopId + } } var pinnedMessage: ChatPinnedMessage? @@ -6014,12 +6135,23 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } |> distinctUntilChanged + let isForum = self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> map { peer -> Bool in + if case let .channel(channel) = peer { + return channel.flags.contains(.isForum) + } else { + return false + } + } + |> distinctUntilChanged + self.cachedDataDisposable = combineLatest(queue: .mainQueue(), self.chatDisplayNode.historyNode.cachedPeerDataAndMessages, hasPendingMessages, isTopReplyThreadMessageShown, topPinnedMessage, - customEmojiAvailable - ).start(next: { [weak self] cachedDataAndMessages, hasPendingMessages, isTopReplyThreadMessageShown, topPinnedMessage, customEmojiAvailable in + customEmojiAvailable, + isForum + ).start(next: { [weak self] cachedDataAndMessages, hasPendingMessages, isTopReplyThreadMessageShown, topPinnedMessage, customEmojiAvailable, isForum in if let strongSelf = self { let (cachedData, messages) = cachedDataAndMessages @@ -6075,14 +6207,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var pinnedMessage: ChatPinnedMessage? switch strongSelf.chatLocation { case let .replyThread(replyThreadMessage): - if isTopReplyThreadMessageShown { + if isForum { pinnedMessageId = nil } else { - pinnedMessageId = replyThreadMessage.effectiveTopId - } - if let pinnedMessageId = pinnedMessageId { - if let message = messages?[pinnedMessageId] { - pinnedMessage = ChatPinnedMessage(message: message, index: 0, totalCount: 1, topMessageId: message.id) + if isTopReplyThreadMessageShown { + pinnedMessageId = nil + } else { + pinnedMessageId = replyThreadMessage.effectiveTopId + } + if let pinnedMessageId = pinnedMessageId { + if let message = messages?[pinnedMessageId] { + pinnedMessage = ChatPinnedMessage(message: message, index: 0, totalCount: 1, topMessageId: message.id) + } } } case .peer: @@ -7705,7 +7841,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self else { return } - strongSelf.openPeer(peerId: peerId, navigation: .default, fromMessage: nil) + let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> deliverOnMainQueue).start(next: { peer in + if let strongSelf = self, let peer = peer { + strongSelf.openPeer(peer: peer, navigation: .default, fromMessage: nil) + } + }) }, openPeerInfo: { [weak self] in self?.navigationButtonAction(.openChatInfo(expandAvatar: false)) }, togglePeerNotifications: { [weak self] in @@ -7767,7 +7908,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if case .scheduledMessages = strongSelf.presentationInterfaceState.subject { isScheduled = true } - strongSelf.openPeer(peerId: peerId, navigation: .withBotStartPayload(ChatControllerInitialBotStart(payload: payload, behavior: .automatic(returnToPeerId: currentPeerId, scheduled: isScheduled))), fromMessage: nil) + let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> deliverOnMainQueue).start(next: { peer in + if let strongSelf = self, let peer = peer { + strongSelf.openPeer(peer: peer, navigation: .withBotStartPayload(ChatControllerInitialBotStart(payload: payload, behavior: .automatic(returnToPeerId: currentPeerId, scheduled: isScheduled))), fromMessage: nil) + } + }) } }, beginMediaRecording: { [weak self] isVideo in guard let strongSelf = self else { @@ -10556,7 +10702,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.leftNavigationButton = nil } - if let button = rightNavigationButtonForChatInterfaceState(updatedChatPresentationInterfaceState, strings: updatedChatPresentationInterfaceState.strings, currentButton: self.rightNavigationButton, target: self, selector: #selector(self.rightNavigationButtonAction), chatInfoNavigationButton: self.chatInfoNavigationButton) { + if let button = rightNavigationButtonForChatInterfaceState(updatedChatPresentationInterfaceState, strings: updatedChatPresentationInterfaceState.strings, currentButton: self.rightNavigationButton, target: self, selector: #selector(self.rightNavigationButtonAction), chatInfoNavigationButton: self.chatInfoNavigationButton, moreInfoNavigationButton: self.moreInfoNavigationButton) { if self.rightNavigationButton != button { var animated = transition.isAnimated if let currentButton = self.rightNavigationButton?.action, currentButton == button.action { @@ -10682,6 +10828,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } + @objc private func moreButtonPressed() { + self.moreBarButton.play() + self.moreBarButton.contextAction?(self.moreBarButton.containerNode, nil) + } + func beginClearHistory(type: InteractiveHistoryClearingType) { guard case let .peer(peerId) = self.chatLocation else { return @@ -12801,7 +12952,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G case let .mention(peerId, mention): switch action { case .tap: - strongSelf.controllerInteraction?.openPeer(peerId, .default, nil, false, nil) + let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> deliverOnMainQueue).start(next: { peer in + if let strongSelf = self, let peer = peer { + strongSelf.controllerInteraction?.openPeer(peer, .default, nil, false) + } + }) case .longTap: strongSelf.controllerInteraction?.longTap(.peerMention(peerId, mention), nil) } @@ -12889,7 +13045,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G case let .mention(peerId, mention): switch action { case .tap: - strongSelf.controllerInteraction?.openPeer(peerId, .default, nil, false, nil) + let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> deliverOnMainQueue).start(next: { peer in + if let strongSelf = self, let peer = peer { + strongSelf.controllerInteraction?.openPeer(peer, .default, nil, false) + } + }) case .longTap: strongSelf.controllerInteraction?.longTap(.peerMention(peerId, mention), nil) } @@ -12998,7 +13159,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G case let .mention(peerId, mention): switch action { case .tap: - strongSelf.controllerInteraction?.openPeer(peerId, .default, nil, false, nil) + let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> deliverOnMainQueue).start(next: { peer in + if let strongSelf = self, let peer = peer { + strongSelf.controllerInteraction?.openPeer(peer, .default, nil, false) + } + }) case .longTap: strongSelf.controllerInteraction?.longTap(.peerMention(peerId, mention), nil) } @@ -14982,9 +15148,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) } - private func openPeer(peerId: PeerId?, navigation: ChatControllerInteractionNavigateToPeer, fromMessage: MessageReference?, fromReactionMessageId: MessageId? = nil, expandAvatar: Bool = false) { + private func openPeer(peer: EnginePeer?, navigation: ChatControllerInteractionNavigateToPeer, fromMessage: MessageReference?, fromReactionMessageId: MessageId? = nil, expandAvatar: Bool = false) { let _ = self.presentVoiceMessageDiscardAlert(action: { - if case let .peer(currentPeerId) = self.chatLocation, peerId == currentPeerId { + if case let .peer(currentPeerId) = self.chatLocation, peer?.id == currentPeerId { switch navigation { case .info: self.navigationButtonAction(.openChatInfo(expandAvatar: expandAvatar)) @@ -15008,7 +15174,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G break } } else { - if let peerId = peerId { + if let peer = peer { do { var chatPeerId: PeerId? if let peer = self.presentationInterfaceState.renderedPeer?.chatMainPeer as? TelegramGroup { @@ -15021,9 +15187,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G case .info, .default: let peerSignal: Signal if let messageId = fromMessage?.id { - peerSignal = loadedPeerFromMessage(account: self.context.account, peerId: peerId, messageId: messageId) + peerSignal = loadedPeerFromMessage(account: self.context.account, peerId: peer.id, messageId: messageId) } else { - peerSignal = self.context.account.postbox.loadedPeerWithId(peerId) |> map(Optional.init) + peerSignal = self.context.account.postbox.loadedPeerWithId(peer.id) |> map(Optional.init) } self.navigationActionDisposable.set((peerSignal |> take(1) |> deliverOnMainQueue).start(next: { [weak self] peer in if let strongSelf = self, let peer = peer { @@ -15048,22 +15214,22 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G })) case let .chat(textInputState, subject, peekData): if let textInputState = textInputState { - let _ = (ChatInterfaceState.update(engine: self.context.engine, peerId: peerId, threadId: nil, { currentState in + let _ = (ChatInterfaceState.update(engine: self.context.engine, peerId: peer.id, threadId: nil, { currentState in return currentState.withUpdatedComposeInputState(textInputState) }) |> deliverOnMainQueue).start(completed: { [weak self] in if let strongSelf = self, let navigationController = strongSelf.effectiveNavigationController { - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId), subject: subject, updateTextInputState: textInputState, peekData: peekData)) + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peer.id), subject: subject, updateTextInputState: textInputState, peekData: peekData)) } }) } else { - self.effectiveNavigationController?.pushViewController(ChatControllerImpl(context: self.context, chatLocation: .peer(id: peerId), subject: subject)) + self.effectiveNavigationController?.pushViewController(ChatControllerImpl(context: self.context, chatLocation: .peer(id: peer.id), subject: subject)) } case let .withBotStartPayload(botStart): - self.effectiveNavigationController?.pushViewController(ChatControllerImpl(context: self.context, chatLocation: .peer(id: peerId), botStart: botStart)) + self.effectiveNavigationController?.pushViewController(ChatControllerImpl(context: self.context, chatLocation: .peer(id: peer.id), botStart: botStart)) case let .withAttachBot(attachBotStart): if let navigationController = self.effectiveNavigationController { - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(id: peerId), attachBotStart: attachBotStart)) + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(id: peer.id), attachBotStart: attachBotStart)) } } } @@ -15177,7 +15343,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G navigation = .chat(textInputState: nil, subject: nil, peekData: nil) } } - strongSelf.openResolved(result: .peer(peer.id, navigation), sourceMessageId: sourceMessageId) + strongSelf.openResolved(result: .peer(peer, navigation), sourceMessageId: sourceMessageId) } else { strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Resolve_ErrorNotFound, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) } @@ -15507,7 +15673,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G switch navigation { case let .chat(_, subject, peekData): - if case .peer(peerId) = strongSelf.chatLocation { + if case .peer(peerId.id) = strongSelf.chatLocation { if let subject = subject, case let .message(messageSubject, _, timecode) = subject { if case let .id(messageId) = messageSubject { strongSelf.navigateToMessage(from: sourceMessageId, to: .id(messageId, timecode)) @@ -15516,10 +15682,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self?.playShakeAnimation() } } else if let navigationController = strongSelf.effectiveNavigationController { - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId), subject: subject, keepStack: .always, peekData: peekData)) + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId.id), subject: subject, keepStack: .always, peekData: peekData)) } case .info: - strongSelf.navigationActionDisposable.set((strongSelf.context.account.postbox.loadedPeerWithId(peerId) + strongSelf.navigationActionDisposable.set((strongSelf.context.account.postbox.loadedPeerWithId(peerId.id) |> take(1) |> deliverOnMainQueue).start(next: { [weak self] peer in if let strongSelf = self, peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil { @@ -15529,16 +15695,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } })) case let .withBotStartPayload(startPayload): - if case .peer(peerId) = strongSelf.chatLocation { + if case .peer(peerId.id) = strongSelf.chatLocation { strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedBotStartPayload(startPayload.payload) }) } else if let navigationController = strongSelf.effectiveNavigationController { - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId), botStart: startPayload, keepStack: .always)) + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId.id), botStart: startPayload, keepStack: .always)) } case let .withAttachBot(attachBotStart): if let navigationController = strongSelf.effectiveNavigationController { - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId), attachBotStart: attachBotStart)) + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId.id), attachBotStart: attachBotStart)) } default: break @@ -16726,6 +16892,34 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return false } + private func presentTopicDiscardAlert(action: @escaping () -> Void = {}, delay: Bool = false, performAction: Bool = true) -> Bool { + if self.chatDisplayNode.emptyType == .topic { + Queue.mainQueue().after(delay ? 0.2 : 0.0) { + self.present(textAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, title: "Delete Topic", text: "Topic isn't created, because you haven't posted a message.\n\nDo you want to discard this topic?", actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Yes, action: { [weak self] in + guard let strongSelf = self else { + return + } + + if case let .replyThread(messagePromise) = strongSelf.chatLocationInfoData { + let _ = (messagePromise.get() + |> deliverOnMainQueue).start(next: { [weak self] message in + if let strongSelf = self, let message = message { + let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: [message.id], type: .forEveryone).start() + } + }) + } + + action() + }), TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_No, action: {})]), in: .window(.root)) + } + + return true + } else if performAction { + action() + } + return false + } + private func presentAutoremoveSetup() { guard let peer = self.presentationInterfaceState.renderedPeer?.peer else { return diff --git a/submodules/TelegramUI/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Sources/ChatControllerInteraction.swift index 6d01a09100..1c739ca79b 100644 --- a/submodules/TelegramUI/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Sources/ChatControllerInteraction.swift @@ -61,7 +61,7 @@ struct UnreadMessageRangeKey: Hashable { public final class ChatControllerInteraction { let openMessage: (Message, ChatControllerInteractionOpenMessageMode) -> Bool - let openPeer: (PeerId?, ChatControllerInteractionNavigateToPeer, MessageReference?, Bool, Peer?) -> Void + let openPeer: (EnginePeer, ChatControllerInteractionNavigateToPeer, MessageReference?, Bool) -> Void let openPeerMention: (String) -> Void let openMessageContextMenu: (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?, CGPoint?) -> Void let updateMessageReaction: (Message, ChatControllerInteractionReaction) -> Void @@ -167,7 +167,7 @@ public final class ChatControllerInteraction { init( openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, - openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, MessageReference?, Bool, Peer?) -> Void, + openPeer: @escaping (EnginePeer, ChatControllerInteractionNavigateToPeer, MessageReference?, Bool) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?, CGPoint?) -> Void, openMessageReactionContextMenu: @escaping (Message, ContextExtractedContentContainingView, ContextGesture?, MessageReaction.Reaction) -> Void, diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 52c9198f45..38dbfcb9ee 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -20,6 +20,7 @@ import SparseItemGrid import ChatPresentationInterfaceState import ChatInputPanelContainer import PremiumUI +import ChatTitleView final class VideoNavigationControllerDropContentItem: NavigationControllerDropContentItem { let itemNode: OverlayMediaItemNode @@ -92,7 +93,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { let historyNodeContainer: ASDisplayNode let loadingNode: ChatLoadingNode private var emptyNode: ChatEmptyNode? - private var emptyType: ChatHistoryNodeLoadState.EmptyType? + private(set) var emptyType: ChatHistoryNodeLoadState.EmptyType? private var didDisplayEmptyGreeting = false private var validEmptyNodeLayout: (CGSize, UIEdgeInsets)? var restrictedNode: ChatRecentActionsEmptyNode? diff --git a/submodules/TelegramUI/Sources/ChatEmptyNode.swift b/submodules/TelegramUI/Sources/ChatEmptyNode.swift index f2e54e3479..0eca0ea389 100644 --- a/submodules/TelegramUI/Sources/ChatEmptyNode.swift +++ b/submodules/TelegramUI/Sources/ChatEmptyNode.swift @@ -838,9 +838,13 @@ final class ChatEmptyNodeTopicChatContent: ASDisplayNode, ChatEmptyNodeContent, if let fileId = self.fileId { iconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: .clear, themeColor: serviceColor.primaryText, loopMode: .count(2)) } else { - iconContent = .topic(title: String(title.prefix(1))) + var colorIndex: Int = 0 + if case let .replyThread(replyThreadMessage) = interfaceState.chatLocation { + colorIndex = Int(clamping: abs(replyThreadMessage.effectiveTopId.id)) + } + iconContent = .topic(title: String(title.prefix(1)), colorIndex: colorIndex) } - + let insets = UIEdgeInsets(top: inset, left: inset, bottom: inset, right: inset) let titleSpacing: CGFloat = 6.0 let iconSpacing: CGFloat = 9.0 diff --git a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift index bb4c7553a4..7e92fa3c9b 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift @@ -253,11 +253,8 @@ func chatHistoryEntriesForView( } } - let replyCount = view.entries.isEmpty ? 0 : 1 - - if hasTopicCreated && replyCount == 0 { - - } else { + if !replyThreadMessage.isForumPost { + let replyCount = view.entries.isEmpty ? 0 : 1 entries.insert(.ReplyCountEntry(messages[0].index, replyThreadMessage.isChannelPost, replyCount, presentationData), at: 1) } } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index c715bc4e72..b14d0e0bba 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -1599,7 +1599,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState actions.insert(.custom(ChatReadReportContextItem(context: context, message: message, hasReadReports: hasReadReports, stats: readStats, action: { c, f, stats, customReactionEmojiPacks, firstCustomEmojiReaction in if reactionCount == 0, let stats = stats, stats.peers.count == 1 { c.dismiss(completion: { - controllerInteraction.openPeer(stats.peers[0].id, .default, nil, false, nil) + controllerInteraction.openPeer(stats.peers[0], .default, nil, false) }) } else if (stats != nil && !stats!.peers.isEmpty) || reactionCount != 0 { var tip: ContextController.Tip? @@ -1641,9 +1641,9 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState back: { [weak c] in c?.popItems() }, - openPeer: { [weak c] id in + openPeer: { [weak c] peer in c?.dismiss(completion: { - controllerInteraction.openPeer(id, .default, MessageReference(message), true, nil) + controllerInteraction.openPeer(peer, .default, MessageReference(message), true) }) } )), tip: tip))) diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift index 10608c01e6..aff0bc9886 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift @@ -76,7 +76,7 @@ func leftNavigationButtonForChatInterfaceState(_ presentationInterfaceState: Cha return nil } -func rightNavigationButtonForChatInterfaceState(_ presentationInterfaceState: ChatPresentationInterfaceState, strings: PresentationStrings, currentButton: ChatNavigationButton?, target: Any?, selector: Selector?, chatInfoNavigationButton: ChatNavigationButton?) -> ChatNavigationButton? { +func rightNavigationButtonForChatInterfaceState(_ presentationInterfaceState: ChatPresentationInterfaceState, strings: PresentationStrings, currentButton: ChatNavigationButton?, target: Any?, selector: Selector?, chatInfoNavigationButton: ChatNavigationButton?, moreInfoNavigationButton: ChatNavigationButton?) -> ChatNavigationButton? { if let _ = presentationInterfaceState.interfaceState.selectionState { if case .forwardedMessages = presentationInterfaceState.subject { return nil @@ -90,6 +90,13 @@ func rightNavigationButtonForChatInterfaceState(_ presentationInterfaceState: Ch } } + if let channel = presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.flags.contains(.isForum), let moreInfoNavigationButton = moreInfoNavigationButton { + if case .replyThread = presentationInterfaceState.chatLocation { + } else { + return moreInfoNavigationButton + } + } + var hasMessages = false if let chatHistoryState = presentationInterfaceState.chatHistoryState { if case .loaded(false) = chatHistoryState { @@ -106,7 +113,8 @@ func rightNavigationButtonForChatInterfaceState(_ presentationInterfaceState: Ch } if case .replyThread = presentationInterfaceState.chatLocation { - if hasMessages { + if let channel = presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.flags.contains(.isForum) { + } else if hasMessages { if case .search = currentButton?.action { return currentButton } else { diff --git a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift index ab0d28c5a8..9655837c36 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -1993,7 +1993,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } item.controllerInteraction.navigateToMessage(item.message.id, sourceMessageId) } else if let peer = forwardInfo.source ?? forwardInfo.author { - item.controllerInteraction.openPeer(peer.id, peer is TelegramUser ? .info : .chat(textInputState: nil, subject: nil, peekData: nil), nil, false, nil) + item.controllerInteraction.openPeer(EnginePeer(peer), peer is TelegramUser ? .info : .chat(textInputState: nil, subject: nil, peekData: nil), nil, false) } else if let _ = forwardInfo.authorSignature { item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, forwardInfoNode, nil) } diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index 9b0c04b56a..9e9f524fb8 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -3295,7 +3295,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode }) } else { return .optionalAction({ - item.controllerInteraction.openPeer(peerId, .chat(textInputState: nil, subject: nil, peekData: nil), nil, false, item.message.peers[peerId]) + if let peer = item.message.peers[peerId] { + item.controllerInteraction.openPeer(EnginePeer(peer), .chat(textInputState: nil, subject: nil, peekData: nil), nil, false) + } }) } } @@ -3327,7 +3329,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode } item.controllerInteraction.navigateToMessage(item.message.id, sourceMessageId) } else if let peer = forwardInfo.source ?? forwardInfo.author { - item.controllerInteraction.openPeer(peer.id, peer is TelegramUser ? .info : .chat(textInputState: nil, subject: nil, peekData: nil), nil, false, nil) + item.controllerInteraction.openPeer(EnginePeer(peer), peer is TelegramUser ? .info : .chat(textInputState: nil, subject: nil, peekData: nil), nil, false) } else if let _ = forwardInfo.authorSignature { item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, forwardInfoNode, nil) } @@ -3365,8 +3367,15 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode self.item?.controllerInteraction.openUrl(url, concealed, nil, self.item?.content.firstMessage) }) case let .peerMention(peerId, _): - return .action({ - self.item?.controllerInteraction.openPeer(peerId, .chat(textInputState: nil, subject: nil, peekData: nil), nil, false, nil) + return .action({ [weak self] in + if let item = self?.item { + let _ = (item.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> deliverOnMainQueue).start(next: { peer in + if let self = self, let item = self.item, let peer = peer { + item.controllerInteraction.openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: nil), nil, false) + } + }) + } }) case let .textMention(name): return .action({ diff --git a/submodules/TelegramUI/Sources/ChatMessageDateHeader.swift b/submodules/TelegramUI/Sources/ChatMessageDateHeader.swift index 89d9363475..78b6e54388 100644 --- a/submodules/TelegramUI/Sources/ChatMessageDateHeader.swift +++ b/submodules/TelegramUI/Sources/ChatMessageDateHeader.swift @@ -635,11 +635,11 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode { if case .ended = recognizer.state { if self.peerId.namespace == Namespaces.Peer.Empty, case let .message(_, id, _, _, _) = self.messageReference?.content { self.controllerInteraction.displayMessageTooltip(id, self.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, self, self.avatarNode.frame) - } else { - if let channel = self.peer as? TelegramChannel, case .broadcast = channel.info { - self.controllerInteraction.openPeer(self.peerId, .chat(textInputState: nil, subject: nil, peekData: nil), self.messageReference, false, nil) + } else if let peer = self.peer { + if let channel = peer as? TelegramChannel, case .broadcast = channel.info { + self.controllerInteraction.openPeer(EnginePeer(peer), .chat(textInputState: nil, subject: nil, peekData: nil), self.messageReference, false) } else { - self.controllerInteraction.openPeer(self.peerId, .info, self.messageReference, false, nil) + self.controllerInteraction.openPeer(EnginePeer(peer), .info, self.messageReference, false) } } } diff --git a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift index 35cb176778..d5f8797394 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift @@ -928,7 +928,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD } item.controllerInteraction.navigateToMessage(item.message.id, sourceMessageId) } else if let peer = forwardInfo.source ?? forwardInfo.author { - item.controllerInteraction.openPeer(peer.id, peer is TelegramUser ? .info : .chat(textInputState: nil, subject: nil, peekData: nil), nil, false, nil) + item.controllerInteraction.openPeer(EnginePeer(peer), peer is TelegramUser ? .info : .chat(textInputState: nil, subject: nil, peekData: nil), nil, false) } else if let _ = forwardInfo.authorSignature { item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, forwardInfoNode, nil) } diff --git a/submodules/TelegramUI/Sources/ChatMessageItemView.swift b/submodules/TelegramUI/Sources/ChatMessageItemView.swift index 7e4ff74531..ce3785ecc7 100644 --- a/submodules/TelegramUI/Sources/ChatMessageItemView.swift +++ b/submodules/TelegramUI/Sources/ChatMessageItemView.swift @@ -9,6 +9,7 @@ import LocalizedPeerData import ContextUI import ChatListUI import TelegramPresentationData +import SwiftSignalKit struct ChatMessageItemWidthFill { var compactInset: CGFloat @@ -851,7 +852,12 @@ public class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol case .setupPoll: break case let .openUserProfile(peerId): - item.controllerInteraction.openPeer(peerId, .info, nil, false, nil) + let _ = (item.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> deliverOnMainQueue).start(next: { peer in + if let peer = peer { + item.controllerInteraction.openPeer(peer, .info, nil, false) + } + }) case let .openWebView(url, simple): item.controllerInteraction.openWebView(button.title, url, simple, false) } diff --git a/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift index a8f8915fe1..0e76bf4fd7 100644 --- a/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift @@ -76,7 +76,12 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { } navigationData = .chat(textInputState: nil, subject: subject, peekData: nil) } - item.controllerInteraction.openPeer(id, navigationData, nil, false, nil) + let _ = (item.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: id)) + |> deliverOnMainQueue).start(next: { peer in + if let peer = peer { + item.controllerInteraction.openPeer(peer, navigationData, nil, false) + } + }) case let .join(_, joinHash): item.controllerInteraction.openJoinLink(joinHash) } diff --git a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift index 62bd25fd6e..25735f6969 100644 --- a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift @@ -851,7 +851,12 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { case .setupPoll: break case let .openUserProfile(peerId): - controllerInteraction.openPeer(peerId, .info, nil, false, nil) + let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> deliverOnMainQueue).start(next: { peer in + if let peer = peer { + controllerInteraction.openPeer(peer, .info, nil, false) + } + }) case let .openWebView(url, simple): controllerInteraction.openWebView(button.title, url, simple, false) } diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift index b8c99d6a02..fdfb28040f 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift @@ -221,7 +221,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { }, openUrl: { url in self?.openUrl(url) }, openPeer: { peer, navigation in - self?.openPeer(peerId: peer.id, peer: peer) + self?.openPeer(peer: EnginePeer(peer)) }, callPeer: { peerId, isVideo in self?.controllerInteraction?.callPeer(peerId, isVideo) }, enqueueMessage: { _ in @@ -248,9 +248,9 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { }, gallerySource: gallerySource)) } return false - }, openPeer: { [weak self] peerId, _, message, _, peer in - if let peerId = peerId, peerId != context.account.peerId { - self?.openPeer(peerId: peerId, peer: peer) + }, openPeer: { [weak self] peer, _, message, _ in + if peer.id != context.account.peerId { + self?.openPeer(peer: peer) } }, openPeerMention: { [weak self] name in self?.openPeerMention(name) @@ -376,7 +376,12 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_LinkDialogOpen, color: .accent, action: { [weak actionSheet] in actionSheet?.dismissAnimated() if let strongSelf = self { - strongSelf.openPeer(peerId: peerId, peer: nil) + let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> deliverOnMainQueue).start(next: { peer in + if let strongSelf = self, let peer = peer { + strongSelf.openPeer(peer: peer) + } + }) } })) if !mention.isEmpty { @@ -762,13 +767,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { self.eventLogContext.setFilter(self.filter) } - private func openPeer(peerId: PeerId, peer: Peer?, peekData: ChatPeekTimeout? = nil) { - let peerSignal: Signal - if let peer = peer { - peerSignal = .single(peer) - } else { - peerSignal = self.context.account.postbox.loadedPeerWithId(peerId) |> map(Optional.init) - } + private func openPeer(peer: EnginePeer, peekData: ChatPeekTimeout? = nil) { + let peerSignal: Signal = .single(peer._asPeer()) self.navigationActionDisposable.set((peerSignal |> take(1) |> deliverOnMainQueue).start(next: { [weak self] peer in if let strongSelf = self, let peer = peer { if peer is TelegramChannel, let navigationController = strongSelf.getNavigationController() { @@ -882,9 +882,9 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { } case .urlAuth: break - case let .peer(peerId, _): - if let peerId = peerId { - strongSelf.openPeer(peerId: peerId, peer: nil) + case let .peer(peer, _): + if let peer = peer { + strongSelf.openPeer(peer: EnginePeer(peer)) } case .inaccessiblePeer: strongSelf.controllerInteraction.presentController(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.controller?.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Conversation_ErrorInaccessibleMessage, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), nil) @@ -892,9 +892,9 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { break case .groupBotStart: break - case let .channelMessage(peerId, messageId, timecode): + case let .channelMessage(peer, messageId, timecode): if let navigationController = strongSelf.getNavigationController() { - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId), subject: .message(id: .id(messageId), highlight: true, timecode: timecode))) + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peer.id), subject: .message(id: .id(messageId), highlight: true, timecode: timecode))) } case let .replyThreadMessage(replyThreadMessage, messageId): if let navigationController = strongSelf.getNavigationController() { @@ -931,17 +931,17 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { case let .instantView(webpage, anchor): strongSelf.pushController(InstantPageController(context: strongSelf.context, webPage: webpage, sourcePeerType: .channel, anchor: anchor)) case let .join(link): - strongSelf.presentController(JoinLinkPreviewController(context: strongSelf.context, link: link, navigateToPeer: { peerId, peekData in + strongSelf.presentController(JoinLinkPreviewController(context: strongSelf.context, link: link, navigateToPeer: { peer, peekData in if let strongSelf = self { - strongSelf.openPeer(peerId: peerId, peer: nil, peekData: peekData) + strongSelf.openPeer(peer: peer, peekData: peekData) } }, parentNavigationController: strongSelf.getNavigationController()), .window(.root), nil) case let .localization(identifier): strongSelf.presentController(LanguageLinkPreviewController(context: strongSelf.context, identifier: identifier), .window(.root), nil) case .proxy, .confirmationCode, .cancelAccountReset, .share: - strongSelf.context.sharedContext.openResolvedUrl(result, context: strongSelf.context, urlContext: .generic, navigationController: strongSelf.getNavigationController(), forceExternal: false, openPeer: { peerId, _ in + strongSelf.context.sharedContext.openResolvedUrl(result, context: strongSelf.context, urlContext: .generic, navigationController: strongSelf.getNavigationController(), forceExternal: false, openPeer: { peer, _ in if let strongSelf = self { - strongSelf.openPeer(peerId: peerId, peer: nil) + strongSelf.openPeer(peer: peer) } }, sendFile: nil, sendSticker: nil, diff --git a/submodules/TelegramUI/Sources/ChatTitleView.swift b/submodules/TelegramUI/Sources/ChatTitleView.swift index 9626ae9e87..8b13789179 100644 --- a/submodules/TelegramUI/Sources/ChatTitleView.swift +++ b/submodules/TelegramUI/Sources/ChatTitleView.swift @@ -1,843 +1 @@ -import Foundation -import UIKit -import AsyncDisplayKit -import Display -import Postbox -import TelegramCore -import SwiftSignalKit -import LegacyComponents -import TelegramPresentationData -import TelegramUIPreferences -import ActivityIndicator -import TelegramStringFormatting -import PeerPresenceStatusManager -import ChatTitleActivityNode -import LocalizedPeerData -import PhoneNumberFormat -import ChatTitleActivityNode -import AnimatedCountLabelNode -import AccountContext -import ComponentFlow -import EmojiStatusComponent -import AnimationCache -import MultiAnimationRenderer -private let titleFont = Font.with(size: 17.0, design: .regular, weight: .semibold, traits: [.monospacedNumbers]) -private let subtitleFont = Font.regular(13.0) - -enum ChatTitleContent { - enum ReplyThreadType { - case comments - case replies - } - - case peer(peerView: PeerView, onlineMemberCount: Int32?, isScheduledMessages: Bool) - case replyThread(type: ReplyThreadType, count: Int) - case custom(String, String?, Bool) -} - -private enum ChatTitleIcon { - case none - case lock - case mute -} - -private enum ChatTitleCredibilityIcon: Equatable { - case none - case fake - case scam - case verified - case premium - case emojiStatus(PeerEmojiStatus) -} - -final class ChatTitleView: UIView, NavigationBarTitleView { - private let context: AccountContext - - private var theme: PresentationTheme - private var hasEmbeddedTitleContent: Bool = false - private var strings: PresentationStrings - private var dateTimeFormat: PresentationDateTimeFormat - private var nameDisplayOrder: PresentationPersonNameOrder - private let animationCache: AnimationCache - private let animationRenderer: MultiAnimationRenderer - - private let contentContainer: ASDisplayNode - let titleContainerView: PortalSourceView - let titleTextNode: ImmediateAnimatedCountLabelNode - let titleLeftIconNode: ASImageNode - let titleRightIconNode: ASImageNode - let titleCredibilityIconView: ComponentHostView - let activityNode: ChatTitleActivityNode - - private let button: HighlightTrackingButtonNode - - private var validLayout: (CGSize, CGRect)? - - private var titleLeftIcon: ChatTitleIcon = .none - private var titleRightIcon: ChatTitleIcon = .none - private var titleCredibilityIcon: ChatTitleCredibilityIcon = .none - - //private var networkStatusNode: ChatTitleNetworkStatusNode? - - private var presenceManager: PeerPresenceStatusManager? - - private var pointerInteraction: PointerInteraction? - - var inputActivities: (PeerId, [(Peer, PeerInputActivity)])? { - didSet { - let _ = self.updateStatus() - } - } - - private func updateNetworkStatusNode(networkState: AccountNetworkState, layout: ContainerViewLayout?) { - self.setNeedsLayout() - } - - var networkState: AccountNetworkState = .online(proxy: nil) { - didSet { - if self.networkState != oldValue { - updateNetworkStatusNode(networkState: self.networkState, layout: self.layout) - let _ = self.updateStatus() - } - } - } - - var layout: ContainerViewLayout? { - didSet { - if self.layout != oldValue { - updateNetworkStatusNode(networkState: self.networkState, layout: self.layout) - } - } - } - - var pressed: (() -> Void)? - var longPressed: (() -> Void)? - - var titleContent: ChatTitleContent? { - didSet { - if let titleContent = self.titleContent { - let titleTheme = self.hasEmbeddedTitleContent ? defaultDarkPresentationTheme : self.theme - - var segments: [AnimatedCountLabelNode.Segment] = [] - var titleLeftIcon: ChatTitleIcon = .none - var titleRightIcon: ChatTitleIcon = .none - var titleCredibilityIcon: ChatTitleCredibilityIcon = .none - var isEnabled = true - switch titleContent { - case let .peer(peerView, _, isScheduledMessages): - if peerView.peerId.isReplies { - let typeText: String = self.strings.DialogList_Replies - segments = [.text(0, NSAttributedString(string: typeText, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))] - isEnabled = false - } else if isScheduledMessages { - if peerView.peerId == self.context.account.peerId { - segments = [.text(0, NSAttributedString(string: self.strings.ScheduledMessages_RemindersTitle, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))] - } else { - segments = [.text(0, NSAttributedString(string: self.strings.ScheduledMessages_Title, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))] - } - isEnabled = false - } else { - if let peer = peerViewMainPeer(peerView) { - if peerView.peerId == self.context.account.peerId { - segments = [.text(0, NSAttributedString(string: self.strings.Conversation_SavedMessages, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))] - } else { - if !peerView.peerIsContact, let user = peer as? TelegramUser, !user.flags.contains(.isSupport), user.botInfo == nil, let phone = user.phone, !phone.isEmpty { - segments = [.text(0, NSAttributedString(string: formatPhoneNumber(phone), font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))] - } else { - segments = [.text(0, NSAttributedString(string: EnginePeer(peer).displayTitle(strings: self.strings, displayOrder: self.nameDisplayOrder), font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))] - } - } - if peer.id != self.context.account.peerId { - let premiumConfiguration = PremiumConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 }) - if peer.isFake { - titleCredibilityIcon = .fake - } else if peer.isScam { - titleCredibilityIcon = .scam - } else if let user = peer as? TelegramUser, let emojiStatus = user.emojiStatus, !premiumConfiguration.isPremiumDisabled { - titleCredibilityIcon = .emojiStatus(emojiStatus) - } else if peer.isVerified { - titleCredibilityIcon = .verified - } else if peer.isPremium && !premiumConfiguration.isPremiumDisabled { - titleCredibilityIcon = .premium - } - } - } - if peerView.peerId.namespace == Namespaces.Peer.SecretChat { - titleLeftIcon = .lock - } - if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings { - if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) { - if titleCredibilityIcon != .verified { - titleRightIcon = .mute - } - } - } - } - case let .replyThread(type, count): - let textFont = titleFont - let textColor = titleTheme.rootController.navigationBar.primaryTextColor - - if count > 0 { - var commentsPart: String - switch type { - case .comments: - commentsPart = self.strings.Conversation_TitleComments(Int32(count)) - case .replies: - commentsPart = self.strings.Conversation_TitleReplies(Int32(count)) - } - - if commentsPart.contains("[") && commentsPart.contains("]") { - if let startIndex = commentsPart.firstIndex(of: "["), let endIndex = commentsPart.firstIndex(of: "]") { - commentsPart.removeSubrange(startIndex ... endIndex) - } - } else { - commentsPart = commentsPart.trimmingCharacters(in: CharacterSet(charactersIn: "0123456789-,.")) - } - - let rawTextAndRanges: PresentationStrings.FormattedString - switch type { - case .comments: - rawTextAndRanges = self.strings.Conversation_TitleCommentsFormat("\(count)", commentsPart) - case .replies: - rawTextAndRanges = self.strings.Conversation_TitleRepliesFormat("\(count)", commentsPart) - } - - let rawText = rawTextAndRanges.string - - var textIndex = 0 - var latestIndex = 0 - for indexAndRange in rawTextAndRanges.ranges { - let index = indexAndRange.index - let range = indexAndRange.range - - var lowerSegmentIndex = range.lowerBound - if index != 0 { - lowerSegmentIndex = min(lowerSegmentIndex, latestIndex) - } else { - if latestIndex < range.lowerBound { - let part = String(rawText[rawText.index(rawText.startIndex, offsetBy: latestIndex) ..< rawText.index(rawText.startIndex, offsetBy: range.lowerBound)]) - segments.append(.text(textIndex, NSAttributedString(string: part, font: textFont, textColor: textColor))) - textIndex += 1 - } - } - latestIndex = range.upperBound - - let part = String(rawText[rawText.index(rawText.startIndex, offsetBy: lowerSegmentIndex) ..< rawText.index(rawText.startIndex, offsetBy: min(rawText.count, range.upperBound))]) - if index == 0 { - segments.append(.number(count, NSAttributedString(string: part, font: textFont, textColor: textColor))) - } else { - segments.append(.text(textIndex, NSAttributedString(string: part, font: textFont, textColor: textColor))) - textIndex += 1 - } - } - if latestIndex < rawText.count { - let part = String(rawText[rawText.index(rawText.startIndex, offsetBy: latestIndex)...]) - segments.append(.text(textIndex, NSAttributedString(string: part, font: textFont, textColor: textColor))) - textIndex += 1 - } - } else { - switch type { - case .comments: - segments = [.text(0, NSAttributedString(string: strings.Conversation_TitleCommentsEmpty, font: textFont, textColor: textColor))] - case .replies: - segments = [.text(0, NSAttributedString(string: strings.Conversation_TitleRepliesEmpty, font: textFont, textColor: textColor))] - } - } - - isEnabled = false - case let .custom(text, _, enabled): - segments = [.text(0, NSAttributedString(string: text, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))] - isEnabled = enabled - } - - var updated = false - - if self.titleTextNode.segments != segments { - self.titleTextNode.segments = segments - updated = true - } - - if titleLeftIcon != self.titleLeftIcon { - self.titleLeftIcon = titleLeftIcon - switch titleLeftIcon { - case .lock: - self.titleLeftIconNode.image = PresentationResourcesChat.chatTitleLockIcon(titleTheme) - default: - self.titleLeftIconNode.image = nil - } - updated = true - } - - if titleCredibilityIcon != self.titleCredibilityIcon { - self.titleCredibilityIcon = titleCredibilityIcon - /*switch titleCredibilityIcon { - case .none: - self.titleCredibilityIconNode.image = nil - case .fake: - self.titleCredibilityIconNode.image = PresentationResourcesChatList.fakeIcon(titleTheme, strings: self.strings, type: .regular) - case .scam: - self.titleCredibilityIconNode.image = PresentationResourcesChatList.scamIcon(titleTheme, strings: self.strings, type: .regular) - case .verified: - self.titleCredibilityIconNode.image = PresentationResourcesChatList.verifiedIcon(titleTheme) - case .premium: - self.titleCredibilityIconNode.image = PresentationResourcesChatList.premiumIcon(titleTheme) - }*/ - updated = true - } - - if titleRightIcon != self.titleRightIcon { - self.titleRightIcon = titleRightIcon - switch titleRightIcon { - case .mute: - self.titleRightIconNode.image = PresentationResourcesChat.chatTitleMuteIcon(titleTheme) - default: - self.titleRightIconNode.image = nil - } - updated = true - } - self.isUserInteractionEnabled = isEnabled - self.button.isUserInteractionEnabled = isEnabled - if !self.updateStatus() { - if updated { - if let (size, clearBounds) = self.validLayout { - self.updateLayout(size: size, clearBounds: clearBounds, transition: .animated(duration: 0.2, curve: .easeInOut)) - } - } - } - } - } - } - - private func updateStatus() -> Bool { - var inputActivitiesAllowed = true - if let titleContent = self.titleContent { - switch titleContent { - case let .peer(peerView, _, isScheduledMessages): - if let peer = peerViewMainPeer(peerView) { - if peer.id == self.context.account.peerId || isScheduledMessages || peer.id.isReplies { - inputActivitiesAllowed = false - } - } - case .replyThread: - inputActivitiesAllowed = true - default: - inputActivitiesAllowed = false - } - } - - let titleTheme = self.hasEmbeddedTitleContent ? defaultDarkPresentationTheme : self.theme - - var state = ChatTitleActivityNodeState.none - switch self.networkState { - case .waitingForNetwork, .connecting, .updating: - var infoText: String - switch self.networkState { - case .waitingForNetwork: - infoText = self.strings.ChatState_WaitingForNetwork - case .connecting: - infoText = self.strings.ChatState_Connecting - case .updating: - infoText = self.strings.ChatState_Updating - case .online: - infoText = "" - } - state = .info(NSAttributedString(string: infoText, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor), .generic) - case .online: - if let (peerId, inputActivities) = self.inputActivities, !inputActivities.isEmpty, inputActivitiesAllowed { - var stringValue = "" - var mergedActivity = inputActivities[0].1 - for (_, activity) in inputActivities { - if activity != mergedActivity { - mergedActivity = .typingText - break - } - } - if peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.SecretChat { - switch mergedActivity { - case .typingText: - stringValue = strings.Conversation_typing - case .uploadingFile: - stringValue = strings.Activity_UploadingDocument - case .recordingVoice: - stringValue = strings.Activity_RecordingAudio - case .uploadingPhoto: - stringValue = strings.Activity_UploadingPhoto - case .uploadingVideo: - stringValue = strings.Activity_UploadingVideo - case .playingGame: - stringValue = strings.Activity_PlayingGame - case .recordingInstantVideo: - stringValue = strings.Activity_RecordingVideoMessage - case .uploadingInstantVideo: - stringValue = strings.Activity_UploadingVideoMessage - case .choosingSticker: - stringValue = strings.Activity_ChoosingSticker - case let .seeingEmojiInteraction(emoticon): - stringValue = strings.Activity_EnjoyingAnimations(emoticon).string - case .speakingInGroupCall, .interactingWithEmoji: - stringValue = "" - } - } else { - if inputActivities.count > 1 { - let peerTitle = EnginePeer(inputActivities[0].0).compactDisplayTitle - if inputActivities.count == 2 { - let secondPeerTitle = EnginePeer(inputActivities[1].0).compactDisplayTitle - stringValue = strings.Chat_MultipleTypingPair(peerTitle, secondPeerTitle).string - } else { - stringValue = strings.Chat_MultipleTypingMore(peerTitle, String(inputActivities.count - 1)).string - } - } else if let (peer, _) = inputActivities.first { - stringValue = EnginePeer(peer).compactDisplayTitle - } - } - let color = titleTheme.rootController.navigationBar.accentTextColor - let string = NSAttributedString(string: stringValue, font: subtitleFont, textColor: color) - switch mergedActivity { - case .typingText: - state = .typingText(string, color) - case .recordingVoice: - state = .recordingVoice(string, color) - case .recordingInstantVideo: - state = .recordingVideo(string, color) - case .uploadingFile, .uploadingInstantVideo, .uploadingPhoto, .uploadingVideo: - state = .uploading(string, color) - case .playingGame: - state = .playingGame(string, color) - case .speakingInGroupCall, .interactingWithEmoji: - state = .typingText(string, color) - case .choosingSticker: - state = .choosingSticker(string, color) - case .seeingEmojiInteraction: - state = .choosingSticker(string, color) - } - } else { - if let titleContent = self.titleContent { - switch titleContent { - case let .peer(peerView, onlineMemberCount, isScheduledMessages): - if let peer = peerViewMainPeer(peerView) { - let servicePeer = isServicePeer(peer) - if peer.id == self.context.account.peerId || isScheduledMessages || peer.id.isReplies { - let string = NSAttributedString(string: "", font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor) - state = .info(string, .generic) - } else if let user = peer as? TelegramUser { - if user.isDeleted { - state = .none - } else if servicePeer { - let string = NSAttributedString(string: "", font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor) - state = .info(string, .generic) - } else if user.flags.contains(.isSupport) { - let statusText = self.strings.Bot_GenericSupportStatus - - let string = NSAttributedString(string: statusText, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor) - state = .info(string, .generic) - } else if let _ = user.botInfo { - let statusText = self.strings.Bot_GenericBotStatus - - let string = NSAttributedString(string: statusText, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor) - state = .info(string, .generic) - } else if let peer = peerViewMainPeer(peerView) { - let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 - let userPresence: TelegramUserPresence - if let presence = peerView.peerPresences[peer.id] as? TelegramUserPresence { - userPresence = presence - self.presenceManager?.reset(presence: EnginePeer.Presence(presence)) - } else { - userPresence = TelegramUserPresence(status: .none, lastActivity: 0) - } - let (string, activity) = stringAndActivityForUserPresence(strings: self.strings, dateTimeFormat: self.dateTimeFormat, presence: EnginePeer.Presence(userPresence), relativeTo: Int32(timestamp)) - let attributedString = NSAttributedString(string: string, font: subtitleFont, textColor: activity ? titleTheme.rootController.navigationBar.accentTextColor : titleTheme.rootController.navigationBar.secondaryTextColor) - state = .info(attributedString, activity ? .online : .lastSeenTime) - } else { - let string = NSAttributedString(string: "", font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor) - state = .info(string, .generic) - } - } else if let group = peer as? TelegramGroup { - var onlineCount = 0 - if let cachedGroupData = peerView.cachedData as? CachedGroupData, let participants = cachedGroupData.participants { - let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 - for participant in participants.participants { - if let presence = peerView.peerPresences[participant.peerId] as? TelegramUserPresence { - let relativeStatus = relativeUserPresenceStatus(EnginePeer.Presence(presence), relativeTo: Int32(timestamp)) - switch relativeStatus { - case .online: - onlineCount += 1 - default: - break - } - } - } - } - if onlineCount > 1 { - let string = NSMutableAttributedString() - - string.append(NSAttributedString(string: "\(strings.Conversation_StatusMembers(Int32(group.participantCount))), ", font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)) - string.append(NSAttributedString(string: strings.Conversation_StatusOnline(Int32(onlineCount)), font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)) - state = .info(string, .generic) - } else { - let string = NSAttributedString(string: strings.Conversation_StatusMembers(Int32(group.participantCount)), font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor) - state = .info(string, .generic) - } - } else if let channel = peer as? TelegramChannel { - if let cachedChannelData = peerView.cachedData as? CachedChannelData, let memberCount = cachedChannelData.participantsSummary.memberCount { - if memberCount == 0 { - let string: NSAttributedString - if case .group = channel.info { - string = NSAttributedString(string: strings.Group_Status, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor) - } else { - string = NSAttributedString(string: strings.Channel_Status, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor) - } - state = .info(string, .generic) - } else { - if case .group = channel.info, let onlineMemberCount = onlineMemberCount, onlineMemberCount > 1 { - let string = NSMutableAttributedString() - - string.append(NSAttributedString(string: "\(strings.Conversation_StatusMembers(Int32(memberCount))), ", font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)) - string.append(NSAttributedString(string: strings.Conversation_StatusOnline(Int32(onlineMemberCount)), font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)) - state = .info(string, .generic) - } else { - let membersString: String - if case .group = channel.info { - membersString = strings.Conversation_StatusMembers(memberCount) - } else { - membersString = strings.Conversation_StatusSubscribers(memberCount) - } - let string = NSAttributedString(string: membersString, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor) - state = .info(string, .generic) - } - } - } else { - switch channel.info { - case .group: - let string = NSAttributedString(string: strings.Group_Status, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor) - state = .info(string, .generic) - case .broadcast: - let string = NSAttributedString(string: strings.Channel_Status, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor) - state = .info(string, .generic) - } - } - } - } - case let .custom(_, subtitle?, _): - let string = NSAttributedString(string: subtitle, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor) - state = .info(string, .generic) - default: - break - } - - var accessibilityText = "" - for segment in self.titleTextNode.segments { - switch segment { - case let .number(_, string): - accessibilityText.append(string.string) - case let .text(_, string): - accessibilityText.append(string.string) - } - } - - self.accessibilityLabel = accessibilityText - self.accessibilityValue = state.string - } else { - self.accessibilityLabel = nil - } - } - } - - if self.activityNode.transitionToState(state, animation: .slide) { - if let (size, clearBounds) = self.validLayout { - self.updateLayout(size: size, clearBounds: clearBounds, transition: .animated(duration: 0.3, curve: .spring)) - } - return true - } else { - return false - } - } - - init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer) { - self.context = context - self.theme = theme - self.strings = strings - self.dateTimeFormat = dateTimeFormat - self.nameDisplayOrder = nameDisplayOrder - self.animationCache = animationCache - self.animationRenderer = animationRenderer - - self.contentContainer = ASDisplayNode() - - self.titleContainerView = PortalSourceView() - self.titleTextNode = ImmediateAnimatedCountLabelNode() - - self.titleLeftIconNode = ASImageNode() - self.titleLeftIconNode.isLayerBacked = true - self.titleLeftIconNode.displayWithoutProcessing = true - self.titleLeftIconNode.displaysAsynchronously = false - - self.titleRightIconNode = ASImageNode() - self.titleRightIconNode.isLayerBacked = true - self.titleRightIconNode.displayWithoutProcessing = true - self.titleRightIconNode.displaysAsynchronously = false - - self.titleCredibilityIconView = ComponentHostView() - self.titleCredibilityIconView.isUserInteractionEnabled = false - - self.activityNode = ChatTitleActivityNode() - self.button = HighlightTrackingButtonNode() - - super.init(frame: CGRect()) - - self.isAccessibilityElement = true - self.accessibilityTraits = .header - - self.addSubnode(self.contentContainer) - self.titleContainerView.addSubnode(self.titleTextNode) - self.contentContainer.view.addSubview(self.titleContainerView) - self.contentContainer.addSubnode(self.activityNode) - self.addSubnode(self.button) - - self.presenceManager = PeerPresenceStatusManager(update: { [weak self] in - let _ = self?.updateStatus() - }) - - self.button.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: [.touchUpInside]) - self.button.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - if highlighted { - strongSelf.titleTextNode.layer.removeAnimation(forKey: "opacity") - strongSelf.activityNode.layer.removeAnimation(forKey: "opacity") - strongSelf.titleCredibilityIconView.layer.removeAnimation(forKey: "opacity") - strongSelf.titleTextNode.alpha = 0.4 - strongSelf.activityNode.alpha = 0.4 - strongSelf.titleCredibilityIconView.alpha = 0.4 - } else { - strongSelf.titleTextNode.alpha = 1.0 - strongSelf.activityNode.alpha = 1.0 - strongSelf.titleCredibilityIconView.alpha = 1.0 - strongSelf.titleTextNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - strongSelf.activityNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - } - } - } - self.button.view.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(self.longPressGesture(_:)))) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func layoutSubviews() { - super.layoutSubviews() - - if let (size, clearBounds) = self.validLayout { - self.updateLayout(size: size, clearBounds: clearBounds, transition: .immediate) - } - } - - func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings, hasEmbeddedTitleContent: Bool) { - self.theme = theme - self.hasEmbeddedTitleContent = hasEmbeddedTitleContent - self.strings = strings - - let titleContent = self.titleContent - self.titleCredibilityIcon = .none - self.titleContent = titleContent - let _ = self.updateStatus() - - if let (size, clearBounds) = self.validLayout { - self.updateLayout(size: size, clearBounds: clearBounds, transition: .immediate) - } - } - - func updateLayout(size: CGSize, clearBounds: CGRect, transition: ContainedViewLayoutTransition) { - self.validLayout = (size, clearBounds) - - self.button.frame = clearBounds - self.contentContainer.frame = clearBounds - - var leftIconWidth: CGFloat = 0.0 - var rightIconWidth: CGFloat = 0.0 - var credibilityIconWidth: CGFloat = 0.0 - - if let image = self.titleLeftIconNode.image { - if self.titleLeftIconNode.supernode == nil { - self.titleTextNode.addSubnode(self.titleLeftIconNode) - } - leftIconWidth = image.size.width + 6.0 - } else if self.titleLeftIconNode.supernode != nil { - self.titleLeftIconNode.removeFromSupernode() - } - - let titleCredibilityContent: EmojiStatusComponent.Content - switch self.titleCredibilityIcon { - case .none: - titleCredibilityContent = .none - case .premium: - titleCredibilityContent = .premium(color: self.theme.list.itemAccentColor) - case .verified: - titleCredibilityContent = .verified(fillColor: self.theme.list.itemCheckColors.fillColor, foregroundColor: self.theme.list.itemCheckColors.foregroundColor, sizeType: .large) - case .fake: - titleCredibilityContent = .text(color: self.theme.chat.message.incoming.scamColor, string: self.strings.Message_FakeAccount.uppercased()) - case .scam: - titleCredibilityContent = .text(color: self.theme.chat.message.incoming.scamColor, string: self.strings.Message_ScamAccount.uppercased()) - case let .emojiStatus(emojiStatus): - titleCredibilityContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: self.theme.list.mediaPlaceholderColor, themeColor: self.theme.list.itemAccentColor, loopMode: .count(2)) - } - - let titleCredibilitySize = self.titleCredibilityIconView.update( - transition: .immediate, - component: AnyComponent(EmojiStatusComponent( - context: self.context, - animationCache: self.animationCache, - animationRenderer: self.animationRenderer, - content: titleCredibilityContent, - isVisibleForAnimations: true, - action: nil - )), - environment: {}, - containerSize: CGSize(width: 20.0, height: 20.0) - ) - - if self.titleCredibilityIcon != .none { - self.titleTextNode.view.addSubview(self.titleCredibilityIconView) - credibilityIconWidth = titleCredibilitySize.width + 3.0 - } else { - if self.titleCredibilityIconView.superview != nil { - self.titleCredibilityIconView.removeFromSuperview() - } - } - - if let image = self.titleRightIconNode.image { - if self.titleRightIconNode.supernode == nil { - self.titleTextNode.addSubnode(self.titleRightIconNode) - } - rightIconWidth = image.size.width + 3.0 - } else if self.titleRightIconNode.supernode != nil { - self.titleRightIconNode.removeFromSupernode() - } - - let titleSideInset: CGFloat = 3.0 - var titleFrame: CGRect - if size.height > 40.0 { - var titleSize = self.titleTextNode.updateLayout(size: CGSize(width: clearBounds.width - leftIconWidth - credibilityIconWidth - rightIconWidth - titleSideInset * 2.0, height: size.height), animated: transition.isAnimated) - titleSize.width += credibilityIconWidth - let activitySize = self.activityNode.updateLayout(clearBounds.size, alignment: .center) - let titleInfoSpacing: CGFloat = 0.0 - - if activitySize.height.isZero { - titleFrame = CGRect(origin: CGPoint(x: floor((clearBounds.width - titleSize.width) / 2.0), y: floor((size.height - titleSize.height) / 2.0)), size: titleSize) - if titleFrame.size.width < size.width { - titleFrame.origin.x = -clearBounds.minX + floor((size.width - titleFrame.width) / 2.0) - } - transition.updateFrameAdditive(view: self.titleContainerView, frame: titleFrame) - transition.updateFrameAdditive(node: self.titleTextNode, frame: CGRect(origin: CGPoint(), size: titleFrame.size)) - } else { - let combinedHeight = titleSize.height + activitySize.height + titleInfoSpacing - - titleFrame = CGRect(origin: CGPoint(x: floor((clearBounds.width - titleSize.width) / 2.0), y: floor((size.height - combinedHeight) / 2.0)), size: titleSize) - if titleFrame.size.width < size.width { - titleFrame.origin.x = -clearBounds.minX + floor((size.width - titleFrame.width) / 2.0) - } - titleFrame.origin.x = max(titleFrame.origin.x, clearBounds.minX + leftIconWidth) - transition.updateFrameAdditive(view: self.titleContainerView, frame: titleFrame) - transition.updateFrameAdditive(node: self.titleTextNode, frame: CGRect(origin: CGPoint(), size: titleFrame.size)) - - var activityFrame = CGRect(origin: CGPoint(x: floor((clearBounds.width - activitySize.width) / 2.0), y: floor((size.height - combinedHeight) / 2.0) + titleSize.height + titleInfoSpacing), size: activitySize) - if activitySize.width < size.width { - activityFrame.origin.x = -clearBounds.minX + floor((size.width - activityFrame.width) / 2.0) - } - self.activityNode.frame = activityFrame - } - - if let image = self.titleLeftIconNode.image { - self.titleLeftIconNode.frame = CGRect(origin: CGPoint(x: -image.size.width - 3.0 - UIScreenPixel, y: 4.0), size: image.size) - } - - self.titleCredibilityIconView.frame = CGRect(origin: CGPoint(x: titleFrame.width - titleCredibilitySize.width, y: floor((titleFrame.height - titleCredibilitySize.height) / 2.0)), size: titleCredibilitySize) - - if let image = self.titleRightIconNode.image { - self.titleRightIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.width + 3.0 + UIScreenPixel, y: 6.0), size: image.size) - } - } else { - let titleSize = self.titleTextNode.updateLayout(size: CGSize(width: floor(clearBounds.width / 2.0 - leftIconWidth - credibilityIconWidth - rightIconWidth - titleSideInset * 2.0), height: size.height), animated: transition.isAnimated) - let activitySize = self.activityNode.updateLayout(CGSize(width: floor(clearBounds.width / 2.0), height: size.height), alignment: .center) - - let titleInfoSpacing: CGFloat = 8.0 - let combinedWidth = titleSize.width + leftIconWidth + credibilityIconWidth + rightIconWidth + activitySize.width + titleInfoSpacing - - titleFrame = CGRect(origin: CGPoint(x: leftIconWidth + floor((clearBounds.width - combinedWidth) / 2.0), y: floor((size.height - titleSize.height) / 2.0)), size: titleSize) - - transition.updateFrameAdditiveToCenter(view: self.titleContainerView, frame: titleFrame) - transition.updateFrameAdditiveToCenter(node: self.titleTextNode, frame: CGRect(origin: CGPoint(), size: titleFrame.size)) - - self.activityNode.frame = CGRect(origin: CGPoint(x: floor((clearBounds.width - combinedWidth) / 2.0 + titleSize.width + leftIconWidth + credibilityIconWidth + rightIconWidth + titleInfoSpacing), y: floor((size.height - activitySize.height) / 2.0)), size: activitySize) - - if let image = self.titleLeftIconNode.image { - self.titleLeftIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.minY + 4.0), size: image.size) - } - - self.titleCredibilityIconView.frame = CGRect(origin: CGPoint(x: titleFrame.maxX - titleCredibilitySize.width, y: floor((titleFrame.height - titleCredibilitySize.height) / 2.0)), size: titleCredibilitySize) - - if let image = self.titleRightIconNode.image { - self.titleRightIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.maxX - image.size.width, y: titleFrame.minY + 6.0), size: image.size) - } - } - - self.pointerInteraction = PointerInteraction(view: self, style: .rectangle(CGSize(width: titleFrame.width + 16.0, height: 40.0))) - } - - @objc private func buttonPressed() { - self.pressed?() - } - - @objc private func longPressGesture(_ gesture: UILongPressGestureRecognizer) { - switch gesture.state { - case .began: - self.longPressed?() - default: - break - } - } - - func animateLayoutTransition() { - UIView.transition(with: self, duration: 0.25, options: [.transitionCrossDissolve], animations: { - }, completion: nil) - } - - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if !self.isUserInteractionEnabled { - return nil - } - if self.button.frame.contains(point) { - return self.button.view - } - return super.hitTest(point, with: event) - } - - final class SnapshotState { - fileprivate let snapshotView: UIView - - fileprivate init(snapshotView: UIView) { - self.snapshotView = snapshotView - } - } - - func prepareSnapshotState() -> SnapshotState { - let snapshotView = self.snapshotView(afterScreenUpdates: false)! - return SnapshotState( - snapshotView: snapshotView - ) - } - - func animateFromSnapshot(_ snapshotState: SnapshotState) { - self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) - self.layer.animatePosition(from: CGPoint(x: 0.0, y: 20.0), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: true, additive: true) - - snapshotState.snapshotView.frame = self.frame - self.superview?.insertSubview(snapshotState.snapshotView, belowSubview: self) - - let snapshotView = snapshotState.snapshotView - snapshotState.snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in - snapshotView?.removeFromSuperview() - }) - snapshotView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -20.0), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true) - } -} diff --git a/submodules/TelegramUI/Sources/DrawingStickersScreen.swift b/submodules/TelegramUI/Sources/DrawingStickersScreen.swift index ac51a66b60..50fe2b34cf 100644 --- a/submodules/TelegramUI/Sources/DrawingStickersScreen.swift +++ b/submodules/TelegramUI/Sources/DrawingStickersScreen.swift @@ -109,7 +109,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode { var selectStickerImpl: ((FileMediaReference, UIView, CGRect) -> Bool)? self.controllerInteraction = ChatControllerInteraction(openMessage: { _, _ in - return false }, openPeer: { _, _, _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in + return false }, openPeer: { _, _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in }, updateMessageReaction: { _, _ in }, activateMessagePinch: { _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in }, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { fileReference, _, _, _, _, node, rect, _, _ in return selectStickerImpl?(fileReference, node, rect) ?? false }, sendEmoji: { _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in diff --git a/submodules/TelegramUI/Sources/NavigateToChatController.swift b/submodules/TelegramUI/Sources/NavigateToChatController.swift index 1939963b76..f7d0889293 100644 --- a/submodules/TelegramUI/Sources/NavigateToChatController.swift +++ b/submodules/TelegramUI/Sources/NavigateToChatController.swift @@ -13,6 +13,7 @@ import SettingsUI import ChatPresentationInterfaceState import AttachmentUI import ForumTopicListScreen +import ForumCreateTopicScreen public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParams) { var found = false @@ -110,6 +111,9 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam }) } else { let viewControllers = params.navigationController.viewControllers.filter({ controller in + if controller is ForumCreateTopicScreen { + return false + } if controller is ChatListController { if let parentGroupId = params.parentGroupId { return parentGroupId != .root @@ -224,7 +228,7 @@ public func isOverlayControllerForChatNotificationOverlayPresentation(_ controll return false } -public func navigateToForumThreadImpl(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, navigationController: NavigationController) -> Signal { +public func navigateToForumThreadImpl(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, navigationController: NavigationController, activateInput: ChatControllerActivateInput?) -> Signal { return fetchAndPreloadReplyThreadInfo(context: context, subject: .groupMessage(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId))), atMessageId: nil, preload: false) |> deliverOnMainQueue |> beforeNext { [weak context, weak navigationController] result in @@ -237,7 +241,12 @@ public func navigateToForumThreadImpl(context: AccountContext, peerId: EnginePee let subject: ChatControllerSubject? subject = nil - context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: chatLocation, chatLocationContextHolder: result.contextHolder, subject: subject, activateInput: result.isEmpty ? .text : nil, keepStack: .always)) + var actualActivateInput: ChatControllerActivateInput? = result.isEmpty ? .text : nil + if let activateInput = activateInput { + actualActivateInput = activateInput + } + + context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: chatLocation, chatLocationContextHolder: result.contextHolder, subject: subject, activateInput: actualActivateInput, keepStack: .never)) } |> ignoreValues |> `catch` { _ -> Signal in diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index 4424cc560f..528aeaa38f 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -45,7 +45,7 @@ private func defaultNavigationForPeerId(_ peerId: PeerId?, navigation: ChatContr } } -func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, forceExternal: Bool, openPeer: @escaping (PeerId, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?, requestMessageActionUrlAuth: ((MessageActionUrlSubject) -> Void)? = nil, joinVoiceChat: ((PeerId, String?, CachedChannelData.ActiveCall) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?) { +func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, forceExternal: Bool, openPeer: @escaping (EnginePeer, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?, requestMessageActionUrlAuth: ((MessageActionUrlSubject) -> Void)? = nil, joinVoiceChat: ((PeerId, String?, CachedChannelData.ActiveCall) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?) { let updatedPresentationData: (initial: PresentationData, signal: Signal)? if case let .chat(_, maybeUpdatedPresentationData) = urlContext { updatedPresentationData = maybeUpdatedPresentationData @@ -60,16 +60,16 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur requestMessageActionUrlAuth?(.url(url)) dismissInput() break - case let .peer(peerId, navigation): - if let peerId = peerId { - openPeer(peerId, defaultNavigationForPeerId(peerId, navigation: navigation)) + case let .peer(peer, navigation): + if let peer = peer { + openPeer(EnginePeer(peer), defaultNavigationForPeerId(peer.id, navigation: navigation)) } else { present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Resolve_ErrorNotFound, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) } case .inaccessiblePeer: present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: presentationData.strings.Conversation_ErrorInaccessibleMessage, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) - case let .botStart(peerId, payload): - openPeer(peerId, .withBotStartPayload(ChatControllerInitialBotStart(payload: payload, behavior: .interactive))) + case let .botStart(peer, payload): + openPeer(EnginePeer(peer), .withBotStartPayload(ChatControllerInitialBotStart(payload: payload, behavior: .interactive))) case let .groupBotStart(botPeerId, payload, adminRights): let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyGroupsAndChannels, .onlyManageable, .excludeDisabled, .excludeRecent, .doNotSearchMessages], hasContactSelector: false, title: presentationData.strings.Bot_AddToChat_Title)) controller.peerSelected = { [weak controller] peer in @@ -155,8 +155,8 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur } dismissInput() navigationController?.pushViewController(controller) - case let .channelMessage(peerId, messageId, timecode): - openPeer(peerId, .chat(textInputState: nil, subject: .message(id: .id(messageId), highlight: true, timecode: timecode), peekData: nil)) + case let .channelMessage(peer, messageId, timecode): + openPeer(EnginePeer(peer), .chat(textInputState: nil, subject: .message(id: .id(messageId), highlight: true, timecode: timecode), peekData: nil)) case let .replyThreadMessage(replyThreadMessage, messageId): if let navigationController = navigationController { let _ = ChatControllerImpl.openMessageReplies(context: context, navigationController: navigationController, present: { c, a in @@ -196,8 +196,8 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur navigationController?.pushViewController(InstantPageController(context: context, webPage: webpage, sourcePeerType: .channel, anchor: anchor)) case let .join(link): dismissInput() - present(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peerId, peekData in - openPeer(peerId, .chat(textInputState: nil, subject: nil, peekData: peekData)) + present(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peer, peekData in + openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: peekData)) }, parentNavigationController: navigationController), nil) case let .localization(identifier): dismissInput() diff --git a/submodules/TelegramUI/Sources/OpenUrl.swift b/submodules/TelegramUI/Sources/OpenUrl.swift index 8ccabf6e46..6daefd7dc3 100644 --- a/submodules/TelegramUI/Sources/OpenUrl.swift +++ b/submodules/TelegramUI/Sources/OpenUrl.swift @@ -190,30 +190,27 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur if case let .externalUrl(value) = resolved { context.sharedContext.applicationBindings.openUrl(value) } else { - context.sharedContext.openResolvedUrl(resolved, context: context, urlContext: .generic, navigationController: navigationController, forceExternal: false, openPeer: { peerId, navigation in + context.sharedContext.openResolvedUrl(resolved, context: context, urlContext: .generic, navigationController: navigationController, forceExternal: false, openPeer: { peer, navigation in switch navigation { case .info: - let _ = (context.account.postbox.loadedPeerWithId(peerId) - |> deliverOnMainQueue).start(next: { peer in - if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { - context.sharedContext.applicationBindings.dismissNativeController() - navigationController?.pushViewController(infoController) - } - }) + if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + context.sharedContext.applicationBindings.dismissNativeController() + navigationController?.pushViewController(infoController) + } case let .chat(_, subject, peekData): context.sharedContext.applicationBindings.dismissNativeController() if let navigationController = navigationController { - context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(id: peerId), subject: subject, peekData: peekData)) + context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(id: peer.id), subject: subject, peekData: peekData)) } case let .withBotStartPayload(payload): context.sharedContext.applicationBindings.dismissNativeController() if let navigationController = navigationController { - context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(id: peerId), botStart: payload)) + context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(id: peer.id), botStart: payload)) } case let .withAttachBot(attachBotStart): context.sharedContext.applicationBindings.dismissNativeController() if let navigationController = navigationController { - context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(id: peerId), attachBotStart: attachBotStart)) + context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(id: peer.id), attachBotStart: attachBotStart)) } default: break diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift index 60378caf0d..dd6a078974 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift @@ -67,7 +67,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu } else { return false } - }, openPeer: { _, _, _, _, _ in + }, openPeer: { _, _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in diff --git a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoGroupsInCommonPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoGroupsInCommonPaneNode.swift index 306880c43c..e2def93ae8 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoGroupsInCommonPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoGroupsInCommonPaneNode.swift @@ -186,7 +186,7 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode { } } let transaction = preparedTransition(from: self.currentEntries, to: entries, context: self.context, presentationData: presentationData, openPeer: { [weak self] peer in - self?.chatControllerInteraction.openPeer(peer.id, .default, nil, false, nil) + self?.chatControllerInteraction.openPeer(EnginePeer(peer), .default, nil, false) }, openPeerContextAction: { [weak self] peer, node, gesture in self?.openPeerContextAction(peer, node, gesture) }) diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift index f232feb612..d1b218dcce 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift @@ -821,6 +821,9 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen } var availablePanes = availablePanes + if let channel = peerView.peers[peerView.peerId] as? TelegramChannel, channel.flags.contains(.isForum) { + availablePanes?.removeAll() + } if let membersData = membersData, case .longList = membersData { if availablePanes != nil { availablePanes?.insert(.members, at: 0) diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index f89306478e..24eae0d90b 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -27,6 +27,7 @@ import EmojiStatusComponent import AnimationCache import MultiAnimationRenderer import ComponentDisplayAdapters +import ChatTitleView enum PeerInfoHeaderButtonKey: Hashable { case message @@ -407,7 +408,17 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { self.containerNode.isGestureEnabled = false } - self.avatarNode.setPeer(context: self.context, theme: theme, peer: EnginePeer(peer), overrideImage: overrideImage, synchronousLoad: self.isFirstAvatarLoading, displayDimensions: CGSize(width: avatarSize, height: avatarSize), storeUnrounded: true) + self.avatarNode.setPeer(context: self.context, theme: theme, peer: EnginePeer(peer), overrideImage: overrideImage, clipStyle: .none, synchronousLoad: self.isFirstAvatarLoading, displayDimensions: CGSize(width: avatarSize, height: avatarSize), storeUnrounded: true) + + let avatarCornerRadius: CGFloat + if let channel = peer as? TelegramChannel, channel.flags.contains(.isForum) { + avatarCornerRadius = floor(avatarSize * 0.25) + } else { + avatarCornerRadius = avatarSize / 2.0 + } + self.avatarNode.layer.cornerRadius = avatarCornerRadius + self.avatarNode.layer.masksToBounds = true + self.isFirstAvatarLoading = false self.containerNode.frame = CGRect(origin: CGPoint(x: -avatarSize / 2.0, y: -avatarSize / 2.0), size: CGSize(width: avatarSize, height: avatarSize)) @@ -708,9 +719,18 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode { overrideImage = item == nil && canEdit ? .editAvatarIcon(forceNone: true) : nil } self.avatarNode.font = avatarPlaceholderFont(size: floor(avatarSize * 16.0 / 37.0)) - self.avatarNode.setPeer(context: self.context, theme: theme, peer: EnginePeer(peer), overrideImage: overrideImage, synchronousLoad: false, displayDimensions: CGSize(width: avatarSize, height: avatarSize)) + self.avatarNode.setPeer(context: self.context, theme: theme, peer: EnginePeer(peer), overrideImage: overrideImage, clipStyle: .none, synchronousLoad: false, displayDimensions: CGSize(width: avatarSize, height: avatarSize)) self.avatarNode.frame = CGRect(origin: CGPoint(x: -avatarSize / 2.0, y: -avatarSize / 2.0), size: CGSize(width: avatarSize, height: avatarSize)) + let avatarCornerRadius: CGFloat + if let channel = peer as? TelegramChannel, channel.flags.contains(.isForum) { + avatarCornerRadius = floor(avatarSize * 0.25) + } else { + avatarCornerRadius = avatarSize / 2.0 + } + self.avatarNode.layer.cornerRadius = avatarCornerRadius + self.avatarNode.layer.masksToBounds = true + if let item = item { let representations: [ImageRepresentationWithReference] let videoRepresentations: [VideoRepresentationWithReference] @@ -2550,8 +2570,9 @@ final class PeerInfoHeaderNode: ASDisplayNode { if self.isSettings, let user = peer as? TelegramUser { var subtitle = formatPhoneNumber(user.phone ?? "") - if let addressName = user.addressName, !addressName.isEmpty { - subtitle = "\(subtitle) • @\(addressName)" + let mainUsername = user.usernames.first?.username ?? user.username + if let mainUsername = mainUsername, !mainUsername.isEmpty { + subtitle = "\(subtitle) • @\(mainUsername)" } smallSubtitleString = NSAttributedString(string: subtitle, font: Font.regular(15.0), textColor: UIColor(rgb: 0xffffff, alpha: 0.7)) subtitleString = NSAttributedString(string: subtitle, font: Font.regular(17.0), textColor: presentationData.theme.list.itemSecondaryTextColor) @@ -2785,6 +2806,13 @@ final class PeerInfoHeaderNode: ASDisplayNode { avatarScale = 1.0 * (1.0 - titleCollapseFraction) + avatarMinScale * titleCollapseFraction avatarOffset = apparentTitleLockOffset + 0.0 * (1.0 - titleCollapseFraction) + 10.0 * titleCollapseFraction } + + let avatarCornerRadius: CGFloat + if let channel = peer as? TelegramChannel, channel.flags.contains(.isForum) { + avatarCornerRadius = floor(avatarSize * 0.25) + } else { + avatarCornerRadius = avatarSize / 2.0 + } if self.isAvatarExpanded { self.avatarListNode.listContainerNode.isHidden = false @@ -2795,9 +2823,9 @@ final class PeerInfoHeaderNode: ASDisplayNode { transition.updateCornerRadius(node: self.avatarListNode.listContainerNode, cornerRadius: 0.0) transition.updateCornerRadius(node: self.avatarListNode.listContainerNode.controlsClippingNode, cornerRadius: 0.0) } - } else if self.avatarListNode.listContainerNode.cornerRadius != avatarSize / 2.0 { - transition.updateCornerRadius(node: self.avatarListNode.listContainerNode.controlsClippingNode, cornerRadius: avatarSize / 2.0) - transition.updateCornerRadius(node: self.avatarListNode.listContainerNode, cornerRadius: avatarSize / 2.0, completion: { [weak self] _ in + } else if self.avatarListNode.listContainerNode.cornerRadius != avatarCornerRadius { + transition.updateCornerRadius(node: self.avatarListNode.listContainerNode.controlsClippingNode, cornerRadius: avatarCornerRadius) + transition.updateCornerRadius(node: self.avatarListNode.listContainerNode, cornerRadius: avatarCornerRadius, completion: { [weak self] _ in guard let strongSelf = self else { return } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index f1fee6ab99..99e753f888 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -76,6 +76,7 @@ import EntityKeyboard import AvatarNode import ComponentFlow import EmojiStatusComponent +import ChatTitleView protocol PeerInfoScreenItem: AnyObject { var id: AnyHashable { get } @@ -502,6 +503,7 @@ private final class PeerInfoInteraction { let openQrCode: () -> Void let editingOpenReactionsSetup: () -> Void let dismissInput: () -> Void + let toggleForumTopics: (Bool) -> Void init( openUsername: @escaping (String) -> Void, @@ -545,7 +547,8 @@ private final class PeerInfoInteraction { openAddMember: @escaping () -> Void, openQrCode: @escaping () -> Void, editingOpenReactionsSetup: @escaping () -> Void, - dismissInput: @escaping () -> Void + dismissInput: @escaping () -> Void, + toggleForumTopics: @escaping (Bool) -> Void ) { self.openUsername = openUsername self.openPhone = openPhone @@ -589,6 +592,7 @@ private final class PeerInfoInteraction { self.openQrCode = openQrCode self.editingOpenReactionsSetup = editingOpenReactionsSetup self.dismissInput = dismissInput + self.toggleForumTopics = toggleForumTopics } } @@ -970,6 +974,12 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese interaction.openUsername(username) }, longTapAction: { sourceNode in interaction.openPeerInfoContextMenu(.link, sourceNode) + }, linkItemAction: { type, item in + if case .tap = type { + if case let .mention(username) = item { + interaction.openUsername(String(username[username.index(username.startIndex, offsetBy: 1)...])) + } + } }, iconAction: { interaction.openQrCode() }, requestLayout: { @@ -1103,6 +1113,12 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese interaction.openUsername(username) }, longTapAction: { sourceNode in interaction.openPeerInfoContextMenu(.link, sourceNode) + }, linkItemAction: { type, item in + if case .tap = type { + if case let .mention(username) = item { + interaction.openUsername(String(username.suffix(from: username.index(username.startIndex, offsetBy: 1)))) + } + } }, iconAction: { interaction.openQrCode() }, requestLayout: { @@ -1248,6 +1264,7 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr case notifications case groupLocation case peerPublicSettings + case peerDataSettings case peerSettings case peerAdditionalSettings case peerActions @@ -1421,6 +1438,7 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr let ItemLocationSetup = 113 let ItemDeleteGroup = 114 let ItemReactions = 115 + let ItemTopics = 116 let isCreator = channel.flags.contains(.isCreator) let isPublic = channel.username != nil @@ -1476,7 +1494,7 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr invitesText = "" } - items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, icon: UIImage(bundleImageName: "Chat/Info/GroupLinksIcon"), action: { + items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemInviteLinks, label: .text(invitesText), text: presentationData.strings.GroupInfo_InviteLinks, icon: UIImage(bundleImageName: "Chat/Info/GroupLinksIcon"), action: { interaction.editingOpenInviteLinksSetup() })) } @@ -1489,7 +1507,7 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr } else { peerTitle = EnginePeer(linkedDiscussionPeer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) } - items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemLinkedChannel, label: .text(peerTitle), text: presentationData.strings.Group_LinkedChannel, icon: UIImage(bundleImageName: "Chat/Info/GroupLinkedChannelIcon"), action: { + items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemLinkedChannel, label: .text(peerTitle), text: presentationData.strings.Group_LinkedChannel, icon: UIImage(bundleImageName: "Chat/Info/GroupLinkedChannelIcon"), action: { interaction.editingOpenDiscussionGroupSetup() })) } @@ -1508,12 +1526,12 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr } else { label = "" } - items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemReactions, label: .text(label), text: presentationData.strings.PeerInfo_Reactions, icon: UIImage(bundleImageName: "Settings/Menu/Reactions"), action: { + items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemReactions, label: .text(label), text: presentationData.strings.PeerInfo_Reactions, icon: UIImage(bundleImageName: "Settings/Menu/Reactions"), action: { interaction.editingOpenReactionsSetup() })) } - if !isPublic, case .known(nil) = cachedData.linkedDiscussionPeerId { + if !isPublic, case .known(nil) = cachedData.linkedDiscussionPeerId, !channel.flags.contains(.isForum){ items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPreHistory, label: .text(cachedData.flags.contains(.preHistoryEnabled) ? presentationData.strings.GroupInfo_GroupHistoryVisible : presentationData.strings.GroupInfo_GroupHistoryHidden), text: presentationData.strings.GroupInfo_GroupHistoryShort, icon: UIImage(bundleImageName: "Chat/Info/GroupDiscussionIcon"), action: { interaction.editingOpenPreHistorySetup() })) @@ -1533,18 +1551,25 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr } else { label = "" } - items[.peerPublicSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemReactions, label: .text(label), text: presentationData.strings.PeerInfo_Reactions, icon: UIImage(bundleImageName: "Settings/Menu/Reactions"), action: { + items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemReactions, label: .text(label), text: presentationData.strings.PeerInfo_Reactions, icon: UIImage(bundleImageName: "Settings/Menu/Reactions"), action: { interaction.editingOpenReactionsSetup() })) } } if cachedData.flags.contains(.canSetStickerSet) && canEditPeerInfo(context: context, peer: channel) { - items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemStickerPack, label: .text(cachedData.stickerPack?.title ?? presentationData.strings.GroupInfo_SharedMediaNone), text: presentationData.strings.Stickers_GroupStickers, icon: UIImage(bundleImageName: "Settings/Menu/Stickers"), action: { + items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemStickerPack, label: .text(cachedData.stickerPack?.title ?? presentationData.strings.GroupInfo_SharedMediaNone), text: presentationData.strings.Stickers_GroupStickers, icon: UIImage(bundleImageName: "Settings/Menu/Stickers"), action: { interaction.editingOpenStickerPackSetup() })) } + if isCreator || (channel.adminRights?.rights.contains(.canChangeInfo) == true) { + //TODO:localize + items[.peerDataSettings]!.append(PeerInfoScreenSwitchItem(id: ItemTopics, text: "Topics", value: channel.flags.contains(.isForum), icon: UIImage(bundleImageName: "Settings/Menu/ChatListFilters"), toggled: { value in + interaction.toggleForumTopics(value) + })) + } + var canViewAdminsAndBanned = false if let _ = channel.adminRights { canViewAdminsAndBanned = true @@ -1959,6 +1984,12 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate }, dismissInput: { [weak self] in self?.view.endEditing(true) + }, + toggleForumTopics: { [weak self] value in + guard let strongSelf = self else { + return + } + let _ = strongSelf.context.engine.peers.setChannelForumMode(id: strongSelf.peerId, isForum: value).start() } ) @@ -1967,10 +1998,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate return false } return strongSelf.openMessage(id: message.id) - }, openPeer: { [weak self] id, navigation, _, _, _ in - if let id = id { - self?.openPeer(peerId: id, navigation: navigation) - } + }, openPeer: { [weak self] peer, navigation, _, _ in + self?.openPeer(peerId: peer.id, navigation: navigation) }, openPeerMention: { _ in }, openMessageContextMenu: { [weak self] message, _, node, frame, anyRecognizer, _ in guard let strongSelf = self, let node = node as? ContextExtractedContentContainingNode else { @@ -2497,7 +2526,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate let items: [ContextMenuItem] = [ .action(ContextMenuActionItem(text: presentationData.strings.Conversation_LinkDialogOpen, icon: { _ in nil }, action: { _, f in f(.dismissWithoutContent) - self?.chatInterfaceInteraction.openPeer(peer.id, .default, nil, false, nil) + self?.chatInterfaceInteraction.openPeer(EnginePeer(peer), .default, nil, false) })) ] let contextController = ContextController(account: strongSelf.context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) @@ -3495,9 +3524,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate if let strongSelf = self { strongSelf.openPeerMention(mention) } - }, openPeer: { [weak self] peerId in + }, openPeer: { [weak self] peer in if let strongSelf = self { - strongSelf.openPeer(peerId: peerId, navigation: .default) + strongSelf.openPeer(peerId: peer.id, navigation: .default) } }, openHashtag: { [weak self] peerName, hashtag in if let strongSelf = self { @@ -3583,27 +3612,23 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate guard let navigationController = self.controller?.navigationController as? NavigationController else { return } - self.context.sharedContext.openResolvedUrl(result, context: self.context, urlContext: .chat(peerId: self.peerId, updatedPresentationData: self.controller?.updatedPresentationData), navigationController: navigationController, forceExternal: false, openPeer: { [weak self] peerId, navigation in + self.context.sharedContext.openResolvedUrl(result, context: self.context, urlContext: .chat(peerId: self.peerId, updatedPresentationData: self.controller?.updatedPresentationData), navigationController: navigationController, forceExternal: false, openPeer: { [weak self] peer, navigation in guard let strongSelf = self else { return } switch navigation { case let .chat(_, subject, peekData): - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId), subject: subject, keepStack: .always, peekData: peekData)) + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peer.id), subject: subject, keepStack: .always, peekData: peekData)) case .info: - strongSelf.navigationActionDisposable.set((strongSelf.context.account.postbox.loadedPeerWithId(peerId) - |> take(1) - |> deliverOnMainQueue).start(next: { [weak self] peer in - if let strongSelf = self, peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil { - if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { - strongSelf.controller?.push(infoController) - } + if let strongSelf = self, peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil { + if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + strongSelf.controller?.push(infoController) } - })) + } case let .withBotStartPayload(startPayload): - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId), botStart: startPayload)) + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peer.id), botStart: startPayload)) case let .withAttachBot(attachBotStart): - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peerId), attachBotStart: attachBotStart)) + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peer.id), attachBotStart: attachBotStart)) default: break } @@ -3630,8 +3655,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate let result: ResolvedUrl = external ? .externalUrl(url) : tempResolved - strongSelf.context.sharedContext.openResolvedUrl(result, context: strongSelf.context, urlContext: .generic, navigationController: strongSelf.controller?.navigationController as? NavigationController, forceExternal: false, openPeer: { peerId, navigation in - self?.openPeer(peerId: peerId, navigation: navigation) + strongSelf.context.sharedContext.openResolvedUrl(result, context: strongSelf.context, urlContext: .generic, navigationController: strongSelf.controller?.navigationController as? NavigationController, forceExternal: false, openPeer: { peer, navigation in + self?.openPeer(peerId: peer.id, navigation: navigation) }, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, @@ -3737,7 +3762,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate navigation = .chat(textInputState: nil, subject: nil, peekData: nil) } } - strongSelf.openResolved(.peer(peer.id, navigation)) + strongSelf.openResolved(.peer(peer, navigation)) } else { strongSelf.controller?.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.controller?.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Resolve_ErrorNotFound, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) } @@ -6748,7 +6773,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate }) if let selectedAccount = selectedAccount { let accountContext = self.context.sharedContext.makeTempAccountContext(account: selectedAccount) - let chatListController = accountContext.sharedContext.makeChatListController(context: accountContext, groupId: .root, controlsHistoryPreload: false, hideNetworkActivityStatus: true, previewing: true, enableDebugActions: false) + let chatListController = accountContext.sharedContext.makeChatListController(context: accountContext, location: .chatList(groupId: EngineChatList.Group(.root)), controlsHistoryPreload: false, hideNetworkActivityStatus: true, previewing: true, enableDebugActions: false) let contextController = ContextController(account: accountContext.account, presentationData: self.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatListController, sourceNode: node)), items: accountContextMenuItems(context: accountContext, logout: { [weak self] in self?.logoutAccount(id: id) diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 3c3fd02ec5..772723cd6c 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -1159,8 +1159,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { navigateToForumChannelImpl(context: context, peerId: peerId, navigationController: navigationController) } - public func navigateToForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, navigationController: NavigationController) -> Signal { - return navigateToForumThreadImpl(context: context, peerId: peerId, threadId: threadId, navigationController: navigationController) + public func navigateToForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, navigationController: NavigationController, activateInput: ChatControllerActivateInput?) -> Signal { + return navigateToForumThreadImpl(context: context, peerId: peerId, threadId: threadId, navigationController: navigationController, activateInput: activateInput) } public func openStorageUsage(context: AccountContext) { @@ -1210,7 +1210,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { return resolveUrlImpl(context: context, peerId: peerId, url: url, skipUrlAuth: skipUrlAuth) } - public func openResolvedUrl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, forceExternal: Bool, openPeer: @escaping (PeerId, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?, requestMessageActionUrlAuth: ((MessageActionUrlSubject) -> Void)?, joinVoiceChat: ((PeerId, String?, CachedChannelData.ActiveCall) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?) { + public func openResolvedUrl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, forceExternal: Bool, openPeer: @escaping (EnginePeer, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?, requestMessageActionUrlAuth: ((MessageActionUrlSubject) -> Void)?, joinVoiceChat: ((PeerId, String?, CachedChannelData.ActiveCall) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?) { openResolvedUrlImpl(resolvedUrl, context: context, urlContext: urlContext, navigationController: navigationController, forceExternal: forceExternal, openPeer: openPeer, sendFile: sendFile, sendSticker: sendSticker, requestMessageActionUrlAuth: requestMessageActionUrlAuth, joinVoiceChat: joinVoiceChat, present: present, dismissInput: dismissInput, contentContext: contentContext) } @@ -1270,8 +1270,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { return createGroupControllerImpl(context: context, peerIds: peerIds, initialTitle: initialTitle, mode: mode, completion: completion) } - public func makeChatListController(context: AccountContext, groupId: PeerGroupId, controlsHistoryPreload: Bool, hideNetworkActivityStatus: Bool, previewing: Bool, enableDebugActions: Bool) -> ChatListController { - return ChatListControllerImpl(context: context, location: .chatList(groupId: EngineChatList.Group(groupId)), controlsHistoryPreload: controlsHistoryPreload, hideNetworkActivityStatus: hideNetworkActivityStatus, previewing: previewing, enableDebugActions: enableDebugActions) + public func makeChatListController(context: AccountContext, location: ChatListControllerLocation, controlsHistoryPreload: Bool, hideNetworkActivityStatus: Bool, previewing: Bool, enableDebugActions: Bool) -> ChatListController { + return ChatListControllerImpl(context: context, location: location, controlsHistoryPreload: controlsHistoryPreload, hideNetworkActivityStatus: hideNetworkActivityStatus, previewing: previewing, enableDebugActions: enableDebugActions) } public func makePeerSelectionController(_ params: PeerSelectionControllerParams) -> PeerSelectionController { @@ -1286,7 +1286,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { let controllerInteraction: ChatControllerInteraction controllerInteraction = ChatControllerInteraction(openMessage: { _, _ in - return false }, openPeer: { _, _, _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in + return false }, openPeer: { _, _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in }, updateMessageReaction: { _, _ in }, activateMessagePinch: { _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in }, tapMessage: { message in diff --git a/submodules/TelegramUI/Sources/TelegramRootController.swift b/submodules/TelegramUI/Sources/TelegramRootController.swift index 6a6e66e61b..cfe86921ea 100644 --- a/submodules/TelegramUI/Sources/TelegramRootController.swift +++ b/submodules/TelegramUI/Sources/TelegramRootController.swift @@ -96,7 +96,7 @@ public final class TelegramRootController: NavigationController { public func addRootControllers(showCallsTab: Bool) { let tabBarController = TabBarControllerImpl(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), theme: TabBarControllerTheme(rootControllerTheme: self.presentationData.theme)) tabBarController.navigationPresentation = .master - let chatListController = self.context.sharedContext.makeChatListController(context: self.context, groupId: .root, controlsHistoryPreload: true, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: !GlobalExperimentalSettings.isAppStoreBuild) + let chatListController = self.context.sharedContext.makeChatListController(context: self.context, location: .chatList(groupId: .root), controlsHistoryPreload: true, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: !GlobalExperimentalSettings.isAppStoreBuild) if let sharedContext = self.context.sharedContext as? SharedAccountContextImpl { chatListController.tabBarItem.badgeValue = sharedContext.switchingData.chatListBadge } diff --git a/submodules/TelegramUI/Sources/TextLinkHandling.swift b/submodules/TelegramUI/Sources/TextLinkHandling.swift index d1098bf776..b27420b1b1 100644 --- a/submodules/TelegramUI/Sources/TextLinkHandling.swift +++ b/submodules/TelegramUI/Sources/TextLinkHandling.swift @@ -22,16 +22,19 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: PeerId?, navigate controller.present(controllerToPresent, in: .window(.root)) } - let openResolvedPeerImpl: (PeerId?, ChatControllerInteractionNavigateToPeer) -> Void = { [weak controller] peerId, navigation in - context.sharedContext.openResolvedUrl(.peer(peerId, navigation), context: context, urlContext: .generic, navigationController: (controller?.navigationController as? NavigationController), forceExternal: false, openPeer: { (peerId, navigation) in + let openResolvedPeerImpl: (EnginePeer?, ChatControllerInteractionNavigateToPeer) -> Void = { [weak controller] peer, navigation in + guard let peer = peer else { + return + } + context.sharedContext.openResolvedUrl(.peer(peer._asPeer(), navigation), context: context, urlContext: .generic, navigationController: (controller?.navigationController as? NavigationController), forceExternal: false, openPeer: { (peer, navigation) in switch navigation { case let .chat(_, subject, peekData): if let navigationController = controller?.navigationController as? NavigationController { - context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(id: peerId), subject: subject, keepStack: .always, peekData: peekData)) + context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(id: peer.id), subject: subject, keepStack: .always, peekData: peekData)) } case .info: let peerSignal: Signal - peerSignal = context.account.postbox.loadedPeerWithId(peerId) |> map(Optional.init) + peerSignal = context.account.postbox.loadedPeerWithId(peer.id) |> map(Optional.init) navigateDisposable.set((peerSignal |> take(1) |> deliverOnMainQueue).start(next: { peer in if let controller = controller, let peer = peer { if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { @@ -55,11 +58,11 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: PeerId?, navigate switch result { case let .externalUrl(url): context.sharedContext.applicationBindings.openUrl(url) - case let .peer(peerId, navigation): - openResolvedPeerImpl(peerId, navigation) - case let .channelMessage(peerId, messageId, timecode): + case let .peer(peer, navigation): + openResolvedPeerImpl(peer.flatMap(EnginePeer.init), navigation) + case let .channelMessage(peer, messageId, timecode): if let navigationController = controller.navigationController as? NavigationController { - context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(id: peerId), subject: .message(id: .id(messageId), highlight: true, timecode: timecode))) + context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(id: peer.id), subject: .message(id: .id(messageId), highlight: true, timecode: timecode))) } case let .replyThreadMessage(replyThreadMessage, messageId): if let navigationController = controller.navigationController as? NavigationController { @@ -73,8 +76,8 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: PeerId?, navigate case let .instantView(webpage, anchor): (controller.navigationController as? NavigationController)?.pushViewController(InstantPageController(context: context, webPage: webpage, sourcePeerType: .group, anchor: anchor)) case let .join(link): - controller.present(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peerId, peekData in - openResolvedPeerImpl(peerId, .chat(textInputState: nil, subject: nil, peekData: peekData)) + controller.present(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peer, peekData in + openResolvedPeerImpl(peer, .chat(textInputState: nil, subject: nil, peekData: peekData)) }, parentNavigationController: controller.navigationController as? NavigationController), in: .window(.root)) default: break @@ -85,7 +88,7 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: PeerId?, navigate let openPeerMentionImpl: (String) -> Void = { mention in navigateDisposable.set((context.engine.peers.resolvePeerByName(name: mention, ageLimit: 10) |> take(1) |> deliverOnMainQueue).start(next: { peer in - openResolvedPeerImpl(peer?.id, .default) + openResolvedPeerImpl(peer, .default) })) } diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index aba787fef7..5c3cfa3552 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -477,13 +477,13 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) |> take(1) |> map { botPeer -> ResolvedUrl? in if let botPeer = botPeer?._asPeer() { - return .peer(peer.id, .withAttachBot(ChatControllerInitialAttachBotStart(botId: botPeer.id, payload: startAttach, justInstalled: false))) + return .peer(peer, .withAttachBot(ChatControllerInitialAttachBotStart(botId: botPeer.id, payload: startAttach, justInstalled: false))) } else { - return .peer(peer.id, .chat(textInputState: nil, subject: nil, peekData: nil)) + return .peer(peer, .chat(textInputState: nil, subject: nil, peekData: nil)) } } } else { - return .single(.peer(peer.id, .chat(textInputState: nil, subject: nil, peekData: nil))) + return .single(.peer(peer, .chat(textInputState: nil, subject: nil, peekData: nil))) } } else { return .single(.peer(nil, .info)) @@ -500,7 +500,7 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) if let parameter = parameter { switch parameter { case let .botStart(payload): - return .single(.botStart(peerId: peer.id, payload: payload)) + return .single(.botStart(peer: peer, payload: payload)) case let .groupBotStart(payload, adminRights): return .single(.groupBotStart(peerId: peer.id, payload: payload, adminRights: adminRights)) case let .attachBotStart(name, payload): @@ -511,13 +511,13 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) } |> mapToSignal { botPeer -> Signal in if let botPeer = botPeer { - return .single(.peer(peer.id, .withAttachBot(ChatControllerInitialAttachBotStart(botId: botPeer.id, payload: payload, justInstalled: false)))) + return .single(.peer(peer, .withAttachBot(ChatControllerInitialAttachBotStart(botId: botPeer.id, payload: payload, justInstalled: false)))) } else { - return .single(.peer(peer.id, .chat(textInputState: nil, subject: nil, peekData: nil))) + return .single(.peer(peer, .chat(textInputState: nil, subject: nil, peekData: nil))) } } case let .channelMessage(id, timecode): - return .single(.channelMessage(peerId: peer.id, messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id), timecode: timecode)) + return .single(.channelMessage(peer: peer, messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id), timecode: timecode)) case let .replyThread(id, replyId): let replyThreadMessageId = MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id) return context.engine.messages.fetchChannelReplyThreadMessage(messageId: replyThreadMessageId, atMessageId: nil) @@ -527,7 +527,7 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) } |> map { result -> ResolvedUrl? in guard let result = result else { - return .channelMessage(peerId: peer.id, messageId: replyThreadMessageId, timecode: nil) + return .channelMessage(peer: peer, messageId: replyThreadMessageId, timecode: nil) } return .replyThreadMessage(replyThreadMessage: result, messageId: MessageId(peerId: result.messageId.peerId, namespace: Namespaces.Message.Cloud, id: replyId)) } @@ -535,7 +535,7 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) return .single(.joinVoiceChat(peer.id, invite)) } } else { - return .single(.peer(peer.id, .chat(textInputState: nil, subject: nil, peekData: nil))) + return .single(.peer(peer, .chat(textInputState: nil, subject: nil, peekData: nil))) } } else { return .single(.peer(nil, .info)) @@ -545,7 +545,7 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) return context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) |> mapToSignal { peer -> Signal in if let peer = peer { - return .single(.peer(peer.id, .chat(textInputState: nil, subject: nil, peekData: nil))) + return .single(.peer(peer._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil))) } else { return .single(.inaccessiblePeer) } @@ -571,12 +571,12 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) } |> map { result -> ResolvedUrl? in guard let result = result else { - return .channelMessage(peerId: foundPeer.id, messageId: replyThreadMessageId, timecode: timecode) + return .channelMessage(peer: foundPeer._asPeer(), messageId: replyThreadMessageId, timecode: timecode) } return .replyThreadMessage(replyThreadMessage: result, messageId: messageId) } } else { - return .single(.peer(foundPeer.id, .chat(textInputState: nil, subject: .message(id: .id(messageId), highlight: true, timecode: timecode), peekData: nil))) + return .single(.peer(foundPeer._asPeer(), .chat(textInputState: nil, subject: .message(id: .id(messageId), highlight: true, timecode: timecode), peekData: nil))) } } else { return .single(.inaccessiblePeer)