Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implements Welcome Links Navigation in Subscriptions #2371

Merged
merged 32 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
40aabe9
SubscriptionRestore View
afterxleep Jan 17, 2024
343fbdc
Restore subscription via Apple ID
afterxleep Jan 18, 2024
8f5f549
Typo
afterxleep Jan 18, 2024
b1dd001
NOOP
afterxleep Jan 18, 2024
3c5b8bb
Typo
afterxleep Jan 18, 2024
20d2b65
Merge branch 'main' into daniel/subscriptions/4.restore-options
afterxleep Jan 18, 2024
d3e54f1
Subscription API Updates (From macOS)
afterxleep Jan 18, 2024
d61d7ba
Add Email to subscription and organize Strings
afterxleep Jan 18, 2024
5177005
Activate via Email
afterxleep Jan 19, 2024
319c96c
Dismiss view after email activation
afterxleep Jan 19, 2024
4fa9429
Another dismissal approach
afterxleep Jan 19, 2024
8b96fb8
Correct navigation after activating subscription
afterxleep Jan 20, 2024
1deb26c
Restore subscription from email
afterxleep Jan 20, 2024
e58f9e5
Restore view for existing email
afterxleep Jan 20, 2024
43f3b8e
Manage subscription email
afterxleep Jan 22, 2024
52d70fc
Email Management flows
afterxleep Jan 22, 2024
e3a4dd4
Lint issues and comments
afterxleep Jan 22, 2024
144b832
Add device from welcome screen
afterxleep Jan 22, 2024
1404dd3
OnAppear navigation
afterxleep Jan 23, 2024
4b0f086
Settings onAppear Navigation
afterxleep Jan 23, 2024
12eb88e
Navigate back to Settings and open an existing subscription feature
afterxleep Jan 24, 2024
9907bb9
Removed whitespace
afterxleep Jan 24, 2024
b5bee18
Merge branch 'main' into daniel/subscriptions/5.email-restore
afterxleep Jan 24, 2024
6e229a6
Merge branch 'daniel/subscriptions/5.email-restore' into daniel/subsc…
afterxleep Jan 24, 2024
ae9d84c
Cleanup + comments
afterxleep Jan 24, 2024
f9e0e6a
Comment
afterxleep Jan 24, 2024
7a054a1
Comment
afterxleep Jan 24, 2024
2704c3c
Comment
afterxleep Jan 24, 2024
02da0d7
Merge branch 'main' into daniel/subscriptions/6.welcome-links
afterxleep Jan 24, 2024
9a34d70
Subscription removal confirmation Toast
afterxleep Jan 24, 2024
1c81e93
Remove unwanted class
afterxleep Jan 24, 2024
6dad555
Cleanup
afterxleep Jan 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -805,7 +805,6 @@
D6D12CAB2B291CAA0054390C /* APIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C9C2B291CA90054390C /* APIService.swift */; };
D6D12CAC2B291CAA0054390C /* AuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C9D2B291CA90054390C /* AuthService.swift */; };
D6D12CAD2B291CAA0054390C /* PurchaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C9E2B291CA90054390C /* PurchaseManager.swift */; };
D6D4B77C2B5AE99500996546 /* SubscriptionFlowNavController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D4B77B2B5AE99500996546 /* SubscriptionFlowNavController.swift */; };
D6E83C122B1E6AB3006C8AFB /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C112B1E6AB3006C8AFB /* SettingsView.swift */; };
D6E83C2E2B1EA06E006C8AFB /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C2D2B1EA06E006C8AFB /* SettingsViewModel.swift */; };
D6E83C312B1EA309006C8AFB /* SettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C302B1EA309006C8AFB /* SettingsCell.swift */; };
Expand Down Expand Up @@ -2458,7 +2457,6 @@
D6D12C9C2B291CA90054390C /* APIService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIService.swift; sourceTree = "<group>"; };
D6D12C9D2B291CA90054390C /* AuthService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthService.swift; sourceTree = "<group>"; };
D6D12C9E2B291CA90054390C /* PurchaseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseManager.swift; sourceTree = "<group>"; };
D6D4B77B2B5AE99500996546 /* SubscriptionFlowNavController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionFlowNavController.swift; sourceTree = "<group>"; };
D6E83C112B1E6AB3006C8AFB /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
D6E83C2D2B1EA06E006C8AFB /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = "<group>"; };
D6E83C302B1EA309006C8AFB /* SettingsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCell.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4562,7 +4560,6 @@
D664C7932B289AA000CBFA76 /* ViewModel */ = {
isa = PBXGroup;
children = (
D6D4B77B2B5AE99500996546 /* SubscriptionFlowNavController.swift */,
D664C7942B289AA000CBFA76 /* SubscriptionFlowViewModel.swift */,
D68DF81D2B5830380023DBEA /* SubscriptionRestoreViewModel.swift */,
D64648AE2B5993890033090B /* SubscriptionEmailViewModel.swift */,
Expand Down Expand Up @@ -6534,7 +6531,6 @@
1E162610296C5C630004127F /* CustomDaxDialogViewModel.swift in Sources */,
8590CB69268A4E190089F6BF /* DebugEtagStorage.swift in Sources */,
D6D12CA62B291CAA0054390C /* AppStoreRestoreFlow.swift in Sources */,
D6D4B77C2B5AE99500996546 /* SubscriptionFlowNavController.swift in Sources */,
C1CDA3162AFB9C7F006D1476 /* AutofillNeverPromptWebsitesManager.swift in Sources */,
F1CA3C371F045878005FADB3 /* PrivacyStore.swift in Sources */,
37FCAAC029930E26000E420A /* FailedAssertionView.swift in Sources */,
Expand Down
2 changes: 1 addition & 1 deletion DuckDuckGo/SettingsState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ struct SettingsState {
// Subscriptions Properties
var subscription: Subscription

// Sync Propertiers
// Sync Properties
var sync: SyncSettings

static var defaults: SettingsState {
Expand Down
12 changes: 7 additions & 5 deletions DuckDuckGo/SettingsSubscriptionView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,14 @@ struct SettingsSubscriptionView: View {
.daxBodyRegular()
.foregroundColor(Color.init(designSystemColor: .accent))
}



private var purchaseSubscriptionView: some View {
return Group {
SettingsCustomCell(content: { subscriptionDescriptionView })
NavigationLink(destination: SubscriptionFlowView(viewModel: SubscriptionFlowViewModel())) {
let viewModel = SubscriptionFlowViewModel(onFeatureSelected: { value in
self.viewModel.onAppearNavigationTarget = value
})
NavigationLink(destination: SubscriptionFlowView(viewModel: viewModel)) {
SettingsCustomCell(content: { learnMoreView })
}
}
Expand All @@ -66,11 +68,11 @@ struct SettingsSubscriptionView: View {
disclosureIndicator: true,
isButton: true)

NavigationLink(destination: Text("Data Broker Protection")) {
NavigationLink(destination: Text("Data Broker Protection"), isActive: $viewModel.shouldNavigateToDBP) {
SettingsCellView(label: UserText.settingsPProDBPTitle, subtitle: UserText.settingsPProDBPSubTitle)
}

NavigationLink(destination: Text("Identity Theft Restoration")) {
NavigationLink(destination: Text("Identity Theft Restoration"), isActive: $viewModel.shouldNavigateToITP) {
SettingsCellView(label: UserText.settingsPProITRTitle, subtitle: UserText.settingsPProITRSubTitle)
}

Expand Down
44 changes: 39 additions & 5 deletions DuckDuckGo/SettingsViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,18 @@ final class SettingsViewModel: ObservableObject {
@UserDefaultsWrapper(key: .subscriptionIsActive, defaultValue: false)
static private var cachedHasActiveSubscription: Bool

// Closures to interact with legacy view controllers throught the container
// Closures to interact with legacy view controllers through the container
var onRequestPushLegacyView: ((UIViewController) -> Void)?
var onRequestPresentLegacyView: ((UIViewController, _ modal: Bool) -> Void)?
var onRequestPopLegacyView: (() -> Void)?
var onRequestDismissSettings: (() -> Void)?

// SwiftUI Programatic Navigation Variables
// Add more views as needed here...
@Published var shouldNavigateToDBP = false
@Published var shouldNavigateToITP = false

// Subscription Entitlement names: TBD
static let entitlementNames = ["dummy1", "dummy2", "dummy3"]

// Our View State
Expand All @@ -82,6 +88,12 @@ final class SettingsViewModel: ObservableObject {

var shouldShowNoMicrophonePermissionAlert: Bool = false

// Used to automatically navigate on Appear to a specific section
enum SettingsSection: String {
case none, netP, dbp, itp
}
@Published var onAppearNavigationTarget: SettingsSection

// MARK: Bindings
var themeBinding: Binding<ThemeName> {
Binding<ThemeName>(
Expand Down Expand Up @@ -183,10 +195,14 @@ final class SettingsViewModel: ObservableObject {
}

// MARK: Default Init
init(state: SettingsState? = nil, legacyViewProvider: SettingsLegacyViewProvider, accountManager: AccountManager) {
init(state: SettingsState? = nil,
legacyViewProvider: SettingsLegacyViewProvider,
accountManager: AccountManager,
navigateOnAppearDestination: SettingsSection = .none) {
self.state = SettingsState.defaults
self.legacyViewProvider = legacyViewProvider
self.accountManager = accountManager
self.onAppearNavigationTarget = navigateOnAppearDestination
}
}

Expand Down Expand Up @@ -287,7 +303,6 @@ extension SettingsViewModel {
completion(true)
}
}


#if SUBSCRIPTION
@available(iOS 15.0, *)
Expand Down Expand Up @@ -348,7 +363,6 @@ extension SettingsViewModel {
}
}
#endif

}

// MARK: Subscribers
Expand All @@ -365,7 +379,7 @@ extension SettingsViewModel {
}
.store(in: &cancellables)
#endif

}
}

Expand All @@ -374,6 +388,7 @@ extension SettingsViewModel {

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

func setAsDefaultBrowser() {
Expand Down Expand Up @@ -401,6 +416,25 @@ extension SettingsViewModel {
onRequestDismissSettings?()
}

@MainActor
private func navigateOnAppear() {
// We need a short delay to let the SwifttUI view lifecycle complete
// Otherwise the transition can be inconsistent
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
switch self.onAppearNavigationTarget {
case .netP:
self.presentLegacyView(.netP)
case .dbp:
self.shouldNavigateToDBP = true
case .itp:
self.shouldNavigateToITP = true
default:
break
}
self.onAppearNavigationTarget = .none
}
}

}

// MARK: Legacy View Presentation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,16 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec
case idle, purchasing, restoring, polling
}

struct FeatureSelection: Codable {
let feature: String
}

@Published var transactionStatus: TransactionStatus = .idle
@Published var hasActiveSubscription = false
@Published var purchaseError: AppStorePurchaseFlow.Error?
@Published var activateSubscription: Bool = false
@Published var emailActivationComplete: Bool = false
@Published var selectedFeature: FeatureSelection?

var broker: UserScriptMessageBroker?

Expand Down Expand Up @@ -232,14 +237,11 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec
}

func featureSelected(params: Any, original: WKScriptMessage) async throws -> Encodable? {
struct FeatureSelection: Codable {
let feature: String
}

guard let featureSelection: FeatureSelection = DecodableHelper.decode(from: params) else {
assertionFailure("SubscriptionPagesUserScript: expected JSON representation of FeatureSelection")
return nil
}
selectedFeature = featureSelection

return nil
}
Expand Down

This file was deleted.

38 changes: 36 additions & 2 deletions DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,24 +29,38 @@ final class SubscriptionFlowViewModel: ObservableObject {
let userScript: SubscriptionPagesUserScript
let subFeature: SubscriptionPagesUseSubscriptionFeature
let purchaseManager: PurchaseManager

let viewTitle = UserText.settingsPProSection

private var cancellables = Set<AnyCancellable>()

// State variables
var purchaseURL = URL.purchaseSubscription

// Closure passed to navigate to a specific section
// after returning to settings
var onFeatureSelected: ((SettingsViewModel.SettingsSection) -> Void)

enum FeatureName {
static let netP = "vpn"
static let itp = "identity-theft-restoration"
static let dbp = "personal-information-removal"
}

// Published properties
@Published var hasActiveSubscription = false
@Published var transactionStatus: SubscriptionPagesUseSubscriptionFeature.TransactionStatus = .idle
@Published var shouldReloadWebView = false
@Published var activatingSubscription = false
@Published var shouldDismissView = false

init(userScript: SubscriptionPagesUserScript = SubscriptionPagesUserScript(),
subFeature: SubscriptionPagesUseSubscriptionFeature = SubscriptionPagesUseSubscriptionFeature(),
purchaseManager: PurchaseManager = PurchaseManager.shared) {
purchaseManager: PurchaseManager = PurchaseManager.shared,
onFeatureSelected: @escaping ((SettingsViewModel.SettingsSection) -> Void)) {
self.userScript = userScript
self.subFeature = subFeature
self.purchaseManager = purchaseManager
self.onFeatureSelected = onFeatureSelected
}

// Observe transaction status
Expand Down Expand Up @@ -76,6 +90,26 @@ final class SubscriptionFlowViewModel: ObservableObject {
}
}
.store(in: &cancellables)

subFeature.$selectedFeature
.receive(on: DispatchQueue.main)
.sink { [weak self] value in
if value != nil {
self?.shouldDismissView = true
switch value?.feature {
case FeatureName.netP:
self?.onFeatureSelected(.netP)
case FeatureName.itp:
self?.onFeatureSelected(.itp)
case FeatureName.dbp:
self?.onFeatureSelected(.dbp)
default:
return
}
}
}
.store(in: &cancellables)

}

@MainActor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ final class SubscriptionRestoreViewModel: ObservableObject {
let subFeature: SubscriptionPagesUseSubscriptionFeature
let purchaseManager: PurchaseManager
let accountManager: AccountManager
let isAddingDevice: Bool
var isAddingDevice: Bool

enum SubscriptionActivationResult {
case unknown, activated, notFound, error
Expand All @@ -56,6 +56,9 @@ final class SubscriptionRestoreViewModel: ObservableObject {

func initializeView() {
subscriptionEmail = accountManager.email
if accountManager.isUserAuthenticated {
isAddingDevice = true
}
}

@MainActor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ final class SubscriptionSettingsViewModel: ObservableObject {

func removeSubscription() {
AccountManager().signOut()
ActionMessageView.present(message: UserText.subscriptionRemovalConfirmation)
}

func manageSubscription() {
Expand Down
6 changes: 2 additions & 4 deletions DuckDuckGo/Subscription/Views/HeadlessWebView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,9 @@ struct HeadlessWebview: UIViewRepresentable {
configuration.userContentController = makeUserContentController()

let webView = WKWebView(frame: .zero, configuration: configuration)
DefaultUserAgentManager.shared.update(webView: webView, isDesktop: false, url: url)

// We're using the macOS agent as the config for iOS has not been deployed in test env
webView.customUserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko)"
// DefaultUserAgentManager.shared.update(webView: webView, isDesktop: false, url: url)

// Just add time if you need to hook the WebView inspector
DispatchQueue.main.asyncAfter(deadline: .now() + 0) {
webView.load(URLRequest(url: url))
}
Expand Down
6 changes: 6 additions & 0 deletions DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import Foundation
@available(iOS 15.0, *)
struct SubscriptionFlowView: View {

@Environment(\.dismiss) var dismiss
@ObservedObject var viewModel: SubscriptionFlowViewModel
@State private var isAlertVisible = false

Expand Down Expand Up @@ -69,6 +70,11 @@ struct SubscriptionFlowView: View {
isAlertVisible = true
}
}
.onChange(of: viewModel.shouldDismissView) { result in
if result {
dismiss()
}
}

.onAppear(perform: {
Task { await viewModel.initializeViewData() }
Expand Down
6 changes: 2 additions & 4 deletions DuckDuckGo/UserText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1058,6 +1058,7 @@ But if you *do* want a peek under the hood, you can find more information about
public static let subscriptionRemoveFromDeviceConfirmText = NSLocalizedString("subscription.remove.from.device.text", value: "You will no longer be able to access your Privacy Pro subscription on this device. This will not cancel your subscription, and it will remain active on your other devices.", comment: "Remove from device confirmation dialog text")
public static let subscriptionRemove = NSLocalizedString("subscription.remove.subscription", value: "Remove Subscription", comment: "Remove subscription button text")
public static let subscriptionRemoveCancel = NSLocalizedString("subscription.remove.subscription.cancel", value: "Cancel", comment: "Remove subscription cancel button text")
public static let subscriptionRemovalConfirmation = NSLocalizedString("subscription.cancel.message", value: "Your subscription has been removed from this device.", comment: "Subscription Removal confirmation message")

// Subscription Restore
public static let subscriptionActivate = NSLocalizedString("subscription.activate", value: "Activate Subscription", comment: "Subscription Activation Window Title")
Expand Down Expand Up @@ -1091,8 +1092,6 @@ But if you *do* want a peek under the hood, you can find more information about
public static let subscriptionManageEmailCancelButton = NSLocalizedString("subscription.activate.manage.email.cancel", value: "Cancel", comment: "Button title for cancelling email deletion")
public static let subscriptionManageEmailOKButton = NSLocalizedString("subscription.activate.manage.email.OK", value: "OK", comment: "Button title for confirming email deletion")



// Subscribe & Restore Flow
public static let subscriptionFoundTitle = NSLocalizedString("subscription.found.title", value: "Subscription Found", comment: "Title for the existing subscription dialog")
public static let subscriptionFoundText = NSLocalizedString("subscription.found.text", value: "We found a subscription associated with this Apple ID.", comment: "Message for the existing subscription dialog")
Expand All @@ -1104,6 +1103,5 @@ But if you *do* want a peek under the hood, you can find more information about
public static let subscriptionRestoreSuccessfulTitle = NSLocalizedString("subscription.restore.success.alert.title", value: "You’re all set.", comment: "Alert title for restored purchase")
public static let subscriptionRestoreSuccessfulMessage = NSLocalizedString("subscription.restore.success.alert.message", value: "Your purchases have been restored.", comment: "Alert message for restored purchase")
public static let subscriptionRestoreSuccessfulButton = NSLocalizedString("subscription.restore.success.alert.button", value: "OK", comment: "Alert button text for restored purchase alert")



}
Loading
Loading