Skip to content

Commit

Permalink
Subscription refactoring - UI handler (#2853)
Browse files Browse the repository at this point in the history
Task/Issue URL:
https://app.asana.com/0/1205842942115003/1206805455884775/f

All UI interactions have been extracted from Subscription business logic and isolated in a mockable UI handler
  • Loading branch information
federicocappelli authored Jun 13, 2024
1 parent f072edf commit e688145
Show file tree
Hide file tree
Showing 8 changed files with 274 additions and 118 deletions.
14 changes: 13 additions & 1 deletion DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -2591,6 +2591,8 @@
F118EA7E2BEA2B8700F77634 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F118EA7C2BEA2B8700F77634 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift */; };
F118EA852BEACC7000F77634 /* NonStandardPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F118EA842BEACC7000F77634 /* NonStandardPixel.swift */; };
F118EA862BEACC7000F77634 /* NonStandardPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F118EA842BEACC7000F77634 /* NonStandardPixel.swift */; };
F1476FC02C1359FB00EAE46A /* SubscriptionUIHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1476FBF2C1359FB00EAE46A /* SubscriptionUIHandler.swift */; };
F1476FC12C1359FB00EAE46A /* SubscriptionUIHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1476FBF2C1359FB00EAE46A /* SubscriptionUIHandler.swift */; };
F188267C2BBEB3AA00D9AC4F /* GeneralPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */; };
F188267D2BBEB3AA00D9AC4F /* GeneralPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */; };
F18826802BBEB58100D9AC4F /* PrivacyProPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */; };
Expand All @@ -2615,6 +2617,8 @@
F1B33DF32BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */; };
F1B33DF62BAD970E001128B3 /* SubscriptionErrorReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B33DF52BAD970E001128B3 /* SubscriptionErrorReporter.swift */; };
F1B33DF72BAD970E001128B3 /* SubscriptionErrorReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B33DF52BAD970E001128B3 /* SubscriptionErrorReporter.swift */; };
F1C5763E2BFF972900C78647 /* SubscriptionUIHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1C5763D2BFF972900C78647 /* SubscriptionUIHandling.swift */; };
F1C5763F2BFF972900C78647 /* SubscriptionUIHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1C5763D2BFF972900C78647 /* SubscriptionUIHandling.swift */; };
F1C70D792BFF50A400599292 /* DataBrokerProtectionLoginItemInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1C70D782BFF50A400599292 /* DataBrokerProtectionLoginItemInterface.swift */; };
F1C70D7A2BFF50A400599292 /* DataBrokerProtectionLoginItemInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1C70D782BFF50A400599292 /* DataBrokerProtectionLoginItemInterface.swift */; };
F1C70D7C2BFF510000599292 /* SubscriptionEnvironment+Default.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1C70D7B2BFF510000599292 /* SubscriptionEnvironment+Default.swift */; };
Expand Down Expand Up @@ -3427,8 +3431,8 @@
7BCB90C12C18626E008E3543 /* VPNControllerXPCClient+ConvenienceInitializers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VPNControllerXPCClient+ConvenienceInitializers.swift"; sourceTree = "<group>"; };
7BD1688D2AD4A4C400D24876 /* NetworkExtensionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkExtensionController.swift; sourceTree = "<group>"; };
7BD3AF5C2A8E7AF1006F9F56 /* KeychainType+ClientDefault.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeychainType+ClientDefault.swift"; sourceTree = "<group>"; };
7BD8679A2A9E9E000063B9F7 /* VPNFeatureGatekeeper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VPNFeatureGatekeeper.swift; sourceTree = "<group>"; };
7BD7B0002C19D3830039D20A /* VPNIPCResources.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNIPCResources.swift; sourceTree = "<group>"; };
7BD8679A2A9E9E000063B9F7 /* VPNFeatureGatekeeper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VPNFeatureGatekeeper.swift; sourceTree = "<group>"; };
7BDA36E52B7E037100AD5388 /* VPNProxyExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = VPNProxyExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
7BDA36EA2B7E037200AD5388 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
7BDA36EB2B7E037200AD5388 /* VPNProxyExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = VPNProxyExtension.entitlements; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4143,11 +4147,13 @@
EEF53E172950CED5002D78F4 /* JSAlertViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSAlertViewModelTests.swift; sourceTree = "<group>"; };
F118EA7C2BEA2B8700F77634 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift"; sourceTree = "<group>"; };
F118EA842BEACC7000F77634 /* NonStandardPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonStandardPixel.swift; sourceTree = "<group>"; };
F1476FBF2C1359FB00EAE46A /* SubscriptionUIHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionUIHandler.swift; sourceTree = "<group>"; };
F188267B2BBEB3AA00D9AC4F /* GeneralPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPixel.swift; sourceTree = "<group>"; };
F188267F2BBEB58100D9AC4F /* PrivacyProPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyProPixel.swift; sourceTree = "<group>"; };
F18826832BBEE31700D9AC4F /* PixelKit+Assertion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PixelKit+Assertion.swift"; sourceTree = "<group>"; };
F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionAppStoreRestorer.swift; sourceTree = "<group>"; };
F1B33DF52BAD970E001128B3 /* SubscriptionErrorReporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionErrorReporter.swift; sourceTree = "<group>"; };
F1C5763D2BFF972900C78647 /* SubscriptionUIHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionUIHandling.swift; sourceTree = "<group>"; };
F1C70D782BFF50A400599292 /* DataBrokerProtectionLoginItemInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataBrokerProtectionLoginItemInterface.swift; sourceTree = "<group>"; };
F1C70D7B2BFF510000599292 /* SubscriptionEnvironment+Default.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SubscriptionEnvironment+Default.swift"; sourceTree = "<group>"; };
F1D042932BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataBrokerProtectionSettings+Environment.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -8314,6 +8320,7 @@
isa = PBXGroup;
children = (
F1C70D7B2BFF510000599292 /* SubscriptionEnvironment+Default.swift */,
F1476FBF2C1359FB00EAE46A /* SubscriptionUIHandler.swift */,
F1D042932BFBA12300A31506 /* DataBrokerProtectionSettings+Environment.swift */,
F118EA7C2BEA2B8700F77634 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift */,
F1DA51842BF607D200CF29FA /* SubscriptionAttributionPixelHandler.swift */,
Expand All @@ -8328,6 +8335,7 @@
isa = PBXGroup;
children = (
1E0C72052ABC63BD00802009 /* SubscriptionPagesUserScript.swift */,
F1C5763D2BFF972900C78647 /* SubscriptionUIHandling.swift */,
F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */,
F1B33DF52BAD970E001128B3 /* SubscriptionErrorReporter.swift */,
);
Expand Down Expand Up @@ -10063,6 +10071,7 @@
3706FBCB293F65D500E42796 /* BookmarkHTMLImporter.swift in Sources */,
4BF97ADC2B43C5E200EB4240 /* VPNFeedbackSender.swift in Sources */,
987799F72999996B005D8EB6 /* BookmarkDatabase.swift in Sources */,
F1C5763F2BFF972900C78647 /* SubscriptionUIHandling.swift in Sources */,
BDA7647D2BC497BE00D0400C /* DefaultVPNLocationFormatter.swift in Sources */,
3706FBCC293F65D500E42796 /* CustomRoundedCornersShape.swift in Sources */,
3706FBCD293F65D500E42796 /* LocaleExtension.swift in Sources */,
Expand Down Expand Up @@ -10203,6 +10212,7 @@
3706FC25293F65D500E42796 /* ColorView.swift in Sources */,
3706FC26293F65D500E42796 /* RecentlyClosedCacheItem.swift in Sources */,
3706FC27293F65D500E42796 /* PopupBlockedPopover.swift in Sources */,
F1476FC12C1359FB00EAE46A /* SubscriptionUIHandler.swift in Sources */,
3706FC28293F65D500E42796 /* SaveCredentialsPopover.swift in Sources */,
3707C728294B5D2900682A9F /* WKWebViewConfigurationExtensions.swift in Sources */,
3706FC29293F65D500E42796 /* QuartzIdleStateProvider.swift in Sources */,
Expand Down Expand Up @@ -11591,6 +11601,7 @@
566B736C2BECC3C600FF1959 /* SyncPausedStateManaging.swift in Sources */,
4BD18F01283F0BC500058124 /* BookmarksBarViewController.swift in Sources */,
379DE4BD27EA31AC002CC3DE /* PreferencesAutofillView.swift in Sources */,
F1476FC02C1359FB00EAE46A /* SubscriptionUIHandler.swift in Sources */,
1DCFBC8A29ADF32B00313531 /* BurnerHomePageView.swift in Sources */,
858A797F26A79EAA00A75A42 /* UserText+PasswordManager.swift in Sources */,
B693954E26F04BEB0015B914 /* LoadingProgressView.swift in Sources */,
Expand Down Expand Up @@ -11710,6 +11721,7 @@
31F28C5328C8EECA00119F70 /* DuckURLSchemeHandler.swift in Sources */,
AA13DCB4271480B0006D48D3 /* FirePopoverViewModel.swift in Sources */,
1DDC84F72B83558F00670238 /* PreferencesPrivateSearchView.swift in Sources */,
F1C5763E2BFF972900C78647 /* SubscriptionUIHandling.swift in Sources */,
1D43EB38292B636E0065E5D6 /* BWCommand.swift in Sources */,
F41D174125CB131900472416 /* NSColorExtension.swift in Sources */,
AAC5E4F625D6BF2C007F5990 /* AddressBarButtonsViewController.swift in Sources */,
Expand Down
16 changes: 10 additions & 6 deletions DuckDuckGo/Application/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
let bookmarksManager = LocalBookmarkManager.shared
var privacyDashboardWindow: NSWindow?

