Bot ad improvements

This commit is contained in:
Ilya Laktyushin 2024-10-29 10:05:00 +04:00
parent 11d7bb1ab8
commit ec9d5fde63
13 changed files with 100 additions and 57 deletions

View File

@ -149,6 +149,7 @@ public final class BrowserBookmarksScreen: ViewController {
}, openWebView: { _, _, _, _ in
}, activateAdAction: { _, _, _, _ in
}, adContextAction: { _, _, _ in
}, removeAd: { _ in
}, openRequestedPeerSelection: { _, _, _, _ in
}, saveMediaToFiles: { _ in
}, openNoAdsDemo: {

View File

@ -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

View File

@ -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<Bool>(false, ignoreRepeated: true)
public var isSearching: Signal<Bool, NoError> {
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

View File

@ -12,6 +12,7 @@ import MultiAnimationRenderer
protocol ChatListSearchPaneNode: ASDisplayNode {
var isReady: Signal<Bool, NoError> { 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()
}

View File

@ -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<ViewControllerComponentContainer.Environment>.self)
let scrollExternalState = ScrollComponent<EnvironmentType>.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

View File

@ -618,6 +618,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, openWebView: { _, _, _, _ in
}, activateAdAction: { _, _, _, _ in
}, adContextAction: { _, _, _ in
}, removeAd: { _ in
}, openRequestedPeerSelection: { _, _, _, _ in
}, saveMediaToFiles: { _ in
}, openNoAdsDemo: {

View File

@ -475,6 +475,7 @@ public final class ChatSendGroupMediaMessageContextPreview: UIView, ChatSendMess
}, openWebView: { _, _, _, _ in
}, activateAdAction: { _, _, _, _ in
}, adContextAction: { _, _, _ in
}, removeAd: { _ in
}, openRequestedPeerSelection: { _, _, _, _ in
}, saveMediaToFiles: { _ in
}, openNoAdsDemo: {

View File

@ -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, 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, 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

View File

@ -3499,6 +3499,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
}, openWebView: { _, _, _, _ in
}, activateAdAction: { _, _, _, _ in
}, adContextAction: { _, _, _ in
}, removeAd: { _ in
}, openRequestedPeerSelection: { _, _, _, _ in
}, saveMediaToFiles: { _ in
}, openNoAdsDemo: {

View File

@ -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

View File

@ -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

View File

@ -167,6 +167,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
}, openWebView: { _, _, _, _ in
}, activateAdAction: { _, _, _, _ in
}, adContextAction: { _, _, _ in
}, removeAd: { _ in
}, openRequestedPeerSelection: { _, _, _, _ in
}, saveMediaToFiles: { _ in
}, openNoAdsDemo: {

View File

@ -1782,6 +1782,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}, openWebView: { _, _, _, _ in
}, activateAdAction: { _, _, _, _ in
}, adContextAction: { _, _, _ in
}, removeAd: { _ in
}, openRequestedPeerSelection: { _, _, _, _ in
}, saveMediaToFiles: { _ in
}, openNoAdsDemo: {