Skip to content

Commit

Permalink
Improvements to subscription settings (#2959)
Browse files Browse the repository at this point in the history
Task/Issue URL: https://app.asana.com/0/1203936086921904/1207147238749956/f

Description:
Make the entry point for managing subscription functionality more obvious so that users have a sense of control over their subscriptions, allowing them to make changes easily without the need for customer support.
  • Loading branch information
miasma13 authored Jun 30, 2024
1 parent 4e559d6 commit 23f26ab
Show file tree
Hide file tree
Showing 19 changed files with 349 additions and 315 deletions.
2 changes: 0 additions & 2 deletions Core/PixelEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -647,7 +647,6 @@ extension Pixel {
case privacyProRestoreAfterPurchaseAttempt
case privacyProSubscriptionActivated
case privacyProWelcomeAddDevice
case privacyProSettingsAddDevice
case privacyProAddDeviceEnterEmail
case privacyProWelcomeVPN
case privacyProWelcomePersonalInformationRemoval
Expand Down Expand Up @@ -1348,7 +1347,6 @@ extension Pixel.Event {
case .privacyProRestoreAfterPurchaseAttempt: return "m_privacy-pro_app_subscription-restore-after-purchase-attempt_success"
case .privacyProSubscriptionActivated: return "m_privacy-pro_app_subscription_activated_u"
case .privacyProWelcomeAddDevice: return "m_privacy-pro_welcome_add-device_click_u"
case .privacyProSettingsAddDevice: return "m_privacy-pro_settings_add-device_click"
case .privacyProAddDeviceEnterEmail: return "m_privacy-pro_add-device_enter-email_click"
case .privacyProWelcomeVPN: return "m_privacy-pro_welcome_vpn_click_u"
case .privacyProWelcomePersonalInformationRemoval: return "m_privacy-pro_welcome_personal-information-removal_click_u"
Expand Down
2 changes: 1 addition & 1 deletion DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -9858,7 +9858,7 @@
repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit";
requirement = {
kind = exactVersion;
version = 163.0.0;
version = 163.0.1;
};
};
9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/DuckDuckGo/BrowserServicesKit",
"state" : {
"revision" : "a51fed4db0c332cd4f02eafca2d9c7a178c0829a",
"version" : "163.0.0"
"revision" : "39e10c8eeddeb03750350597bd55fd8c43b5fd83",
"version" : "163.0.1"
}
},
{
Expand Down
16 changes: 5 additions & 11 deletions DuckDuckGo/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,8 @@ import WebKit

AppDependencyProvider.shared.userBehaviorMonitor.handleAction(.reopenApp)

AppDependencyProvider.shared.subscriptionManager.loadInitialData()

setUpAutofillPixelReporter()

return true
Expand Down Expand Up @@ -551,18 +553,10 @@ import WebKit
}

func updateSubscriptionStatus() {
Task {
guard let token = accountManager.accessToken else { return }
var subscriptionService: SubscriptionEndpointService {
AppDependencyProvider.shared.subscriptionManager.subscriptionEndpointService
}
if case .success(let subscription) = await subscriptionService.getSubscription(accessToken: token,
cachePolicy: .reloadIgnoringLocalCacheData) {
if subscription.isActive {
DailyPixel.fire(pixel: .privacyProSubscriptionActive)
}
AppDependencyProvider.shared.subscriptionManager.updateSubscriptionStatus { isActive in
if isActive {
DailyPixel.fire(pixel: .privacyProSubscriptionActive)
}
await accountManager.fetchEntitlements(cachePolicy: .reloadIgnoringLocalCacheData)
}
}

Expand Down
3 changes: 0 additions & 3 deletions DuckDuckGo/SettingsRootView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,6 @@ struct SettingsRootView: View {
SubscriptionContainerViewFactory.makeSubscribeFlow(origin: origin,
navigationCoordinator: subscriptionNavigationCoordinator,
subscriptionManager: AppDependencyProvider.shared.subscriptionManager)
case .subscriptionRestoreFlow:
SubscriptionContainerViewFactory.makeRestoreFlow(navigationCoordinator: subscriptionNavigationCoordinator,
subscriptionManager: AppDependencyProvider.shared.subscriptionManager)
default:
EmptyView()
}
Expand Down
2 changes: 0 additions & 2 deletions DuckDuckGo/SettingsViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,6 @@ extension SettingsViewModel {
case dbp
case itr
case subscriptionFlow(origin: String? = nil)
case subscriptionRestoreFlow
// Add other cases as needed

var id: String {
Expand All @@ -618,7 +617,6 @@ extension SettingsViewModel {
case .dbp: return "dbp"
case .itr: return "itr"
case .subscriptionFlow: return "subscriptionFlow"
case .subscriptionRestoreFlow: return "subscriptionRestoreFlow"
// Ensure all cases are covered
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,19 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec
private let subscriptionManager: SubscriptionManager
private var accountManager: AccountManager { subscriptionManager.accountManager }
private let appStorePurchaseFlow: AppStorePurchaseFlow

private let appStoreRestoreFlow: AppStoreRestoreFlow
private let appStoreAccountManagementFlow: AppStoreAccountManagementFlow

init(subscriptionManager: SubscriptionManager,
subscriptionAttributionOrigin: String?,
appStorePurchaseFlow: AppStorePurchaseFlow,
appStoreRestoreFlow: AppStoreRestoreFlow) {
appStoreRestoreFlow: AppStoreRestoreFlow,
appStoreAccountManagementFlow: AppStoreAccountManagementFlow) {
self.subscriptionManager = subscriptionManager
self.appStorePurchaseFlow = appStorePurchaseFlow
self.appStoreRestoreFlow = appStoreRestoreFlow
self.appStoreAccountManagementFlow = appStoreAccountManagementFlow
self.subscriptionAttributionOrigin = subscriptionAttributionOrigin
}

Expand Down Expand Up @@ -187,7 +191,9 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec

// MARK: Broker Methods (Called from WebView via UserScripts)
func getSubscription(params: Any, original: WKScriptMessage) async -> Encodable? {
await appStoreAccountManagementFlow.refreshAuthTokenIfNeeded()
let authToken = accountManager.authToken ?? Constants.empty

return [Constants.token: authToken]
}

Expand Down Expand Up @@ -399,7 +405,6 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec
// MARK: Native methods - Called from ViewModels
func restoreAccountFromAppStorePurchase() async throws {
setTransactionStatus(.restoring)
// let appStoreRestoreFlow = AppStoreRestoreFlow(subscriptionManager: subscriptionManager)
let result = await appStoreRestoreFlow.restoreAccountFromPastPurchase()
switch result {
case .success:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,13 @@ final class SubscriptionContainerViewModel: ObservableObject {
self.userScript = userScript
subFeature.cleanup()
self.subFeature = subFeature
let appStoreAccountManagementFlow = DefaultAppStoreAccountManagementFlow(subscriptionManager: subscriptionManager)
self.flow = SubscriptionFlowViewModel(origin: origin,
userScript: userScript,
subFeature: subFeature,
subscriptionManager: subscriptionManager)
self.restore = SubscriptionRestoreViewModel(userScript: userScript,
subFeature: subFeature,
subscriptionManager: subscriptionManager,
appStoreAccountManagementFlow: appStoreAccountManagementFlow)
subscriptionManager: subscriptionManager)
self.email = SubscriptionEmailViewModel(userScript: userScript,
subFeature: subFeature,
subscriptionManager: subscriptionManager)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ final class SubscriptionEmailViewModel: ObservableObject {
webViewModel.url?.forComparison() == subscriptionPurchaseURL.forComparison()
}

private var isVerifySubscriptionPage: Bool {
let confirmSubscriptionURL = subscriptionManager.url(for: .baseURL).appendingPathComponent("confirm")
return webViewModel.url?.forComparison() == confirmSubscriptionURL.forComparison()
}

init(userScript: SubscriptionPagesUserScript,
subFeature: SubscriptionPagesUseSubscriptionFeature,
subscriptionManager: SubscriptionManager) {
Expand Down Expand Up @@ -132,7 +137,7 @@ final class SubscriptionEmailViewModel: ObservableObject {
let addEmailToSubscriptionURL = subscriptionManager.url(for: .addEmail)
let manageSubscriptionEmailURL = subscriptionManager.url(for: .manageEmail)
emailURL = accountManager.email == nil ? addEmailToSubscriptionURL : manageSubscriptionEmailURL
state.viewTitle = accountManager.email == nil ? UserText.subscriptionRestoreAddEmailTitle : UserText.subscriptionManageEmailTitle
state.viewTitle = accountManager.email == nil ? UserText.subscriptionRestoreAddEmailTitle : UserText.subscriptionEditEmailTitle

// Also we assume subscription requires managing, and not activation
state.managingSubscriptionEmail = true
Expand Down Expand Up @@ -224,15 +229,13 @@ final class SubscriptionEmailViewModel: ObservableObject {
private func updateBackButton(canNavigateBack: Bool) {

// If the view is not Activation Success, or Welcome page, allow WebView Back Navigation
if !isWelcomePageOrSuccessPage {
if !isWelcomePageOrSuccessPage && !isVerifySubscriptionPage {
self.state.canNavigateBack = canNavigateBack
self.state.backButtonTitle = UserText.backButtonTitle
} else {
self.state.canNavigateBack = false
self.state.backButtonTitle = UserText.settingsTitle
}


}

// MARK: -
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ final class SubscriptionRestoreViewModel: ObservableObject {
let subFeature: SubscriptionPagesUseSubscriptionFeature
let subscriptionManager: SubscriptionManager
var accountManager: AccountManager { subscriptionManager.accountManager }
let appStoreAccountManagementFlow: AppStoreAccountManagementFlow

private var cancellables = Set<AnyCancellable>()

Expand All @@ -39,7 +38,6 @@ final class SubscriptionRestoreViewModel: ObservableObject {
}

struct State {
var isAddingDevice: Bool = false
var transactionStatus: SubscriptionTransactionStatus = .idle
var activationResult: SubscriptionActivationResult = .unknown
var subscriptionEmail: String?
Expand All @@ -60,68 +58,28 @@ final class SubscriptionRestoreViewModel: ObservableObject {
init(userScript: SubscriptionPagesUserScript,
subFeature: SubscriptionPagesUseSubscriptionFeature,
subscriptionManager: SubscriptionManager,
appStoreAccountManagementFlow: AppStoreAccountManagementFlow,
isAddingDevice: Bool = false) {
self.userScript = userScript
self.subFeature = subFeature
self.subscriptionManager = subscriptionManager
self.appStoreAccountManagementFlow = appStoreAccountManagementFlow
self.state.isAddingDevice = false
}

func onAppear() {
DispatchQueue.main.async {
self.resetState()
}
Task { await setupContent() }
}

func onFirstAppear() async {
Pixel.fire(pixel: .privacyProSettingsAddDevice)
await setupTransactionObserver()
await refreshToken()
}

private func cleanUp() {
cancellables.removeAll()
}

private func refreshToken() async {
if state.isAddingDevice {
await appStoreAccountManagementFlow.refreshAuthTokenIfNeeded()
}
}

private func setupContent() async {
if state.isAddingDevice {
DispatchQueue.main.async {
self.state.isLoading = true
}

guard let token = accountManager.accessToken else { return }
switch await accountManager.fetchAccountDetails(with: token) {
case .success(let details):
DispatchQueue.main.async {
self.state.subscriptionEmail = details.email
self.state.isLoading = false
self.state.viewTitle = UserText.subscriptionAddDeviceTitle
}
default:
DispatchQueue.main.async {
self.state.viewTitle = UserText.subscriptionActivate
self.state.isLoading = false
}
}
}
}

@MainActor
private func resetState() {
state.isAddingDevice = false
if accountManager.isUserAuthenticated {
state.isAddingDevice = true
}

state.isShowingActivationFlow = false
state.shouldShowPlans = false
state.isShowingWelcomePage = false
Expand Down
Loading

0 comments on commit 23f26ab

Please sign in to comment.