diff --git a/RevenueCat.xcodeproj/project.pbxproj b/RevenueCat.xcodeproj/project.pbxproj index ed9ef9c5c4..c88060a944 100644 --- a/RevenueCat.xcodeproj/project.pbxproj +++ b/RevenueCat.xcodeproj/project.pbxproj @@ -235,6 +235,7 @@ 351B51C226D450E800BD2BD7 /* ProductRequestDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E350E57B0A393455A72B40 /* ProductRequestDataTests.swift */; }; 351B51C326D450F200BD2BD7 /* InMemoryCachedObjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E35E3250FBBB03D92E06EC /* InMemoryCachedObjectTests.swift */; }; 352137322CDBA2AA00FE961B /* FeatureEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 352137312CDBA2A700FE961B /* FeatureEvent.swift */; }; + 3523048A2D0A0F30003971A4 /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 352304892D0A0F2B003971A4 /* ErrorView.swift */; }; 3525D8A42C4AB3D600C21D99 /* CustomerCenterEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3525D8A32C4AB3D500C21D99 /* CustomerCenterEnvironment.swift */; }; 35272E1B26D0029300F22C3B /* DeviceCacheSubscriberAttributesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E35C4A795A0F056381A1B3 /* DeviceCacheSubscriberAttributesTests.swift */; }; 35272E2226D0048D00F22C3B /* HTTPClientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E353CBE9CF2572A72A347F /* HTTPClientTests.swift */; }; @@ -1517,6 +1518,7 @@ 351B517326D44F4B00BD2BD7 /* MockPaymentDiscount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPaymentDiscount.swift; sourceTree = ""; }; 351B517926D44FF000BD2BD7 /* MockRequestFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRequestFetcher.swift; sourceTree = ""; }; 352137312CDBA2A700FE961B /* FeatureEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureEvent.swift; sourceTree = ""; }; + 352304892D0A0F2B003971A4 /* ErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorView.swift; sourceTree = ""; }; 3525D8A32C4AB3D500C21D99 /* CustomerCenterEnvironment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomerCenterEnvironment.swift; sourceTree = ""; }; 352B7D7827BD919B002A47DD /* DangerousSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DangerousSettings.swift; sourceTree = ""; }; 3530C18822653E8F00D6DF52 /* AdSupport.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AdSupport.framework; path = System/Library/Frameworks/AdSupport.framework; sourceTree = SDKROOT; }; @@ -3563,6 +3565,7 @@ C3AD12B92C6EA61F00A4F86F /* CompatibilityNavigationStack.swift */, C3AD12BB2C6EA69D00A4F86F /* SubscriptionDetailsView.swift */, 77372D982C6F8C7B008E59D3 /* AppUpdateWarningView.swift */, + 352304892D0A0F2B003971A4 /* ErrorView.swift */, ); path = Views; sourceTree = ""; @@ -6490,6 +6493,7 @@ 88A543E72C37A4C40039C6A5 /* TierSelectorView.swift in Sources */, 2C74571F2CE7028E004ACE52 /* ScreenCondition.swift in Sources */, 88A543E12C37A4820039C6A5 /* TemplateView+MultiTier.swift in Sources */, + 3523048A2D0A0F30003971A4 /* ErrorView.swift in Sources */, 887A60B72C1D037000E1A461 /* WatchTemplateView.swift in Sources */, 887A60722C1D037000E1A461 /* PaywallViewMode+Extensions.swift in Sources */, 887A60C32C1D037000E1A461 /* FooterView.swift in Sources */, diff --git a/RevenueCatUI/CustomerCenter/Abstractions/CustomerCenterPurchasesType.swift b/RevenueCatUI/CustomerCenter/Abstractions/CustomerCenterPurchasesType.swift index 03fc70f92e..11f16713b2 100644 --- a/RevenueCatUI/CustomerCenter/Abstractions/CustomerCenterPurchasesType.swift +++ b/RevenueCatUI/CustomerCenter/Abstractions/CustomerCenterPurchasesType.swift @@ -40,4 +40,8 @@ protocol CustomerCenterPurchasesType: Sendable { func track(customerCenterEvent: any CustomerCenterEventType) + func loadCustomerCenter() async throws -> CustomerCenterConfigData + + func restorePurchases() async throws -> CustomerInfo + } diff --git a/RevenueCatUI/CustomerCenter/Data/CustomerCenterPurchases.swift b/RevenueCatUI/CustomerCenter/Data/CustomerCenterPurchases.swift index 1504bd4a25..28847aeba2 100644 --- a/RevenueCatUI/CustomerCenter/Data/CustomerCenterPurchases.swift +++ b/RevenueCatUI/CustomerCenter/Data/CustomerCenterPurchases.swift @@ -54,4 +54,12 @@ final class CustomerCenterPurchases: CustomerCenterPurchasesType { Purchases.shared.track(customerCenterEvent: customerCenterEvent) } + func loadCustomerCenter() async throws -> CustomerCenterConfigData { + try await Purchases.shared.loadCustomerCenter() + } + + func restorePurchases() async throws -> CustomerInfo { + try await Purchases.shared.restorePurchases() + } + } diff --git a/RevenueCatUI/CustomerCenter/ViewModels/CustomerCenterViewModel.swift b/RevenueCatUI/CustomerCenter/ViewModels/CustomerCenterViewModel.swift index 21b6c6b32e..26258056d9 100644 --- a/RevenueCatUI/CustomerCenter/ViewModels/CustomerCenterViewModel.swift +++ b/RevenueCatUI/CustomerCenter/ViewModels/CustomerCenterViewModel.swift @@ -69,10 +69,6 @@ import RevenueCat } } - var isLoaded: Bool { - return state != .notLoaded && configuration != nil - } - private let currentVersionFetcher: CurrentVersionFetcher internal let customerCenterActionHandler: CustomerCenterActionHandler? @@ -105,55 +101,20 @@ import RevenueCat #endif - func loadPurchaseInformation() async { + func loadScreen() async { do { - let customerInfo = try await purchasesProvider.customerInfo(fetchPolicy: .fetchCurrent) - let hasActiveProducts = - !customerInfo.activeSubscriptions.isEmpty || !customerInfo.nonSubscriptions.isEmpty - - if !hasActiveProducts { - self.purchaseInformation = nil - self.state = .success - return - } - - guard let activeTransaction = findActiveTransaction(customerInfo: customerInfo) else { - Logger.warning(Strings.could_not_find_subscription_information) - self.purchaseInformation = nil - throw CustomerCenterError.couldNotFindSubscriptionInformation - } - - let entitlement = customerInfo.entitlements.all.values - .first(where: { $0.productIdentifier == activeTransaction.productIdentifier }) - - self.purchaseInformation = try await createPurchaseInformation(for: activeTransaction, - entitlement: entitlement) - + try await self.loadPurchaseInformation() + try await self.loadCustomerCenterConfig() self.state = .success } catch { self.state = .error(error) } } - func loadCustomerCenterConfig() async { - do { - self.configuration = try await Purchases.shared.loadCustomerCenter() - if let productId = configuration?.productId { - self.onUpdateAppClick = { - // productId is a positive integer, so it should be safe to construct a URL from it. - let url = URL(string: "https://itunes.apple.com/app/id\(productId)")! - URLUtilities.openURLIfNotAppExtension(url) - } - } - } catch { - self.state = .error(error) - } - } - func performRestore() async -> RestorePurchasesAlert.AlertType { self.customerCenterActionHandler?(.restoreStarted) do { - let customerInfo = try await Purchases.shared.restorePurchases() + let customerInfo = try await purchasesProvider.restorePurchases() self.customerCenterActionHandler?(.restoreCompleted(customerInfo)) let hasEntitlements = customerInfo.entitlements.active.count > 0 return hasEntitlements ? .purchasesRecovered : .purchasesNotFound @@ -182,6 +143,42 @@ import RevenueCat @available(watchOS, unavailable) private extension CustomerCenterViewModel { + func loadPurchaseInformation() async throws { + let customerInfo = try await purchasesProvider.customerInfo(fetchPolicy: .fetchCurrent) + + let hasActiveProducts = + !customerInfo.activeSubscriptions.isEmpty || !customerInfo.nonSubscriptions.isEmpty + + if !hasActiveProducts { + self.purchaseInformation = nil + self.state = .success + return + } + + guard let activeTransaction = findActiveTransaction(customerInfo: customerInfo) else { + Logger.warning(Strings.could_not_find_subscription_information) + self.purchaseInformation = nil + throw CustomerCenterError.couldNotFindSubscriptionInformation + } + + let entitlement = customerInfo.entitlements.all.values + .first(where: { $0.productIdentifier == activeTransaction.productIdentifier }) + + self.purchaseInformation = try await createPurchaseInformation(for: activeTransaction, + entitlement: entitlement) + } + + func loadCustomerCenterConfig() async throws { + self.configuration = try await purchasesProvider.loadCustomerCenter() + if let productId = configuration?.productId { + self.onUpdateAppClick = { + // productId is a positive integer, so it should be safe to construct a URL from it. + let url = URL(string: "https://itunes.apple.com/app/id\(productId)")! + URLUtilities.openURLIfNotAppExtension(url) + } + } + } + func findActiveTransaction(customerInfo: CustomerInfo) -> Transaction? { let activeSubscriptions = customerInfo.subscriptionsByProductIdentifier.values .filter(\.isActive) diff --git a/RevenueCatUI/CustomerCenter/Views/CustomerCenterView.swift b/RevenueCatUI/CustomerCenter/Views/CustomerCenterView.swift index e1de03b60c..88dc1a8b15 100644 --- a/RevenueCatUI/CustomerCenter/Views/CustomerCenterView.swift +++ b/RevenueCatUI/CustomerCenter/Views/CustomerCenterView.swift @@ -71,15 +71,20 @@ public struct CustomerCenterView: View { // swiftlint:disable:next missing_docs public var body: some View { Group { - if !self.viewModel.isLoaded { + switch self.viewModel.state { + case .error: + ErrorView() + case .notLoaded: TintedProgressView() - } else { + case .success: if let configuration = self.viewModel.configuration { destinationView(configuration: configuration) .environment(\.localization, configuration.localization) .environment(\.appearance, configuration.appearance) .environment(\.supportInformation, configuration.support) .environment(\.customerCenterPresentationMode, self.mode) + } else { + TintedProgressView() } } } @@ -104,9 +109,8 @@ public struct CustomerCenterView: View { private extension CustomerCenterView { func loadInformationIfNeeded() async { - if !viewModel.isLoaded { - await viewModel.loadPurchaseInformation() - await viewModel.loadCustomerCenterConfig() + if viewModel.state == .notLoaded { + await viewModel.loadScreen() } } diff --git a/RevenueCatUI/CustomerCenter/Views/ErrorView.swift b/RevenueCatUI/CustomerCenter/Views/ErrorView.swift new file mode 100644 index 0000000000..3eaeacb99b --- /dev/null +++ b/RevenueCatUI/CustomerCenter/Views/ErrorView.swift @@ -0,0 +1,60 @@ +// +// Copyright RevenueCat Inc. All Rights Reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// ErrorView.swift +// +// Created by Cesar de la Vega on 11/12/24. + +import SwiftUI + +#if os(iOS) + +@available(iOS 15.0, *) +@available(macOS, unavailable) +@available(tvOS, unavailable) +@available(watchOS, unavailable) +struct ErrorView: View { + + @Environment(\.locale) + private var locale + + var body: some View { + VStack(spacing: 20) { + let errorMessage: String = Localization.localizedBundle(self.locale) + .localizedString(forKey: "Something went wrong", + value: "Something went wrong", + table: nil) + CompatibilityContentUnavailableView( + String(errorMessage), + systemImage: "exclamationmark.triangle.fill", + description: nil + ) + } + .padding(.vertical, 24) + .padding(.horizontal, 16) + .background(Color(uiColor: .secondarySystemGroupedBackground)) + .cornerRadius(16) + } + +} + +#if DEBUG +@available(iOS 15.0, *) +@available(macOS, unavailable) +@available(tvOS, unavailable) +@available(watchOS, unavailable) +struct ErrorView_Previews: PreviewProvider { + + static var previews: some View { + ErrorView() + } +} +#endif + +#endif diff --git a/RevenueCatUI/Resources/ar.lproj/Localizable.strings b/RevenueCatUI/Resources/ar.lproj/Localizable.strings index 3aace4902f..f3d230a2f8 100644 --- a/RevenueCatUI/Resources/ar.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/ar.lproj/Localizable.strings @@ -21,3 +21,4 @@ "pay_as_you_go_period" = "%@ خلال %@"; "pay_up_front_period" = "%@ مقابل %@"; "then_price_per_period" = "ثم %@"; +"Something went wrong" = "هناك خطأ ما"; diff --git a/RevenueCatUI/Resources/bg.lproj/Localizable.strings b/RevenueCatUI/Resources/bg.lproj/Localizable.strings index 23b0618865..33a7f7bd75 100644 --- a/RevenueCatUI/Resources/bg.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/bg.lproj/Localizable.strings @@ -21,3 +21,4 @@ "pay_as_you_go_period" = "%@ през %@"; "pay_up_front_period" = "%@ за %@"; "then_price_per_period" = "след това %@"; +"Something went wrong" = "Нещо се обърка"; diff --git a/RevenueCatUI/Resources/ca.lproj/Localizable.strings b/RevenueCatUI/Resources/ca.lproj/Localizable.strings index 96cc53662c..0d0d87889e 100644 --- a/RevenueCatUI/Resources/ca.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/ca.lproj/Localizable.strings @@ -21,3 +21,4 @@ "pay_as_you_go_period" = "%@ durant %@"; "pay_up_front_period" = "%@ per %@"; "then_price_per_period" = "després %@"; +"Something went wrong" = "Alguna cosa ha anat malament"; diff --git a/RevenueCatUI/Resources/cs.lproj/Localizable.strings b/RevenueCatUI/Resources/cs.lproj/Localizable.strings index a983223de3..4811e79ff7 100644 --- a/RevenueCatUI/Resources/cs.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/cs.lproj/Localizable.strings @@ -21,3 +21,4 @@ "pay_as_you_go_period" = "%@ během %@"; "pay_up_front_period" = "%@ za %@"; "then_price_per_period" = "poté %@"; +"Something went wrong" = "Něco se pokazilo"; diff --git a/RevenueCatUI/Resources/da.lproj/Localizable.strings b/RevenueCatUI/Resources/da.lproj/Localizable.strings index 6855b8676e..73cd92ebc0 100644 --- a/RevenueCatUI/Resources/da.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/da.lproj/Localizable.strings @@ -21,3 +21,4 @@ "pay_as_you_go_period" = "%@ i løbet af %@"; "pay_up_front_period" = "%@ for %@"; "then_price_per_period" = "derefter %@"; +"Something went wrong" = "Noget gik galt"; diff --git a/RevenueCatUI/Resources/de.lproj/Localizable.strings b/RevenueCatUI/Resources/de.lproj/Localizable.strings index 9bb16b1af1..575fe100de 100644 --- a/RevenueCatUI/Resources/de.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/de.lproj/Localizable.strings @@ -21,3 +21,4 @@ "pay_as_you_go_period" = "%@ während %@"; "pay_up_front_period" = "%@ für %@"; "then_price_per_period" = "dann %@"; +"Something went wrong" = "Etwas ist schief gelaufen"; diff --git a/RevenueCatUI/Resources/el.lproj/Localizable.strings b/RevenueCatUI/Resources/el.lproj/Localizable.strings index da14431673..3e099c94d9 100644 --- a/RevenueCatUI/Resources/el.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/el.lproj/Localizable.strings @@ -21,3 +21,4 @@ "pay_as_you_go_period" = "%@ κατά τη διάρκεια %@"; "pay_up_front_period" = "%@ για %@"; "then_price_per_period" = "μετά %@"; +"Something went wrong" = "Κάτι πήγε στραβά"; diff --git a/RevenueCatUI/Resources/en.lproj/Localizable.strings b/RevenueCatUI/Resources/en.lproj/Localizable.strings index 2967676d6e..61a9f41f02 100644 --- a/RevenueCatUI/Resources/en.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/en.lproj/Localizable.strings @@ -22,3 +22,4 @@ "pay_as_you_go_period" = "%@ during %@"; "pay_up_front_period" = "%@ for %@"; "then_price_per_period" = "then %@"; +"Something went wrong" = "Something went wrong"; diff --git a/RevenueCatUI/Resources/en_AU.lproj/Localizable.strings b/RevenueCatUI/Resources/en_AU.lproj/Localizable.strings index 2967676d6e..61a9f41f02 100644 --- a/RevenueCatUI/Resources/en_AU.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/en_AU.lproj/Localizable.strings @@ -22,3 +22,4 @@ "pay_as_you_go_period" = "%@ during %@"; "pay_up_front_period" = "%@ for %@"; "then_price_per_period" = "then %@"; +"Something went wrong" = "Something went wrong"; diff --git a/RevenueCatUI/Resources/en_CA.lproj/Localizable.strings b/RevenueCatUI/Resources/en_CA.lproj/Localizable.strings index 2967676d6e..61a9f41f02 100644 --- a/RevenueCatUI/Resources/en_CA.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/en_CA.lproj/Localizable.strings @@ -22,3 +22,4 @@ "pay_as_you_go_period" = "%@ during %@"; "pay_up_front_period" = "%@ for %@"; "then_price_per_period" = "then %@"; +"Something went wrong" = "Something went wrong"; diff --git a/RevenueCatUI/Resources/en_GB.lproj/Localizable.strings b/RevenueCatUI/Resources/en_GB.lproj/Localizable.strings index 2967676d6e..61a9f41f02 100644 --- a/RevenueCatUI/Resources/en_GB.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/en_GB.lproj/Localizable.strings @@ -22,3 +22,4 @@ "pay_as_you_go_period" = "%@ during %@"; "pay_up_front_period" = "%@ for %@"; "then_price_per_period" = "then %@"; +"Something went wrong" = "Something went wrong"; diff --git a/RevenueCatUI/Resources/en_US.lproj/Localizable.strings b/RevenueCatUI/Resources/en_US.lproj/Localizable.strings index 2967676d6e..61a9f41f02 100644 --- a/RevenueCatUI/Resources/en_US.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/en_US.lproj/Localizable.strings @@ -22,3 +22,4 @@ "pay_as_you_go_period" = "%@ during %@"; "pay_up_front_period" = "%@ for %@"; "then_price_per_period" = "then %@"; +"Something went wrong" = "Something went wrong"; diff --git a/RevenueCatUI/Resources/es_419.lproj/Localizable.strings b/RevenueCatUI/Resources/es_419.lproj/Localizable.strings index 759517d754..6e66ae90d1 100644 --- a/RevenueCatUI/Resources/es_419.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/es_419.lproj/Localizable.strings @@ -22,3 +22,4 @@ "pay_as_you_go_period" = "%@ durante %@"; "pay_up_front_period" = "%@ por %@"; "then_price_per_period" = "luego %@"; +"Something went wrong" = "Algo salió mal"; diff --git a/RevenueCatUI/Resources/es_ES.lproj/Localizable.strings b/RevenueCatUI/Resources/es_ES.lproj/Localizable.strings index 759517d754..6e66ae90d1 100644 --- a/RevenueCatUI/Resources/es_ES.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/es_ES.lproj/Localizable.strings @@ -22,3 +22,4 @@ "pay_as_you_go_period" = "%@ durante %@"; "pay_up_front_period" = "%@ por %@"; "then_price_per_period" = "luego %@"; +"Something went wrong" = "Algo salió mal"; diff --git a/RevenueCatUI/Resources/fi.lproj/Localizable.strings b/RevenueCatUI/Resources/fi.lproj/Localizable.strings index d455a3544d..c55429f39d 100644 --- a/RevenueCatUI/Resources/fi.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/fi.lproj/Localizable.strings @@ -21,3 +21,4 @@ "pay_as_you_go_period" = "%@ aikana %@"; "pay_up_front_period" = "%@ hinnalla %@"; "then_price_per_period" = "sitten %@"; +"Something went wrong" = "Jotain meni pieleen"; diff --git a/RevenueCatUI/Resources/fr_CA.lproj/Localizable.strings b/RevenueCatUI/Resources/fr_CA.lproj/Localizable.strings index 6463faf3ff..6dff6e0bca 100644 --- a/RevenueCatUI/Resources/fr_CA.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/fr_CA.lproj/Localizable.strings @@ -21,3 +21,4 @@ "pay_as_you_go_period" = "%@ pendant %@"; "pay_up_front_period" = "%@ pour %@"; "then_price_per_period" = "puis %@"; +"Something went wrong" = "Quelque chose s'est mal passé"; diff --git a/RevenueCatUI/Resources/he.lproj/Localizable.strings b/RevenueCatUI/Resources/he.lproj/Localizable.strings index c5ab671e45..d0205667a9 100644 --- a/RevenueCatUI/Resources/he.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/he.lproj/Localizable.strings @@ -21,3 +21,4 @@ "pay_as_you_go_period" = "%@ במהלך %@"; "pay_up_front_period" = "%@ עבור %@"; "then_price_per_period" = "לאחר מכן %@"; +"Something went wrong" = "משהו השתבש"; diff --git a/RevenueCatUI/Resources/hi.lproj/Localizable.strings b/RevenueCatUI/Resources/hi.lproj/Localizable.strings index 52dc2f0bcb..7f194a891d 100644 --- a/RevenueCatUI/Resources/hi.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/hi.lproj/Localizable.strings @@ -21,3 +21,4 @@ "pay_as_you_go_period" = "%@ के दौरान %@"; "pay_up_front_period" = "%@ के लिए %@"; "then_price_per_period" = "फिर %@"; +"Something went wrong" = "कुछ गलत हो गया"; diff --git a/RevenueCatUI/Resources/hr.lproj/Localizable.strings b/RevenueCatUI/Resources/hr.lproj/Localizable.strings index f93d44b0d6..09e373cfa1 100644 --- a/RevenueCatUI/Resources/hr.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/hr.lproj/Localizable.strings @@ -21,3 +21,4 @@ "pay_as_you_go_period" = "%@ tijekom %@"; "pay_up_front_period" = "%@ za %@"; "then_price_per_period" = "zatim %@"; +"Something went wrong" = "Nešto je pošlo po zlu"; diff --git a/RevenueCatUI/Resources/hu.lproj/Localizable.strings b/RevenueCatUI/Resources/hu.lproj/Localizable.strings index 14f0c6769d..db9acfc4f7 100644 --- a/RevenueCatUI/Resources/hu.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/hu.lproj/Localizable.strings @@ -21,3 +21,4 @@ "pay_as_you_go_period" = "%@ során %@"; "pay_up_front_period" = "%@ ért %@"; "then_price_per_period" = "majd %@"; +"Something went wrong" = "Valami elromlott"; diff --git a/RevenueCatUI/Resources/id.lproj/Localizable.strings b/RevenueCatUI/Resources/id.lproj/Localizable.strings index 9279e949f3..8ae0b2005f 100644 --- a/RevenueCatUI/Resources/id.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/id.lproj/Localizable.strings @@ -21,3 +21,4 @@ "pay_as_you_go_period" = "%@ selama %@"; "pay_up_front_period" = "%@ untuk %@"; "then_price_per_period" = "kemudian %@"; +"Something went wrong" = "Ada yang salah"; diff --git a/RevenueCatUI/Resources/it.lproj/Localizable.strings b/RevenueCatUI/Resources/it.lproj/Localizable.strings index 8ba4a01894..f85b6c2b98 100644 --- a/RevenueCatUI/Resources/it.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/it.lproj/Localizable.strings @@ -21,3 +21,4 @@ "pay_as_you_go_period" = "%@ durante %@"; "pay_up_front_period" = "%@ per %@"; "then_price_per_period" = "poi %@"; +"Something went wrong" = "Qualcosa è andato storto"; diff --git a/RevenueCatUI/Resources/ja.lproj/Localizable.strings b/RevenueCatUI/Resources/ja.lproj/Localizable.strings index 22f68a3f64..5f29d078ef 100644 --- a/RevenueCatUI/Resources/ja.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/ja.lproj/Localizable.strings @@ -21,3 +21,4 @@ "pay_as_you_go_period" = "%@ の期間 %@"; "pay_up_front_period" = "%@ で %@"; "then_price_per_period" = "次に %@"; +"Something went wrong" = "問題が発生しました"; diff --git a/RevenueCatUI/Resources/kk.lproj/Localizable.strings b/RevenueCatUI/Resources/kk.lproj/Localizable.strings index 1b56e0be15..f2defb0c1f 100644 --- a/RevenueCatUI/Resources/kk.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/kk.lproj/Localizable.strings @@ -21,3 +21,4 @@ "pay_as_you_go_period" = "%@ ішінде %@"; "pay_up_front_period" = "%@ үшін %@"; "then_price_per_period" = "содан кейін %@"; +"Something went wrong" = "Бірдеңе дұрыс болмады"; diff --git a/RevenueCatUI/Resources/ko.lproj/Localizable.strings b/RevenueCatUI/Resources/ko.lproj/Localizable.strings index 061d5d7d95..994d14a942 100644 --- a/RevenueCatUI/Resources/ko.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/ko.lproj/Localizable.strings @@ -21,3 +21,4 @@ "pay_as_you_go_period" = "%@ 동안 %@"; "pay_up_front_period" = "%@ 에 %@"; "then_price_per_period" = "그런 다음 %@"; +"Something went wrong" = "문제가 발생했습니다"; diff --git a/RevenueCatUI/Resources/ms.lproj/Localizable.strings b/RevenueCatUI/Resources/ms.lproj/Localizable.strings index d141dd7899..8a3a003dad 100644 --- a/RevenueCatUI/Resources/ms.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/ms.lproj/Localizable.strings @@ -21,3 +21,4 @@ "pay_as_you_go_period" = "%@ semasa %@"; "pay_up_front_period" = "%@ untuk %@"; "then_price_per_period" = "kemudian %@"; +"Something went wrong" = "Ada sesuatu yang tidak kena"; diff --git a/RevenueCatUI/Resources/nl.lproj/Localizable.strings b/RevenueCatUI/Resources/nl.lproj/Localizable.strings index b18eab7dcf..3dd13fc222 100644 --- a/RevenueCatUI/Resources/nl.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/nl.lproj/Localizable.strings @@ -21,3 +21,4 @@ "pay_as_you_go_period" = "%@ tijdens %@"; "pay_up_front_period" = "%@ voor %@"; "then_price_per_period" = "dan %@"; +"Something went wrong" = "Er is iets misgegaan"; diff --git a/RevenueCatUI/Resources/no.lproj/Localizable.strings b/RevenueCatUI/Resources/no.lproj/Localizable.strings index c5c4496972..828cac71e9 100644 --- a/RevenueCatUI/Resources/no.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/no.lproj/Localizable.strings @@ -21,3 +21,4 @@ "pay_as_you_go_period" = "%@ i løpet av %@"; "pay_up_front_period" = "%@ for %@"; "then_price_per_period" = "deretter %@"; +"Something went wrong" = "Noe gikk galt"; diff --git a/RevenueCatUI/Resources/pl.lproj/Localizable.strings b/RevenueCatUI/Resources/pl.lproj/Localizable.strings index debb53b40f..0f625dd117 100644 --- a/RevenueCatUI/Resources/pl.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/pl.lproj/Localizable.strings @@ -21,3 +21,4 @@ "pay_as_you_go_period" = "%@ przez %@"; "pay_up_front_period" = "%@ za %@"; "then_price_per_period" = "potem %@"; +"Something went wrong" = "Coś poszło nie tak"; diff --git a/RevenueCatUI/Resources/pt_BR.lproj/Localizable.strings b/RevenueCatUI/Resources/pt_BR.lproj/Localizable.strings index 6ce8744b5d..45de6b1e87 100644 --- a/RevenueCatUI/Resources/pt_BR.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/pt_BR.lproj/Localizable.strings @@ -21,3 +21,4 @@ "pay_as_you_go_period" = "%@ durante %@"; "pay_up_front_period" = "%@ por %@"; "then_price_per_period" = "depois %@"; +"Something went wrong" = "Algo deu errado"; diff --git a/RevenueCatUI/Resources/pt_PT.lproj/Localizable.strings b/RevenueCatUI/Resources/pt_PT.lproj/Localizable.strings index 6ce8744b5d..fe1718af21 100644 --- a/RevenueCatUI/Resources/pt_PT.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/pt_PT.lproj/Localizable.strings @@ -21,3 +21,4 @@ "pay_as_you_go_period" = "%@ durante %@"; "pay_up_front_period" = "%@ por %@"; "then_price_per_period" = "depois %@"; +"Something went wrong" = "Algo correu mal"; diff --git a/RevenueCatUI/Resources/ro.lproj/Localizable.strings b/RevenueCatUI/Resources/ro.lproj/Localizable.strings index 5f9ba6a1ee..659e0b0c3a 100644 --- a/RevenueCatUI/Resources/ro.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/ro.lproj/Localizable.strings @@ -21,3 +21,4 @@ "pay_as_you_go_period" = "%@ pe durata %@"; "pay_up_front_period" = "%@ pentru %@"; "then_price_per_period" = "apoi %@"; +"Something went wrong" = "Ceva n-a mers bine"; diff --git a/RevenueCatUI/Resources/ru.lproj/Localizable.strings b/RevenueCatUI/Resources/ru.lproj/Localizable.strings index 75e52b3066..4561319b80 100644 --- a/RevenueCatUI/Resources/ru.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/ru.lproj/Localizable.strings @@ -21,3 +21,4 @@ "pay_as_you_go_period" = "%@ в течение %@"; "pay_up_front_period" = "%@ за %@"; "then_price_per_period" = "затем %@"; +"Something went wrong" = "Что-то пошло не так"; diff --git a/RevenueCatUI/Resources/sk.lproj/Localizable.strings b/RevenueCatUI/Resources/sk.lproj/Localizable.strings index f41fcec7e8..2454de25e8 100644 --- a/RevenueCatUI/Resources/sk.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/sk.lproj/Localizable.strings @@ -21,3 +21,4 @@ "pay_as_you_go_period" = "%@ počas %@"; "pay_up_front_period" = "%@ za %@"; "then_price_per_period" = "potom %@"; +"Something went wrong" = "Niečo sa pokazilo"; diff --git a/RevenueCatUI/Resources/sv.lproj/Localizable.strings b/RevenueCatUI/Resources/sv.lproj/Localizable.strings index 7bf780bb61..b212349eb3 100644 --- a/RevenueCatUI/Resources/sv.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/sv.lproj/Localizable.strings @@ -21,3 +21,4 @@ "pay_as_you_go_period" = "%@ under %@"; "pay_up_front_period" = "%@ för %@"; "then_price_per_period" = "därefter %@"; +"Something went wrong" = "Något gick fel"; diff --git a/RevenueCatUI/Resources/th.lproj/Localizable.strings b/RevenueCatUI/Resources/th.lproj/Localizable.strings index 3939dcb506..20f09bc02a 100644 --- a/RevenueCatUI/Resources/th.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/th.lproj/Localizable.strings @@ -21,3 +21,4 @@ "pay_as_you_go_period" = "%@ ในช่วง %@"; "pay_up_front_period" = "%@ สำหรับ %@"; "then_price_per_period" = "จากนั้น %@"; +"Something went wrong" = "มีบางอย่างผิดพลาด"; diff --git a/RevenueCatUI/Resources/tr.lproj/Localizable.strings b/RevenueCatUI/Resources/tr.lproj/Localizable.strings index f31fe7e1b9..2f84fe78f8 100644 --- a/RevenueCatUI/Resources/tr.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/tr.lproj/Localizable.strings @@ -21,3 +21,4 @@ "pay_as_you_go_period" = "%@ boyunca %@"; "pay_up_front_period" = "%@ için %@"; "then_price_per_period" = "sonra %@"; +"Something went wrong" = "Bir şeyler yanlış gitti"; diff --git a/RevenueCatUI/Resources/uk.lproj/Localizable.strings b/RevenueCatUI/Resources/uk.lproj/Localizable.strings index 14b2d93f32..af0db82e63 100644 --- a/RevenueCatUI/Resources/uk.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/uk.lproj/Localizable.strings @@ -21,3 +21,4 @@ "pay_as_you_go_period" = "%@ протягом %@"; "pay_up_front_period" = "%@ за %@"; "then_price_per_period" = "потім %@"; +"Something went wrong" = "Щось пішло не так"; diff --git a/RevenueCatUI/Resources/vi.lproj/Localizable.strings b/RevenueCatUI/Resources/vi.lproj/Localizable.strings index ad9a48a245..93d6ba273b 100644 --- a/RevenueCatUI/Resources/vi.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/vi.lproj/Localizable.strings @@ -21,3 +21,4 @@ "pay_as_you_go_period" = "%@ trong %@"; "pay_up_front_period" = "%@ cho %@"; "then_price_per_period" = "sau %@"; +"Something went wrong" = "Đã xảy ra lỗi"; diff --git a/RevenueCatUI/Resources/zh_Hans.lproj/Localizable.strings b/RevenueCatUI/Resources/zh_Hans.lproj/Localizable.strings index c73ff4c52f..31736a407d 100644 --- a/RevenueCatUI/Resources/zh_Hans.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/zh_Hans.lproj/Localizable.strings @@ -21,3 +21,4 @@ "pay_as_you_go_period" = "%@ 在 %@"; "pay_up_front_period" = "%@ 为 %@"; "then_price_per_period" = "然后 %@"; +"Something went wrong" = "出现错误"; diff --git a/RevenueCatUI/Resources/zh_Hant.lproj/Localizable.strings b/RevenueCatUI/Resources/zh_Hant.lproj/Localizable.strings index ae576c9454..e5396949ca 100644 --- a/RevenueCatUI/Resources/zh_Hant.lproj/Localizable.strings +++ b/RevenueCatUI/Resources/zh_Hant.lproj/Localizable.strings @@ -21,3 +21,4 @@ "pay_as_you_go_period" = "%@ 在 %@"; "pay_up_front_period" = "%@ 為 %@"; "then_price_per_period" = "然後 %@"; +"Something went wrong" = "發生錯誤"; diff --git a/Tests/RevenueCatUITests/CustomerCenter/CustomerCenterViewModelTests.swift b/Tests/RevenueCatUITests/CustomerCenter/CustomerCenterViewModelTests.swift index 7323437211..91b6c9c042 100644 --- a/Tests/RevenueCatUITests/CustomerCenter/CustomerCenterViewModelTests.swift +++ b/Tests/RevenueCatUITests/CustomerCenter/CustomerCenterViewModelTests.swift @@ -13,7 +13,7 @@ // Created by Cesar de la Vega on 11/6/24. // -// swiftlint:disable file_length type_body_length function_body_length +// swiftlint:disable type_body_length function_body_length import Nimble import RevenueCat @@ -44,7 +44,7 @@ class CustomerCenterViewModelTests: TestCase { expect(viewModel.state) == .notLoaded expect(viewModel.purchaseInformation).to(beNil()) - expect(viewModel.isLoaded) == false + expect(viewModel.state) == .notLoaded } func testStateChangeToError() { @@ -63,12 +63,12 @@ class CustomerCenterViewModelTests: TestCase { func testIsLoaded() { let viewModel = CustomerCenterViewModel(customerCenterActionHandler: nil) - expect(viewModel.isLoaded) == false + expect(viewModel.state) == .notLoaded viewModel.state = .success viewModel.configuration = CustomerCenterConfigTestData.customerCenterData - expect(viewModel.isLoaded) == true + expect(viewModel.state) == .success } func testLoadPurchaseInformationAlwaysRefreshesCustomerInfo() async throws { @@ -81,21 +81,22 @@ class CustomerCenterViewModelTests: TestCase { purchasesProvider: mockPurchases ) - await viewModel.loadPurchaseInformation() + await viewModel.loadScreen() expect(mockPurchases.customerInfoFetchPolicy) == .fetchCurrent } - func testLoadHasSubscriptionsApple() async throws { - let mockPurchases = - MockCustomerCenterPurchases(customerInfo: CustomerCenterViewModelTests.customerInfoWithAppleSubscriptions) + func testLoadAppleSubscriptions() async throws { + let mockPurchases = MockCustomerCenterPurchases( + customerInfo: CustomerCenterViewModelTests.customerInfoWithAppleSubscriptions + ) let viewModel = CustomerCenterViewModel( customerCenterActionHandler: nil, purchasesProvider: mockPurchases ) - await viewModel.loadPurchaseInformation() + await viewModel.loadScreen() let purchaseInformation = try XCTUnwrap(viewModel.purchaseInformation) expect(purchaseInformation.store) == .appStore @@ -103,37 +104,39 @@ class CustomerCenterViewModelTests: TestCase { } func testLoadHasSubscriptionsGoogle() async throws { - let mockPurchases = - MockCustomerCenterPurchases(customerInfo: CustomerCenterViewModelTests.customerInfoWithGoogleSubscriptions) + let mockPurchases = MockCustomerCenterPurchases( + customerInfo: CustomerCenterViewModelTests.customerInfoWithGoogleSubscriptions + ) let viewModel = CustomerCenterViewModel( customerCenterActionHandler: nil, purchasesProvider: mockPurchases ) - await viewModel.loadPurchaseInformation() + await viewModel.loadScreen() let purchaseInformation = try XCTUnwrap(viewModel.purchaseInformation) expect(purchaseInformation.store) == .playStore expect(viewModel.state) == .success } - func testLoadHasSubscriptionsNonActive() async { - let mockPurchases = - MockCustomerCenterPurchases(customerInfo: CustomerCenterViewModelTests.customerInfoWithoutSubscriptions) + func testLoadHasSubscriptionsNonActive() async throws { + let mockPurchases = MockCustomerCenterPurchases( + customerInfo: CustomerCenterViewModelTests.customerInfoWithoutSubscriptions + ) let viewModel = CustomerCenterViewModel( customerCenterActionHandler: nil, purchasesProvider: mockPurchases ) - await viewModel.loadPurchaseInformation() + await viewModel.loadScreen() expect(viewModel.purchaseInformation).to(beNil()) expect(viewModel.state) == .success } - func testLoadHasSubscriptionsFailure() async { + func testLoadHasSubscriptionsFailure() async throws { let mockPurchases = MockCustomerCenterPurchases(customerInfoError: error) let viewModel = CustomerCenterViewModel( @@ -141,7 +144,7 @@ class CustomerCenterViewModelTests: TestCase { purchasesProvider: mockPurchases ) - await viewModel.loadPurchaseInformation() + await viewModel.loadScreen() expect(viewModel.purchaseInformation).to(beNil()) switch viewModel.state { @@ -187,7 +190,7 @@ class CustomerCenterViewModelTests: TestCase { )) // Act - await viewModel.loadPurchaseInformation() + await viewModel.loadScreen() // Assert expect(viewModel.state) == .success @@ -234,7 +237,7 @@ class CustomerCenterViewModelTests: TestCase { )) // Act - await viewModel.loadPurchaseInformation() + await viewModel.loadScreen() // Assert expect(viewModel.state) == .success @@ -314,7 +317,7 @@ class CustomerCenterViewModelTests: TestCase { )) // Act - await viewModel.loadPurchaseInformation() + await viewModel.loadScreen() // Assert expect(viewModel.state) == .success @@ -404,7 +407,7 @@ class CustomerCenterViewModelTests: TestCase { products: products )) - await viewModel.loadPurchaseInformation() + await viewModel.loadScreen() expect(viewModel.state) == .success @@ -455,7 +458,7 @@ class CustomerCenterViewModelTests: TestCase { products: products )) - await viewModel.loadPurchaseInformation() + await viewModel.loadScreen() expect(viewModel.state) == .success @@ -530,7 +533,7 @@ class CustomerCenterViewModelTests: TestCase { )) // Act - await viewModel.loadPurchaseInformation() + await viewModel.loadScreen() // Assert expect(viewModel.state) == .success @@ -613,7 +616,7 @@ class CustomerCenterViewModelTests: TestCase { )) // Act - await viewModel.loadPurchaseInformation() + await viewModel.loadScreen() // Assert expect(viewModel.state) == .success @@ -632,24 +635,24 @@ class CustomerCenterViewModelTests: TestCase { } } - func testLoadScreenNoActiveSubscription() async { + func testLoadScreenNoActiveSubscription() async throws { let customerInfo = CustomerInfoFixtures.customerInfoWithExpiredAppleSubscriptions let mockPurchases = MockCustomerCenterPurchases(customerInfo: customerInfo) let viewModel = CustomerCenterViewModel(customerCenterActionHandler: nil, purchasesProvider: mockPurchases) - await viewModel.loadPurchaseInformation() + await viewModel.loadScreen() expect(viewModel.purchaseInformation).to(beNil()) expect(viewModel.state) == .success } - func testLoadScreenFailure() async { + func testLoadScreenFailure() async throws { let mockPurchases = MockCustomerCenterPurchases(customerInfoError: error) let viewModel = CustomerCenterViewModel(customerCenterActionHandler: nil, purchasesProvider: mockPurchases) - await viewModel.loadPurchaseInformation() + await viewModel.loadScreen() expect(viewModel.purchaseInformation).to(beNil()) expect(viewModel.state) == .error(error) diff --git a/Tests/RevenueCatUITests/CustomerCenter/MockCustomerCenterPurchases.swift b/Tests/RevenueCatUITests/CustomerCenter/MockCustomerCenterPurchases.swift index 50ed6b40d7..389636049a 100644 --- a/Tests/RevenueCatUITests/CustomerCenter/MockCustomerCenterPurchases.swift +++ b/Tests/RevenueCatUITests/CustomerCenter/MockCustomerCenterPurchases.swift @@ -39,7 +39,10 @@ final class MockCustomerCenterPurchases: @unchecked Sendable, CustomerCenterPurc duration: .month, price: 2.99)], showManageSubscriptionsError: Error? = nil, - beginRefundShouldFail: Bool = false + beginRefundShouldFail: Bool = false, + customerCenterConfigData: CustomerCenterConfigData = CustomerCenterConfigTestData.customerCenterData( + lastPublishedAppVersion: "2.0.0" + ) ) { self.customerInfo = customerInfo self.customerInfoError = customerInfoError @@ -48,6 +51,7 @@ final class MockCustomerCenterPurchases: @unchecked Sendable, CustomerCenterPurc })) self.showManageSubscriptionsError = showManageSubscriptionsError self.beginRefundShouldFail = beginRefundShouldFail + self.loadCustomerCenterResult = .success(customerCenterConfigData) } var customerInfoFetchPolicy: CacheFetchPolicy? @@ -89,4 +93,18 @@ final class MockCustomerCenterPurchases: @unchecked Sendable, CustomerCenterPurc trackCallCount += 1 trackedEvents.append(customerCenterEvent) } + + var loadCustomerCenterCallCount = 0 + var loadCustomerCenterResult: Result = .failure(NSError(domain: "", code: -1)) + func loadCustomerCenter() async throws -> CustomerCenterConfigData { + loadCustomerCenterCallCount += 1 + return try loadCustomerCenterResult.get() + } + + var restorePurchasesCallCount = 0 + var restorePurchasesResult: Result = .failure(NSError(domain: "", code: -1)) + func restorePurchases() async throws -> CustomerInfo { + restorePurchasesCallCount += 1 + return try restorePurchasesResult.get() + } }