Skip to content

Commit

Permalink
Subscriptions. 28. Intercept Privacy Pro URL + Navigation Update Ship…
Browse files Browse the repository at this point in the history
… review fixes (#2670)

Task/Issue URL: https://app.asana.com/0/0/1206983541370659/f

Implements a URLInterceptor class to define custom actions when visiting specific URLs
Other UI fixes based on Ship Review for navigation Updates
Minor updates to cancellable removals and deInit cleanUp
  • Loading branch information
afterxleep authored Apr 4, 2024
1 parent 8ce1531 commit 3251097
Show file tree
Hide file tree
Showing 19 changed files with 408 additions and 114 deletions.
12 changes: 12 additions & 0 deletions DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -802,7 +802,9 @@
D60B1F272B9DDE5A00AE4760 /* SubscriptionGoogleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60B1F262B9DDE5A00AE4760 /* SubscriptionGoogleView.swift */; };
D61CDA162B7CF77300A0FBB9 /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = D61CDA152B7CF77300A0FBB9 /* Subscription */; };
D61CDA182B7CF78300A0FBB9 /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = D61CDA172B7CF78300A0FBB9 /* ZIPFoundation */; };
D625AAEC2BBEF27600BC189A /* TabURLInterceptorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D625AAEA2BBEEFC900BC189A /* TabURLInterceptorTests.swift */; };
D63657192A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */; };
D63677F52BBDB1C300605BA5 /* DaxLogoNavbarTitle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63677F42BBDB1C300605BA5 /* DaxLogoNavbarTitle.swift */; };
D64648AD2B59936B0033090B /* SubscriptionEmailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64648AC2B59936B0033090B /* SubscriptionEmailView.swift */; };
D64648AF2B5993890033090B /* SubscriptionEmailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64648AE2B5993890033090B /* SubscriptionEmailViewModel.swift */; };
D652498E2B515A6A0056B0DE /* SubscriptionSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D652498D2B515A6A0056B0DE /* SubscriptionSettingsViewModel.swift */; };
Expand All @@ -828,6 +830,7 @@
D68DF81C2B58302E0023DBEA /* SubscriptionRestoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68DF81B2B58302E0023DBEA /* SubscriptionRestoreView.swift */; };
D68DF81E2B5830380023DBEA /* SubscriptionRestoreViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68DF81D2B5830380023DBEA /* SubscriptionRestoreViewModel.swift */; };
D69FBF762B28BE3600B505F1 /* SettingsSubscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69FBF752B28BE3600B505F1 /* SettingsSubscriptionView.swift */; };
D6ACEA322BBD55BF008FADDF /* TabURLInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6ACEA312BBD55BF008FADDF /* TabURLInterceptor.swift */; };
D6BFCB5F2B7524AA0051FF81 /* SubscriptionPIRView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BFCB5E2B7524AA0051FF81 /* SubscriptionPIRView.swift */; };
D6BFCB612B7525160051FF81 /* SubscriptionPIRViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BFCB602B7525160051FF81 /* SubscriptionPIRViewModel.swift */; };
D6D95CE32B6D9F8800960317 /* AsyncHeadlessWebViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D95CE22B6D9F8800960317 /* AsyncHeadlessWebViewModel.swift */; };
Expand Down Expand Up @@ -2476,7 +2479,9 @@
CBFCB30D2B2CD47800253E9E /* ConfigurationURLDebugViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationURLDebugViewController.swift; sourceTree = "<group>"; };
D60170BB2BA32DD6001911B5 /* Subscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Subscription.swift; sourceTree = "<group>"; };
D60B1F262B9DDE5A00AE4760 /* SubscriptionGoogleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionGoogleView.swift; sourceTree = "<group>"; };
D625AAEA2BBEEFC900BC189A /* TabURLInterceptorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabURLInterceptorTests.swift; sourceTree = "<group>"; };
D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmailManagerRequestDelegate.swift; sourceTree = "<group>"; };
D63677F42BBDB1C300605BA5 /* DaxLogoNavbarTitle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DaxLogoNavbarTitle.swift; sourceTree = "<group>"; };
D64648AC2B59936B0033090B /* SubscriptionEmailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionEmailView.swift; sourceTree = "<group>"; };
D64648AE2B5993890033090B /* SubscriptionEmailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionEmailViewModel.swift; sourceTree = "<group>"; };
D652498D2B515A6A0056B0DE /* SubscriptionSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionSettingsViewModel.swift; sourceTree = "<group>"; };
Expand All @@ -2502,6 +2507,7 @@
D68DF81B2B58302E0023DBEA /* SubscriptionRestoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionRestoreView.swift; sourceTree = "<group>"; };
D68DF81D2B5830380023DBEA /* SubscriptionRestoreViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionRestoreViewModel.swift; sourceTree = "<group>"; };
D69FBF752B28BE3600B505F1 /* SettingsSubscriptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSubscriptionView.swift; sourceTree = "<group>"; };
D6ACEA312BBD55BF008FADDF /* TabURLInterceptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabURLInterceptor.swift; sourceTree = "<group>"; };
D6BFCB5E2B7524AA0051FF81 /* SubscriptionPIRView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPIRView.swift; sourceTree = "<group>"; };
D6BFCB602B7525160051FF81 /* SubscriptionPIRViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPIRViewModel.swift; sourceTree = "<group>"; };
D6D95CE22B6D9F8800960317 /* AsyncHeadlessWebViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncHeadlessWebViewModel.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4677,6 +4683,7 @@
D6F93E3D2B50A8A0004C268D /* SubscriptionSettingsView.swift */,
D60B1F262B9DDE5A00AE4760 /* SubscriptionGoogleView.swift */,
D670E5BA2BB6A75200941A42 /* SubscriptionNavigationCoordinator.swift */,
D63677F42BBDB1C300605BA5 /* DaxLogoNavbarTitle.swift */,
);
path = Views;
sourceTree = "<group>";
Expand Down Expand Up @@ -5121,6 +5128,7 @@
B60DFF062872B64B0061E7C2 /* JSAlertController.swift */,
B6BA95E728924730004ABA20 /* JSAlertController.storyboard */,
85010501292FB1000033978F /* FireproofFaviconUpdater.swift */,
D6ACEA312BBD55BF008FADDF /* TabURLInterceptor.swift */,
);
name = UI;
sourceTree = "<group>";
Expand All @@ -5134,6 +5142,7 @@
F13B4BFA1F18E3D900814661 /* TabsModelPersistenceExtensionTests.swift */,
F13B4BF81F18CA0600814661 /* TabsModelTests.swift */,
F189AED61F18F6DE001EBAE1 /* TabTests.swift */,
D625AAEA2BBEEFC900BC189A /* TabURLInterceptorTests.swift */,
);
name = Tabs;
sourceTree = "<group>";
Expand Down Expand Up @@ -6653,6 +6662,7 @@
C1D21E2D293A5965006E5A05 /* AutofillLoginSession.swift in Sources */,
4B53648A26718D0E001AA041 /* EmailWaitlist.swift in Sources */,
027F48762A4B5FBE001A1C6C /* AppTPLinkButton.swift in Sources */,
D63677F52BBDB1C300605BA5 /* DaxLogoNavbarTitle.swift in Sources */,
8524CC98246D66E100E59D45 /* String+Markdown.swift in Sources */,
CBEFB9142AE0844700DEDE7B /* CriticalAlerts.swift in Sources */,
020108A329A561C300644F9D /* AppTPActivityView.swift in Sources */,
Expand Down Expand Up @@ -6918,6 +6928,7 @@
D6E83C562B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift in Sources */,
98B31292218CCB8C00E54DE1 /* AppDependencyProvider.swift in Sources */,
D670E5BD2BB6AA0000941A42 /* View+AppearModifiers.swift in Sources */,
D6ACEA322BBD55BF008FADDF /* TabURLInterceptor.swift in Sources */,
C13F3F6A2B7F883A0083BE40 /* AuthConfirmationPromptViewController.swift in Sources */,
02C57C4B2514FEFB009E5129 /* DoNotSellSettingsViewController.swift in Sources */,
02A54A9C2A097C95000C8FED /* AppTPHomeViewSectionRenderer.swift in Sources */,
Expand Down Expand Up @@ -7060,6 +7071,7 @@
85C11E4120904BBE00BFFEB4 /* VariantManagerTests.swift in Sources */,
F1134ECE1F40EA9C00B73467 /* AtbParserTests.swift in Sources */,
F189AEE41F18FDAF001EBAE1 /* LinkTests.swift in Sources */,
D625AAEC2BBEF27600BC189A /* TabURLInterceptorTests.swift in Sources */,
987130C7294AAB9F00AB05E0 /* MenuBookmarksViewModelTests.swift in Sources */,
858650D32469BFAD00C36F8A /* DaxDialogTests.swift in Sources */,
31C138B227A4097800FFD4B2 /* DownloadTestsHelper.swift in Sources */,
Expand Down
7 changes: 5 additions & 2 deletions DuckDuckGo/MainViewController+Segues.swift
Original file line number Diff line number Diff line change
Expand Up @@ -244,14 +244,17 @@ extension MainViewController {
}
}

