mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Bot ad improvements
This commit is contained in:
parent
11d7bb1ab8
commit
ec9d5fde63
@ -149,6 +149,7 @@ public final class BrowserBookmarksScreen: ViewController {
|
||||
}, openWebView: { _, _, _, _ in
|
||||
}, activateAdAction: { _, _, _, _ in
|
||||
}, adContextAction: { _, _, _ in
|
||||
}, removeAd: { _ in
|
||||
}, openRequestedPeerSelection: { _, _, _, _ in
|
||||
}, saveMediaToFiles: { _ in
|
||||
}, openNoAdsDemo: {
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -618,6 +618,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
}, openWebView: { _, _, _, _ in
|
||||
}, activateAdAction: { _, _, _, _ in
|
||||
}, adContextAction: { _, _, _ in
|
||||
}, removeAd: { _ in
|
||||
}, openRequestedPeerSelection: { _, _, _, _ in
|
||||
}, saveMediaToFiles: { _ in
|
||||
}, openNoAdsDemo: {
|
||||
|
@ -475,6 +475,7 @@ public final class ChatSendGroupMediaMessageContextPreview: UIView, ChatSendMess
|
||||
}, openWebView: { _, _, _, _ in
|
||||
}, activateAdAction: { _, _, _, _ in
|
||||
}, adContextAction: { _, _, _ in
|
||||
}, removeAd: { _ in
|
||||
}, openRequestedPeerSelection: { _, _, _, _ in
|
||||
}, saveMediaToFiles: { _ in
|
||||
}, openNoAdsDemo: {
|
||||
|
@ -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
|
||||
|
@ -3499,6 +3499,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
}, openWebView: { _, _, _, _ in
|
||||
}, activateAdAction: { _, _, _, _ in
|
||||
}, adContextAction: { _, _, _ in
|
||||
}, removeAd: { _ in
|
||||
}, openRequestedPeerSelection: { _, _, _, _ in
|
||||
}, saveMediaToFiles: { _ in
|
||||
}, openNoAdsDemo: {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -167,6 +167,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
||||
}, openWebView: { _, _, _, _ in
|
||||
}, activateAdAction: { _, _, _, _ in
|
||||
}, adContextAction: { _, _, _ in
|
||||
}, removeAd: { _ in
|
||||
}, openRequestedPeerSelection: { _, _, _, _ in
|
||||
}, saveMediaToFiles: { _ in
|
||||
}, openNoAdsDemo: {
|
||||
|
@ -1782,6 +1782,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
}, openWebView: { _, _, _, _ in
|
||||
}, activateAdAction: { _, _, _, _ in
|
||||
}, adContextAction: { _, _, _ in
|
||||
}, removeAd: { _ in
|
||||
}, openRequestedPeerSelection: { _, _, _, _ in
|
||||
}, saveMediaToFiles: { _ in
|
||||
}, openNoAdsDemo: {
|
||||
|
Loading…
x
Reference in New Issue
Block a user