diff --git a/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift b/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift index 68c2f404e9..9515edaf6a 100644 --- a/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift +++ b/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift @@ -149,6 +149,7 @@ public final class BrowserBookmarksScreen: ViewController { }, openWebView: { _, _, _, _ in }, activateAdAction: { _, _, _, _ in }, adContextAction: { _, _, _ in + }, removeAd: { _ in }, openRequestedPeerSelection: { _, _, _, _ in }, saveMediaToFiles: { _ in }, openNoAdsDemo: { diff --git a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift index cac9c67636..07d5a2e46a 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift @@ -634,6 +634,11 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo override public func searchTextUpdated(text: String) { let searchQuery: String? = !text.isEmpty ? text : nil + + if !text.hasPrefix("#") && self.paneContainerNode.currentPaneKey == .publicPosts { + self.paneContainerNode.requestSelectPane(.chats) + } + self.searchQuery.set(.single(searchQuery)) self.searchQueryValue = searchQuery diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 416332d70f..d5c691cc9a 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -1340,6 +1340,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { private var searchQueryValue: String? private var searchOptionsValue: ChatListSearchOptions? + var isCurrent: Bool = false + private let _isSearching = ValuePromise(false, ignoreRepeated: true) public var isSearching: Signal { return self._isSearching.get() @@ -2188,12 +2190,53 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { let foundPublicMessages: Signal<([FoundRemoteMessages], Bool), NoError> if key == .chats || key == .publicPosts, let query, query.hasPrefix("#") { - let searchSignal = context.engine.messages.searchHashtagPosts(hashtag: finalQuery, state: nil, limit: 50) + let searchSignal = context.engine.messages.searchHashtagPosts(hashtag: finalQuery, state: nil, limit: 10) + let loadMore: Signal<([FoundRemoteMessages], Bool), NoError> + if key == .publicPosts { + loadMore = searchContexts.get() + |> mapToSignal { searchContexts -> Signal<([FoundRemoteMessages], Bool), NoError> in + let i = 0 + if let searchContext = searchContexts[i], searchContext.result.hasMore { + if let _ = searchContext.loadMoreIndex { + return context.engine.messages.searchHashtagPosts(hashtag: finalQuery, state: searchContext.result.state, limit: 80) + |> map { result, updatedState -> ChatListSearchMessagesResult in + return ChatListSearchMessagesResult(query: finalQuery, messages: result.messages.map({ EngineMessage($0) }).sorted(by: { $0.index > $1.index }), readStates: result.readStates.mapValues { EnginePeerReadCounters(state: $0, isMuted: false) }, threadInfo: result.threadInfo, hasMore: !result.completed, totalCount: result.totalCount, state: updatedState) + } + |> mapToSignal { foundMessages -> Signal<([FoundRemoteMessages], Bool), NoError> in + updateSearchContexts { previous in + let updated = ChatListSearchMessagesContext(result: foundMessages, loadMoreIndex: nil) + var previous = previous + previous[i] = updated + return (previous, true) + } + return .complete() + } + } else { + var currentResults: [FoundRemoteMessages] = [] + if let currentContext = searchContexts[i] { + currentResults.append(FoundRemoteMessages(messages: currentContext.result.messages, readCounters: currentContext.result.readStates, threadsData: currentContext.result.threadInfo, totalCount: currentContext.result.totalCount)) + } + return .single((currentResults, false)) + } + } + + return .complete() + } + } else { + loadMore = .complete() + } + foundPublicMessages = .single(([FoundRemoteMessages(messages: [], readCounters: [:], threadsData: [:], totalCount: 0)], true)) |> then( searchSignal |> map { result -> ([FoundRemoteMessages], Bool) in + updateSearchContexts { _ in + var resultContexts: [Int: ChatListSearchMessagesContext] = [:] + resultContexts[0] = ChatListSearchMessagesContext(result: ChatListSearchMessagesResult(query: finalQuery, messages: result.0.messages.map({ EngineMessage($0) }).sorted(by: { $0.index > $1.index }), readStates: result.0.readStates.mapValues { EnginePeerReadCounters(state: $0, isMuted: false) }, threadInfo: result.0.threadInfo, hasMore: !result.0.completed, totalCount: result.0.totalCount, state: result.1), loadMoreIndex: nil) + return (resultContexts, true) + } + let foundMessages = result.0 let messages: [EngineMessage] if key == .chats { @@ -2204,6 +2247,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { return ([FoundRemoteMessages(messages: messages, readCounters: foundMessages.readStates.mapValues { EnginePeerReadCounters(state: $0, isMuted: false) }, threadsData: foundMessages.threadInfo, totalCount: foundMessages.totalCount)], false) } |> delay(0.2, queue: Queue.concurrentDefaultQueue()) + |> then(loadMore) ) } else { foundPublicMessages = .single(([FoundRemoteMessages(messages: [], readCounters: [:], threadsData: [:], totalCount: 0)], false)) @@ -2380,7 +2424,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { foundThreads ) |> map { accountPeer, foundLocalPeers, foundRemotePeers, foundRemoteMessages, foundPublicMessages, presentationData, searchState, selectionState, resolvedMessage, recentPeers, allAndFoundThreads -> ([ChatListSearchEntry], Bool)? in - let isSearching = foundRemotePeers.2 || foundRemoteMessages.1 + let isSearching = foundRemotePeers.2 || foundRemoteMessages.1 || foundPublicMessages.1 var entries: [ChatListSearchEntry] = [] var index = 0 diff --git a/submodules/ChatListUI/Sources/ChatListSearchPaneContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchPaneContainerNode.swift index 209b5b6819..37f101cf1a 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchPaneContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchPaneContainerNode.swift @@ -12,6 +12,7 @@ import MultiAnimationRenderer protocol ChatListSearchPaneNode: ASDisplayNode { var isReady: Signal { get } + var isCurrent: Bool { get set } func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) func scrollToTop() -> Bool @@ -568,6 +569,7 @@ final class ChatListSearchPaneContainerNode: ASDisplayNode, ASGestureRecognizerD }) } pane.update(size: paneFrame.size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, presentationData: presentationData, synchronous: paneWasAdded, transition: paneTransition) + pane.node.isCurrent = key == self.currentPaneKey if paneWasAdded && key == self.currentPaneKey { pane.node.didBecomeFocused() } diff --git a/submodules/TelegramUI/Components/Ads/AdsInfoScreen/Sources/AdsInfoScreen.swift b/submodules/TelegramUI/Components/Ads/AdsInfoScreen/Sources/AdsInfoScreen.swift index ae72c3c965..cfebf87ff2 100644 --- a/submodules/TelegramUI/Components/Ads/AdsInfoScreen/Sources/AdsInfoScreen.swift +++ b/submodules/TelegramUI/Components/Ads/AdsInfoScreen/Sources/AdsInfoScreen.swift @@ -381,7 +381,6 @@ private final class ContainerComponent: CombinedComponent { var bottomContentOffset: CGFloat? var cachedMoreImage: (UIImage, PresentationTheme)? - var cachedCloseImage: (UIImage, PresentationTheme)? } func makeState() -> State { @@ -393,9 +392,7 @@ private final class ContainerComponent: CombinedComponent { let scroll = Child(ScrollComponent.self) let scrollExternalState = ScrollComponent.ExternalState() - let buttonsBackground = Child(RoundedRectangle.self) let moreButton = Child(Button.self) - let closeButton = Child(Button.self) return { context in let environment = context.environment[EnvironmentType.self] @@ -447,20 +444,11 @@ private final class ContainerComponent: CombinedComponent { ) if case .bot = context.component.mode { - let buttonsBackground = buttonsBackground.update( - component: RoundedRectangle(color: UIColor(rgb: 0x808084, alpha: 0.1), cornerRadius: 15.0), - availableSize: CGSize(width: 73.0, height: 30.0), - transition: .immediate - ) - context.add(buttonsBackground - .position(CGPoint(x: context.availableSize.width - 16.0 - buttonsBackground.size.width / 2.0, y: 13.0 + buttonsBackground.size.height / 2.0)) - ) - let moreImage: UIImage if let (image, theme) = state.cachedMoreImage, theme === environment.theme { moreImage = image } else { - moreImage = generateMoreButtonImage(color: environment.theme.actionSheet.inputClearButtonColor)! + moreImage = generateMoreButtonImage(backgroundColor: UIColor(rgb: 0x808084, alpha: 0.1), foregroundColor: environment.theme.actionSheet.inputClearButtonColor)! state.cachedMoreImage = (moreImage, environment.theme) } let moreButton = moreButton.update( @@ -474,28 +462,7 @@ private final class ContainerComponent: CombinedComponent { transition: .immediate ) context.add(moreButton - .position(CGPoint(x: context.availableSize.width - 16.0 - buttonsBackground.size.width + moreButton.size.width / 2.0 + 3.0, y: 13.0 + buttonsBackground.size.height / 2.0)) - ) - - let closeImage: UIImage - if let (image, theme) = state.cachedCloseImage, theme === environment.theme { - closeImage = image - } else { - closeImage = generateCloseButtonImage(color: environment.theme.actionSheet.inputClearButtonColor)! - state.cachedCloseImage = (closeImage, environment.theme) - } - let closeButton = closeButton.update( - component: Button( - content: AnyComponent(Image(image: closeImage)), - action: { - dismiss() - } - ), - availableSize: CGSize(width: 30.0, height: 30.0), - transition: .immediate - ) - context.add(closeButton - .position(CGPoint(x: context.availableSize.width - 16.0 - closeButton.size.width / 2.0, y: 13.0 + buttonsBackground.size.height / 2.0)) + .position(CGPoint(x: context.availableSize.width - 16.0 - moreButton.size.width / 2.0, y: 13.0 + moreButton.size.height / 2.0)) ) } @@ -1549,11 +1516,14 @@ private final class FooterComponent: Component { } } -private func generateMoreButtonImage(color: UIColor) -> UIImage? { +private func generateMoreButtonImage(backgroundColor: UIColor, foregroundColor: UIColor) -> UIImage? { return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(color.cgColor) + context.setFillColor(backgroundColor.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) + + context.setFillColor(foregroundColor.cgColor) let circleSize = CGSize(width: 4.0, height: 4.0) context.fillEllipse(in: CGRect(origin: CGPoint(x: floorToScreenPixels((size.height - circleSize.width) / 2.0), y: floorToScreenPixels((size.height - circleSize.height) / 2.0)), size: circleSize)) @@ -1564,24 +1534,6 @@ private func generateMoreButtonImage(color: UIColor) -> UIImage? { }) } -private func generateCloseButtonImage(color: UIColor) -> UIImage? { - return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - - context.setLineWidth(2.0) - context.setLineCap(.round) - context.setStrokeColor(color.cgColor) - - context.move(to: CGPoint(x: 10.0, y: 10.0)) - context.addLine(to: CGPoint(x: 20.0, y: 20.0)) - context.strokePath() - - context.move(to: CGPoint(x: 20.0, y: 10.0)) - context.addLine(to: CGPoint(x: 10.0, y: 20.0)) - context.strokePath() - }) -} - private final class AdsInfoContextReferenceContentSource: ContextReferenceContentSource { let controller: ViewController let sourceView: UIView diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift index 246632b716..c5e707c86c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift @@ -618,6 +618,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { }, openWebView: { _, _, _, _ in }, activateAdAction: { _, _, _, _ in }, adContextAction: { _, _, _ in + }, removeAd: { _ in }, openRequestedPeerSelection: { _, _, _, _ in }, saveMediaToFiles: { _ in }, openNoAdsDemo: { diff --git a/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift b/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift index d19b20b1db..ce99d5b1f8 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift @@ -475,6 +475,7 @@ public final class ChatSendGroupMediaMessageContextPreview: UIView, ChatSendMess }, openWebView: { _, _, _, _ in }, activateAdAction: { _, _, _, _ in }, adContextAction: { _, _, _ in + }, removeAd: { _ in }, openRequestedPeerSelection: { _, _, _, _ in }, saveMediaToFiles: { _ in }, openNoAdsDemo: { diff --git a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift index acb01cf7ec..fa52fd8c41 100644 --- a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift @@ -254,6 +254,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol public let openWebView: (String, String, Bool, ChatOpenWebViewSource) -> Void public let activateAdAction: (EngineMessage.Id, Promise?, Bool, Bool) -> Void public let adContextAction: (Message, ASDisplayNode, ContextGesture?) -> Void + public let removeAd: (Data) -> Void public let openRequestedPeerSelection: (EngineMessage.Id, ReplyMarkupButtonRequestPeerType, Int32, Int32) -> Void public let saveMediaToFiles: (EngineMessage.Id) -> Void public let openNoAdsDemo: () -> Void @@ -386,6 +387,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol openWebView: @escaping (String, String, Bool, ChatOpenWebViewSource) -> Void, activateAdAction: @escaping (EngineMessage.Id, Promise?, Bool, Bool) -> Void, adContextAction: @escaping (Message, ASDisplayNode, ContextGesture?) -> Void, + removeAd: @escaping (Data) -> Void, openRequestedPeerSelection: @escaping (EngineMessage.Id, ReplyMarkupButtonRequestPeerType, Int32, Int32) -> Void, saveMediaToFiles: @escaping (EngineMessage.Id) -> Void, openNoAdsDemo: @escaping () -> Void, @@ -497,6 +499,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol self.openWebView = openWebView self.activateAdAction = activateAdAction self.adContextAction = adContextAction + self.removeAd = removeAd self.openRequestedPeerSelection = openRequestedPeerSelection self.saveMediaToFiles = saveMediaToFiles self.openNoAdsDemo = openNoAdsDemo diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 61e90e92b7..1eb66f5d3e 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -3499,6 +3499,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro }, openWebView: { _, _, _, _ in }, activateAdAction: { _, _, _, _ in }, adContextAction: { _, _, _ in + }, removeAd: { _ in }, openRequestedPeerSelection: { _, _, _, _ in }, saveMediaToFiles: { _ in }, openNoAdsDemo: { diff --git a/submodules/TelegramUI/Sources/ChatAdPanelNode.swift b/submodules/TelegramUI/Sources/ChatAdPanelNode.swift index 1cdf043d98..c3e42603d3 100644 --- a/submodules/TelegramUI/Sources/ChatAdPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatAdPanelNode.swift @@ -48,6 +48,8 @@ final class ChatAdPanelNode: ASDisplayNode { private let removeBackgroundNode: ASImageNode private let removeTextNode: ImmediateTextNode + private let closeButton: HighlightableButtonNode + private let imageNode: TransformImageNode private let imageNodeContainer: ASDisplayNode @@ -104,6 +106,10 @@ final class ChatAdPanelNode: ASDisplayNode { self.imageNodeContainer = ASDisplayNode() + self.closeButton = HighlightableButtonNode() + self.closeButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0) + self.closeButton.displaysAsynchronously = false + super.init() self.addSubnode(self.contextContainer) @@ -183,6 +189,9 @@ final class ChatAdPanelNode: ASDisplayNode { } self.controllerInteraction?.adContextAction(message, self.contextContainer, gesture) } + + self.closeButton.addTarget(self, action: #selector(self.closePressed), forControlEvents: [.touchUpInside]) + self.addSubnode(self.closeButton) } deinit { @@ -191,6 +200,14 @@ final class ChatAdPanelNode: ASDisplayNode { private var theme: PresentationTheme? + @objc private func closePressed() { + if self.context.isPremium, let adAttribute = self.message?.adAttribute { + self.controllerInteraction?.removeAd(adAttribute.opaqueId) + } else { + self.controllerInteraction?.openNoAdsDemo() + } + } + func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> CGFloat { self.message = interfaceState.adMessage @@ -199,13 +216,16 @@ final class ChatAdPanelNode: ASDisplayNode { self.separatorNode.backgroundColor = interfaceState.theme.rootController.navigationBar.separatorColor self.removeBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 15.0, color: interfaceState.theme.chat.inputPanel.panelControlAccentColor.withMultipliedAlpha(0.1)) self.removeTextNode.attributedText = NSAttributedString(string: interfaceState.strings.Chat_BotAd_WhatIsThis, font: Font.regular(11.0), textColor: interfaceState.theme.chat.inputPanel.panelControlAccentColor) + self.closeButton.setImage(PresentationResourcesChat.chatInputPanelCloseIconImage(interfaceState.theme), for: []) } self.contextContainer.isGestureEnabled = false let panelHeight: CGFloat + var hasCloseButton = true if let message = interfaceState.adMessage { panelHeight = self.enqueueTransition(width: width, leftInset: leftInset, rightInset: rightInset, transition: .immediate, animation: nil, message: message, theme: interfaceState.theme, strings: interfaceState.strings, nameDisplayOrder: interfaceState.nameDisplayOrder, dateTimeFormat: interfaceState.dateTimeFormat, accountPeerId: self.context.account.peerId, firstTime: false, isReplyThread: false, translateToLanguage: nil) + hasCloseButton = message.media.isEmpty } else { panelHeight = 50.0 } @@ -218,6 +238,12 @@ final class ChatAdPanelNode: ASDisplayNode { self.clippingContainer.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: panelHeight)) self.contentContainer.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: panelHeight)) + let contentRightInset: CGFloat = 14.0 + rightInset + let closeButtonSize = self.closeButton.measure(CGSize(width: 100.0, height: 100.0)) + self.closeButton.frame = CGRect(origin: CGPoint(x: width - contentRightInset - closeButtonSize.width, y: floorToScreenPixels((panelHeight - closeButtonSize.height) / 2.0)), size: closeButtonSize) + + self.closeButton.isHidden = !hasCloseButton + self.currentLayout = (width, leftInset, rightInset) return panelHeight diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index f87e8e55d5..7fa12d2c88 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -3982,6 +3982,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self?.removeAd(opaqueId: opaqueId) } self.effectiveNavigationController?.pushViewController(controller) + }, removeAd: { [weak self] opaqueId in + guard let self else { + return + } + self.removeAd(opaqueId: opaqueId) }, openRequestedPeerSelection: { [weak self] messageId, peerType, buttonId, maxQuantity in guard let self else { return diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift index b076ce0d8d..f51c17d359 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift @@ -167,6 +167,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu }, openWebView: { _, _, _, _ in }, activateAdAction: { _, _, _, _ in }, adContextAction: { _, _, _ in + }, removeAd: { _ in }, openRequestedPeerSelection: { _, _, _, _ in }, saveMediaToFiles: { _ in }, openNoAdsDemo: { diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index efeb2b4840..d5bb8ab525 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -1782,6 +1782,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { }, openWebView: { _, _, _, _ in }, activateAdAction: { _, _, _, _ in }, adContextAction: { _, _, _ in + }, removeAd: { _ in }, openRequestedPeerSelection: { _, _, _, _ in }, saveMediaToFiles: { _ in }, openNoAdsDemo: {