Skip to content
This repository has been archived by the owner on May 10, 2024. It is now read-only.

Commit

Permalink
Fix #3872: Adds Brave News Display Ads (#3897)
Browse files Browse the repository at this point in the history
Also bumps the brave-core build to 1.28.104
  • Loading branch information
kylehickinson authored Aug 9, 2021
1 parent 342209a commit 2c055d2
Show file tree
Hide file tree
Showing 14 changed files with 477 additions and 52 deletions.
4 changes: 4 additions & 0 deletions Client.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,7 @@
27F94A3A21909A5900F4FADF /* SearchSuggestionsPromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27F94A3921909A5900F4FADF /* SearchSuggestionsPromptView.swift */; };
27FCA8E02447708300A8CA48 /* FavoritesSectionProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27FCA8DF2447708300A8CA48 /* FavoritesSectionProvider.swift */; };
27FCA8E3244770AB00A8CA48 /* FavoritesOverflowSectionProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27FCA8E2244770AB00A8CA48 /* FavoritesOverflowSectionProvider.swift */; };
27FCFB0C2673F265008E43AB /* AdCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27FCFB0B2673F265008E43AB /* AdCardView.swift */; };
27FD2CAB2146C31C00A5A779 /* RequestDesktopSiteActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27FD2CA12146C31C00A5A779 /* RequestDesktopSiteActivity.swift */; };
27FD2CAC2146C31C00A5A779 /* FindInPageActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27FD2CA92146C31C00A5A779 /* FindInPageActivity.swift */; };
27FD2CAD2146C31C00A5A779 /* AddToFavoritesActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27FD2CAA2146C31C00A5A779 /* AddToFavoritesActivity.swift */; };
Expand Down Expand Up @@ -1898,6 +1899,7 @@
27F94A3921909A5900F4FADF /* SearchSuggestionsPromptView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchSuggestionsPromptView.swift; sourceTree = "<group>"; };
27FCA8DF2447708300A8CA48 /* FavoritesSectionProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesSectionProvider.swift; sourceTree = "<group>"; };
27FCA8E2244770AB00A8CA48 /* FavoritesOverflowSectionProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesOverflowSectionProvider.swift; sourceTree = "<group>"; };
27FCFB0B2673F265008E43AB /* AdCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdCardView.swift; sourceTree = "<group>"; };
27FD2CA12146C31C00A5A779 /* RequestDesktopSiteActivity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestDesktopSiteActivity.swift; sourceTree = "<group>"; };
27FD2CA92146C31C00A5A779 /* FindInPageActivity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FindInPageActivity.swift; sourceTree = "<group>"; };
27FD2CAA2146C31C00A5A779 /* AddToFavoritesActivity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddToFavoritesActivity.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3851,6 +3853,7 @@
27A1AC0824859D0900344503 /* FeedHeadlineViews.swift */,
2719DA08249D37490080AB48 /* SponsorCardView.swift */,
27036EC2256718DA004EF6B6 /* PartnerCardView.swift */,
27FCFB0B2673F265008E43AB /* AdCardView.swift */,
);
path = Cards;
sourceTree = "<group>";
Expand Down Expand Up @@ -7255,6 +7258,7 @@
27C647832550AF34006D72FC /* WalletTransferCompleteView.swift in Sources */,
4422D4B821BFFB7600BF1855 /* histogram.cc in Sources */,
27FD3FBA25C8D20200696156 /* FeedDataSource+RSS.swift in Sources */,
27FCFB0C2673F265008E43AB /* AdCardView.swift in Sources */,
0A0D3D3921A4BD0600BEE65B /* SafeBrowsing.swift in Sources */,
278C6FFF24F6EA3700A246C8 /* ReportBrokenSiteView.swift in Sources */,
27676D472555F34D00BC955A /* BraveRewardsStatusView.swift in Sources */,
Expand Down
2 changes: 1 addition & 1 deletion Client/Application/Shortcuts/ActivityShortcutManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ class ActivityShortcutManager: NSObject {
guard let newTabPageController = bvc.tabManager.selectedTab?.newTabPageViewController else { return }
newTabPageController.scrollToBraveNews()
} else {
let controller = BraveNewsSettingsViewController(dataSource: bvc.feedDataSource)
let controller = BraveNewsSettingsViewController(dataSource: bvc.feedDataSource, rewards: bvc.rewards)
let container = UINavigationController(rootViewController: controller)
bvc.present(container, animated: true)
}
Expand Down
108 changes: 108 additions & 0 deletions Client/Frontend/Brave Today/Cards/AdCardView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Copyright 2021 The Brave Authors. All rights reserved.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

