diff --git a/Sources/Brave/Frontend/Browser/BrowserViewController/BrowserViewController+Callout.swift b/Sources/Brave/Frontend/Browser/BrowserViewController/BrowserViewController+Callout.swift index b5017195e91..eb0e68be1c5 100644 --- a/Sources/Brave/Frontend/Browser/BrowserViewController/BrowserViewController+Callout.swift +++ b/Sources/Brave/Frontend/Browser/BrowserViewController/BrowserViewController+Callout.swift @@ -53,6 +53,8 @@ extension BrowserViewController { presentDefaultBrowserScreenCallout() case .rewards: presentBraveRewardsScreenCallout(skipSafeGuards: skipSafeGuards) + case .blockCookieConsentNotices: + presentCookieNotificationBlockingCallout(skipSafeGuards: skipSafeGuards) case .vpnPromotion: presentVPNPromotionCallout(skipSafeGuards: skipSafeGuards) case .vpnLinkReceipt: @@ -181,6 +183,25 @@ extension BrowserViewController { present(controller, animated: true) } + private func presentCookieNotificationBlockingCallout(skipSafeGuards: Bool = false) { + if !skipSafeGuards { + // Show Cookie Block Callout if setting is enabled and on second launch + // After Basic onboarding is shown + guard FilterListStorage.shared.isEnabled(for: FilterList.cookieConsentNoticesComponentID), + Preferences.FullScreenCallout.omniboxCalloutCompleted.value else { + return + } + } + + let popover = PopoverController( + contentController: CookieNotificationBlockingConsentViewController(), + contentSizeBehavior: .preferredContentSize) + popover.addsConvenientDismissalMargins = false + + isOnboardingOrFullScreenCalloutPresented = true + popover.present(from: topToolbar.locationView.shieldsButton, on: self) + } + private func presentVPNPromotionCallout(skipSafeGuards: Bool = false) { if !skipSafeGuards { // Onboarding should be completed to show callouts diff --git a/Sources/Brave/Frontend/Settings/Debug/RetentionPreferencesDebugMenuViewController.swift b/Sources/Brave/Frontend/Settings/Debug/RetentionPreferencesDebugMenuViewController.swift index 7ebbea1d8c6..1b1992a35ab 100644 --- a/Sources/Brave/Frontend/Settings/Debug/RetentionPreferencesDebugMenuViewController.swift +++ b/Sources/Brave/Frontend/Settings/Debug/RetentionPreferencesDebugMenuViewController.swift @@ -159,6 +159,14 @@ class RetentionPreferencesDebugMenuViewController: TableViewController { } }, cellReuseId: "DefaultBrowserCalloutCell"), + .boolRow( + title: "Cookie consent notice blocking callout shown", + detailText: "Flag determining if callout to show the cookie consent blocking callout was shown", + toggleValue: Preferences.FullScreenCallout.blockCookieConsentNoticesCalloutCompleted.value, + valueChange: { + Preferences.FullScreenCallout.blockCookieConsentNoticesCalloutCompleted.value = $0 + }, + cellReuseId: "CookieConsentNoticeBlockingCalloutCell"), ], footer: .title("These are the preferences that stored in preferences for determining the If certain elements are shown to user.") ) diff --git a/Sources/BraveStrings/BraveStrings.swift b/Sources/BraveStrings/BraveStrings.swift index f61e67ab615..8d179b32f42 100644 --- a/Sources/BraveStrings/BraveStrings.swift +++ b/Sources/BraveStrings/BraveStrings.swift @@ -4724,12 +4724,36 @@ extension Strings { // MARK: - Cookie consent notices extension Strings { - /// A title for a popup that notifies the user that we can block cookie consent notices + /// A title for a setting that enables cookie consent notices public static let blockCookieConsentNotices = NSLocalizedString( "BlockCookieConsentNotices", tableName: "BraveShared", bundle: .module, value: "Block Cookie Consent Notices", - comment: "A title for a toggle that allows user to block cookie consent notices" + comment: "A title for a setting that enables cookie consent notices" + ) + + /// A title for a popup that informs the user that brave is blocking cookie consent notices + public static let blockCookieConsentNoticesPopupTitle = NSLocalizedString( + "BlockCookieConsentNoticesPopupTitle", + tableName: "BraveShared", bundle: .module, + value: "Brave is blocking cookie consent notices", + comment: "A title for a popup that informs the user that brave is blocking cookie consent notices" + ) + + /// A title for a popup that informs the user that brave is blocking cookie consent notices + public static let blockCookieConsentNoticesPopupDescription = NSLocalizedString( + "BlockCookieConsentNoticesPopupDescription", + tableName: "BraveShared", bundle: .module, + value: "Brave already blocks harmful cookies. Now, we automatically block those annoying cookie consent notices, too.", + comment: "A description of why we are blocking cookie consent notices found on the cookie consent blocking popup" + ) + + /// A title for a button that dismisses the popup informing a use that brave is blocking cookie consent notices + public static let blockCookieConsentNoticesDismissButtonTitle = NSLocalizedString( + "BlockCookieConsentNoticesDismissButtonTitle", + tableName: "BraveShared", bundle: .module, + value: "Got it", + comment: "A title for a button that dismisses the popup informing a use that brave is blocking cookie consent notices" ) } diff --git a/Sources/Onboarding/Assets/Images.xcassets/CookieConsent/Contents.json b/Sources/Onboarding/Assets/Images.xcassets/CookieConsent/Contents.json new file mode 100644 index 00000000000..73c00596a7f --- /dev/null +++ b/Sources/Onboarding/Assets/Images.xcassets/CookieConsent/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Onboarding/Assets/Images.xcassets/CookieConsent/cookie-consent-background.imageset/Background@2x-1.png b/Sources/Onboarding/Assets/Images.xcassets/CookieConsent/cookie-consent-background.imageset/Background@2x-1.png new file mode 100644 index 00000000000..5ed325db209 Binary files /dev/null and b/Sources/Onboarding/Assets/Images.xcassets/CookieConsent/cookie-consent-background.imageset/Background@2x-1.png differ diff --git a/Sources/Onboarding/Assets/Images.xcassets/CookieConsent/cookie-consent-background.imageset/Background@2x.png b/Sources/Onboarding/Assets/Images.xcassets/CookieConsent/cookie-consent-background.imageset/Background@2x.png new file mode 100644 index 00000000000..679762f8675 Binary files /dev/null and b/Sources/Onboarding/Assets/Images.xcassets/CookieConsent/cookie-consent-background.imageset/Background@2x.png differ diff --git a/Sources/Onboarding/Assets/Images.xcassets/CookieConsent/cookie-consent-background.imageset/Background@3x-1.png b/Sources/Onboarding/Assets/Images.xcassets/CookieConsent/cookie-consent-background.imageset/Background@3x-1.png new file mode 100644 index 00000000000..47b0d7296ce Binary files /dev/null and b/Sources/Onboarding/Assets/Images.xcassets/CookieConsent/cookie-consent-background.imageset/Background@3x-1.png differ diff --git a/Sources/Onboarding/Assets/Images.xcassets/CookieConsent/cookie-consent-background.imageset/Background@3x.png b/Sources/Onboarding/Assets/Images.xcassets/CookieConsent/cookie-consent-background.imageset/Background@3x.png new file mode 100644 index 00000000000..8e702d47ec2 Binary files /dev/null and b/Sources/Onboarding/Assets/Images.xcassets/CookieConsent/cookie-consent-background.imageset/Background@3x.png differ diff --git a/Sources/Onboarding/Assets/Images.xcassets/CookieConsent/cookie-consent-background.imageset/Contents.json b/Sources/Onboarding/Assets/Images.xcassets/CookieConsent/cookie-consent-background.imageset/Contents.json new file mode 100644 index 00000000000..6a3731a6ffb --- /dev/null +++ b/Sources/Onboarding/Assets/Images.xcassets/CookieConsent/cookie-consent-background.imageset/Contents.json @@ -0,0 +1,57 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Background@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "Background@2x-1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Background@3x.png", + "idiom" : "universal", + "scale" : "3x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "Background@3x-1.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/Sources/Onboarding/Assets/Images.xcassets/CookieConsent/cookie-consent-icon.imageset/Contents.json b/Sources/Onboarding/Assets/Images.xcassets/CookieConsent/cookie-consent-icon.imageset/Contents.json new file mode 100644 index 00000000000..5026122ca9f --- /dev/null +++ b/Sources/Onboarding/Assets/Images.xcassets/CookieConsent/cookie-consent-icon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "cookie.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Sources/Onboarding/Assets/Images.xcassets/CookieConsent/cookie-consent-icon.imageset/cookie.pdf b/Sources/Onboarding/Assets/Images.xcassets/CookieConsent/cookie-consent-icon.imageset/cookie.pdf new file mode 100644 index 00000000000..4f11d9b7262 Binary files /dev/null and b/Sources/Onboarding/Assets/Images.xcassets/CookieConsent/cookie-consent-icon.imageset/cookie.pdf differ diff --git a/Sources/Onboarding/OnboardingPreferences.swift b/Sources/Onboarding/OnboardingPreferences.swift index a8fb0f3e725..69b27e9741e 100644 --- a/Sources/Onboarding/OnboardingPreferences.swift +++ b/Sources/Onboarding/OnboardingPreferences.swift @@ -39,6 +39,11 @@ extension Preferences { extension Preferences { public final class FullScreenCallout { + /// Whether the block cookie consent notices callout is shown. + public static let blockCookieConsentNoticesCalloutCompleted = Option( + key: "fullScreenCallout.full-screen-cookie-consent-notices-callout-completed", + default: false) + /// Whether the bottom bar callout is shown. public static let bottomBarCalloutCompleted = Option( key: "fullScreenCallout.full-screen-bottom-bar-callout-completed", diff --git a/Sources/Onboarding/ProductNotifications/CookieNotificationBlockingConsentView.swift b/Sources/Onboarding/ProductNotifications/CookieNotificationBlockingConsentView.swift new file mode 100644 index 00000000000..09c21194930 --- /dev/null +++ b/Sources/Onboarding/ProductNotifications/CookieNotificationBlockingConsentView.swift @@ -0,0 +1,91 @@ +// Copyright 2023 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 https://mozilla.org/MPL/2.0/. + +import SwiftUI +import Strings +import DesignSystem +import BraveShared +import Growth +import BraveUI + +public struct CookieNotificationBlockingConsentView: View { + public static let contentHeight = 400.0 + public static let contentWidth = 344.0 + private static let textPadding = 24.0 + @Environment(\.presentationMode) @Binding private var presentationMode + + public var body: some View { + ScrollView { + VStack(alignment: .center, spacing: 48) { + VStack(alignment: .center, spacing: Self.textPadding) { + Image("cookie-consent-icon", bundle: .module) + .resizable() + .frame(width: 48, height: 48) + + Text(Strings.blockCookieConsentNoticesPopupTitle).font(.title) + Text(Strings.blockCookieConsentNoticesPopupDescription).font(.body) + } + .padding(Self.textPadding) + .foregroundColor(Color(UIColor.braveLabel)) + .multilineTextAlignment(.center) + .fixedSize(horizontal: false, vertical: true) + + Button(Strings.blockCookieConsentNoticesDismissButtonTitle, + action: { + presentationMode.dismiss() + } + ) + .buttonStyle(BraveFilledButtonStyle(size: .large)) + .multilineTextAlignment(.center) + } + } + .frame(width: Self.contentWidth, height: Self.contentHeight) + .background( + Image("cookie-consent-background", bundle: .module), + alignment: .bottomLeading + ) + .background(Color(UIColor.braveBackground)) + .onAppear { + recordCookieListPromptP3A(answer: .seen) + } + } + + private enum P3AAnswer: Int, CaseIterable { + case seen = 1 + } + + private func recordCookieListPromptP3A(answer: P3AAnswer) { + // Q68 If you have viewed the cookie consent block prompt, how did you react? + UmaHistogramEnumeration("Brave.Shields.CookieListPrompt", sample: answer) + } +} + +#if DEBUG +struct CookieNotificationBlockingConsentView_Previews: PreviewProvider { + static var previews: some View { + CookieNotificationBlockingConsentView() + } +} +#endif + +public class CookieNotificationBlockingConsentViewController: UIHostingController, PopoverContentComponent { + public init() { + super.init(rootView: CookieNotificationBlockingConsentView()) + + self.preferredContentSize = CGSize( + width: CookieNotificationBlockingConsentView.contentWidth, + height: CookieNotificationBlockingConsentView.contentHeight + ) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = UIColor.braveBackground + } +} diff --git a/Sources/Onboarding/ProductNotifications/FullScreenCalloutManager.swift b/Sources/Onboarding/ProductNotifications/FullScreenCalloutManager.swift index c49f64f57e7..d2b84f5cfbc 100644 --- a/Sources/Onboarding/ProductNotifications/FullScreenCalloutManager.swift +++ b/Sources/Onboarding/ProductNotifications/FullScreenCalloutManager.swift @@ -22,7 +22,7 @@ public enum FullScreenCalloutType: CaseIterable { - Cookie Notification - VPN Link Receipt */ - case p3a, vpnUpdateBilling, bottomBar, vpnPromotion, defaultBrowser, rewards, vpnLinkReceipt + case p3a, vpnUpdateBilling, bottomBar, vpnPromotion, defaultBrowser, rewards, blockCookieConsentNotices, vpnLinkReceipt /// The number of days passed to show certain type of callout var period: Int { @@ -33,6 +33,7 @@ public enum FullScreenCalloutType: CaseIterable { case .vpnPromotion: return 4 case .defaultBrowser: return 10 case .rewards: return 8 + case .blockCookieConsentNotices: return 0 case .vpnLinkReceipt: return 0 } } @@ -52,6 +53,8 @@ public enum FullScreenCalloutType: CaseIterable { return Preferences.DefaultBrowserIntro.completed case .rewards: return Preferences.FullScreenCallout.rewardsCalloutCompleted + case .blockCookieConsentNotices: + return Preferences.FullScreenCallout.blockCookieConsentNoticesCalloutCompleted case .vpnLinkReceipt: return Preferences.Onboarding.vpnLinkReceiptShown }