From bbacdd73a2ef35e4010ead1f49081a07d29e4796 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Mon, 11 Mar 2024 23:42:32 +0100 Subject: [PATCH] Subscriptions: 19. Error handling and minor updates (#2567) --- DuckDuckGo/SettingsSubscriptionView.swift | 14 ++-- DuckDuckGo/SettingsViewModel.swift | 18 ++--- .../AsyncHeadlessWebView.swift | 3 + .../AsyncHeadlessWebViewModel.swift | 1 + .../HeadlessWebView.swift | 2 + .../HeadlessWebViewCoordinator.swift | 11 ++- ...IdentityTheftRestorationPagesFeature.swift | 9 ++- .../SubscriptionEmailViewModel.swift | 12 ++++ .../ViewModel/SubscriptionFlowViewModel.swift | 23 +++++- .../ViewModel/SubscriptionITPViewModel.swift | 15 +++- .../SubscriptionRestoreViewModel.swift | 2 +- .../Views/SubscriptionEmailView.swift | 9 +++ .../Views/SubscriptionFlowView.swift | 70 +++++++++++++++---- .../Views/SubscriptionITPView.swift | 11 +++ .../Views/SubscriptionRestoreView.swift | 12 +++- DuckDuckGo/UserText.swift | 4 ++ DuckDuckGo/en.lproj/Localizable.strings | 9 +++ submodules/privacy-reference-tests | 2 +- 18 files changed, 187 insertions(+), 40 deletions(-) diff --git a/DuckDuckGo/SettingsSubscriptionView.swift b/DuckDuckGo/SettingsSubscriptionView.swift index 4b2c71b8e5..a727b87dc0 100644 --- a/DuckDuckGo/SettingsSubscriptionView.swift +++ b/DuckDuckGo/SettingsSubscriptionView.swift @@ -138,18 +138,14 @@ struct SettingsSubscriptionView: View { SettingsCellView(label: UserText.settingsPProDBPTitle, 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()) { @@ -157,6 +153,12 @@ struct SettingsSubscriptionView: View { } } + .sheet(isPresented: $isShowingDBP) { + SubscriptionPIRView() + } + .sheet(isPresented: $isShowingITP) { + SubscriptionITPView() + } } var body: some View { diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index fd7ea5e1de..7aaba476b6 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -25,6 +25,7 @@ import Common import Combine import SyncUI + #if SUBSCRIPTION import Subscription #endif @@ -47,12 +48,20 @@ final class SettingsViewModel: ObservableObject { private var legacyViewProvider: SettingsLegacyViewProvider private lazy var versionProvider: AppVersion = AppVersion.shared private let voiceSearchHelper: VoiceSearchHelperProtocol + #if SUBSCRIPTION private var accountManager: AccountManager private var signOutObserver: Any? + + // Sheet Presentation & Navigation @Published var isRestoringSubscription: Bool = false @Published var shouldDisplayRestoreSubscriptionError: Bool = false + @Published var shouldShowNetP = false + @Published var shouldShowDBP = false + @Published var shouldShowITP = false #endif + @UserDefaultsWrapper(key: .subscriptionIsActive, defaultValue: false) + static private var cachedHasActiveSubscription: Bool #if NETWORK_PROTECTION @@ -63,10 +72,6 @@ final class SettingsViewModel: ObservableObject { private lazy var isPad = UIDevice.current.userInterfaceIdiom == .pad private var cancellables = Set() - // Defaults - @UserDefaultsWrapper(key: .subscriptionIsActive, defaultValue: false) - static private var cachedHasActiveSubscription: Bool - // Closures to interact with legacy view controllers through the container var onRequestPushLegacyView: ((UIViewController) -> Void)? var onRequestPresentLegacyView: ((UIViewController, _ modal: Bool) -> Void)? @@ -78,10 +83,6 @@ final class SettingsViewModel: ObservableObject { @Published var shouldNavigateToDBP = false @Published var shouldNavigateToITP = false @Published var shouldNavigateToSubscriptionFlow = false - - @Published var shouldShowNetP = false - @Published var shouldShowDBP = false - @Published var shouldShowITP = false // Our View State @Published private(set) var state: SettingsState @@ -372,7 +373,6 @@ extension SettingsViewModel { } } } - default: // Account is active but there's not a valid subscription / entitlements signOutUser() diff --git a/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebView.swift b/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebView.swift index aa6a8fdbce..306a636443 100644 --- a/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebView.swift +++ b/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebView.swift @@ -65,6 +65,9 @@ struct AsyncHeadlessWebView: View { onContentType: { value in viewModel.contentType = value }, + onNavigationError: { value in + viewModel.navigationError = value + }, navigationCoordinator: viewModel.navigationCoordinator ) .frame(width: geometry.size.width, height: geometry.size.height) diff --git a/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebViewModel.swift b/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebViewModel.swift index 190352d246..688f0ed213 100644 --- a/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebViewModel.swift +++ b/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebViewModel.swift @@ -37,6 +37,7 @@ final class AsyncHeadlessWebViewViewModel: ObservableObject { @Published var canGoBack: Bool = false @Published var canGoForward: Bool = false @Published var contentType: String = "" + @Published var navigationError: Error? @Published var allowedDomains: [String]? var navigationCoordinator = HeadlessWebViewNavCoordinator(webView: nil) diff --git a/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebView.swift b/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebView.swift index 7467489ac2..3452302d90 100644 --- a/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebView.swift +++ b/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebView.swift @@ -32,6 +32,7 @@ struct HeadlessWebView: UIViewRepresentable { var onCanGoBack: ((Bool) -> Void)? var onCanGoForward: ((Bool) -> Void)? var onContentType: ((String) -> Void)? + var onNavigationError: ((Error?) -> Void)? var navigationCoordinator: HeadlessWebViewNavCoordinator func makeUIView(context: Context) -> WKWebView { @@ -73,6 +74,7 @@ struct HeadlessWebView: UIViewRepresentable { onCanGoBack: onCanGoBack, onCanGoForward: onCanGoForward, onContentType: onContentType, + onNavigationError: onNavigationError, settings: settings ) } diff --git a/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebViewCoordinator.swift b/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebViewCoordinator.swift index 92c8f1a7a4..d8fc66a461 100644 --- a/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebViewCoordinator.swift +++ b/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebViewCoordinator.swift @@ -30,6 +30,7 @@ final class HeadlessWebViewCoordinator: NSObject { var onCanGoBack: ((Bool) -> Void)? var onCanGoForward: ((Bool) -> Void)? var onContentType: ((String) -> Void)? + var onNavigationError: ((Error?) -> Void)? var settings: AsyncHeadlessWebViewSettings var size: CGSize = .zero @@ -52,6 +53,7 @@ final class HeadlessWebViewCoordinator: NSObject { onCanGoBack: ((Bool) -> Void)?, onCanGoForward: ((Bool) -> Void)?, onContentType: ((String) -> Void)?, + onNavigationError: ((Error?) -> Void)?, allowedDomains: [String]? = nil, settings: AsyncHeadlessWebViewSettings = AsyncHeadlessWebViewSettings()) { self.parent = parent @@ -60,6 +62,7 @@ final class HeadlessWebViewCoordinator: NSObject { self.onURLChange = onURLChange self.onCanGoBack = onCanGoBack self.onCanGoForward = onCanGoForward + self.onNavigationError = onNavigationError self.onContentType = onContentType self.settings = settings } @@ -106,6 +109,7 @@ final class HeadlessWebViewCoordinator: NSObject { onCanGoBack = nil onCanGoForward = nil onContentType = nil + onNavigationError = nil } } @@ -128,6 +132,7 @@ extension HeadlessWebViewCoordinator: WKNavigationDelegate { } func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { + onNavigationError?(nil) if let url = webView.url, url != lastURL { onURLChange?(url) lastURL = url @@ -182,8 +187,12 @@ extension HeadlessWebViewCoordinator: WKNavigationDelegate { decisionHandler(.allow) } + func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: any Error) { + onNavigationError?(error) + } + func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { - // NOOP + onNavigationError?(error) } // Javascript Confirm dialogs Delegate diff --git a/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift b/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift index 276ef14926..a577a61613 100644 --- a/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift @@ -66,10 +66,13 @@ final class IdentityTheftRestorationPagesFeature: Subfeature, ObservableObject { return nil } } - + func getAccessToken(params: Any, original: WKScriptMessage) async throws -> Encodable? { - let authToken = AccountManager().authToken ?? "" - return [Constants.token: authToken] + if let accessToken = AccountManager().accessToken { + return [Constants.token: accessToken] + } else { + return [String: String]() + } } deinit { diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift index 6cb2ea102f..8a5874fff9 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift @@ -38,6 +38,7 @@ final class SubscriptionEmailViewModel: ObservableObject { @Published var activateSubscription = false @Published var managingSubscriptionEmail = false @Published var transactionError: SubscriptionRestoreError? + @Published var navigationError: Bool = false @Published var shouldDisplayInactiveError: Bool = false var webViewModel: AsyncHeadlessWebViewViewModel @@ -101,6 +102,17 @@ final class SubscriptionEmailViewModel: ObservableObject { } } .store(in: &cancellables) + + webViewModel.$navigationError + .receive(on: DispatchQueue.main) + .sink { [weak self] error in + guard let strongSelf = self else { return } + DispatchQueue.main.async { + strongSelf.navigationError = error != nil ? true : false + } + + } + .store(in: &cancellables) } private func handleTransactionError(error: SubscriptionPagesUseSubscriptionFeature.UseSubscriptionError) { diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift index 6d0cb332d7..295b22e45b 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift @@ -224,10 +224,24 @@ final class SubscriptionFlowViewModel: ObservableObject { } .store(in: &cancellables) + webViewModel.$navigationError + .receive(on: DispatchQueue.main) + .sink { [weak self] error in + guard let strongSelf = self else { return } + DispatchQueue.main.async { + strongSelf.transactionError = error != nil ? .generalError : nil + } + + } + .store(in: &cancellables) + canGoBackCancellable = webViewModel.$canGoBack .receive(on: DispatchQueue.main) .sink { [weak self] value in - self?.canNavigateBack = value + guard let strongSelf = self else { return } + + let shouldNavigateBack = value && (strongSelf.webViewModel.url?.lastPathComponent != URL.subscriptionBaseURL.lastPathComponent) + strongSelf.canNavigateBack = shouldNavigateBack } } @@ -242,6 +256,12 @@ final class SubscriptionFlowViewModel: ObservableObject { canNavigateBack = false } + private func urlRemovingQueryParams(_ url: URL) -> URL? { + var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) + urlComponents?.query = nil // Remove the query string + return urlComponents?.url + } + func initializeViewData() async { await self.setupTransactionObserver() await self .setupWebViewObservers() @@ -257,6 +277,7 @@ final class SubscriptionFlowViewModel: ObservableObject { userTappedRestoreButton = false shouldShowNavigationBar = false selectedFeature = nil + transactionError = nil canNavigateBack = false shouldDismissView = true subFeature.cleanup() diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift index 6e613491a8..99694a5468 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift @@ -46,6 +46,7 @@ final class SubscriptionITPViewModel: ObservableObject { @Published var isDownloadableContent: Bool = false @Published var activityItems: [Any] = [] @Published var attachmentURL: URL? + @Published var navigationError: Bool = false var webViewModel: AsyncHeadlessWebViewViewModel @Published var shouldNavigateToExternalURL: URL? @@ -81,9 +82,20 @@ final class SubscriptionITPViewModel: ObservableObject { settings: webViewSettings) } - // Observe transaction status + // swiftlint:disable function_body_length private func setupSubscribers() async { + webViewModel.$navigationError + .receive(on: DispatchQueue.main) + .sink { [weak self] error in + guard let strongSelf = self else { return } + DispatchQueue.main.async { + strongSelf.navigationError = error != nil ? true : false + } + + } + .store(in: &cancellables) + webViewModel.$scrollPosition .receive(on: DispatchQueue.main) .throttle(for: .milliseconds(100), scheduler: DispatchQueue.main, latest: true) @@ -134,6 +146,7 @@ final class SubscriptionITPViewModel: ObservableObject { self?.canNavigateBack = value } } + // swiftlint:enable function_body_length func initializeView() { webViewModel.navigationCoordinator.navigateTo(url: manageITPURL ) diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift index 3da604761f..cbeee2fb26 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift @@ -81,7 +81,7 @@ final class SubscriptionRestoreViewModel: ObservableObject { private func handleRestoreError(error: SubscriptionPagesUseSubscriptionFeature.UseSubscriptionError) { switch error { case .failedToRestorePastPurchase: - activationResult = .notFound + activationResult = .error case .subscriptionExpired: activationResult = .expired case .subscriptionNotFound: diff --git a/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift b/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift index 8712560f36..a8b3d72c83 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift @@ -50,6 +50,15 @@ struct SubscriptionEmailView: View { ) } + .alert(isPresented: $viewModel.navigationError) { + Alert( + title: Text(UserText.subscriptionBackendErrorTitle), + message: Text(UserText.subscriptionBackendErrorMessage), + dismissButton: .cancel(Text(UserText.subscriptionBackendErrorButton)) { + dismiss() + }) + } + .onAppear { viewModel.loadURL() } diff --git a/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift b/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift index 85e820fd3a..f611c5ead8 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift @@ -31,7 +31,8 @@ struct SubscriptionFlowView: View { @State private var shouldShowNavigationBar = false @State private var isActive = false @State private var transactionError: SubscriptionFlowViewModel.SubscriptionPurchaseError? - @State private var shouldPresentError = false + @State private var errorMessage: SubscriptionErrorMessage = .general + @State private var shouldPresentError: Bool = false @State private var isFirstOnAppear = true enum Constants { @@ -42,6 +43,13 @@ struct SubscriptionFlowView: View { static let backButtonImage = "chevron.left" } + enum SubscriptionErrorMessage { + case activeSubscription + case appStore + case backend + case general + } + var body: some View { NavigationView { baseView @@ -134,10 +142,28 @@ struct SubscriptionFlowView: View { } .onChange(of: viewModel.transactionError) { value in - if value != nil { - shouldPresentError = true + + if !shouldPresentError { + let displayError: Bool = { + switch value { + case .hasActiveSubscription: + errorMessage = .activeSubscription + return true + case .failedToRestorePastPurchase, .purchaseFailed: + errorMessage = .appStore + return true + case .failedToGetSubscriptionOptions, .generalError: + errorMessage = .backend + return true + default: + return false + } + }() + + if displayError { + shouldPresentError = true + } } - transactionError = value } .onAppear(perform: { @@ -165,7 +191,8 @@ struct SubscriptionFlowView: View { }) .alert(isPresented: $shouldPresentError) { - getAlert() + getAlert(error: self.errorMessage) + } // The trailing close button should be hidden when a transaction is in progress @@ -173,12 +200,12 @@ struct SubscriptionFlowView: View { ? Button(UserText.subscriptionCloseButton) { viewModel.finalizeSubscriptionFlow() } : nil) } - - private func getAlert() -> Alert { - switch transactionError { - case .hasActiveSubscription: - Alert( + private func getAlert(error: SubscriptionErrorMessage) -> Alert { + + switch error { + case .activeSubscription: + return Alert( title: Text(UserText.subscriptionFoundTitle), message: Text(UserText.subscriptionFoundText), primaryButton: .cancel(Text(UserText.subscriptionFoundCancel)) { @@ -188,14 +215,23 @@ struct SubscriptionFlowView: View { viewModel.restoreAppstoreTransaction() } ) - default: - Alert( + case .appStore: + return Alert( title: Text(UserText.subscriptionAppStoreErrorTitle), message: Text(UserText.subscriptionAppStoreErrorMessage), dismissButton: .cancel(Text(UserText.actionOK)) { Task { await viewModel.initializeViewData() } } ) + case .backend, .general: + return Alert( + title: Text(UserText.subscriptionBackendErrorTitle), + message: Text(UserText.subscriptionBackendErrorMessage), + dismissButton: .cancel(Text(UserText.subscriptionBackendErrorButton)) { + viewModel.finalizeSubscriptionFlow() + dismiss() + } + ) } } @@ -203,8 +239,14 @@ struct SubscriptionFlowView: View { private var webView: some View { ZStack(alignment: .top) { - // Restore View Hidden Link - NavigationLink(destination: SubscriptionRestoreView(), isActive: $isActive) { + + // Restore View Hidden Link + let restoreView = SubscriptionRestoreView( + onDismissStack: { + viewModel.finalizeSubscriptionFlow() + dismiss() + }) + NavigationLink(destination: restoreView, isActive: $isActive) { EmptyView() }.isDetailLink(false) diff --git a/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift b/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift index 32f0f60d0e..96e2653cf6 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift @@ -85,6 +85,17 @@ struct SubscriptionITPView: View { } .tint(Color(designSystemColor: .textPrimary)) + + .alert(isPresented: $viewModel.navigationError) { + Alert( + title: Text(UserText.subscriptionBackendErrorTitle), + message: Text(UserText.subscriptionBackendErrorMessage), + dismissButton: .cancel(Text(UserText.subscriptionBackendErrorButton)) { + dismiss() + }) + } + + .sheet(isPresented: Binding( get: { viewModel.shouldShowExternalURLSheet }, set: { if !$0 { viewModel.shouldNavigateToExternalURL = nil } } diff --git a/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift b/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift index 7185163b3f..d6b1d7cf6a 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift @@ -32,6 +32,7 @@ struct SubscriptionRestoreView: View { @State private var expandedItemId: Int = 0 @State private var isAlertVisible = false @State private var isActive: Bool = false + var onDismissStack: (() -> Void)? private enum Constants { static let heroImage = "ManageSubscriptionHero" @@ -273,10 +274,15 @@ struct SubscriptionRestoreView: View { dismiss() }), secondaryButton: .cancel()) - case .error: - return Alert(title: Text(UserText.subscriptionAppStoreErrorTitle), message: Text(UserText.subscriptionAppStoreErrorMessage)) default: - return Alert(title: Text(UserText.subscriptionAppStoreErrorTitle), message: Text(UserText.subscriptionAppStoreErrorMessage)) + return Alert( + title: Text(UserText.subscriptionBackendErrorTitle), + message: Text(UserText.subscriptionBackendErrorMessage), + dismissButton: .cancel(Text(UserText.subscriptionBackendErrorButton)) { + onDismissStack?() + dismiss() + } + ) } } diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index 3aeeba2b15..c7cf2b241d 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -1135,6 +1135,10 @@ But if you *do* want a peek under the hood, you can find more information about public static let subscriptionAppStoreErrorTitle = NSLocalizedString("subscription.restore.general.error.title", value: "Something Went Wrong", comment: "Alert for general error title") public static let subscriptionAppStoreErrorMessage = NSLocalizedString("subscription.restore.general.error.message", value: "The App Store was unable to process your purchase. Please try again later.", comment: "Alert for general error message") + public static let subscriptionBackendErrorTitle = NSLocalizedString("subscription.restore.backend.error.title", value: "Something Went Wrong", comment: "Alert for general error title") + public static let subscriptionBackendErrorMessage = NSLocalizedString("subscription.restore.backend.error.message", value: "We’re having trouble connecting. Please try again later.", comment: "Alert for general error message") + public static let subscriptionBackendErrorButton = NSLocalizedString("subscription.restore.backend.error.button", value: "Back to Settings", comment: "Button text for general error message") + // PIR: public static let subscriptionPIRHeroText = NSLocalizedString("subscription.pir.hero", value: "Activate Privacy Pro on desktop to set up Personal Information Removal", comment: "Hero Text for Personal information removal") public static let subscriptionPIRHeroDetail = NSLocalizedString("subscription.pir.heroText", value: "In the DuckDuckGo browser for desktop, go to %@ and click %@ to get started.", comment: "Description on how to use Personal information removal in desktop. The first placeholder references a location in the Desktop application. Privacy Pro>, and the second, the menu entry. i.e. ") diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index cf0aa4ca37..400655f967 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -2151,6 +2151,15 @@ But if you *do* want a peek under the hood, you can find more information about /* text for renewal string */ "subscription.renews" = "renews"; +/* Button text for general error message */ +"subscription.restore.backend.error.button" = "Back to Settings"; + +/* Alert for general error message */ +"subscription.restore.backend.error.message" = "We’re having trouble connecting. Please try again later."; + +/* Alert for general error title */ +"subscription.restore.backend.error.title" = "Something Went Wrong"; + /* Alert for general error message */ "subscription.restore.general.error.message" = "The App Store was unable to process your purchase. Please try again later."; diff --git a/submodules/privacy-reference-tests b/submodules/privacy-reference-tests index 40ce86837d..6b7ad1e7f1 160000 --- a/submodules/privacy-reference-tests +++ b/submodules/privacy-reference-tests @@ -1 +1 @@ -Subproject commit 40ce86837def0adbf558f00ed0531ab4df5839a8 +Subproject commit 6b7ad1e7f15270f9dfeb58a272199f4d57c3eb22