import Foundation

class AdCardView: FeedCardBackgroundButton, FeedCardContent {
var actionHandler: ((Int, FeedItemAction) -> Void)?
var contextMenu: FeedItemMenu?

let feedView = FeedItemView(layout: .ad).then {
$0.thumbnailImageView.contentMode = .scaleAspectFit
}

private let adCalloutView = BraveAdCalloutView()
private var contextMenuDelegate: NSObject?

required init() {
super.init(frame: .zero)

addSubview(feedView)
feedView.snp.makeConstraints {
$0.edges.equalToSuperview()
}

addTarget(self, action: #selector(tappedSelf), for: .touchUpInside)
feedView.callToActionButton.addTarget(self, action: #selector(tappedSelf), for: .touchUpInside)

let contextMenuDelegate = FeedContextMenuDelegate(
performedPreviewAction: { [weak self] in
self?.actionHandler?(0, .opened())
},
menu: { [weak self] in
return self?.contextMenu?.menu?(0)
}
)
addInteraction(UIContextMenuInteraction(delegate: contextMenuDelegate))
self.contextMenuDelegate = contextMenuDelegate

isAccessibilityElement = false
accessibilityElements = [feedView, feedView.callToActionButton]
feedView.accessibilityTraits.insert(.button)
shouldGroupAccessibilityChildren = true

addSubview(adCalloutView)
adCalloutView.snp.makeConstraints {
$0.top.trailing.equalToSuperview().inset(8)
$0.leading.greaterThanOrEqualToSuperview().inset(8)
$0.bottom.lessThanOrEqualToSuperview().inset(8)
}
}

override var accessibilityLabel: String? {
get { feedView.accessibilityLabel }
set { assertionFailure("Accessibility label is inherited from a subview: \(String(describing: newValue)) ignored") }
}

@objc private func tappedSelf() {
actionHandler?(0, .opened())
}
}

private class BraveAdCalloutView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)

backgroundColor = .white
layer.cornerRadius = 4
layer.cornerCurve = .continuous
layer.borderColor = UIColor.braveLighterBlurple.cgColor
layer.borderWidth = 1
layer.masksToBounds = true

let stackView = UIStackView()
stackView.spacing = 3
stackView.alignment = .center
stackView.layoutMargins = .init(equalInset: 5)
stackView.isLayoutMarginsRelativeArrangement = true
stackView.addStackViewItems(
.view(UIImageView(image: UIImage(imageLiteralResourceName: "bat-small")).then {
$0.contentMode = .scaleAspectFit
$0.snp.makeConstraints {
$0.size.equalTo(14)
}
}),
.view(UILabel().then {
$0.text = "Ad"
$0.textColor = .braveBlurple
$0.font = {
let metrics = UIFontMetrics(forTextStyle: .footnote)
let desc = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .footnote)
let font = UIFont.systemFont(ofSize: desc.pointSize, weight: .semibold)
return metrics.scaledFont(for: font)
}()
$0.adjustsFontForContentSizeCategory = true
})
)
addSubview(stackView)
stackView.snp.makeConstraints {
$0.edges.equalToSuperview()
}
}
@available(*, unavailable)
required init(coder: NSCoder) {
fatalError()
}
}
9 changes: 9 additions & 0 deletions Client/Frontend/Brave Today/Composer/FeedCard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

import Foundation
import BraveCore

/// A set of 2 items
struct FeedPair: Equatable {
Expand All @@ -26,6 +27,8 @@ enum FeedCard: Equatable {
case deals(_ feeds: [FeedItem], title: String)
/// A brave partner item
case partner(_ feed: FeedItem)
/// A brave display ad card
case ad(InlineContentAd)
/// A single item displayed prompinently with an image
case headline(_ feed: FeedItem)
/// A pair of `headline` items that should be displayed side by side horizontally with equal sizes
Expand All @@ -44,6 +47,8 @@ enum FeedCard: Equatable {
return FeedItemView.Layout.brandedHeadline.estimatedHeight(for: width)
case .partner:
return FeedItemView.Layout.partner.estimatedHeight(for: width)
case .ad:
return FeedItemView.Layout.ad.estimatedHeight(for: width)
case .headlinePair:
return 300
case .group, .numbered, .deals:
Expand All @@ -60,6 +65,8 @@ enum FeedCard: Equatable {
return [pair.first, pair.second]
case .group(let items, _, _, _), .numbered(let items, _), .deals(let items, _):
return items
case .ad:
return []
}
}

Expand Down Expand Up @@ -99,6 +106,8 @@ enum FeedCard: Equatable {
return self
case .partner:
return .partner(replacementItem)
case .ad(let ad):
return .ad(ad)
}
}
}
55 changes: 44 additions & 11 deletions Client/Frontend/Brave Today/Composer/FeedDataSource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Data
import Shared
import BraveShared
import FeedKit
import BraveCore

