Skip to content

Commit

Permalink
Pete/feature/pir freemium entry points ux (#3164)
Browse files Browse the repository at this point in the history
Task/Issue URL: https://app.asana.com/0/0/1208161235633538/f

**Description**: This **PR to the feature branch** includes the
following UX changes relating to Freemium PIR:
  • Loading branch information
aataraxiaa authored Aug 29, 2024
1 parent 8da7069 commit 7520b68
Show file tree
Hide file tree
Showing 23 changed files with 606 additions and 17 deletions.
30 changes: 30 additions & 0 deletions DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -2589,6 +2589,14 @@
C17CA7B32B9B5317008EC3C1 /* MockAutofillPopoverPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17CA7B12B9B5317008EC3C1 /* MockAutofillPopoverPresenter.swift */; };
C18194592C7CA9AB00381092 /* FreemiumPIRFeatureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C18194582C7CA9AB00381092 /* FreemiumPIRFeatureTests.swift */; };
C181945A2C7CA9AB00381092 /* FreemiumPIRFeatureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C18194582C7CA9AB00381092 /* FreemiumPIRFeatureTests.swift */; };
C181945C2C7CDCC700381092 /* PromotionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C181945B2C7CDCC700381092 /* PromotionView.swift */; };
C181945D2C7CDCC700381092 /* PromotionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C181945B2C7CDCC700381092 /* PromotionView.swift */; };
C181945F2C7CDD0E00381092 /* PromotionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C181945E2C7CDD0E00381092 /* PromotionViewModel.swift */; };
C18194602C7CDD0E00381092 /* PromotionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C181945E2C7CDD0E00381092 /* PromotionViewModel.swift */; };
C18194642C7DF7D600381092 /* FreemiumPIRPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C18194632C7DF7D600381092 /* FreemiumPIRPresenter.swift */; };
C18194652C7DF7D600381092 /* FreemiumPIRPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C18194632C7DF7D600381092 /* FreemiumPIRPresenter.swift */; };
C18194672C7F60E500381092 /* FreemiumPIRPresenterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C18194662C7F60E500381092 /* FreemiumPIRPresenterTests.swift */; };
C18194682C7F60E500381092 /* FreemiumPIRPresenterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C18194662C7F60E500381092 /* FreemiumPIRPresenterTests.swift */; };
C1858CD22C7C971D00C9BEAB /* FreemiumPIRFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1858CD12C7C971D00C9BEAB /* FreemiumPIRFeature.swift */; };
C1858CD32C7C971D00C9BEAB /* FreemiumPIRFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1858CD12C7C971D00C9BEAB /* FreemiumPIRFeature.swift */; };
C18BF9CC2C73678500ED6B8A /* Freemium in Frameworks */ = {isa = PBXBuildFile; productRef = C18BF9CB2C73678500ED6B8A /* Freemium */; };
Expand All @@ -2597,6 +2605,8 @@
C18BF9D22C736C9700ED6B8A /* Freemium in Frameworks */ = {isa = PBXBuildFile; productRef = C18BF9D12C736C9700ED6B8A /* Freemium */; };
C1B1CBE12BE1915100B6049C /* DataImportShortcutsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1B1CBE02BE1915100B6049C /* DataImportShortcutsViewModel.swift */; };
C1B1CBE22BE1915100B6049C /* DataImportShortcutsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1B1CBE02BE1915100B6049C /* DataImportShortcutsViewModel.swift */; };
C1C405872C7F80E50089DE8A /* PromotionView+FreemiumPIR.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C405862C7F80E50089DE8A /* PromotionView+FreemiumPIR.swift */; };
C1C405882C7F80E50089DE8A /* PromotionView+FreemiumPIR.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C405862C7F80E50089DE8A /* PromotionView+FreemiumPIR.swift */; };
C1D8BE452C1739E70057E426 /* DataBrokerProtectionMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D8BE442C1739E70057E426 /* DataBrokerProtectionMocks.swift */; };
C1D8BE462C1739EC0057E426 /* DataBrokerProtectionMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D8BE442C1739E70057E426 /* DataBrokerProtectionMocks.swift */; };
C1DAF3B52B9A44860059244F /* AutofillPopoverPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1DAF3B42B9A44860059244F /* AutofillPopoverPresenter.swift */; };
Expand Down Expand Up @@ -4260,8 +4270,13 @@
C17CA7AC2B9B52E6008EC3C1 /* NavigationBarPopoversTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarPopoversTests.swift; sourceTree = "<group>"; };
C17CA7B12B9B5317008EC3C1 /* MockAutofillPopoverPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAutofillPopoverPresenter.swift; sourceTree = "<group>"; };
C18194582C7CA9AB00381092 /* FreemiumPIRFeatureTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FreemiumPIRFeatureTests.swift; sourceTree = "<group>"; };
C181945B2C7CDCC700381092 /* PromotionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromotionView.swift; sourceTree = "<group>"; };
C181945E2C7CDD0E00381092 /* PromotionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromotionViewModel.swift; sourceTree = "<group>"; };
C18194632C7DF7D600381092 /* FreemiumPIRPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FreemiumPIRPresenter.swift; sourceTree = "<group>"; };
C18194662C7F60E500381092 /* FreemiumPIRPresenterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FreemiumPIRPresenterTests.swift; sourceTree = "<group>"; };
C1858CD12C7C971D00C9BEAB /* FreemiumPIRFeature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FreemiumPIRFeature.swift; sourceTree = "<group>"; };
C1B1CBE02BE1915100B6049C /* DataImportShortcutsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataImportShortcutsViewModel.swift; sourceTree = "<group>"; };
C1C405862C7F80E50089DE8A /* PromotionView+FreemiumPIR.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PromotionView+FreemiumPIR.swift"; sourceTree = "<group>"; };
C1D8BE442C1739E70057E426 /* DataBrokerProtectionMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataBrokerProtectionMocks.swift; sourceTree = "<group>"; };
C1DAF3B42B9A44860059244F /* AutofillPopoverPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillPopoverPresenter.swift; sourceTree = "<group>"; };
C1E961E72B879E4D001760E1 /* MockAutofillActionPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAutofillActionPresenter.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -6326,6 +6341,7 @@
56D145EA29E6C99B00E3488A /* DataImportStatusProviding.swift */,
560C3FFE2BCD5A1E00F589CE /* PermanentSurveyManager.swift */,
3768D83A2C24C0A8004120AE /* RemoteMessageViewModel.swift */,
C181945E2C7CDD0E00381092 /* PromotionViewModel.swift */,
);
path = Model;
sourceTree = "<group>";
Expand Down Expand Up @@ -7873,6 +7889,7 @@
85F0FF1227CFAB04001C7C6E /* RecentlyVisitedView.swift */,
3768D8372C24BFF5004120AE /* RemoteMessageView.swift */,
3707EC492C47E36A00B67CBE /* CloseButton.swift */,
C181945B2C7CDCC700381092 /* PromotionView.swift */,
);
path = View;
sourceTree = "<group>";
Expand Down Expand Up @@ -8507,6 +8524,7 @@
isa = PBXGroup;
children = (
C18194582C7CA9AB00381092 /* FreemiumPIRFeatureTests.swift */,
C18194662C7F60E500381092 /* FreemiumPIRPresenterTests.swift */,
);
path = PIR;
sourceTree = "<group>";
Expand All @@ -8523,6 +8541,8 @@
isa = PBXGroup;
children = (
C1858CD12C7C971D00C9BEAB /* FreemiumPIRFeature.swift */,
C18194632C7DF7D600381092 /* FreemiumPIRPresenter.swift */,
C1C405862C7F80E50089DE8A /* PromotionView+FreemiumPIR.swift */,
);
path = PIR;
sourceTree = "<group>";
Expand Down Expand Up @@ -10144,6 +10164,7 @@
3706FB0B293F65D500E42796 /* DefaultBrowserPromptView.swift in Sources */,
3706FB0E293F65D500E42796 /* FaviconManager.swift in Sources */,
3706FB0F293F65D500E42796 /* ChromiumFaviconsReader.swift in Sources */,
C18194652C7DF7D600381092 /* FreemiumPIRPresenter.swift in Sources */,
4B0BD7B82A9FE6E600EF609D /* NetworkProtectionOnboardingMenu.swift in Sources */,
3706FB10293F65D500E42796 /* SuggestionTableRowView.swift in Sources */,
3706FB11293F65D500E42796 /* DownloadsPreferences.swift in Sources */,
Expand Down Expand Up @@ -10307,6 +10328,7 @@
B6BCC5502AFE4F7D002C5499 /* DataImportTypePicker.swift in Sources */,
7BAF9E4B2A8A3CC9002D3B6E /* UserDefaults+NetworkProtectionShared.swift in Sources */,
B6685E3D29A602D90043D2EE /* ExternalAppSchemeHandler.swift in Sources */,
C1C405882C7F80E50089DE8A /* PromotionView+FreemiumPIR.swift in Sources */,
B6E1491029A5C30500AAFBE8 /* ContentBlockingTabExtension.swift in Sources */,
3706FB82293F65D500E42796 /* PasswordManagementNoteItemView.swift in Sources */,
4BF97AD82B43C5B300EB4240 /* NetworkProtectionAppEvents.swift in Sources */,
Expand Down Expand Up @@ -10589,6 +10611,7 @@
B6F1B02F2BCE6B47005E863C /* TunnelControllerProvider.swift in Sources */,
31A83FB62BE28D7D00F74E67 /* UserText+DBP.swift in Sources */,
56BA1E832BAC506F001CF69F /* SSLErrorPageUserScript.swift in Sources */,
C18194602C7CDD0E00381092 /* PromotionViewModel.swift in Sources */,
3706FC31293F65D500E42796 /* PermissionButton.swift in Sources */,
9F6434622BEC82B700D2D8A0 /* AttributionPixelHandler.swift in Sources */,
3706FC32293F65D500E42796 /* MoreOptionsMenu.swift in Sources */,
Expand Down Expand Up @@ -10764,6 +10787,7 @@
3706FCA4293F65D500E42796 /* RecentlyClosedMenu.swift in Sources */,
4B9DB02D2A983B24000927DB /* WaitlistKeychainStorage.swift in Sources */,
EECE10E629DD77E60044D027 /* FeatureFlag.swift in Sources */,
C181945D2C7CDCC700381092 /* PromotionView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -11034,6 +11058,7 @@
3706FE73293F661700E42796 /* PermissionManagerMock.swift in Sources */,
3706FE74293F661700E42796 /* WebsiteDataStoreMock.swift in Sources */,
3706FE75293F661700E42796 /* WebsiteBreakageReportTests.swift in Sources */,
C18194682C7F60E500381092 /* FreemiumPIRPresenterTests.swift in Sources */,
56D145EF29E6DAD900E3488A /* DataImportProviderTests.swift in Sources */,
569277C529DEE09D00B633EF /* ContinueSetUpModelTests.swift in Sources */,
3706FE76293F661700E42796 /* MockSecureVault.swift in Sources */,
Expand Down Expand Up @@ -11642,6 +11667,7 @@
3701C9CE29BD040C00305B15 /* FirefoxBerkeleyDatabaseReader.swift in Sources */,
37054FCE2876472D00033B6F /* WebViewSnapshotView.swift in Sources */,
4BBC16A027C4859400E00A38 /* DeviceAuthenticationService.swift in Sources */,
C181945C2C7CDCC700381092 /* PromotionView.swift in Sources */,
CB24F70C29A3D9CB006DCC58 /* AppConfigurationURLProvider.swift in Sources */,
1DEF3BAD2BD145A9004A2FBA /* AutoClearHandler.swift in Sources */,
37CBCA9A2A8966E60050218F /* SyncSettingsAdapter.swift in Sources */,
Expand Down Expand Up @@ -11745,6 +11771,7 @@
4B723E0D26B0006100E14D75 /* SecureVaultLoginImporter.swift in Sources */,
B645D8F629FA95440024461F /* WKProcessPoolExtension.swift in Sources */,
9D9AE86B2AA76CF90026E7DC /* LoginItemsManager.swift in Sources */,
C18194642C7DF7D600381092 /* FreemiumPIRPresenter.swift in Sources */,
857E5AF52A79045800FC0FB4 /* PixelExperiment.swift in Sources */,
B6C416A7294A4AE500C4F2E7 /* DuckPlayerTabExtension.swift in Sources */,
AA5C1DD5285C780C0089850C /* RecentlyClosedCoordinator.swift in Sources */,
Expand Down Expand Up @@ -12041,6 +12068,7 @@
1DDC84FF2B835BC000670238 /* SearchPreferences.swift in Sources */,
B693955726F04BEC0015B914 /* MouseOverButton.swift in Sources */,
AA61C0D02722159B00E6B681 /* FireInfoViewController.swift in Sources */,
C1C405872C7F80E50089DE8A /* PromotionView+FreemiumPIR.swift in Sources */,
9D9AE8692AA76CDC0026E7DC /* LoginItem+NetworkProtection.swift in Sources */,
9F9C49F92BC7BC970099738D /* BookmarkAllTabsDialogView.swift in Sources */,
B64C85422694590B0048FEBE /* PermissionButton.swift in Sources */,
Expand Down Expand Up @@ -12103,6 +12131,7 @@
85B7184C27677C6500B4277F /* OnboardingViewController.swift in Sources */,
4B379C1E27BDB7FF008A968E /* DeviceAuthenticator.swift in Sources */,
1456D6E124EFCBC300775049 /* TabBarCollectionView.swift in Sources */,
C181945F2C7CDD0E00381092 /* PromotionViewModel.swift in Sources */,
4B4D60BF2A0C848A00BCD287 /* NetworkProtection+ConvenienceInitializers.swift in Sources */,
7B5A23682C468233007213AC /* ExcludedDomainsViewController.swift in Sources */,
BDA7647C2BC497BE00D0400C /* DefaultVPNLocationFormatter.swift in Sources */,
Expand Down Expand Up @@ -12512,6 +12541,7 @@
EEF53E182950CED5002D78F4 /* JSAlertViewModelTests.swift in Sources */,
376C4DB928A1A48A00CC0F5B /* FirePopoverViewModelTests.swift in Sources */,
AAEC74B62642CC6A00C2EFBC /* HistoryStoringMock.swift in Sources */,
C18194672C7F60E500381092 /* FreemiumPIRPresenterTests.swift in Sources */,
AA652CB125DD825B009059CC /* LocalBookmarkStoreTests.swift in Sources */,
B630794226731F5400DCEE41 /* WKDownloadMock.swift in Sources */,
B6C0B24626E9CB190031CB7F /* RunLoopExtensionTests.swift in Sources */,
Expand Down
11 changes: 11 additions & 0 deletions DuckDuckGo/Common/Localizables/UserText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1272,4 +1272,15 @@ struct UserText {
// Comment: "Progress view title when completing the purchase"
static let completingPurchaseTitle = "Completing purchase..."

// Key: "freemium.pir.menu.item"
// Comment: "Title for Freemium Personal Information Removal (Scan-Only) item in the options menu"
static let freemiumPIROptionsMenuItem = "Personal Information Scan"

// Key: "home.page.promotion.freemium.pir.description"
// Comment: "Description for the Freemium PIR Home Page Promotion"
static let homePagePromotionFreemiumPIRDescription = "Find your personal information from sites that store and sell it."

// Key: "home.page.promotion.freemium.pir.button.title"
// Comment: "Title for the Freemium PIR Home Page Promotion Button"
static let homePagePromotionFreemiumPIRButtonTitle = "Scan"
}
2 changes: 2 additions & 0 deletions DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ public struct UserDefaultsWrapper<T> {
case homePageIsContinueSetupVisible = "home.page.is.continue.setup.visible"
case homePageIsRecentActivityVisible = "home.page.is.recent.activity.visible"
case homePageIsFirstSession = "home.page.is.first.session"
case homePagePromotionVisible = "home.page.promotion.visible"
case homePagePromotionDidDismiss = "home.page.promotion.did.dismiss"

case appIsRelaunchingAutomatically = "app-relaunching-automatically"

Expand Down
8 changes: 6 additions & 2 deletions DuckDuckGo/DBP/DBPHomeViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ final class DBPHomeViewController: NSViewController {
private let pixelHandler: EventMapping<DataBrokerProtectionPixels> = DataBrokerProtectionPixelsHandler()
private var currentChildViewController: NSViewController?
private var observer: NSObjectProtocol?
private var freemiumPIRFeature: FreemiumPIRFeature

private let prerequisiteVerifier: DataBrokerPrerequisitesStatusVerifier
private lazy var errorViewController: DataBrokerProtectionErrorViewController = {
Expand Down Expand Up @@ -69,9 +70,12 @@ final class DBPHomeViewController: NSViewController {
})
}()

init(dataBrokerProtectionManager: DataBrokerProtectionManager, prerequisiteVerifier: DataBrokerPrerequisitesStatusVerifier = DefaultDataBrokerPrerequisitesStatusVerifier()) {
init(dataBrokerProtectionManager: DataBrokerProtectionManager,
prerequisiteVerifier: DataBrokerPrerequisitesStatusVerifier = DefaultDataBrokerPrerequisitesStatusVerifier(),
freemiumPIRFeature: FreemiumPIRFeature) {
self.dataBrokerProtectionManager = dataBrokerProtectionManager
self.prerequisiteVerifier = prerequisiteVerifier
self.freemiumPIRFeature = freemiumPIRFeature
super.init(nibName: nil, bundle: nil)
}

Expand All @@ -93,7 +97,7 @@ final class DBPHomeViewController: NSViewController {
override func viewDidAppear() {
super.viewDidAppear()

if !dataBrokerProtectionManager.isUserAuthenticated() {
if !dataBrokerProtectionManager.isUserAuthenticated() && !freemiumPIRFeature.isAvailable {
assertionFailure("This UI should never be presented if the user is not authenticated")
closeUI()
}
Expand Down
53 changes: 53 additions & 0 deletions DuckDuckGo/Freemium/PIR/FreemiumPIRPresenter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// FreemiumPIRPresenter.swift
//
// Copyright © 2024 DuckDuckGo. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Foundation

/// Conforming types provide functionality to show Freemium PIR
protocol FreemiumPIRPresenter {
func showFreemiumPIR(didOnboard: Bool, windowControllerManager: WindowControllersManagerProtocol?)
}

/// Default implementation of `FreemiumPIRPresenter`
struct DefaultFreemiumPIRPresenter: FreemiumPIRPresenter {

@MainActor
/// Displays Freemium PIR
/// If the `didOnboard` parameter is true, opens PIR directly
/// If the `didOnboard` parameter is false, opens Freemium PIR onboarding
/// - Parameter didOnboard: Bool indicating if the user has onboarded already
func showFreemiumPIR(didOnboard: Bool, windowControllerManager: WindowControllersManagerProtocol? = nil) {

let windowControllerManager = windowControllerManager ?? WindowControllersManager.shared

if didOnboard {
windowControllerManager.showTab(with: .dataBrokerProtection)
} else {
// TODO: - Onboard (i.e Ts and Cs)
showFreemiumPIROnboarding()
}
}
}

private extension DefaultFreemiumPIRPresenter {

@MainActor
func showFreemiumPIROnboarding() {
// TODO: - Show onboarding if we decide to do this
}
}
33 changes: 33 additions & 0 deletions DuckDuckGo/Freemium/PIR/PromotionView+FreemiumPIR.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// PromotionView+FreemiumPIR.swift
//
// Copyright © 2024 DuckDuckGo. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Foundation

extension PromotionViewModel {
static func freemiumPIRPromotion(proceedAction: @escaping () -> Void, closeAction: @escaping () -> Void) -> PromotionViewModel {

let description = UserText.homePagePromotionFreemiumPIRDescription
let actionButtonText = UserText.homePagePromotionFreemiumPIRButtonTitle

return PromotionViewModel(image: .gift96,
description: description,
proceedButtonText: actionButtonText,
proceedAction: proceedAction,
closeAction: closeAction)
}
}
Loading

0 comments on commit 7520b68

Please sign in to comment.