From 5499e1e8d5cec9198b92db55672e6fe4a0cbb2fc Mon Sep 17 00:00:00 2001 From: Fernando Bunn Date: Fri, 19 Apr 2024 21:42:37 +0100 Subject: [PATCH 01/14] WIP: Handle bad permissions and path for DBP --- DuckDuckGo.xcodeproj/project.pbxproj | 26 +++ DuckDuckGo/DBP/DBPHomeViewController.swift | 181 ++++++++++-------- ...ataBrokerPrerequisitesStatusVerifier.swift | 49 +++++ .../DataBrokerProtectionPixelsHandler.swift | 97 ++++++++++ ...aBrokerProtectionErrorViewController.swift | 71 +++++++ 5 files changed, 340 insertions(+), 84 deletions(-) create mode 100644 DuckDuckGo/DBP/DataBrokerPrerequisitesStatusVerifier.swift create mode 100644 DuckDuckGo/DBP/DataBrokerProtectionPixelsHandler.swift create mode 100644 DuckDuckGo/DBP/ErrorView/DataBrokerProtectionErrorViewController.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 36eb9784e0..248686b6dd 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -166,6 +166,12 @@ 315AA07028CA5CC800200030 /* YoutubePlayerNavigationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 315AA06F28CA5CC800200030 /* YoutubePlayerNavigationHandler.swift */; }; 3168506D2AF3AD1D009A2828 /* WaitlistViewControllerPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3168506C2AF3AD1C009A2828 /* WaitlistViewControllerPresenter.swift */; }; 3168506E2AF3AD1D009A2828 /* WaitlistViewControllerPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3168506C2AF3AD1C009A2828 /* WaitlistViewControllerPresenter.swift */; }; + 316913232BD2B6250051B46D /* DataBrokerProtectionPixelsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 316913222BD2B6250051B46D /* DataBrokerProtectionPixelsHandler.swift */; }; + 316913242BD2B6250051B46D /* DataBrokerProtectionPixelsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 316913222BD2B6250051B46D /* DataBrokerProtectionPixelsHandler.swift */; }; + 316913262BD2B76F0051B46D /* DataBrokerPrerequisitesStatusVerifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 316913252BD2B76F0051B46D /* DataBrokerPrerequisitesStatusVerifier.swift */; }; + 316913272BD2B76F0051B46D /* DataBrokerPrerequisitesStatusVerifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 316913252BD2B76F0051B46D /* DataBrokerPrerequisitesStatusVerifier.swift */; }; + 316913292BD2C7570051B46D /* DataBrokerProtectionErrorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 316913282BD2C7570051B46D /* DataBrokerProtectionErrorViewController.swift */; }; + 3169132A2BD2C7570051B46D /* DataBrokerProtectionErrorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 316913282BD2C7570051B46D /* DataBrokerProtectionErrorViewController.swift */; }; 3171D6B82889849F0068632A /* CookieManagedNotificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3171D6B72889849F0068632A /* CookieManagedNotificationView.swift */; }; 3171D6BA288984D00068632A /* BadgeAnimationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3171D6B9288984D00068632A /* BadgeAnimationView.swift */; }; 3171D6DB2889B64D0068632A /* CookieManagedNotificationContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3171D6DA2889B64D0068632A /* CookieManagedNotificationContainerView.swift */; }; @@ -2781,6 +2787,9 @@ 315AA06F28CA5CC800200030 /* YoutubePlayerNavigationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YoutubePlayerNavigationHandler.swift; sourceTree = ""; }; 3168506C2AF3AD1C009A2828 /* WaitlistViewControllerPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WaitlistViewControllerPresenter.swift; sourceTree = ""; }; 316850712AF3AD58009A2828 /* DataBrokerProtectionDebugMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataBrokerProtectionDebugMenu.swift; sourceTree = ""; }; + 316913222BD2B6250051B46D /* DataBrokerProtectionPixelsHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataBrokerProtectionPixelsHandler.swift; sourceTree = ""; }; + 316913252BD2B76F0051B46D /* DataBrokerPrerequisitesStatusVerifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataBrokerPrerequisitesStatusVerifier.swift; sourceTree = ""; }; + 316913282BD2C7570051B46D /* DataBrokerProtectionErrorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataBrokerProtectionErrorViewController.swift; sourceTree = ""; }; 3171D6B72889849F0068632A /* CookieManagedNotificationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CookieManagedNotificationView.swift; sourceTree = ""; }; 3171D6B9288984D00068632A /* BadgeAnimationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BadgeAnimationView.swift; sourceTree = ""; }; 3171D6DA2889B64D0068632A /* CookieManagedNotificationContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CookieManagedNotificationContainerView.swift; sourceTree = ""; }; @@ -4405,6 +4414,14 @@ path = Subscription; sourceTree = ""; }; + 3169132B2BD2C7960051B46D /* ErrorView */ = { + isa = PBXGroup; + children = ( + 316913282BD2C7570051B46D /* DataBrokerProtectionErrorViewController.swift */, + ); + path = ErrorView; + sourceTree = ""; + }; 3171D6DC2889B6700068632A /* CookieManaged */ = { isa = PBXGroup; children = ( @@ -4427,6 +4444,8 @@ 3192EC862A4DCF0E001E97A5 /* DBP */ = { isa = PBXGroup; children = ( + 3169132B2BD2C7960051B46D /* ErrorView */, + 316913222BD2B6250051B46D /* DataBrokerProtectionPixelsHandler.swift */, 316850712AF3AD58009A2828 /* DataBrokerProtectionDebugMenu.swift */, 3192EC872A4DCF21001E97A5 /* DBPHomeViewController.swift */, 3139A1512AA4B3C000969C7D /* DataBrokerProtectionManager.swift */, @@ -4439,6 +4458,7 @@ BBDFDC592B2B8A0900F62D90 /* DataBrokerProtectionExternalWaitlistPixels.swift */, BB5789712B2CA70F0009DFE2 /* DataBrokerProtectionSubscriptionEventHandler.swift */, 4B37EE652B4CFC9500A89A61 /* RemoteMessaging */, + 316913252BD2B76F0051B46D /* DataBrokerPrerequisitesStatusVerifier.swift */, ); path = DBP; sourceTree = ""; @@ -9615,6 +9635,7 @@ B66260E129AC6EBD00E9E3EE /* HistoryTabExtension.swift in Sources */, 3706FB8E293F65D500E42796 /* FirefoxEncryptionKeyReader.swift in Sources */, 3706FB8F293F65D500E42796 /* BookmarkManagementSplitViewController.swift in Sources */, + 316913272BD2B76F0051B46D /* DataBrokerPrerequisitesStatusVerifier.swift in Sources */, 3706FB90293F65D500E42796 /* CookieManagedNotificationContainerView.swift in Sources */, 3706FB91293F65D500E42796 /* FileManagerExtension.swift in Sources */, 3706FB92293F65D500E42796 /* PermissionModel.swift in Sources */, @@ -9649,6 +9670,7 @@ 3706FBA2293F65D500E42796 /* GeolocationService.swift in Sources */, 4B4D60C42A0C849600BCD287 /* NetworkProtectionInvitePresenter.swift in Sources */, 3706FBA3293F65D500E42796 /* FireproofingURLExtensions.swift in Sources */, + 3169132A2BD2C7570051B46D /* DataBrokerProtectionErrorViewController.swift in Sources */, 1DDD3EC12B84F5D5004CBF2B /* PreferencesCookiePopupProtectionView.swift in Sources */, 3706FBA4293F65D500E42796 /* ContentOverlayPopover.swift in Sources */, 3706FBA5293F65D500E42796 /* TabShadowView.swift in Sources */, @@ -9950,6 +9972,7 @@ 7BFE955A2A9DF4550081ABE9 /* NetworkProtectionWaitlistFeatureFlagOverridesMenu.swift in Sources */, 9FDA6C222B79A59D00E099A9 /* BookmarkFavoriteView.swift in Sources */, C1372EF52BBC5BAD003F8793 /* SecureTextField.swift in Sources */, + 316913242BD2B6250051B46D /* DataBrokerProtectionPixelsHandler.swift in Sources */, 3706FC77293F65D500E42796 /* PageObserverUserScript.swift in Sources */, 4BF0E5132AD25A2600FFEC9E /* DuckDuckGoUserAgent.swift in Sources */, 3706FC78293F65D500E42796 /* SecureVaultErrorReporter.swift in Sources */, @@ -10582,6 +10605,7 @@ AAC30A2E268F1EE300D2D9CD /* CrashReportPromptPresenter.swift in Sources */, 1D2DC00629016798008083A1 /* BWCredential.swift in Sources */, EEA3EEB12B24EBD000E8333A /* NetworkProtectionVPNCountryLabelsModel.swift in Sources */, + 316913232BD2B6250051B46D /* DataBrokerProtectionPixelsHandler.swift in Sources */, 37AFCE8727DA334800471A10 /* PreferencesRootView.swift in Sources */, B684590825C9027900DC17B6 /* AppStateChangedPublisher.swift in Sources */, 4B92928F26670D1700AD2C21 /* BookmarkTableCellView.swift in Sources */, @@ -10719,6 +10743,7 @@ 4B379C2227BDBA29008A968E /* LocalAuthenticationService.swift in Sources */, 37CEFCA92A6737A2001EF741 /* CredentialsCleanupErrorHandling.swift in Sources */, 4BB99D0326FE191E001E4761 /* SafariBookmarksReader.swift in Sources */, + 316913292BD2C7570051B46D /* DataBrokerProtectionErrorViewController.swift in Sources */, 1DA6D0FD2A1FF9A100540406 /* HTTPCookie.swift in Sources */, AACF6FD626BC366D00CF09F9 /* SafariVersionReader.swift in Sources */, 4BE65485271FCD7B008D1D63 /* LoginFaviconView.swift in Sources */, @@ -10999,6 +11024,7 @@ 85D885B326A5A9DE0077C374 /* NSAlert+PasswordManager.swift in Sources */, 983DFB2528B67036006B7E34 /* UserContentUpdating.swift in Sources */, 1D9A4E5A2B43213B00F449E2 /* TabSnapshotExtension.swift in Sources */, + 316913262BD2B76F0051B46D /* DataBrokerPrerequisitesStatusVerifier.swift in Sources */, 4B7A57CF279A4EF300B1C70E /* ChromiumPreferences.swift in Sources */, AA6AD95B2704B6DB00159F8A /* FirePopoverViewController.swift in Sources */, 4BE4005327CF3DC3007D3161 /* SavePaymentMethodPopover.swift in Sources */, diff --git a/DuckDuckGo/DBP/DBPHomeViewController.swift b/DuckDuckGo/DBP/DBPHomeViewController.swift index 30f4a4c0ce..56bb464867 100644 --- a/DuckDuckGo/DBP/DBPHomeViewController.swift +++ b/DuckDuckGo/DBP/DBPHomeViewController.swift @@ -16,8 +16,6 @@ // limitations under the License. // -#if DBP - import Foundation import DataBrokerProtection import AppKit @@ -25,6 +23,7 @@ import Common import SwiftUI import BrowserServicesKit import PixelKit +import Combine public extension Notification.Name { static let dbpDidClose = Notification.Name("com.duckduckgo.DBP.DBPDidClose") @@ -34,8 +33,15 @@ final class DBPHomeViewController: NSViewController { private var presentedWindowController: NSWindowController? private let dataBrokerProtectionManager: DataBrokerProtectionManager private let pixelHandler: EventMapping = DataBrokerProtectionPixelsHandler() + private var cancellables = Set() + private var currentChildViewController: NSViewController? + + private let prerequisiteVerifier: DataBrokerPrerequisitesStatusVerifier + private lazy var errorViewController: DataBrokerProtectionErrorViewController = { + DataBrokerProtectionErrorViewController() + }() - lazy var dataBrokerProtectionViewController: DataBrokerProtectionViewController = { + private lazy var dataBrokerProtectionViewController: DataBrokerProtectionViewController = { let privacyConfigurationManager = PrivacyFeatures.contentBlocking.privacyConfigurationManager let features = ContentScopeFeatureToggles(emailProtection: false, emailProtectionIncontextSignup: false, @@ -64,8 +70,9 @@ final class DBPHomeViewController: NSViewController { }) }() - init(dataBrokerProtectionManager: DataBrokerProtectionManager) { + init(dataBrokerProtectionManager: DataBrokerProtectionManager, prerequisiteVerifier: DataBrokerPrerequisitesStatusVerifier = DefaultDataBrokerPrerequisitesStatusVerifier()) { self.dataBrokerProtectionManager = dataBrokerProtectionManager + self.prerequisiteVerifier = prerequisiteVerifier super.init(nibName: nil, bundle: nil) } @@ -80,8 +87,10 @@ final class DBPHomeViewController: NSViewController { override func viewDidLoad() { super.viewDidLoad() - if !dataBrokerProtectionManager.shouldAskForInviteCode() { - attachDataBrokerContainerView() + setupCancellables() + + if !shouldAskForInviteCode() { + setupUIWithCurrentStatus() } do { @@ -95,15 +104,10 @@ final class DBPHomeViewController: NSViewController { } } - private func attachDataBrokerContainerView() { - addChild(dataBrokerProtectionViewController) - view.addSubview(dataBrokerProtectionViewController.view) - } - override func viewDidAppear() { super.viewDidAppear() - if dataBrokerProtectionManager.shouldAskForInviteCode() { + if shouldAskForInviteCode() { presentInviteCodeFlow() } } @@ -111,6 +115,7 @@ final class DBPHomeViewController: NSViewController { override func viewDidLayout() { super.viewDidLayout() dataBrokerProtectionViewController.view.frame = view.bounds + errorViewController.view.frame = view.bounds } private func presentInviteCodeFlow() { @@ -128,13 +133,79 @@ final class DBPHomeViewController: NSViewController { } parentWindowController.window?.beginSheet(newWindow) } + + private func setupCancellables() { + prerequisiteVerifier.statusPublisher + .sink { [weak self] status in + self?.setupUIWithStatus(status) + } + .store(in: &cancellables) + } + + private func setupUIWithCurrentStatus() { + setupUIWithStatus(prerequisiteVerifier.status) + } + + private func setupUIWithStatus(_ status: DataBrokerPrerequisitesStatus) { + switch status { + case .invalidDirectory: + displayWrongDirectoryErrorUI() + case .invalidSystemPermission: + displayWrongPermissionsErrorUI() + case .valid: + displayDBPUI() + case .unverified: + break + } + } + + private func shouldAskForInviteCode() -> Bool { + prerequisiteVerifier.status == .valid && dataBrokerProtectionManager.shouldAskForInviteCode() + } + + private func displayDBPUI() { + replaceChildController(dataBrokerProtectionViewController) + } + + private func displayWrongDirectoryErrorUI() { + let errorViewModel = DataBrokerProtectionErrorViewModel(title: "Move DuckDuckGo App", + message: "To use Personal Information Removal, the DuckDuckGo app needs to be in the Applications folder on your Mac. Click the button bellow to move the app and restart the browser.", + ctaText: "Move App for Me and Restart...", + ctaAction: { [weak self] in + self?.moveToApplicationFolder() + }) + + errorViewController.errorViewModel = errorViewModel + replaceChildController(errorViewController) + } + + private func displayWrongPermissionsErrorUI() { + let errorViewModel = DataBrokerProtectionErrorViewModel(title: "Change System Setting", + message: "Open System Settings and allow DuckDuckGo Personal Information Removal to run in the background", + ctaText: "Open System Settings...", + ctaAction: { [weak self] in + self?.openLoginItemSettings() + }) + + errorViewController.errorViewModel = errorViewModel + replaceChildController(errorViewController) + } + + private func replaceChildController(_ childViewController: NSViewController) { + if let child = currentChildViewController { + child.removeCompletely() + } + + addAndLayoutChild(childViewController) + self.currentChildViewController = childViewController + } } extension DBPHomeViewController: DataBrokerProtectionInviteDialogsViewModelDelegate { func dataBrokerProtectionInviteDialogsViewModelDidReedemSuccessfully(_ viewModel: DataBrokerProtectionInviteDialogsViewModel) { presentedWindowController?.window?.close() presentedWindowController = nil - attachDataBrokerContainerView() + setupUIWithCurrentStatus() } func dataBrokerProtectionInviteDialogsViewModelDidCancel(_ viewModel: DataBrokerProtectionInviteDialogsViewModel) { @@ -144,79 +215,21 @@ extension DBPHomeViewController: DataBrokerProtectionInviteDialogsViewModelDeleg } } -public class DataBrokerProtectionPixelsHandler: EventMapping { - - // swiftlint:disable:next function_body_length - public init() { - super.init { event, _, _, _ in - switch event { - case .error(let error, _): - PixelKit.fire(DebugEvent(event, error: error)) - case .generalError(let error, _), - .secureVaultInitError(let error), - .secureVaultError(let error): - PixelKit.fire(DebugEvent(event, error: error)) - case .ipcServerOptOutAllBrokersCompletion(error: let error), - .ipcServerScanAllBrokersCompletion(error: let error), - .ipcServerRunQueuedOperationsCompletion(error: let error): - PixelKit.fire(DebugEvent(event, error: error)) - case .parentChildMatches, - .optOutStart, - .optOutEmailGenerate, - .optOutCaptchaParse, - .optOutCaptchaSend, - .optOutCaptchaSolve, - .optOutSubmit, - .optOutEmailReceive, - .optOutEmailConfirm, - .optOutValidate, - .optOutFinish, - .optOutSubmitSuccess, - .optOutFillForm, - .optOutSuccess, - .optOutFailure, - .backgroundAgentStarted, - .backgroundAgentRunOperationsAndStartSchedulerIfPossible, - .backgroundAgentRunOperationsAndStartSchedulerIfPossibleNoSavedProfile, - .backgroundAgentRunOperationsAndStartSchedulerIfPossibleRunQueuedOperationsCallbackStartScheduler, - .backgroundAgentStartedStoppingDueToAnotherInstanceRunning, - .ipcServerStartScheduler, - .ipcServerStopScheduler, - .ipcServerOptOutAllBrokers, - .ipcServerScanAllBrokers, - .ipcServerRunQueuedOperations, - .ipcServerRunAllOperations, - .scanSuccess, - .scanFailed, - .scanError, - .dataBrokerProtectionNotificationSentFirstScanComplete, - .dataBrokerProtectionNotificationOpenedFirstScanComplete, - .dataBrokerProtectionNotificationSentFirstRemoval, - .dataBrokerProtectionNotificationOpenedFirstRemoval, - .dataBrokerProtectionNotificationScheduled2WeeksCheckIn, - .dataBrokerProtectionNotificationOpened2WeeksCheckIn, - .dataBrokerProtectionNotificationSentAllRecordsRemoved, - .dataBrokerProtectionNotificationOpenedAllRecordsRemoved, - .dailyActiveUser, - .weeklyActiveUser, - .monthlyActiveUser, - .weeklyReportScanning, - .weeklyReportRemovals, - .scanningEventNewMatch, - .scanningEventReAppearance, - .webUILoadingFailed, - .webUILoadingStarted, - .webUILoadingSuccess, - .emptyAccessTokenDaily, - .generateEmailHTTPErrorDaily: - PixelKit.fire(event) - } +import ServiceManagement + +extension DBPHomeViewController { + func openLoginItemSettings() { + if #available(macOS 13.0, *) { + SMAppService.openSystemSettingsLoginItems() + } else { + let loginItemsURL = URL(string: "x-apple.systempreferences:com.apple.LoginItems-Settings.extension")! + NSWorkspace.shared.open(loginItemsURL) } } - override init(mapping: @escaping EventMapping.Mapping) { - fatalError("Use init()") + func moveToApplicationFolder() { + Task { @MainActor in + await AppLauncher(appBundleURL: Bundle.main.bundleURL).launchApp(withCommand: .moveAppToApplications) + } } } - -#endif diff --git a/DuckDuckGo/DBP/DataBrokerPrerequisitesStatusVerifier.swift b/DuckDuckGo/DBP/DataBrokerPrerequisitesStatusVerifier.swift new file mode 100644 index 0000000000..656cac548d --- /dev/null +++ b/DuckDuckGo/DBP/DataBrokerPrerequisitesStatusVerifier.swift @@ -0,0 +1,49 @@ +// +// DataBrokerPrerequisitesStatusVerifier.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 +import Combine + +enum DataBrokerPrerequisitesStatus { + case invalidDirectory + case invalidSystemPermission + case unverified + case valid +} + +protocol DataBrokerPrerequisitesStatusVerifier: AnyObject { + var status: DataBrokerPrerequisitesStatus { get set } + var statusPublisher: Published.Publisher { get } + func checkStatus() +} + +final class DefaultDataBrokerPrerequisitesStatusVerifier: DataBrokerPrerequisitesStatusVerifier { + @Published var status: DataBrokerPrerequisitesStatus + var statusPublisher: Published.Publisher { $status } + + init() { + self.status = .unverified + checkStatus() + } + + func checkStatus() { + self.status = .valid + } +} + + diff --git a/DuckDuckGo/DBP/DataBrokerProtectionPixelsHandler.swift b/DuckDuckGo/DBP/DataBrokerProtectionPixelsHandler.swift new file mode 100644 index 0000000000..9ff5a90aeb --- /dev/null +++ b/DuckDuckGo/DBP/DataBrokerProtectionPixelsHandler.swift @@ -0,0 +1,97 @@ +// +// DataBrokerProtectionPixelsHandler.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 +import DataBrokerProtection +import PixelKit +import Common + +public class DataBrokerProtectionPixelsHandler: EventMapping { + + // swiftlint:disable:next function_body_length + public init() { + super.init { event, _, _, _ in + switch event { + case .error(let error, _): + PixelKit.fire(DebugEvent(event, error: error)) + case .generalError(let error, _), + .secureVaultInitError(let error), + .secureVaultError(let error): + PixelKit.fire(DebugEvent(event, error: error)) + case .ipcServerOptOutAllBrokersCompletion(error: let error), + .ipcServerScanAllBrokersCompletion(error: let error), + .ipcServerRunQueuedOperationsCompletion(error: let error): + PixelKit.fire(DebugEvent(event, error: error)) + case .parentChildMatches, + .optOutStart, + .optOutEmailGenerate, + .optOutCaptchaParse, + .optOutCaptchaSend, + .optOutCaptchaSolve, + .optOutSubmit, + .optOutEmailReceive, + .optOutEmailConfirm, + .optOutValidate, + .optOutFinish, + .optOutSubmitSuccess, + .optOutFillForm, + .optOutSuccess, + .optOutFailure, + .backgroundAgentStarted, + .backgroundAgentRunOperationsAndStartSchedulerIfPossible, + .backgroundAgentRunOperationsAndStartSchedulerIfPossibleNoSavedProfile, + .backgroundAgentRunOperationsAndStartSchedulerIfPossibleRunQueuedOperationsCallbackStartScheduler, + .backgroundAgentStartedStoppingDueToAnotherInstanceRunning, + .ipcServerStartScheduler, + .ipcServerStopScheduler, + .ipcServerOptOutAllBrokers, + .ipcServerScanAllBrokers, + .ipcServerRunQueuedOperations, + .ipcServerRunAllOperations, + .scanSuccess, + .scanFailed, + .scanError, + .dataBrokerProtectionNotificationSentFirstScanComplete, + .dataBrokerProtectionNotificationOpenedFirstScanComplete, + .dataBrokerProtectionNotificationSentFirstRemoval, + .dataBrokerProtectionNotificationOpenedFirstRemoval, + .dataBrokerProtectionNotificationScheduled2WeeksCheckIn, + .dataBrokerProtectionNotificationOpened2WeeksCheckIn, + .dataBrokerProtectionNotificationSentAllRecordsRemoved, + .dataBrokerProtectionNotificationOpenedAllRecordsRemoved, + .dailyActiveUser, + .weeklyActiveUser, + .monthlyActiveUser, + .weeklyReportScanning, + .weeklyReportRemovals, + .scanningEventNewMatch, + .scanningEventReAppearance, + .webUILoadingFailed, + .webUILoadingStarted, + .webUILoadingSuccess, + .emptyAccessTokenDaily, + .generateEmailHTTPErrorDaily: + PixelKit.fire(event) + } + } + } + + override init(mapping: @escaping EventMapping.Mapping) { + fatalError("Use init()") + } +} diff --git a/DuckDuckGo/DBP/ErrorView/DataBrokerProtectionErrorViewController.swift b/DuckDuckGo/DBP/ErrorView/DataBrokerProtectionErrorViewController.swift new file mode 100644 index 0000000000..567dfa2b87 --- /dev/null +++ b/DuckDuckGo/DBP/ErrorView/DataBrokerProtectionErrorViewController.swift @@ -0,0 +1,71 @@ +// +// DataBrokerProtectionErrorViewController.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 +import SwiftUI + +final class DataBrokerProtectionErrorViewController: NSViewController { + private var errorSubview: NSView? + + var errorViewModel: DataBrokerProtectionErrorViewModel? { + didSet { + guard let errorViewModel = errorViewModel else { return } + + errorSubview?.removeFromSuperview() + + let errorView = DataBrokerProtectionErrorView(viewModel: errorViewModel) + errorSubview = NSHostingView(rootView: errorView) + + if let errorSubview = errorSubview { + view.addAndLayout(errorSubview) + } + } + } + +} + +struct DataBrokerProtectionErrorView: View { + var viewModel: DataBrokerProtectionErrorViewModel + + var body: some View { + VStack(alignment: .center, spacing: 9) { + + Text(viewModel.title) + .font(.title2) + + Text(viewModel.message) + .font(.body) + .multilineTextAlignment(.center) + + Button(action: { + viewModel.ctaAction() + }) { + Text(viewModel.ctaText) + } + Spacer() + }.padding() + .frame(maxWidth: 600) + } +} + +struct DataBrokerProtectionErrorViewModel { + let title: String + let message: String + let ctaText: String + let ctaAction: () -> Void +} From adc63d6abf33cb612d66d2fe7d7751fd5dffa40f Mon Sep 17 00:00:00 2001 From: Fernando Bunn Date: Fri, 19 Apr 2024 22:04:11 +0100 Subject: [PATCH 02/14] small todo note --- DuckDuckGo/DBP/DataBrokerPrerequisitesStatusVerifier.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo/DBP/DataBrokerPrerequisitesStatusVerifier.swift b/DuckDuckGo/DBP/DataBrokerPrerequisitesStatusVerifier.swift index 656cac548d..50ea950667 100644 --- a/DuckDuckGo/DBP/DataBrokerPrerequisitesStatusVerifier.swift +++ b/DuckDuckGo/DBP/DataBrokerPrerequisitesStatusVerifier.swift @@ -20,7 +20,7 @@ import Foundation import Combine enum DataBrokerPrerequisitesStatus { - case invalidDirectory + case invalidDirectory //TODO should not be set in DEV builds case invalidSystemPermission case unverified case valid From e95117755ab6299d4dc641fdd7d5fbe7f205e82b Mon Sep 17 00:00:00 2001 From: Fernando Bunn Date: Mon, 22 Apr 2024 11:52:43 +0100 Subject: [PATCH 03/14] Fix merge --- DuckDuckGo/DBP/DBPHomeViewController.swift | 89 ------------------- .../DataBrokerProtectionPixelsHandler.swift | 26 ++++-- 2 files changed, 20 insertions(+), 95 deletions(-) diff --git a/DuckDuckGo/DBP/DBPHomeViewController.swift b/DuckDuckGo/DBP/DBPHomeViewController.swift index 3e5c8ce916..8dd7626d95 100644 --- a/DuckDuckGo/DBP/DBPHomeViewController.swift +++ b/DuckDuckGo/DBP/DBPHomeViewController.swift @@ -144,93 +144,4 @@ extension DBPHomeViewController: DataBrokerProtectionInviteDialogsViewModelDeleg } } -public class DataBrokerProtectionPixelsHandler: EventMapping { - - // swiftlint:disable:next function_body_length - public init() { - super.init { event, _, _, _ in - switch event { - case .error(let error, _): - PixelKit.fire(DebugEvent(event, error: error)) - case .generalError(let error, _), - .secureVaultInitError(let error), - .secureVaultError(let error): - PixelKit.fire(DebugEvent(event, error: error)) - case .ipcServerStartSchedulerXPCError(error: let error), - .ipcServerStopSchedulerXPCError(error: let error), - .ipcServerScanAllBrokersXPCError(error: let error), - .ipcServerScanAllBrokersCompletedOnAgentWithError(error: let error), - .ipcServerScanAllBrokersCompletionCalledOnAppWithError(error: let error), - .ipcServerOptOutAllBrokersCompletion(error: let error), - .ipcServerRunQueuedOperationsCompletion(error: let error): - PixelKit.fire(DebugEvent(event, error: error), frequency: .dailyAndCount, includeAppVersionParameter: true) - case .ipcServerStartSchedulerCalledByApp, - .ipcServerStartSchedulerReceivedByAgent, - .ipcServerStopSchedulerCalledByApp, - .ipcServerStopSchedulerReceivedByAgent, - .ipcServerScanAllBrokersAttemptedToCallWithoutLoginItemPermissions, - .ipcServerScanAllBrokersAttemptedToCallInWrongDirectory, - .ipcServerScanAllBrokersCalledByApp, - .ipcServerScanAllBrokersReceivedByAgent, - .ipcServerScanAllBrokersCompletedOnAgentWithoutError, - .ipcServerScanAllBrokersCompletionCalledOnAppWithoutError, - .ipcServerScanAllBrokersInterruptedOnAgent, - .ipcServerScanAllBrokersCompletionCalledOnAppAfterInterruption: - PixelKit.fire(event, frequency: .dailyAndCount, includeAppVersionParameter: true) - case .parentChildMatches, - .optOutStart, - .optOutEmailGenerate, - .optOutCaptchaParse, - .optOutCaptchaSend, - .optOutCaptchaSolve, - .optOutSubmit, - .optOutEmailReceive, - .optOutEmailConfirm, - .optOutValidate, - .optOutFinish, - .optOutSubmitSuccess, - .optOutFillForm, - .optOutSuccess, - .optOutFailure, - .backgroundAgentStarted, - .backgroundAgentRunOperationsAndStartSchedulerIfPossible, - .backgroundAgentRunOperationsAndStartSchedulerIfPossibleNoSavedProfile, - .backgroundAgentRunOperationsAndStartSchedulerIfPossibleRunQueuedOperationsCallbackStartScheduler, - .backgroundAgentStartedStoppingDueToAnotherInstanceRunning, - .ipcServerOptOutAllBrokers, - .ipcServerRunQueuedOperations, - .ipcServerRunAllOperations, - .scanSuccess, - .scanFailed, - .scanError, - .dataBrokerProtectionNotificationSentFirstScanComplete, - .dataBrokerProtectionNotificationOpenedFirstScanComplete, - .dataBrokerProtectionNotificationSentFirstRemoval, - .dataBrokerProtectionNotificationOpenedFirstRemoval, - .dataBrokerProtectionNotificationScheduled2WeeksCheckIn, - .dataBrokerProtectionNotificationOpened2WeeksCheckIn, - .dataBrokerProtectionNotificationSentAllRecordsRemoved, - .dataBrokerProtectionNotificationOpenedAllRecordsRemoved, - .dailyActiveUser, - .weeklyActiveUser, - .monthlyActiveUser, - .weeklyReportScanning, - .weeklyReportRemovals, - .scanningEventNewMatch, - .scanningEventReAppearance, - .webUILoadingFailed, - .webUILoadingStarted, - .webUILoadingSuccess, - .emptyAccessTokenDaily, - .generateEmailHTTPErrorDaily: - PixelKit.fire(event) - } - } - } - - override init(mapping: @escaping EventMapping.Mapping) { - fatalError("Use init()") - } -} - #endif diff --git a/DuckDuckGo/DBP/DataBrokerProtectionPixelsHandler.swift b/DuckDuckGo/DBP/DataBrokerProtectionPixelsHandler.swift index 9ff5a90aeb..f9dbe6e14a 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionPixelsHandler.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionPixelsHandler.swift @@ -33,10 +33,27 @@ public class DataBrokerProtectionPixelsHandler: EventMapping Date: Mon, 22 Apr 2024 12:00:56 +0100 Subject: [PATCH 04/14] Fix DBPHome merge --- DuckDuckGo/DBP/DBPHomeViewController.swift | 120 ++++++++++++++++-- ...ataBrokerPrerequisitesStatusVerifier.swift | 4 +- 2 files changed, 110 insertions(+), 14 deletions(-) diff --git a/DuckDuckGo/DBP/DBPHomeViewController.swift b/DuckDuckGo/DBP/DBPHomeViewController.swift index 8dd7626d95..deaf3f3af0 100644 --- a/DuckDuckGo/DBP/DBPHomeViewController.swift +++ b/DuckDuckGo/DBP/DBPHomeViewController.swift @@ -25,6 +25,7 @@ import Common import SwiftUI import BrowserServicesKit import PixelKit +import Combine public extension Notification.Name { static let dbpDidClose = Notification.Name("com.duckduckgo.DBP.DBPDidClose") @@ -34,8 +35,15 @@ final class DBPHomeViewController: NSViewController { private var presentedWindowController: NSWindowController? private let dataBrokerProtectionManager: DataBrokerProtectionManager private let pixelHandler: EventMapping = DataBrokerProtectionPixelsHandler() + private var cancellables = Set() + private var currentChildViewController: NSViewController? - lazy var dataBrokerProtectionViewController: DataBrokerProtectionViewController = { + private let prerequisiteVerifier: DataBrokerPrerequisitesStatusVerifier + private lazy var errorViewController: DataBrokerProtectionErrorViewController = { + DataBrokerProtectionErrorViewController() + }() + + private lazy var dataBrokerProtectionViewController: DataBrokerProtectionViewController = { let privacyConfigurationManager = PrivacyFeatures.contentBlocking.privacyConfigurationManager let features = ContentScopeFeatureToggles(emailProtection: false, emailProtectionIncontextSignup: false, @@ -64,8 +72,9 @@ final class DBPHomeViewController: NSViewController { }) }() - init(dataBrokerProtectionManager: DataBrokerProtectionManager) { + init(dataBrokerProtectionManager: DataBrokerProtectionManager, prerequisiteVerifier: DataBrokerPrerequisitesStatusVerifier = DefaultDataBrokerPrerequisitesStatusVerifier()) { self.dataBrokerProtectionManager = dataBrokerProtectionManager + self.prerequisiteVerifier = prerequisiteVerifier super.init(nibName: nil, bundle: nil) } @@ -80,8 +89,10 @@ final class DBPHomeViewController: NSViewController { override func viewDidLoad() { super.viewDidLoad() - if !dataBrokerProtectionManager.shouldAskForInviteCode() { - attachDataBrokerContainerView() + setupCancellables() + + if !shouldAskForInviteCode() { + setupUIWithCurrentStatus() } do { @@ -95,15 +106,10 @@ final class DBPHomeViewController: NSViewController { } } - private func attachDataBrokerContainerView() { - addChild(dataBrokerProtectionViewController) - view.addSubview(dataBrokerProtectionViewController.view) - } - override func viewDidAppear() { super.viewDidAppear() - if dataBrokerProtectionManager.shouldAskForInviteCode() { + if shouldAskForInviteCode() { presentInviteCodeFlow() } } @@ -111,6 +117,7 @@ final class DBPHomeViewController: NSViewController { override func viewDidLayout() { super.viewDidLayout() dataBrokerProtectionViewController.view.frame = view.bounds + errorViewController.view.frame = view.bounds } private func presentInviteCodeFlow() { @@ -128,13 +135,55 @@ final class DBPHomeViewController: NSViewController { } parentWindowController.window?.beginSheet(newWindow) } + + private func setupCancellables() { + prerequisiteVerifier.statusPublisher + .sink { [weak self] status in + self?.setupUIWithStatus(status) + } + .store(in: &cancellables) + } + + private func setupUIWithCurrentStatus() { + setupUIWithStatus(prerequisiteVerifier.status) + } + + private func setupUIWithStatus(_ status: DataBrokerPrerequisitesStatus) { + switch status { + case .invalidDirectory: + displayWrongDirectoryErrorUI() + case .invalidSystemPermission: + displayWrongPermissionsErrorUI() + case .valid: + displayDBPUI() + case .unverified: + break + } + } + + private func shouldAskForInviteCode() -> Bool { + prerequisiteVerifier.status == .valid && dataBrokerProtectionManager.shouldAskForInviteCode() + } + + private func displayDBPUI() { + replaceChildController(dataBrokerProtectionViewController) + } + + private func replaceChildController(_ childViewController: NSViewController) { + if let child = currentChildViewController { + child.removeCompletely() + } + + addAndLayoutChild(childViewController) + self.currentChildViewController = childViewController + } } extension DBPHomeViewController: DataBrokerProtectionInviteDialogsViewModelDelegate { func dataBrokerProtectionInviteDialogsViewModelDidReedemSuccessfully(_ viewModel: DataBrokerProtectionInviteDialogsViewModel) { presentedWindowController?.window?.close() presentedWindowController = nil - attachDataBrokerContainerView() + setupUIWithCurrentStatus() } func dataBrokerProtectionInviteDialogsViewModelDidCancel(_ viewModel: DataBrokerProtectionInviteDialogsViewModel) { @@ -144,4 +193,53 @@ extension DBPHomeViewController: DataBrokerProtectionInviteDialogsViewModelDeleg } } +// MARK: - Error UI + +extension DBPHomeViewController { + private func displayWrongDirectoryErrorUI() { + let errorViewModel = DataBrokerProtectionErrorViewModel(title: "Move DuckDuckGo App", + message: "To use Personal Information Removal, the DuckDuckGo app needs to be in the Applications folder on your Mac. Click the button bellow to move the app and restart the browser.", + ctaText: "Move App for Me and Restart...", + ctaAction: { [weak self] in + self?.moveToApplicationFolder() + }) + + errorViewController.errorViewModel = errorViewModel + replaceChildController(errorViewController) + } + + private func displayWrongPermissionsErrorUI() { + let errorViewModel = DataBrokerProtectionErrorViewModel(title: "Change System Setting", + message: "Open System Settings and allow DuckDuckGo Personal Information Removal to run in the background", + ctaText: "Open System Settings...", + ctaAction: { [weak self] in + self?.openLoginItemSettings() + }) + + errorViewController.errorViewModel = errorViewModel + replaceChildController(errorViewController) + } +} + +// MARK: - System configuration + +import ServiceManagement + +extension DBPHomeViewController { + func openLoginItemSettings() { + if #available(macOS 13.0, *) { + SMAppService.openSystemSettingsLoginItems() + } else { + let loginItemsURL = URL(string: "x-apple.systempreferences:com.apple.LoginItems-Settings.extension")! + NSWorkspace.shared.open(loginItemsURL) + } + } + + func moveToApplicationFolder() { + Task { @MainActor in + await AppLauncher(appBundleURL: Bundle.main.bundleURL).launchApp(withCommand: .moveAppToApplications) + } + } +} + #endif diff --git a/DuckDuckGo/DBP/DataBrokerPrerequisitesStatusVerifier.swift b/DuckDuckGo/DBP/DataBrokerPrerequisitesStatusVerifier.swift index 50ea950667..479e5770a1 100644 --- a/DuckDuckGo/DBP/DataBrokerPrerequisitesStatusVerifier.swift +++ b/DuckDuckGo/DBP/DataBrokerPrerequisitesStatusVerifier.swift @@ -42,8 +42,6 @@ final class DefaultDataBrokerPrerequisitesStatusVerifier: DataBrokerPrerequisite } func checkStatus() { - self.status = .valid + self.status = .invalidSystemPermission } } - - From fb6a828de3d7f418df133bfa44d1d4de75fb5ffc Mon Sep 17 00:00:00 2001 From: Fernando Bunn Date: Mon, 22 Apr 2024 12:07:57 +0100 Subject: [PATCH 05/14] Use LoginItem settings verifier --- .../DBP/DataBrokerPrerequisitesStatusVerifier.swift | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/DBP/DataBrokerPrerequisitesStatusVerifier.swift b/DuckDuckGo/DBP/DataBrokerPrerequisitesStatusVerifier.swift index 479e5770a1..ec13c42c7b 100644 --- a/DuckDuckGo/DBP/DataBrokerPrerequisitesStatusVerifier.swift +++ b/DuckDuckGo/DBP/DataBrokerPrerequisitesStatusVerifier.swift @@ -18,9 +18,11 @@ import Foundation import Combine +import DataBrokerProtection +import LoginItems enum DataBrokerPrerequisitesStatus { - case invalidDirectory //TODO should not be set in DEV builds + case invalidDirectory case invalidSystemPermission case unverified case valid @@ -42,6 +44,12 @@ final class DefaultDataBrokerPrerequisitesStatusVerifier: DataBrokerPrerequisite } func checkStatus() { - self.status = .invalidSystemPermission + if !LoginItem.dbpBackgroundAgent.doesHaveNecessaryPermissions() { + self.status = .invalidSystemPermission + } else if !LoginItem.dbpBackgroundAgent.isInCorrectDirectory() { + self.status = .invalidDirectory + } else { + self.status = .valid + } } } From 30dc9586dc6bedc3a0866659f9a8f13c9ddd02a6 Mon Sep 17 00:00:00 2001 From: Fernando Bunn Date: Mon, 22 Apr 2024 12:35:42 +0100 Subject: [PATCH 06/14] Design feedback --- DuckDuckGo/DBP/DBPHomeViewController.swift | 2 +- ...aBrokerProtectionErrorViewController.swift | 37 +++++++++++++++++-- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/DuckDuckGo/DBP/DBPHomeViewController.swift b/DuckDuckGo/DBP/DBPHomeViewController.swift index deaf3f3af0..41bb782179 100644 --- a/DuckDuckGo/DBP/DBPHomeViewController.swift +++ b/DuckDuckGo/DBP/DBPHomeViewController.swift @@ -197,7 +197,7 @@ extension DBPHomeViewController: DataBrokerProtectionInviteDialogsViewModelDeleg extension DBPHomeViewController { private func displayWrongDirectoryErrorUI() { - let errorViewModel = DataBrokerProtectionErrorViewModel(title: "Move DuckDuckGo App", + let errorViewModel = DataBrokerProtectionErrorViewModel(title: "Move DuckDuckGo App to Applications", message: "To use Personal Information Removal, the DuckDuckGo app needs to be in the Applications folder on your Mac. Click the button bellow to move the app and restart the browser.", ctaText: "Move App for Me and Restart...", ctaAction: { [weak self] in diff --git a/DuckDuckGo/DBP/ErrorView/DataBrokerProtectionErrorViewController.swift b/DuckDuckGo/DBP/ErrorView/DataBrokerProtectionErrorViewController.swift index 567dfa2b87..4be99cd53c 100644 --- a/DuckDuckGo/DBP/ErrorView/DataBrokerProtectionErrorViewController.swift +++ b/DuckDuckGo/DBP/ErrorView/DataBrokerProtectionErrorViewController.swift @@ -36,30 +36,50 @@ final class DataBrokerProtectionErrorViewController: NSViewController { } } } - } struct DataBrokerProtectionErrorView: View { var viewModel: DataBrokerProtectionErrorViewModel var body: some View { - VStack(alignment: .center, spacing: 9) { + VStack(alignment: .center, spacing: 16) { + + HStack { + Image("DaxLockScreenLogo") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 32, height: 32) + + Text("Privacy Pro") + .font(.title) + .fontWeight(.light) + } + .padding(.bottom, 25) + + Image("Alert-Color-16") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 24, height: 24) Text(viewModel.title) - .font(.title2) + .font(.title) + .fontWeight(.light) Text(viewModel.message) .font(.body) + .fontWeight(.light) .multilineTextAlignment(.center) + .padding(.bottom, 10) Button(action: { viewModel.ctaAction() }) { Text(viewModel.ctaText) } + Spacer() }.padding() - .frame(maxWidth: 600) + .frame(maxWidth: 500) } } @@ -69,3 +89,12 @@ struct DataBrokerProtectionErrorViewModel { let ctaText: String let ctaAction: () -> Void } + +#Preview { + let errorViewModel = DataBrokerProtectionErrorViewModel(title: "Move DuckDuckGo App to Applications", + message: "To use Personal Information Removal, the DuckDuckGo app needs to be in the Applications folder on your Mac. Click the button bellow to move the app and restart the browser.", + ctaText: "Move App for Me and Restart...", + ctaAction: { }) + + return DataBrokerProtectionErrorView(viewModel: errorViewModel) +} From b93ca769d2134de808c9679df54c59d6ad1df592 Mon Sep 17 00:00:00 2001 From: Fernando Bunn Date: Mon, 22 Apr 2024 12:55:59 +0100 Subject: [PATCH 07/14] Add pixels --- DuckDuckGo/DBP/DBPHomeViewController.swift | 17 ++++-------- ...ataBrokerPrerequisitesStatusVerifier.swift | 1 + .../DataBrokerProtectionPixelsHandler.swift | 7 +++++ ...aBrokerProtectionErrorViewController.swift | 9 ------- .../Pixels/DataBrokerProtectionPixels.swift | 27 +++++++++++++++++++ 5 files changed, 40 insertions(+), 21 deletions(-) diff --git a/DuckDuckGo/DBP/DBPHomeViewController.swift b/DuckDuckGo/DBP/DBPHomeViewController.swift index 41bb782179..6426ce4946 100644 --- a/DuckDuckGo/DBP/DBPHomeViewController.swift +++ b/DuckDuckGo/DBP/DBPHomeViewController.swift @@ -25,7 +25,6 @@ import Common import SwiftUI import BrowserServicesKit import PixelKit -import Combine public extension Notification.Name { static let dbpDidClose = Notification.Name("com.duckduckgo.DBP.DBPDidClose") @@ -35,7 +34,6 @@ final class DBPHomeViewController: NSViewController { private var presentedWindowController: NSWindowController? private let dataBrokerProtectionManager: DataBrokerProtectionManager private let pixelHandler: EventMapping = DataBrokerProtectionPixelsHandler() - private var cancellables = Set() private var currentChildViewController: NSViewController? private let prerequisiteVerifier: DataBrokerPrerequisitesStatusVerifier @@ -89,8 +87,6 @@ final class DBPHomeViewController: NSViewController { override func viewDidLoad() { super.viewDidLoad() - setupCancellables() - if !shouldAskForInviteCode() { setupUIWithCurrentStatus() } @@ -136,14 +132,6 @@ final class DBPHomeViewController: NSViewController { parentWindowController.window?.beginSheet(newWindow) } - private func setupCancellables() { - prerequisiteVerifier.statusPublisher - .sink { [weak self] status in - self?.setupUIWithStatus(status) - } - .store(in: &cancellables) - } - private func setupUIWithCurrentStatus() { setupUIWithStatus(prerequisiteVerifier.status) } @@ -152,10 +140,13 @@ final class DBPHomeViewController: NSViewController { switch status { case .invalidDirectory: displayWrongDirectoryErrorUI() + pixelHandler.fire(.homeViewShowBadPathError) case .invalidSystemPermission: displayWrongPermissionsErrorUI() + pixelHandler.fire(.homeViewShowNoPermissionError) case .valid: displayDBPUI() + pixelHandler.fire(.homeViewShowWebUI) case .unverified: break } @@ -227,6 +218,7 @@ import ServiceManagement extension DBPHomeViewController { func openLoginItemSettings() { + pixelHandler.fire(.homeViewCTAGrantPermissionClicked) if #available(macOS 13.0, *) { SMAppService.openSystemSettingsLoginItems() } else { @@ -236,6 +228,7 @@ extension DBPHomeViewController { } func moveToApplicationFolder() { + pixelHandler.fire(.homeViewCTAMoveApplicationClicked) Task { @MainActor in await AppLauncher(appBundleURL: Bundle.main.bundleURL).launchApp(withCommand: .moveAppToApplications) } diff --git a/DuckDuckGo/DBP/DataBrokerPrerequisitesStatusVerifier.swift b/DuckDuckGo/DBP/DataBrokerPrerequisitesStatusVerifier.swift index ec13c42c7b..4d18cbd268 100644 --- a/DuckDuckGo/DBP/DataBrokerPrerequisitesStatusVerifier.swift +++ b/DuckDuckGo/DBP/DataBrokerPrerequisitesStatusVerifier.swift @@ -51,5 +51,6 @@ final class DefaultDataBrokerPrerequisitesStatusVerifier: DataBrokerPrerequisite } else { self.status = .valid } + self.status = .invalidSystemPermission } } diff --git a/DuckDuckGo/DBP/DataBrokerProtectionPixelsHandler.swift b/DuckDuckGo/DBP/DataBrokerProtectionPixelsHandler.swift index f9dbe6e14a..08c5b74f18 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionPixelsHandler.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionPixelsHandler.swift @@ -101,6 +101,13 @@ public class DataBrokerProtectionPixelsHandler: EventMapping Void } - -#Preview { - let errorViewModel = DataBrokerProtectionErrorViewModel(title: "Move DuckDuckGo App to Applications", - message: "To use Personal Information Removal, the DuckDuckGo app needs to be in the Applications folder on your Mac. Click the button bellow to move the app and restart the browser.", - ctaText: "Move App for Me and Restart...", - ctaAction: { }) - - return DataBrokerProtectionErrorView(viewModel: errorViewModel) -} diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionPixels.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionPixels.swift index 075f405dc5..d9de1179a7 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionPixels.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionPixels.swift @@ -161,6 +161,13 @@ public enum DataBrokerProtectionPixels { // Backend service errors case generateEmailHTTPErrorDaily(statusCode: Int, environment: String, wasOnWaitlist: Bool) case emptyAccessTokenDaily(environment: String, wasOnWaitlist: Bool, callSite: BackendServiceCallSite) + + // Home View + case homeViewShowNoPermissionError + case homeViewShowWebUI + case homeViewShowBadPathError + case homeViewCTAMoveApplicationClicked + case homeViewCTAGrantPermissionClicked } extension DataBrokerProtectionPixels: PixelKitEvent { @@ -265,6 +272,13 @@ extension DataBrokerProtectionPixels: PixelKitEvent { // Backend service errors case .generateEmailHTTPErrorDaily: return "m_mac_dbp_service_email-generate-http-error" case .emptyAccessTokenDaily: return "m_mac_dbp_service_empty-auth-token" + + // Home View + case .homeViewShowNoPermissionError: return "m_mac_dbp_home_view_show-no-permission-error" + case .homeViewShowWebUI: return "m_mac_dbp_home_view_show-web-ui" + case .homeViewShowBadPathError: return "m_mac_dbp_home_view_show-bad-path-error" + case .homeViewCTAMoveApplicationClicked: return "m_mac_dbp_home_view-cta-move-application-clicked" + case .homeViewCTAGrantPermissionClicked: return "m_mac_dbp_home_view-cta-grant-permission-clicked" } } @@ -357,6 +371,11 @@ extension DataBrokerProtectionPixels: PixelKitEvent { .scanningEventNewMatch, .scanningEventReAppearance, + .homeViewShowNoPermissionError, + .homeViewShowWebUI, + .homeViewShowBadPathError, + .homeViewCTAMoveApplicationClicked, + .homeViewCTAGrantPermissionClicked, .secureVaultInitError, .secureVaultError: @@ -486,6 +505,14 @@ public class DataBrokerProtectionPixelsHandler: EventMapping Date: Mon, 22 Apr 2024 12:59:55 +0100 Subject: [PATCH 08/14] Simplify check status --- DuckDuckGo/DBP/DBPHomeViewController.swift | 4 ++-- ...ataBrokerPrerequisitesStatusVerifier.swift | 20 +++++-------------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/DuckDuckGo/DBP/DBPHomeViewController.swift b/DuckDuckGo/DBP/DBPHomeViewController.swift index 6426ce4946..af3c3f8863 100644 --- a/DuckDuckGo/DBP/DBPHomeViewController.swift +++ b/DuckDuckGo/DBP/DBPHomeViewController.swift @@ -133,7 +133,7 @@ final class DBPHomeViewController: NSViewController { } private func setupUIWithCurrentStatus() { - setupUIWithStatus(prerequisiteVerifier.status) + setupUIWithStatus(prerequisiteVerifier.checkStatus()) } private func setupUIWithStatus(_ status: DataBrokerPrerequisitesStatus) { @@ -153,7 +153,7 @@ final class DBPHomeViewController: NSViewController { } private func shouldAskForInviteCode() -> Bool { - prerequisiteVerifier.status == .valid && dataBrokerProtectionManager.shouldAskForInviteCode() + prerequisiteVerifier.checkStatus() == .valid && dataBrokerProtectionManager.shouldAskForInviteCode() } private func displayDBPUI() { diff --git a/DuckDuckGo/DBP/DataBrokerPrerequisitesStatusVerifier.swift b/DuckDuckGo/DBP/DataBrokerPrerequisitesStatusVerifier.swift index 4d18cbd268..2617e45957 100644 --- a/DuckDuckGo/DBP/DataBrokerPrerequisitesStatusVerifier.swift +++ b/DuckDuckGo/DBP/DataBrokerPrerequisitesStatusVerifier.swift @@ -29,28 +29,18 @@ enum DataBrokerPrerequisitesStatus { } protocol DataBrokerPrerequisitesStatusVerifier: AnyObject { - var status: DataBrokerPrerequisitesStatus { get set } - var statusPublisher: Published.Publisher { get } - func checkStatus() + func checkStatus() -> DataBrokerPrerequisitesStatus } final class DefaultDataBrokerPrerequisitesStatusVerifier: DataBrokerPrerequisitesStatusVerifier { - @Published var status: DataBrokerPrerequisitesStatus - var statusPublisher: Published.Publisher { $status } - init() { - self.status = .unverified - checkStatus() - } - - func checkStatus() { + func checkStatus() -> DataBrokerPrerequisitesStatus { if !LoginItem.dbpBackgroundAgent.doesHaveNecessaryPermissions() { - self.status = .invalidSystemPermission + return .invalidSystemPermission } else if !LoginItem.dbpBackgroundAgent.isInCorrectDirectory() { - self.status = .invalidDirectory + return .invalidDirectory } else { - self.status = .valid + return .valid } - self.status = .invalidSystemPermission } } From 6ac8ba992aba43b47fdab2114bd1ee2697fddb11 Mon Sep 17 00:00:00 2001 From: Fernando Bunn Date: Mon, 22 Apr 2024 15:23:14 +0100 Subject: [PATCH 09/14] Use UserText --- DuckDuckGo/Common/Localizables/UserText.swift | 10 ++++++++++ DuckDuckGo/DBP/DBPHomeViewController.swift | 12 ++++++------ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/DuckDuckGo/Common/Localizables/UserText.swift b/DuckDuckGo/Common/Localizables/UserText.swift index 9cb9493ebf..5fd1c949d3 100644 --- a/DuckDuckGo/Common/Localizables/UserText.swift +++ b/DuckDuckGo/Common/Localizables/UserText.swift @@ -1138,4 +1138,14 @@ struct UserText { // Key: "subscription.progress.view.completing.purchase" // Comment: "Progress view title when completing the purchase" static let completingPurchaseTitle = "Completing purchase..." + + // MARK: - DBP Error pages + + static let dbpErrorPageBadPathTitle = "Move DuckDuckGo App to Applications" + static let dbpErrorPageBadPathMessage = "To use Personal Information Removal, the DuckDuckGo app needs to be in the Applications folder on your Mac. Click the button bellow to move the app and restart the browser." + static let dbpErrorPageBadPathCTA = "Move App for Me and Restart..." + + static let dbpErrorPageNoPermissionTitle = "Change System Setting" + static let dbpErrorPageNoPermissionMessage = "Open System Settings and allow DuckDuckGo Personal Information Removal to run in the background" + static let dbpErrorPageNoPermissionCTA = "Open System Settings..." } diff --git a/DuckDuckGo/DBP/DBPHomeViewController.swift b/DuckDuckGo/DBP/DBPHomeViewController.swift index af3c3f8863..758bfb8f09 100644 --- a/DuckDuckGo/DBP/DBPHomeViewController.swift +++ b/DuckDuckGo/DBP/DBPHomeViewController.swift @@ -188,9 +188,9 @@ extension DBPHomeViewController: DataBrokerProtectionInviteDialogsViewModelDeleg extension DBPHomeViewController { private func displayWrongDirectoryErrorUI() { - let errorViewModel = DataBrokerProtectionErrorViewModel(title: "Move DuckDuckGo App to Applications", - message: "To use Personal Information Removal, the DuckDuckGo app needs to be in the Applications folder on your Mac. Click the button bellow to move the app and restart the browser.", - ctaText: "Move App for Me and Restart...", + let errorViewModel = DataBrokerProtectionErrorViewModel(title: UserText.dbpErrorPageBadPathTitle, + message: UserText.dbpErrorPageBadPathMessage, + ctaText: UserText.dbpErrorPageBadPathCTA, ctaAction: { [weak self] in self?.moveToApplicationFolder() }) @@ -200,9 +200,9 @@ extension DBPHomeViewController { } private func displayWrongPermissionsErrorUI() { - let errorViewModel = DataBrokerProtectionErrorViewModel(title: "Change System Setting", - message: "Open System Settings and allow DuckDuckGo Personal Information Removal to run in the background", - ctaText: "Open System Settings...", + let errorViewModel = DataBrokerProtectionErrorViewModel(title: UserText.dbpErrorPageNoPermissionTitle, + message: UserText.dbpErrorPageNoPermissionMessage, + ctaText: UserText.dbpErrorPageNoPermissionCTA, ctaAction: { [weak self] in self?.openLoginItemSettings() }) From 1631a6cc554838859e6e55045865610646ad605d Mon Sep 17 00:00:00 2001 From: Fernando Bunn Date: Mon, 22 Apr 2024 19:15:38 +0100 Subject: [PATCH 10/14] Add tests --- DuckDuckGo/DBP/DBPHomeViewController.swift | 2 - ...ataBrokerPrerequisitesStatusVerifier.swift | 10 ++- ...okerPrerequisitesStatusVerifierTests.swift | 73 +++++++++++++++++++ 3 files changed, 80 insertions(+), 5 deletions(-) create mode 100644 UnitTests/DBP/DataBrokerPrerequisitesStatusVerifierTests.swift diff --git a/DuckDuckGo/DBP/DBPHomeViewController.swift b/DuckDuckGo/DBP/DBPHomeViewController.swift index 758bfb8f09..5cbc6b6934 100644 --- a/DuckDuckGo/DBP/DBPHomeViewController.swift +++ b/DuckDuckGo/DBP/DBPHomeViewController.swift @@ -147,8 +147,6 @@ final class DBPHomeViewController: NSViewController { case .valid: displayDBPUI() pixelHandler.fire(.homeViewShowWebUI) - case .unverified: - break } } diff --git a/DuckDuckGo/DBP/DataBrokerPrerequisitesStatusVerifier.swift b/DuckDuckGo/DBP/DataBrokerPrerequisitesStatusVerifier.swift index 2617e45957..f5f5c4d091 100644 --- a/DuckDuckGo/DBP/DataBrokerPrerequisitesStatusVerifier.swift +++ b/DuckDuckGo/DBP/DataBrokerPrerequisitesStatusVerifier.swift @@ -24,7 +24,6 @@ import LoginItems enum DataBrokerPrerequisitesStatus { case invalidDirectory case invalidSystemPermission - case unverified case valid } @@ -33,11 +32,16 @@ protocol DataBrokerPrerequisitesStatusVerifier: AnyObject { } final class DefaultDataBrokerPrerequisitesStatusVerifier: DataBrokerPrerequisitesStatusVerifier { + private let statusChecker: DBPLoginItemStatusChecker + + init(statusChecker: DBPLoginItemStatusChecker = LoginItem.dbpBackgroundAgent) { + self.statusChecker = statusChecker + } func checkStatus() -> DataBrokerPrerequisitesStatus { - if !LoginItem.dbpBackgroundAgent.doesHaveNecessaryPermissions() { + if !statusChecker.doesHaveNecessaryPermissions() { return .invalidSystemPermission - } else if !LoginItem.dbpBackgroundAgent.isInCorrectDirectory() { + } else if !statusChecker.isInCorrectDirectory() { return .invalidDirectory } else { return .valid diff --git a/UnitTests/DBP/DataBrokerPrerequisitesStatusVerifierTests.swift b/UnitTests/DBP/DataBrokerPrerequisitesStatusVerifierTests.swift new file mode 100644 index 0000000000..02c94e7feb --- /dev/null +++ b/UnitTests/DBP/DataBrokerPrerequisitesStatusVerifierTests.swift @@ -0,0 +1,73 @@ +// +// DataBrokerPrerequisitesStatusVerifierTests.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 XCTest +@testable import DuckDuckGo_Privacy_Browser +import DataBrokerProtection + +final class DataBrokerPrerequisitesStatusVerifierTests: XCTestCase { + private let statusChecker = MockDBPLoginItemStatusChecker() + + override func setUpWithError() throws { + } + + override func tearDownWithError() throws { + statusChecker.reset() + } + + func testIncorrectDirectory_thenReturnIncorrectDirectoryStatus() { + statusChecker.isInCorrectDirectoryValue = false + let result = DefaultDataBrokerPrerequisitesStatusVerifier(statusChecker: statusChecker).checkStatus() + XCTAssertEqual(result, DataBrokerPrerequisitesStatus.invalidDirectory) + } + + func testIncorrectPermission_thenReturnIncorrectPermissionStatus() { + statusChecker.doesHavePermissionValue = false + let result = DefaultDataBrokerPrerequisitesStatusVerifier(statusChecker: statusChecker).checkStatus() + XCTAssertEqual(result, DataBrokerPrerequisitesStatus.invalidSystemPermission) + } + + func testIncorrectDirectoryAndIncorrectPermission_thenReturnIncorrectPermissionStatus() { + statusChecker.isInCorrectDirectoryValue = false + statusChecker.doesHavePermissionValue = false + let result = DefaultDataBrokerPrerequisitesStatusVerifier(statusChecker: statusChecker).checkStatus() + XCTAssertEqual(result, DataBrokerPrerequisitesStatus.invalidSystemPermission) + } + + func testCorrectStatus_thenReturnValidStatus() { + let result = DefaultDataBrokerPrerequisitesStatusVerifier(statusChecker: statusChecker).checkStatus() + XCTAssertEqual(result, DataBrokerPrerequisitesStatus.valid) + } +} + +private final class MockDBPLoginItemStatusChecker: DBPLoginItemStatusChecker { + var doesHavePermissionValue = true + var isInCorrectDirectoryValue = true + + func doesHaveNecessaryPermissions() -> Bool { + return doesHavePermissionValue + } + func isInCorrectDirectory() -> Bool { + return isInCorrectDirectoryValue + } + + func reset() { + doesHavePermissionValue = true + isInCorrectDirectoryValue = true + } +} From 56d9df5a69bd59d964b763184f3835b8726676a5 Mon Sep 17 00:00:00 2001 From: Fernando Bunn Date: Mon, 22 Apr 2024 19:26:10 +0100 Subject: [PATCH 11/14] Add notification check --- DuckDuckGo.xcodeproj/project.pbxproj | 7 ++++++- DuckDuckGo/DBP/DBPHomeViewController.swift | 24 +++++++++++++++++++--- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 240bc87f91..fe39db8d4d 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -192,6 +192,8 @@ 31C9ADE62AF0564500CEF57D /* WaitlistFeatureSetupHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31C9ADE42AF0564500CEF57D /* WaitlistFeatureSetupHandler.swift */; }; 31CF3432288B0B1B0087244B /* NavigationBarBadgeAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31CF3431288B0B1B0087244B /* NavigationBarBadgeAnimator.swift */; }; 31D5375C291D944100407A95 /* PasswordManagementBitwardenItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D5375B291D944100407A95 /* PasswordManagementBitwardenItemView.swift */; }; + 31DC2F222BD6DE6C001354EF /* DataBrokerPrerequisitesStatusVerifierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31DC2F202BD6DE65001354EF /* DataBrokerPrerequisitesStatusVerifierTests.swift */; }; + 31DC2F232BD6E028001354EF /* DataBrokerPrerequisitesStatusVerifierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31DC2F202BD6DE65001354EF /* DataBrokerPrerequisitesStatusVerifierTests.swift */; }; 31E163BA293A56F400963C10 /* BrokenSiteReportingReferenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E163B9293A56F400963C10 /* BrokenSiteReportingReferenceTests.swift */; }; 31E163BD293A579E00963C10 /* PrivacyReferenceTestHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31E163BC293A579E00963C10 /* PrivacyReferenceTestHelper.swift */; }; 31E163C0293A581900963C10 /* privacy-reference-tests in Resources */ = {isa = PBXBuildFile; fileRef = 31E163BF293A581900963C10 /* privacy-reference-tests */; }; @@ -1420,7 +1422,6 @@ 560C3FFD2BC9911000F589CE /* PermanentSurveyManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 560C3FFB2BC9911000F589CE /* PermanentSurveyManagerTests.swift */; }; 560C3FFF2BCD5A1E00F589CE /* PermanentSurveyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 560C3FFE2BCD5A1E00F589CE /* PermanentSurveyManager.swift */; }; 560C40002BCD5A1E00F589CE /* PermanentSurveyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 560C3FFE2BCD5A1E00F589CE /* PermanentSurveyManager.swift */; }; - 560C40012BCD5A1E00F589CE /* PermanentSurveyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 560C3FFE2BCD5A1E00F589CE /* PermanentSurveyManager.swift */; }; 561D66662B95C45A008ACC5C /* Suggestion.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 561D66692B95C45A008ACC5C /* Suggestion.storyboard */; }; 561D66672B95C45A008ACC5C /* Suggestion.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 561D66692B95C45A008ACC5C /* Suggestion.storyboard */; }; 562984702AC4610100AC20EB /* SyncPreferencesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5629846E2AC4610100AC20EB /* SyncPreferencesTests.swift */; }; @@ -2834,6 +2835,7 @@ 31C9ADE42AF0564500CEF57D /* WaitlistFeatureSetupHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistFeatureSetupHandler.swift; sourceTree = ""; }; 31CF3431288B0B1B0087244B /* NavigationBarBadgeAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarBadgeAnimator.swift; sourceTree = ""; }; 31D5375B291D944100407A95 /* PasswordManagementBitwardenItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordManagementBitwardenItemView.swift; sourceTree = ""; }; + 31DC2F202BD6DE65001354EF /* DataBrokerPrerequisitesStatusVerifierTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataBrokerPrerequisitesStatusVerifierTests.swift; sourceTree = ""; }; 31E163B9293A56F400963C10 /* BrokenSiteReportingReferenceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrokenSiteReportingReferenceTests.swift; sourceTree = ""; }; 31E163BC293A579E00963C10 /* PrivacyReferenceTestHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyReferenceTestHelper.swift; sourceTree = ""; }; 31E163BF293A581900963C10 /* privacy-reference-tests */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "privacy-reference-tests"; path = "Submodules/privacy-reference-tests"; sourceTree = SOURCE_ROOT; }; @@ -4501,6 +4503,7 @@ isa = PBXGroup; children = ( 31A2FD162BAB41C500D0E741 /* DataBrokerProtectionVisibilityTests.swift */, + 31DC2F202BD6DE65001354EF /* DataBrokerPrerequisitesStatusVerifierTests.swift */, ); path = DBP; sourceTree = ""; @@ -10327,6 +10330,7 @@ 1D9FDEBE2B9B5F0F0040B78C /* CookiePopupProtectionPreferencesTests.swift in Sources */, 028904212A7B25770028369C /* AppConfigurationURLProviderTests.swift in Sources */, 3706FE6F293F661700E42796 /* LocalStatisticsStoreTests.swift in Sources */, + 31DC2F232BD6E028001354EF /* DataBrokerPrerequisitesStatusVerifierTests.swift in Sources */, 3706FE70293F661700E42796 /* HistoryCoordinatorTests.swift in Sources */, 9F3344632BBFBDA40040CBEB /* BookmarksBarVisibilityManagerTests.swift in Sources */, 3706FE71293F661700E42796 /* SavedStateMock.swift in Sources */, @@ -11562,6 +11566,7 @@ 142879DC24CE1185005419BB /* SuggestionContainerViewModelTests.swift in Sources */, 566B195D29CDB692007E38F4 /* MoreOptionsMenuTests.swift in Sources */, AA0877B826D5160D00B05660 /* SafariVersionReaderTests.swift in Sources */, + 31DC2F222BD6DE6C001354EF /* DataBrokerPrerequisitesStatusVerifierTests.swift in Sources */, B69B50452726C5C200758A2B /* AtbParserTests.swift in Sources */, 1D8C2FED2B70F5D0005E4BBD /* MockViewSnapshotRenderer.swift in Sources */, B6106BAF26A7C6180013B453 /* PermissionStoreMock.swift in Sources */, diff --git a/DuckDuckGo/DBP/DBPHomeViewController.swift b/DuckDuckGo/DBP/DBPHomeViewController.swift index 5cbc6b6934..55d08d7d62 100644 --- a/DuckDuckGo/DBP/DBPHomeViewController.swift +++ b/DuckDuckGo/DBP/DBPHomeViewController.swift @@ -35,6 +35,7 @@ final class DBPHomeViewController: NSViewController { private let dataBrokerProtectionManager: DataBrokerProtectionManager private let pixelHandler: EventMapping = DataBrokerProtectionPixelsHandler() private var currentChildViewController: NSViewController? + private var observer: NSObjectProtocol? private let prerequisiteVerifier: DataBrokerPrerequisitesStatusVerifier private lazy var errorViewController: DataBrokerProtectionErrorViewController = { @@ -87,9 +88,8 @@ final class DBPHomeViewController: NSViewController { override func viewDidLoad() { super.viewDidLoad() - if !shouldAskForInviteCode() { - setupUIWithCurrentStatus() - } + setupUI() + setupObserver() do { if try dataBrokerProtectionManager.dataManager.fetchProfile() != nil { @@ -116,6 +116,18 @@ final class DBPHomeViewController: NSViewController { errorViewController.view.frame = view.bounds } + private func setupUI() { + if !shouldAskForInviteCode() { + setupUIWithCurrentStatus() + } + } + + private func setupObserver() { + observer = NotificationCenter.default.addObserver(forName: NSApplication.didBecomeActiveNotification, object: nil, queue: nil) { [weak self] _ in + self?.setupUI() + } + } + private func presentInviteCodeFlow() { let viewModel = DataBrokerProtectionInviteDialogsViewModel(delegate: self) @@ -166,6 +178,12 @@ final class DBPHomeViewController: NSViewController { addAndLayoutChild(childViewController) self.currentChildViewController = childViewController } + + deinit { + if let observer = observer { + NotificationCenter.default.removeObserver(observer) + } + } } extension DBPHomeViewController: DataBrokerProtectionInviteDialogsViewModelDelegate { From 0bee3a1b4075e3569aa74d17b1a46aadd9a1476b Mon Sep 17 00:00:00 2001 From: Fernando Bunn Date: Tue, 23 Apr 2024 14:51:04 +0100 Subject: [PATCH 12/14] Update error views with ship review feedback --- .../Contents.json | 0 .../DBP-Icon.imageset/Contents.json | 0 .../DBP-Icon.imageset/DBP-Icon.pdf | Bin .../Contents.json | 0 .../DBP-Information-Remover.svg | 0 .../dbp-error-info.imageset/Contents.json | 15 +++++++++++++++ .../dbp-error-info.imageset/dbp-error-info.pdf | Bin 0 -> 1128 bytes ...DataBrokerProtectionErrorViewController.swift | 15 ++++++++------- 8 files changed, 23 insertions(+), 7 deletions(-) rename DuckDuckGo/Assets.xcassets/Images/{DataBrokerProtectionWaitlist => DataBrokerProtection}/Contents.json (100%) rename DuckDuckGo/Assets.xcassets/Images/{DataBrokerProtectionWaitlist => DataBrokerProtection}/DBP-Icon.imageset/Contents.json (100%) rename DuckDuckGo/Assets.xcassets/Images/{DataBrokerProtectionWaitlist => DataBrokerProtection}/DBP-Icon.imageset/DBP-Icon.pdf (100%) rename DuckDuckGo/Assets.xcassets/Images/{DataBrokerProtectionWaitlist => DataBrokerProtection}/DBP-Information-Remover.imageset/Contents.json (100%) rename DuckDuckGo/Assets.xcassets/Images/{DataBrokerProtectionWaitlist => DataBrokerProtection}/DBP-Information-Remover.imageset/DBP-Information-Remover.svg (100%) create mode 100644 DuckDuckGo/Assets.xcassets/Images/DataBrokerProtection/dbp-error-info.imageset/Contents.json create mode 100644 DuckDuckGo/Assets.xcassets/Images/DataBrokerProtection/dbp-error-info.imageset/dbp-error-info.pdf diff --git a/DuckDuckGo/Assets.xcassets/Images/DataBrokerProtectionWaitlist/Contents.json b/DuckDuckGo/Assets.xcassets/Images/DataBrokerProtection/Contents.json similarity index 100% rename from DuckDuckGo/Assets.xcassets/Images/DataBrokerProtectionWaitlist/Contents.json rename to DuckDuckGo/Assets.xcassets/Images/DataBrokerProtection/Contents.json diff --git a/DuckDuckGo/Assets.xcassets/Images/DataBrokerProtectionWaitlist/DBP-Icon.imageset/Contents.json b/DuckDuckGo/Assets.xcassets/Images/DataBrokerProtection/DBP-Icon.imageset/Contents.json similarity index 100% rename from DuckDuckGo/Assets.xcassets/Images/DataBrokerProtectionWaitlist/DBP-Icon.imageset/Contents.json rename to DuckDuckGo/Assets.xcassets/Images/DataBrokerProtection/DBP-Icon.imageset/Contents.json diff --git a/DuckDuckGo/Assets.xcassets/Images/DataBrokerProtectionWaitlist/DBP-Icon.imageset/DBP-Icon.pdf b/DuckDuckGo/Assets.xcassets/Images/DataBrokerProtection/DBP-Icon.imageset/DBP-Icon.pdf similarity index 100% rename from DuckDuckGo/Assets.xcassets/Images/DataBrokerProtectionWaitlist/DBP-Icon.imageset/DBP-Icon.pdf rename to DuckDuckGo/Assets.xcassets/Images/DataBrokerProtection/DBP-Icon.imageset/DBP-Icon.pdf diff --git a/DuckDuckGo/Assets.xcassets/Images/DataBrokerProtectionWaitlist/DBP-Information-Remover.imageset/Contents.json b/DuckDuckGo/Assets.xcassets/Images/DataBrokerProtection/DBP-Information-Remover.imageset/Contents.json similarity index 100% rename from DuckDuckGo/Assets.xcassets/Images/DataBrokerProtectionWaitlist/DBP-Information-Remover.imageset/Contents.json rename to DuckDuckGo/Assets.xcassets/Images/DataBrokerProtection/DBP-Information-Remover.imageset/Contents.json diff --git a/DuckDuckGo/Assets.xcassets/Images/DataBrokerProtectionWaitlist/DBP-Information-Remover.imageset/DBP-Information-Remover.svg b/DuckDuckGo/Assets.xcassets/Images/DataBrokerProtection/DBP-Information-Remover.imageset/DBP-Information-Remover.svg similarity index 100% rename from DuckDuckGo/Assets.xcassets/Images/DataBrokerProtectionWaitlist/DBP-Information-Remover.imageset/DBP-Information-Remover.svg rename to DuckDuckGo/Assets.xcassets/Images/DataBrokerProtection/DBP-Information-Remover.imageset/DBP-Information-Remover.svg diff --git a/DuckDuckGo/Assets.xcassets/Images/DataBrokerProtection/dbp-error-info.imageset/Contents.json b/DuckDuckGo/Assets.xcassets/Images/DataBrokerProtection/dbp-error-info.imageset/Contents.json new file mode 100644 index 0000000000..9869fe984d --- /dev/null +++ b/DuckDuckGo/Assets.xcassets/Images/DataBrokerProtection/dbp-error-info.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "dbp-error-info.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/DuckDuckGo/Assets.xcassets/Images/DataBrokerProtection/dbp-error-info.imageset/dbp-error-info.pdf b/DuckDuckGo/Assets.xcassets/Images/DataBrokerProtection/dbp-error-info.imageset/dbp-error-info.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c9486753e58a3af9f18ee9f0049ae4025ab7e564 GIT binary patch literal 1128 zcmY!laBvK;I`dFTEr~!5FAK2q*+Jp}3?dH8Gc~f^liEcb=%Bz@6VB#f{H*?0b}!f+2m~Vf|(iTK5B2UpAz;|A|T6cGKkNT7?^HChE?)KULdovdI=EU00>>l2*5)g^d!=G`8927sSlH)AXQo z*EX|5!RK5;#B&riJrY~aYMt2`+t9Q4Vnq9Q<%o9X0+*W`th2ghr7#w()jU2!d23}v01{~J!?JbGHX{J_fH$2a7P)ZS^BUbsH_AOBXDH+`UF z0gYE!0s+N1B;lBt8N!o|30xGIob(;@^72cH6{2IIi6x}6AXPy>ATb@5dVEt;G83Kh zD-@y?4D<}Z0KqUafe9g4Fq48(i}Op1l2eNnKnWO>@<55vIX|x?H4kVbDD}bw0}_jX zVhW~EA*3`85`w3FeecW^pi>n{Qe=H?h0Ele!ns!ECyGjmdlxInSz=>iN6jpF>=R80kq%oI&Xpy&tX=a(pe gJPZzC{ou^1RG{m@89%Y81neD419L7_RabvE04{HW=>Px# literal 0 HcmV?d00001 diff --git a/DuckDuckGo/DBP/ErrorView/DataBrokerProtectionErrorViewController.swift b/DuckDuckGo/DBP/ErrorView/DataBrokerProtectionErrorViewController.swift index abf53f3831..fa628b686e 100644 --- a/DuckDuckGo/DBP/ErrorView/DataBrokerProtectionErrorViewController.swift +++ b/DuckDuckGo/DBP/ErrorView/DataBrokerProtectionErrorViewController.swift @@ -56,14 +56,15 @@ struct DataBrokerProtectionErrorView: View { } .padding(.bottom, 25) - Image("Alert-Color-16") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 24, height: 24) + HStack { + Image("dbp-error-info") + .resizable() + .frame(width: 24, height: 24) - Text(viewModel.title) - .font(.title) - .fontWeight(.light) + Text(viewModel.title) + .font(.title) + .fontWeight(.light) + } Text(viewModel.message) .font(.body) From 9d28254b0d506d196c9c7297ae8e6229909b002d Mon Sep 17 00:00:00 2001 From: Fernando Bunn Date: Tue, 23 Apr 2024 19:48:52 +0100 Subject: [PATCH 13/14] Update copy --- DuckDuckGo/Common/Localizables/UserText.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo/Common/Localizables/UserText.swift b/DuckDuckGo/Common/Localizables/UserText.swift index 5fd1c949d3..a76df34f95 100644 --- a/DuckDuckGo/Common/Localizables/UserText.swift +++ b/DuckDuckGo/Common/Localizables/UserText.swift @@ -1142,10 +1142,10 @@ struct UserText { // MARK: - DBP Error pages static let dbpErrorPageBadPathTitle = "Move DuckDuckGo App to Applications" - static let dbpErrorPageBadPathMessage = "To use Personal Information Removal, the DuckDuckGo app needs to be in the Applications folder on your Mac. Click the button bellow to move the app and restart the browser." - static let dbpErrorPageBadPathCTA = "Move App for Me and Restart..." + static let dbpErrorPageBadPathMessage = "To use Personal Information Removal, the DuckDuckGo app needs to be in the Applications folder on your Mac. You move the app yourself and restart the browser, or we can do it for you." + static let dbpErrorPageBadPathCTA = "Move App for Me..." static let dbpErrorPageNoPermissionTitle = "Change System Setting" - static let dbpErrorPageNoPermissionMessage = "Open System Settings and allow DuckDuckGo Personal Information Removal to run in the background" + static let dbpErrorPageNoPermissionMessage = "Open System Settings and allow DuckDuckGo Personal Information Removal to run in the background." static let dbpErrorPageNoPermissionCTA = "Open System Settings..." } From f1c068be2fcdd9642e41ebc99688e8160a499a2f Mon Sep 17 00:00:00 2001 From: Fernando Bunn Date: Tue, 23 Apr 2024 20:05:19 +0100 Subject: [PATCH 14/14] fix typo --- DuckDuckGo/Common/Localizables/UserText.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo/Common/Localizables/UserText.swift b/DuckDuckGo/Common/Localizables/UserText.swift index a76df34f95..85eb68cad2 100644 --- a/DuckDuckGo/Common/Localizables/UserText.swift +++ b/DuckDuckGo/Common/Localizables/UserText.swift @@ -1142,7 +1142,7 @@ struct UserText { // MARK: - DBP Error pages static let dbpErrorPageBadPathTitle = "Move DuckDuckGo App to Applications" - static let dbpErrorPageBadPathMessage = "To use Personal Information Removal, the DuckDuckGo app needs to be in the Applications folder on your Mac. You move the app yourself and restart the browser, or we can do it for you." + static let dbpErrorPageBadPathMessage = "To use Personal Information Removal, the DuckDuckGo app needs to be in the Applications folder on your Mac. You can move the app yourself and restart the browser, or we can do it for you." static let dbpErrorPageBadPathCTA = "Move App for Me..." static let dbpErrorPageNoPermissionTitle = "Change System Setting"