Skip to content

Commit

Permalink
Subscriptions: 20 - Subscription Caching (#2569)
Browse files Browse the repository at this point in the history
Task/Issue URL: https://app.asana.com/0/0/1206800657723184/f
BSK PR: duckduckgo/BrowserServicesKit#710

Description:

Bumps BSK to use subscription caching:
Push Purchase Cancel update to Webview on user cancellation
Minor presentation updates for sheets and subscription-related Settings cleanup

Co-authored-by: Michal Smaga <[email protected]>
  • Loading branch information
afterxleep and miasma13 authored Mar 11, 2024
1 parent bbacdd7 commit 454fb21
Show file tree
Hide file tree
Showing 7 changed files with 51 additions and 67 deletions.
2 changes: 1 addition & 1 deletion DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10044,7 +10044,7 @@
repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit";
requirement = {
kind = exactVersion;
version = 122.0.0;
version = 122.1.0;
};
};
B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = {
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" : "83bcbbf0dace717db6e518e4d867d617c846a3b5",
"version" : "122.0.0"
"revision" : "4042a8e04396584566df24fb90c7b529bbe1c661",
"version" : "122.1.0"
}
},
{
Expand Down
32 changes: 14 additions & 18 deletions DuckDuckGo/SettingsSubscriptionView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ struct SettingsSubscriptionView: View {
action: { isShowingsubScriptionFlow = true },
isButton: true )

// Subscription Restore
.sheet(isPresented: $isShowingsubScriptionFlow,
onDismiss: { Task { viewModel.onAppear() } },
content: { SubscriptionFlowView(viewModel: subscriptionFlowViewModel).interactiveDismissDisabled() })

SettingsCustomCell(content: { iHaveASubscriptionView },
action: {
isShowingsubScriptionFlow = true
Expand Down Expand Up @@ -139,26 +144,29 @@ struct SettingsSubscriptionView: View {
subtitle: UserText.settingsPProDBPSubTitle,
action: { isShowingDBP.toggle() }, isButton: true)

.sheet(isPresented: $isShowingDBP) {
SubscriptionPIRView()
}

}

if viewModel.shouldShowITP {
SettingsCellView(label: UserText.settingsPProITRTitle,
subtitle: UserText.settingsPProITRSubTitle,
action: { isShowingITP.toggle() }, isButton: true)

.sheet(isPresented: $isShowingITP) {
SubscriptionITPView()
}

}

NavigationLink(destination: SubscriptionSettingsView()) {
SettingsCustomCell(content: { manageSubscriptionView })
}

}
.sheet(isPresented: $isShowingDBP) {
SubscriptionPIRView()
}
.sheet(isPresented: $isShowingITP) {
SubscriptionITPView()
}

}

var body: some View {
Expand All @@ -181,18 +189,6 @@ struct SettingsSubscriptionView: View {
}

}
// Subscription Restore
.sheet(isPresented: $isShowingsubScriptionFlow) {
SubscriptionFlowView(viewModel: subscriptionFlowViewModel).interactiveDismissDisabled()
}


// Refresh subscription when dismissing the Subscription Flow
.onChange(of: isShowingsubScriptionFlow, perform: { value in
if !value {
Task { viewModel.onAppear() }
}
})