// Named `logger` because we are using math function `log`
private let logger = Logger.browserLogger
Expand Down Expand Up @@ -53,6 +54,9 @@ class FeedDataSource {
private(set) var sources: [FeedItem.Source] = []
private var items: [FeedItem.Content] = []

/// An ads object to handle inserting Inline Content Ads within the Brave News sequence
var rewards: BraveRewards?

/// Add a closure that will execute when `state` is changed.
///
/// Executes the closure on the main queue by default
Expand Down Expand Up @@ -596,17 +600,18 @@ class FeedDataSource {
category: \.content.offersCategory
)

rewards?.ads.purgeOrphanedAdEvents(.inlineContentAd)
var contentAdsQueryFailed = false

let rules: [FeedSequenceElement] = [
.sponsor,
.fillUsing(FilteredFillStrategy(isIncluded: { $0.source.category == Self.topNewsCategory }), [
.headline(paired: false)
]),
.fillUsing(dealsCategoryFillStrategy, [
.deals
]),
.braveAd,
.repeating([
.repeating([.headline(paired: false)], times: 2),
.repeating([.headline(paired: true)], times: 2),
.headline(paired: true),
.partner,
.fillUsing(
CategoryFillStrategy(
Expand All @@ -617,19 +622,22 @@ class FeedDataSource {
.categoryGroup,
]
),
.headline(paired: false),
.repeating([.headline(paired: false)], times: 2),
.repeating([.headline(paired: true)], times: 2),
.braveAd,
.repeating([.headline(paired: false)], times: 2),
.brandedGroup(numbered: true),
.group,
.fillUsing(RandomizedFillStrategy(isIncluded: { Date().timeIntervalSince($0.content.publishTime) < 48.hours }), [
.headline(paired: false)
]),
.fillUsing(dealsCategoryFillStrategy, [
.deals
]),
.headline(paired: false),
.headline(paired: true),
.brandedGroup(numbered: true),
.group,
.fillUsing(RandomizedFillStrategy(isIncluded: { Date().timeIntervalSince($0.content.publishTime) < 48.hours }), [
.headline(paired: false),
.headline(paired: true),
.headline(paired: false),
])
]),
])
]

Expand All @@ -651,6 +659,29 @@ class FeedDataSource {
return fillStrategy.next(from: &partners, where: imageExists).map {
[.partner($0)]
}
case .braveAd:
// If we fail to obtain inline content ads during a card gen it can be assumed that
// all further calls will fail since cards are generated all at once
guard !contentAdsQueryFailed, let rewards = rewards else { return nil }
let group = DispatchGroup()
group.enter()
var contentAd: InlineContentAd?
DispatchQueue.main.async {
rewards.ads.inlineContentAds(dimensions: "900x750", completion: { success, dimensions, ad in
if success {
contentAd = ad
} else {
contentAdsQueryFailed = true
logger.debug("Inline content ads could not be filled; Skipping for the rest of this feed generation")
}
group.leave()
})
}
let result = group.wait(timeout: .now() + .seconds(1))
if result == .success, let ad = contentAd {
return [.ad(ad)]
}
return nil
case .headline(let paired):
if articles.isEmpty { return nil }
let imageExists = { (item: FeedItem) -> Bool in
Expand Down Expand Up @@ -738,6 +769,8 @@ extension FeedDataSource {
case sponsor
/// Display a headline from a list of partnered items
case partner
/// Displays a Brave ad from the ads catalog
case braveAd
/// Displays a horizontal list of deals with the content type of `brave_offers`
case deals
/// Displays an `article` type item in a headline card. Can also be displayed as two (smaller) paired
Expand Down
Loading

0 comments on commit 2c055d2

Please sign in to comment.