private func launchSettings(completion: ((SettingsViewModel) -> Void)? = nil) {
func launchSettings(completion: ((SettingsViewModel) -> Void)? = nil,
deepLinkTarget: SettingsViewModel.SettingsDeepLinkSection? = nil) {
let legacyViewProvider = SettingsLegacyViewProvider(syncService: syncService,
syncDataProviders: syncDataProviders,
appSettings: appSettings,
bookmarksDatabase: bookmarksDatabase,
tabManager: tabManager)
#if SUBSCRIPTION
let settingsViewModel = SettingsViewModel(legacyViewProvider: legacyViewProvider, accountManager: AccountManager())
let settingsViewModel = SettingsViewModel(legacyViewProvider: legacyViewProvider,
accountManager: AccountManager(),
deepLink: deepLinkTarget)
#else
let settingsViewModel = SettingsViewModel(legacyViewProvider: legacyViewProvider)
#endif
Expand Down
18 changes: 17 additions & 1 deletion DuckDuckGo/MainViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ class MainViewController: UIViewController {
private var syncFeatureFlagsCancellable: AnyCancellable?
private var favoritesDisplayModeCancellable: AnyCancellable?
private var emailCancellables = Set<AnyCancellable>()
private var urlInterceptorCancellables = Set<AnyCancellable>()

#if NETWORK_PROTECTION
private let tunnelDefaults = UserDefaults.networkProtectionGroupDefaults
Expand Down Expand Up @@ -268,7 +269,8 @@ class MainViewController: UIViewController {
previewsSource.prepare()
addLaunchTabNotificationObserver()
subscribeToEmailProtectionStatusNotifications()

subscribeToURLInterceptorNotifications()

#if NETWORK_PROTECTION && SUBSCRIPTION
subscribeToNetworkProtectionEvents()
#endif
Expand Down Expand Up @@ -1351,6 +1353,20 @@ class MainViewController: UIViewController {
}
.store(in: &emailCancellables)
}

private func subscribeToURLInterceptorNotifications() {
NotificationCenter.default.publisher(for: .urlInterceptPrivacyPro)
.receive(on: DispatchQueue.main)
.sink { [weak self] notification in
switch notification.name {
case .urlInterceptPrivacyPro:
self?.launchSettings(deepLinkTarget: .subscriptionFlow)
default:
return
}
}
.store(in: &urlInterceptorCancellables)
}

#if NETWORK_PROTECTION && SUBSCRIPTION
private func subscribeToNetworkProtectionEvents() {
Expand Down
23 changes: 0 additions & 23 deletions DuckDuckGo/Subscription/Extensions/View+AppearModifiers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,33 +38,10 @@ public struct OnFirstAppearModifier: ViewModifier {
}
}

public struct OnFirstDisappearModifier: ViewModifier {

private let onFirstDisappearAction: () -> Void
@State private var hasDisappeared = false

public init(_ onFirstDisappearAction: @escaping () -> Void) {
self.onFirstDisappearAction = onFirstDisappearAction
}

public func body(content: Content) -> some View {
content
.onDisappear {
guard !hasDisappeared else { return }
hasDisappeared = true
onFirstDisappearAction()
}
}
}

extension View {

func onFirstAppear(_ onFirstAppearAction: @escaping () -> Void ) -> some View {
return modifier(OnFirstAppearModifier(onFirstAppearAction))
}

func onFirstDisappear(_ onFirstDisappearAction: @escaping () -> Void ) -> some View {
return modifier(OnFirstDisappearModifier(onFirstDisappearAction))
}

}
78 changes: 51 additions & 27 deletions DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ final class SubscriptionEmailViewModel: ObservableObject {
let subFeature: SubscriptionPagesUseSubscriptionFeature

private var canGoBackCancellable: AnyCancellable?
private var urlCancellable: AnyCancellable?

var emailURL = URL.activateSubscriptionViaEmail
var viewTitle = UserText.subscriptionActivateEmailTitle
var webViewModel: AsyncHeadlessWebViewViewModel

enum SelectedFeature {
Expand All @@ -50,10 +50,12 @@ final class SubscriptionEmailViewModel: ObservableObject {
var canNavigateBack: Bool = false
var shouldDismissView: Bool = false
var subscriptionActive: Bool = false
var isWelcomePageVisible: Bool = false
var backButtonTitle: String = UserText.backButtonTitle
var selectedFeature: SelectedFeature = .none
var shouldPopToSubscriptionSettings: Bool = false
var shouldPopToAppSettings: Bool = false
var viewTitle = UserText.subscriptionActivateEmailTitle
}

// Read only View State - Should only be modified from the VM
Expand All @@ -68,6 +70,11 @@ final class SubscriptionEmailViewModel: ObservableObject {
}

private var cancellables = Set<AnyCancellable>()

private var isWelcomePageOrSuccessPage: Bool {
webViewModel.url?.forComparison() == URL.subscriptionActivateSuccess.forComparison() ||
webViewModel.url?.forComparison() == URL.subscriptionPurchase.forComparison()
}

init(userScript: SubscriptionPagesUserScript,
subFeature: SubscriptionPagesUseSubscriptionFeature,
Expand Down Expand Up @@ -103,33 +110,34 @@ final class SubscriptionEmailViewModel: ObservableObject {

@MainActor
func onFirstAppear() {
setupObservers()
if accountManager.isUserAuthenticated {
setupWebObservers()
setupFeatureObservers()
}

private func cleanUp() {
canGoBackCancellable?.cancel()
subFeature.cleanup()
cancellables.removeAll()
}

func onAppear() {
state.shouldDismissView = false
// If the user is Authenticated & not in the Welcome page
if accountManager.isUserAuthenticated && !isWelcomePageOrSuccessPage {
// If user is authenticated, we want to "Add or manage email" instead of activating
emailURL = accountManager.email == nil ? URL.addEmailToSubscription : URL.manageSubscriptionEmail
viewTitle = accountManager.email == nil ? UserText.subscriptionRestoreAddEmailTitle : UserText.subscriptionManageEmailTitle
state.viewTitle = accountManager.email == nil ? UserText.subscriptionRestoreAddEmailTitle : UserText.subscriptionManageEmailTitle

// Also we assume subscription requires managing, and not activation
state.managingSubscriptionEmail = true
}
if webViewModel.url?.forComparison() != URL.subscriptionActivateSuccess {
// Load the Email Management URL unless the user has activated a subscription or is on the welcome page
if !isWelcomePageOrSuccessPage {
self.webViewModel.navigationCoordinator.navigateTo(url: self.emailURL)
}
}

func onFirstDisappear() {
cancellables.removeAll()
canGoBackCancellable = nil
}

private func setupObservers() {

// Webview navigation
canGoBackCancellable = webViewModel.$canGoBack
.receive(on: DispatchQueue.main)
.sink { [weak self] value in
self?.updateBackButton(canNavigateBack: value)
}
private func setupFeatureObservers() {

// Feature Callback
subFeature.onSetSubscription = {
Expand Down Expand Up @@ -172,7 +180,26 @@ final class SubscriptionEmailViewModel: ObservableObject {
}
}
.store(in: &cancellables)

}

private func setupWebObservers() {

// Webview navigation
canGoBackCancellable = webViewModel.$canGoBack
.receive(on: DispatchQueue.main)
.sink { [weak self] value in
self?.updateBackButton(canNavigateBack: value)
}

// Webview navigation
urlCancellable = webViewModel.$url
.receive(on: DispatchQueue.main)
.sink { [weak self] _ in
if self?.isWelcomePageOrSuccessPage ?? false {
self?.state.viewTitle = UserText.subscriptionTitle
}
}

webViewModel.$navigationError
.receive(on: DispatchQueue.main)
.sink { [weak self] error in
Expand All @@ -185,17 +212,14 @@ final class SubscriptionEmailViewModel: ObservableObject {
.store(in: &cancellables)
}

func updateBackButton(canNavigateBack: Bool) {

// Disable Browser navigation by default
self.state.canNavigateBack = false
private func updateBackButton(canNavigateBack: Bool) {

// If the view is not Activation Success, or Welcome page, allow WebView Back Navigation
if self.webViewModel.url?.forComparison() != URL.subscriptionActivateSuccess.forComparison() &&
self.webViewModel.url?.forComparison() != URL.subscriptionPurchase.forComparison() {
if !isWelcomePageOrSuccessPage {
self.state.canNavigateBack = canNavigateBack
self.state.backButtonTitle = UserText.backButtonTitle
} else {
self.state.canNavigateBack = false
self.state.backButtonTitle = UserText.settingsTitle
}

Expand Down Expand Up @@ -228,9 +252,9 @@ final class SubscriptionEmailViewModel: ObservableObject {
}

deinit {
cancellables.removeAll()
cleanUp()
canGoBackCancellable = nil

}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,11 @@ final class SubscriptionExternalLinkViewModel: ObservableObject {
func onFirstAppear() {
Task { await setupSubscribers() }
webViewModel.navigationCoordinator.navigateTo(url: url)

}

private func cleanUp() {
canGoBackCancellable?.cancel()
cancellables.removeAll()
}

@MainActor
Expand All @@ -65,7 +69,8 @@ final class SubscriptionExternalLinkViewModel: ObservableObject {
}

deinit {
cancellables.removeAll()
cleanUp()
canGoBackCancellable = nil
}

}
Expand Down
Loading

0 comments on commit 3251097

Please sign in to comment.