.onChange(of: viewModel.shouldNavigateToDBP, perform: { value in
if value {
Expand Down
44 changes: 18 additions & 26 deletions DuckDuckGo/SettingsViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -248,8 +248,9 @@ extension SettingsViewModel {
// This manual (re)initialization will go away once appSettings and
// other dependencies are observable (Such as AppIcon and netP)
// and we can use subscribers (Currently called from the view onAppear)
private func initState() {
self.state = SettingsState(
@MainActor
private func initState() async {
self.state = await SettingsState(
appTheme: appSettings.currentThemeName,
appIcon: AppIconManager.shared.appIcon,
fireButtonAnimation: appSettings.currentFireButtonAnimation,
Expand All @@ -275,15 +276,6 @@ extension SettingsViewModel {

setupSubscribers()

#if SUBSCRIPTION
if #available(iOS 15, *) {
Task {
if state.subscription.enabled {
await setupSubscriptionEnvironment()
}
}
}
#endif
}

private func getNetworkProtectionState() -> SettingsState.NetworkProtection {
Expand All @@ -297,14 +289,24 @@ extension SettingsViewModel {
return SettingsState.NetworkProtection(enabled: enabled, status: "")
}

private func getSubscriptionState() -> SettingsState.Subscription {
private func getSubscriptionState() async -> SettingsState.Subscription {
var enabled = false
var canPurchase = false
let hasActiveSubscription = Self.cachedHasActiveSubscription
#if SUBSCRIPTION
var hasActiveSubscription = false

#if SUBSCRIPTION
if #available(iOS 15, *) {
enabled = featureFlagger.isFeatureOn(.subscription)
canPurchase = SubscriptionPurchaseEnvironment.canPurchase
#endif
await setupSubscriptionEnvironment()
if let token = AccountManager().accessToken {
let subscriptionResult = await SubscriptionService.getSubscription(accessToken: token)
if case .success(let subscription) = subscriptionResult {
hasActiveSubscription = subscription.isActive
}
}
}
#endif
return SettingsState.Subscription(enabled: enabled,
canPurchase: canPurchase,
hasActiveSubscription: hasActiveSubscription)
Expand Down Expand Up @@ -354,9 +356,6 @@ extension SettingsViewModel {

case .success(let subscription) where subscription.isActive:

// Cache Subscription state
cacheSubscriptionState(active: true)

// Check entitlements and update UI accordingly
let entitlements: [Entitlement.ProductName] = [.identityTheftRestoration, .dataBrokerProtection, .networkProtection]
for entitlement in entitlements {
Expand All @@ -382,18 +381,11 @@ extension SettingsViewModel {
@available(iOS 15.0, *)
private func signOutUser() {
AccountManager().signOut()
cacheSubscriptionState(active: false)
setupSubscriptionPurchaseOptions()
}

private func cacheSubscriptionState(active: Bool) {
self.state.subscription.hasActiveSubscription = active
Self.cachedHasActiveSubscription = active
}

@available(iOS 15.0, *)
private func setupSubscriptionPurchaseOptions() {
cacheSubscriptionState(active: false)
PurchaseManager.shared.$availableProducts
.receive(on: RunLoop.main)
.sink { [weak self] products in
Expand Down Expand Up @@ -470,7 +462,7 @@ extension SettingsViewModel {
extension SettingsViewModel {

func onAppear() {
initState()
Task { await initState() }
Task { await MainActor.run { navigateOnAppear() } }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,8 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec
switch error {
case .cancelledByUser:
setTransactionError(.cancelledByUser)
await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate(type: "canceled"))
return nil
case .accountCreationFailed:
setTransactionError(.accountCreationFailed)
case .activeSubscriptionAlreadyPresent:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,24 +56,16 @@ final class SubscriptionSettingsViewModel: ObservableObject {
}()

@MainActor
func fetchAndUpdateSubscriptionDetails() {
func fetchAndUpdateSubscriptionDetails(cachePolicy: SubscriptionService.CachePolicy = .returnCacheDataElseLoad) {
Task {
guard let token = accountManager.accessToken else { return }

if let cachedDate = SubscriptionService.cachedGetSubscriptionResponse?.expiresOrRenewsAt,
let cachedStatus = SubscriptionService.cachedGetSubscriptionResponse?.status,
let productID = SubscriptionService.cachedGetSubscriptionResponse?.productId {
updateSubscriptionDetails(status: cachedStatus, date: cachedDate, product: productID)
}

if case .success(let subscription) = await SubscriptionService.getSubscription(accessToken: token) {
if !subscription.isActive {
AccountManager().signOut()
shouldDismissView = true
return
} else {
updateSubscriptionDetails(status: subscription.status, date: subscription.expiresOrRenewsAt, product: subscription.productId)
}
guard let token = self.accountManager.accessToken else { return }
let subscriptionResult = await SubscriptionService.getSubscription(accessToken: token, cachePolicy: cachePolicy)
switch subscriptionResult {
case .success(let subscription):
updateSubscriptionDetails(status: subscription.status, date: subscription.expiresOrRenewsAt, product: subscription.productId)
case .failure(let error):
AccountManager().signOut()
shouldDismissView = true
}
}
}
Expand All @@ -86,11 +78,13 @@ final class SubscriptionSettingsViewModel: ObservableObject {
}
}

// Re-fetch subscription from server ignoring cache
// This ensure that if the user changed something on the Apple view, state will be updated
private func setupSubscriptionUpdater() {
subscriptionUpdateTimer = Timer.scheduledTimer(withTimeInterval: 10.0, repeats: true) { [weak self] _ in
guard let strongSelf = self else { return }
Task {
await strongSelf.fetchAndUpdateSubscriptionDetails()
await strongSelf.fetchAndUpdateSubscriptionDetails(cachePolicy: .reloadIgnoringLocalCacheData)
}
}
}
Expand All @@ -104,7 +98,7 @@ final class SubscriptionSettingsViewModel: ObservableObject {

func removeSubscription() {
AccountManager().signOut()
let messageView = ActionMessageView()
_ = ActionMessageView()
ActionMessageView.present(message: UserText.subscriptionRemovalConfirmation,
presentationLocation: .withoutBottomBar)
}
Expand Down

0 comments on commit 454fb21

Please sign in to comment.