Bot ads improvements

This commit is contained in:
Ilya Laktyushin 2024-10-29 06:16:44 +04:00
parent 30e4041149
commit 193699c7fd
6 changed files with 1087 additions and 322 deletions

View File

@ -13138,7 +13138,7 @@ Sorry for the inconvenience.";
"AttachmentMenu.AddDocument" = "Add Document";
"Chat.BotAd.Title" = "Ad";
"Chat.BotAd.Remove" = "remove";
"Chat.BotAd.WhatIsThis" = "what's this?";
"ChatList.Search.TopAppsInfo" = "Which apps are included here? [Learn >]()";

View File

@ -29,6 +29,9 @@ swift_library(
"//submodules/AppBundle",
"//submodules/TelegramStringFormatting",
"//submodules/PresentationDataUtils",
"//submodules/ContextUI",
"//submodules/UndoUI",
"//submodules/TelegramUI/Components/Ads/AdsReportScreen",
],
visibility = [
"//visibility:public",

View File

@ -124,6 +124,39 @@ final class GiftOptionsScreenComponent: Component {
private let tabSelector = ComponentView<Empty>()
private var starsFilter: StarsFilter = .all
private var _effectiveStarGifts: ([StarGift], StarsFilter)?
private var effectiveStarGifts: [StarGift]? {
get {
if case .all = self.starsFilter {
return self.state?.starGifts
} else {
if let (currentGifts, currentFilter) = self._effectiveStarGifts, currentFilter == self.starsFilter {
return currentGifts
} else if let allGifts = self.state?.starGifts {
let filteredGifts: [StarGift] = allGifts.filter {
switch self.starsFilter {
case .all:
return true
case .limited:
if $0.availability != nil {
return true
}
case let .stars(stars):
if $0.price == stars {
return true
}
}
return false
}
self._effectiveStarGifts = (filteredGifts, self.starsFilter)
return filteredGifts
} else {
return nil
}
}
}
}
private var isUpdating: Bool = false
private var starsStateDisposable: Disposable?
@ -243,7 +276,7 @@ final class GiftOptionsScreenComponent: Component {
}
let visibleBounds = self.scrollView.bounds.insetBy(dx: 0.0, dy: -10.0)
if let starGifts = self.state?.starGifts {
if let starGifts = self.effectiveStarGifts {
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
let optionSpacing: CGFloat = 10.0
@ -262,19 +295,6 @@ final class GiftOptionsScreenComponent: Component {
}
if isVisible {
switch self.starsFilter {
case .all:
break
case .limited:
if gift.availability == nil {
continue
}
case let .stars(stars):
if gift.price != stars {
continue
}
}
let itemId = AnyHashable(gift.id)
validIds.append(itemId)
@ -898,7 +918,7 @@ final class GiftOptionsScreenComponent: Component {
contentHeight += tabSelectorSize.height
contentHeight += 19.0
if let starGifts = state.starGifts {
if let starGifts = self.effectiveStarGifts {
self.starsItemsOrigin = contentHeight
let starsOptionSize = CGSize(width: optionWidth, height: 154.0)

View File

@ -198,10 +198,10 @@ final class ChatAdPanelNode: ASDisplayNode {
self.theme = interfaceState.theme
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_Remove, font: Font.regular(11.0), textColor: interfaceState.theme.chat.inputPanel.panelControlAccentColor)
self.removeTextNode.attributedText = NSAttributedString(string: interfaceState.strings.Chat_BotAd_WhatIsThis, font: Font.regular(11.0), textColor: interfaceState.theme.chat.inputPanel.panelControlAccentColor)
}
self.contextContainer.isGestureEnabled = true
self.contextContainer.isGestureEnabled = false
let panelHeight: CGFloat
if let message = interfaceState.adMessage {

View File

@ -3963,189 +3963,22 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.chatDisplayNode.historyNode.adMessagesContext?.markAction(opaqueId: adAttribute.opaqueId, media: media, fullscreen: fullscreen)
self.controllerInteraction?.openUrl(ChatControllerInteraction.OpenUrl(url: adAttribute.url, concealed: false, external: true, progress: progress))
}, adContextAction: { [weak self] message, sourceNode, gesture in
guard let self, let adAttribute = message.adAttribute else {
guard let self else {
return
}
let controllerInteraction = self.controllerInteraction
let chatPresentationInterfaceState = self.presentationInterfaceState
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
var actions: [ContextMenuItem] = []
if adAttribute.sponsorInfo != nil || adAttribute.additionalInfo != nil {
actions.append(.action(ContextMenuActionItem(text: presentationData.strings.Chat_ContextMenu_AdSponsorInfo, textColor: .primary, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Channels"), color: theme.actionSheet.primaryTextColor)
}, iconSource: nil, action: { c, _ in
var subItems: [ContextMenuItem] = []
subItems.append(.action(ContextMenuActionItem(text: presentationData.strings.Common_Back, textColor: .primary, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.actionSheet.primaryTextColor)
}, iconSource: nil, iconPosition: .left, action: { c, _ in
c?.popItems()
})))
subItems.append(.separator)
if let sponsorInfo = adAttribute.sponsorInfo {
subItems.append(.action(ContextMenuActionItem(text: sponsorInfo, textColor: .primary, textLayout: .multiline, textFont: .custom(font: Font.regular(floor(presentationData.listsFontSize.baseDisplaySize * 0.8)), height: nil, verticalOffset: nil), badge: nil, icon: { theme in
return nil
}, iconSource: nil, action: { [weak controllerInteraction] c, _ in
c?.dismiss(completion: {
UIPasteboard.general.string = sponsorInfo
let content: UndoOverlayContent = .copy(text: presentationData.strings.Chat_ContextMenu_AdSponsorInfoCopied)
controllerInteraction?.displayUndo(content)
})
})))
}
if let additionalInfo = adAttribute.additionalInfo {
subItems.append(.action(ContextMenuActionItem(text: additionalInfo, textColor: .primary, textLayout: .multiline, textFont: .custom(font: Font.regular(floor(presentationData.listsFontSize.baseDisplaySize * 0.8)), height: nil, verticalOffset: nil), badge: nil, icon: { theme in
return nil
}, iconSource: nil, action: { [weak controllerInteraction] c, _ in
c?.dismiss(completion: {
UIPasteboard.general.string = additionalInfo
let content: UndoOverlayContent = .copy(text: presentationData.strings.Chat_ContextMenu_AdSponsorInfoCopied)
controllerInteraction?.displayUndo(content)
})
})))
}
c?.pushItems(items: .single(ContextController.Items(content: .list(subItems))))
})))
actions.append(.separator)
}
if adAttribute.canReport {
actions.append(.action(ContextMenuActionItem(text: presentationData.strings.Chat_ContextMenu_AboutAd, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.actionSheet.primaryTextColor)
}, iconSource: nil, action: { [weak self] _, f in
f(.dismissWithoutContent)
var isBot = false
if let self, let user = self.presentationInterfaceState.renderedPeer?.peer as? TelegramUser, user.botInfo != nil {
if let user = self.presentationInterfaceState.renderedPeer?.peer as? TelegramUser, user.botInfo != nil {
isBot = true
}
self?.effectiveNavigationController?.pushViewController(AdsInfoScreen(context: context, mode: isBot ? .bot : .channel))
})))
actions.append(.action(ContextMenuActionItem(text: presentationData.strings.Chat_ContextMenu_ReportAd, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Restrict"), color: theme.actionSheet.primaryTextColor)
}, iconSource: nil, action: { [weak self] _, f in
f(.default)
let _ = (context.engine.messages.reportAdMessage(peerId: message.id.peerId, opaqueId: adAttribute.opaqueId, option: nil)
|> deliverOnMainQueue).start(next: { [weak self] result in
if case let .options(title, options) = result {
self?.effectiveNavigationController?.pushViewController(
AdsReportScreen(
let controller = AdsInfoScreen(
context: context,
peerId: message.id.peerId,
opaqueId: adAttribute.opaqueId,
title: title,
options: options,
completed: { [weak self] in
self?.removeAd(opaqueId: adAttribute.opaqueId)
}
)
mode: isBot ? .bot : .channel,
message: message
)
controller.removeAd = { [weak self] opaqueId in
self?.removeAd(opaqueId: opaqueId)
}
})
})))
actions.append(.separator)
actions.append(.action(ContextMenuActionItem(text: presentationData.strings.Chat_ContextMenu_RemoveAd, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.primaryTextColor)
}, iconSource: nil, action: { [weak controllerInteraction] c, _ in
c?.dismiss(completion: {
controllerInteraction?.openNoAdsDemo()
})
})))
} else {
actions.append(.action(ContextMenuActionItem(text: presentationData.strings.SponsoredMessageMenu_Info, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.actionSheet.primaryTextColor)
}, iconSource: nil, action: { [weak self] _, f in
f(.dismissWithoutContent)
self?.effectiveNavigationController?.pushViewController(AdInfoScreen(context: context, forceDark: true))
})))
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
if !chatPresentationInterfaceState.isPremium && !premiumConfiguration.isPremiumDisabled {
actions.append(.action(ContextMenuActionItem(text: presentationData.strings.SponsoredMessageMenu_Hide, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.primaryTextColor)
}, iconSource: nil, action: { [weak self] c, _ in
c?.dismiss(completion: {
var replaceImpl: ((ViewController) -> Void)?
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .noAds, forceDark: false, action: {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .ads, forceDark: false, dismissed: nil)
replaceImpl?(controller)
}, dismissed: nil)
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
self?.effectiveNavigationController?.pushViewController(controller)
})
})))
}
actions.append(.separator)
if chatPresentationInterfaceState.copyProtectionEnabled {
} else {
actions.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_ContextMenuCopy, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.actionSheet.primaryTextColor)
}, action: { [weak controllerInteraction] _, f in
var messageEntities: [MessageTextEntity]?
var restrictedText: String?
for attribute in message.attributes {
if let attribute = attribute as? TextEntitiesMessageAttribute {
messageEntities = attribute.entities
}
if let attribute = attribute as? RestrictedContentMessageAttribute {
restrictedText = attribute.platformText(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) ?? ""
}
}
if let restrictedText = restrictedText {
storeMessageTextInPasteboard(restrictedText, entities: nil)
} else {
if let translationState = chatPresentationInterfaceState.translationState, translationState.isEnabled,
let translation = message.attributes.first(where: { ($0 as? TranslationMessageAttribute)?.toLang == translationState.toLang }) as? TranslationMessageAttribute, !translation.text.isEmpty {
storeMessageTextInPasteboard(translation.text, entities: translation.entities)
} else {
storeMessageTextInPasteboard(message.text, entities: messageEntities)
}
}
Queue.mainQueue().after(0.2, {
let content: UndoOverlayContent = .copy(text: presentationData.strings.Conversation_MessageCopied)
controllerInteraction?.displayUndo(content)
})
f(.default)
})))
}
if let author = message.author, let addressName = author.addressName {
let link = "https://t.me/\(addressName)"
actions.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_ContextMenuCopyLink, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.actionSheet.primaryTextColor)
}, action: { [weak controllerInteraction] _, f in
UIPasteboard.general.string = link
Queue.mainQueue().after(0.2, {
controllerInteraction?.displayUndo(.linkCopied(text: presentationData.strings.Conversation_LinkCopied))
})
f(.default)
})))
}
}
let contextController = ContextController(presentationData: presentationData, source: .reference(ChatControllerContextReferenceContentSource(controller: self, sourceView: sourceNode.view, insets: .zero, contentInsets: .zero)), items: .single(ContextController.Items(content: .list(actions))), gesture: gesture)
self.presentInGlobalOverlay(contextController)
self.effectiveNavigationController?.pushViewController(controller)
}, openRequestedPeerSelection: { [weak self] messageId, peerType, buttonId, maxQuantity in
guard let self else {
return