private var accountManager: AccountManaging {
subscriptionManager.accountManager
}
public let subscriptionManager: SubscriptionManaging
public let subscriptionUIHandler: SubscriptionUIHandling

public let vpnSettings = VPNSettings(defaults: .netP)

// MARK: - VPN
Expand Down Expand Up @@ -126,7 +125,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
)

return VPNRedditSessionWorkaround(
accountManager: accountManager,
accountManager: subscriptionManager.accountManager,
ipcClient: ipcClient,
statusReporter: statusReporter
)
Expand Down Expand Up @@ -220,6 +219,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate {

// Configure Subscription
subscriptionManager = SubscriptionManager()
subscriptionUIHandler = SubscriptionUIHandler(windowControllersManagerProvider: {
return WindowControllersManager.shared
})

// Update VPN environment and match the Subscription environment
vpnSettings.alignTo(subscriptionEnvironment: subscriptionManager.currentEnvironment)
Expand Down Expand Up @@ -347,7 +349,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
#endif

#if DBP
DataBrokerProtectionAppEvents(featureGatekeeper: DefaultDataBrokerProtectionFeatureGatekeeper(accountManager: accountManager)).applicationDidFinishLaunching()
DataBrokerProtectionAppEvents(featureGatekeeper: DefaultDataBrokerProtectionFeatureGatekeeper(accountManager: subscriptionManager.accountManager)).applicationDidFinishLaunching()
#endif

setUpAutoClearHandler()
Expand Down Expand Up @@ -377,7 +379,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate {

NetworkProtectionAppEvents(featureGatekeeper: DefaultVPNFeatureGatekeeper(subscriptionManager: subscriptionManager)).applicationDidBecomeActive()
#if DBP
DataBrokerProtectionAppEvents(featureGatekeeper: DefaultDataBrokerProtectionFeatureGatekeeper(accountManager: accountManager)).applicationDidBecomeActive()
DataBrokerProtectionAppEvents(featureGatekeeper:
DefaultDataBrokerProtectionFeatureGatekeeper(accountManager:
subscriptionManager.accountManager)).applicationDidBecomeActive()
#endif

AppPrivacyFeatures.shared.contentBlocking.privacyConfigurationManager.toggleProtectionsCounter.sendEventsIfNeeded()
Expand Down
10 changes: 3 additions & 7 deletions DuckDuckGo/Preferences/View/PreferencesRootView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,9 @@ enum Preferences {
let sheetActionHandler = SubscriptionAccessActionHandlers(restorePurchases: {
if #available(macOS 12.0, *) {
Task {
guard let mainViewController = WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController,
let windowControllerManager = WindowControllersManager.shared.lastKeyMainWindowController else {
return
}

let subscriptionAppStoreRestorer = SubscriptionAppStoreRestorer(subscriptionManager: Application.appDelegate.subscriptionManager)
await subscriptionAppStoreRestorer.restoreAppStoreSubscription(mainViewController: mainViewController, windowController: windowControllerManager)
let subscriptionAppStoreRestorer = SubscriptionAppStoreRestorer(subscriptionManager: Application.appDelegate.subscriptionManager,
uiHandler: Application.appDelegate.subscriptionUIHandler)
await subscriptionAppStoreRestorer.restoreAppStoreSubscription()
}
}
},
Expand Down
111 changes: 111 additions & 0 deletions DuckDuckGo/Subscription/SubscriptionUIHandler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
//
// SubscriptionUIHandler.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 SubscriptionUI

@MainActor
final class SubscriptionUIHandler: SubscriptionUIHandling {

fileprivate var currentWindow: NSWindow? { windowControllersManagerProvider().lastKeyMainWindowController?.window }
fileprivate var currentMainViewController: MainViewController? {
windowControllersManagerProvider().lastKeyMainWindowController?.mainViewController
}
fileprivate var windowControllersManager: WindowControllersManager { windowControllersManagerProvider() }
typealias WindowControllersManagerProvider = () -> WindowControllersManager
fileprivate nonisolated let windowControllersManagerProvider: WindowControllersManagerProvider
fileprivate var progressViewController: ProgressViewController?

nonisolated init(windowControllersManagerProvider: @escaping WindowControllersManagerProvider) {
self.windowControllersManagerProvider = windowControllersManagerProvider
}

// MARK: - SubscriptionUIHandling

func presentProgressViewController(withTitle: String) {
progressViewController = ProgressViewController(title: UserText.purchasingSubscriptionTitle)
currentMainViewController?.presentAsSheet(progressViewController!)
}

func dismissProgressViewController() {
progressViewController?.dismiss()
progressViewController = nil
}

func updateProgressViewController(title: String) {
progressViewController?.updateTitleText(UserText.completingPurchaseTitle)
}

func presentSubscriptionAccessViewController(handler: any SubscriptionAccessActionHandling, message: WKScriptMessage) {
let actionHandlers = SubscriptionAccessActionHandlers(restorePurchases: {
handler.subscriptionAccessActionRestorePurchases(message: message)
}, openURLHandler: { url in
handler.subscriptionAccessActionOpenURLHandler(url: url)
}, uiActionHandler: { event in
handler.subscriptionAccessActionHandleAction(event: event)
})

let newSubscriptionAccessViewController = SubscriptionAccessViewController(subscriptionManager: Application.appDelegate.subscriptionManager,
actionHandlers: actionHandlers)
currentMainViewController?.presentAsSheet(newSubscriptionAccessViewController)
}

func show(alertType: SubscriptionAlertType, text: String? = nil, firstButtonAction: (() -> Void)? = nil) {

var alert: NSAlert?
switch alertType {
case .somethingWentWrong:
alert = .somethingWentWrongAlert()
case .subscriptionNotFound:
alert = .subscriptionNotFoundAlert()
case .subscriptionInactive:
alert = .subscriptionInactiveAlert()
case .subscriptionFound:
alert = .subscriptionFoundAlert()
case .appleIDSyncFailed:
guard let text else {
assertionFailure("Trying to present appleIDSyncFailed alert without required text")
return
}
alert = .appleIDSyncFailedAlert(text: text)
}

guard let alert else {
assertionFailure("Missing subscription alert")
return
}

currentWindow?.show(alert, firstButtonAction: firstButtonAction)
}

func show(alertType: SubscriptionAlertType) {
show(alertType: alertType, text: nil, firstButtonAction: nil)
}

func show(alertType: SubscriptionAlertType, firstButtonAction: (() -> Void)?) {
show(alertType: alertType, text: nil, firstButtonAction: firstButtonAction)
}

func show(alertType: SubscriptionAlertType, text: String?) {
show(alertType: alertType, text: text, firstButtonAction: nil)
}

func showTab(with content: Tab.TabContent) {
self.windowControllersManager.showTab(with: content)
}
}
Loading

0 comments on commit e688145

Please sign in to comment.