Skip to content

Commit

Permalink
Support toggling update warnings & show update in restore flow
Browse files Browse the repository at this point in the history
  • Loading branch information
fire-at-will authored Dec 11, 2024
1 parent fa27846 commit f87bd03
Show file tree
Hide file tree
Showing 11 changed files with 203 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ enum CustomerCenterConfigTestData {

@available(iOS 14.0, *)
// swiftlint:disable:next function_body_length
static func customerCenterData(lastPublishedAppVersion: String?) -> CustomerCenterConfigData {
static func customerCenterData(
lastPublishedAppVersion: String?,
shouldWarnCustomerToUpdate: Bool = false
) -> CustomerCenterConfigData {
CustomerCenterConfigData(
screens: [.management:
.init(
Expand Down Expand Up @@ -111,7 +114,10 @@ enum CustomerCenterConfigTestData {
"back": "Back"
]
),
support: .init(email: "[email protected]"),
support: .init(
email: "[email protected]",
shouldWarnCustomerToUpdate: shouldWarnCustomerToUpdate
),
lastPublishedAppVersion: lastPublishedAppVersion,
productId: 1
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ import RevenueCat
private(set) var appIsLatestVersion: Bool = defaultAppIsLatestVersion
private(set) var purchasesProvider: CustomerCenterPurchasesType

@Published
private(set) var onUpdateAppClick: (() -> Void)?

/// Whether or not the Customer Center should warn the customer that they're on an outdated version of the app.
var shouldShowAppUpdateWarnings: Bool {
return !appIsLatestVersion && (configuration?.support.shouldWarnCustomerToUpdate ?? true)
}

// @PublicForExternalTesting
@Published
var state: CustomerCenterViewState {
Expand Down Expand Up @@ -130,6 +138,13 @@ import RevenueCat
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)
}
Expand Down
11 changes: 0 additions & 11 deletions RevenueCatUI/CustomerCenter/Views/AppUpdateWarningView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,6 @@ struct AppUpdateWarningView: View {
self.onContinueAnywayClick = onContinueAnywayClick
}

init(productId: UInt, onContinueAnywayClick: @escaping () -> Void) {
self.init(
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)
},
onContinueAnywayClick: onContinueAnywayClick
)
}

@Environment(\.localization)
private var localization: CustomerCenterConfigData.Localization
@Environment(\.appearance)
Expand Down
5 changes: 3 additions & 2 deletions RevenueCatUI/CustomerCenter/Views/CustomerCenterView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,10 @@ private extension CustomerCenterView {
if let purchaseInformation = viewModel.purchaseInformation {
if purchaseInformation.store == .appStore,
let screen = configuration.screens[.management] {
if let productId = configuration.productId, !ignoreAppUpdateWarning && !viewModel.appIsLatestVersion {
if let onUpdateAppClick = viewModel.onUpdateAppClick,
!ignoreAppUpdateWarning && viewModel.shouldShowAppUpdateWarnings {
AppUpdateWarningView(
productId: productId,
onUpdateAppClick: onUpdateAppClick,
onContinueAnywayClick: {
withAnimation {
ignoreAppUpdateWarning = true
Expand Down
151 changes: 106 additions & 45 deletions RevenueCatUI/CustomerCenter/Views/RestorePurchasesAlert.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,56 +57,117 @@ struct RestorePurchasesAlert: ViewModifier {

func body(content: Content) -> some View {
content
.alert(isPresented: $isPresented) {
switch self.alertType {
case .restorePurchases:
return Alert(
title: Text(localization.commonLocalizedString(for: .restorePurchases)),
message: Text(localization.commonLocalizedString(for: .goingToCheckPurchases)),
primaryButton: .default(Text(localization.commonLocalizedString(for: .checkPastPurchases)),
action: {
Task {
let alertType =
await self.customerCenterViewModel.performRestore()
self.setAlertType(alertType)
}
}),
secondaryButton: .cancel(Text(localization.commonLocalizedString(for: .cancel)))
)

case .purchasesRecovered:
return Alert(title: Text(localization.commonLocalizedString(for: .purchasesRecovered)),
message: Text(localization.commonLocalizedString(for: .purchasesRecoveredExplanation)),
dismissButton: .cancel(Text(localization.commonLocalizedString(for: .dismiss))) {
dismiss()
})

case .purchasesNotFound:
let message = Text(localization.commonLocalizedString(for: .purchasesNotRecovered))
if let url = supportURL {
return Alert(title: Text(""),
message: message,
primaryButton: .default(
Text(localization.commonLocalizedString(for: .contactSupport))
) {
Task {
openURL(url)
}
},
secondaryButton: .cancel(Text(localization.commonLocalizedString(for: .dismiss))) {
dismiss()
})
} else {
return Alert(title: Text(""),
message: message,
dismissButton: .default(Text(localization.commonLocalizedString(for: .dismiss))) {
dismiss()
})
.confirmationDialog(
alertTitle(),
isPresented: $isPresented,
actions: {
switch alertType {
case .purchasesRecovered:
PurchasesRecoveredActions()
case .purchasesNotFound:
PurchasesNotFoundActions()
case .restorePurchases:
RestorePurchasesActions()
}
},
message: {
Text(alertMessage())
}
)
}

// MARK: - Actions
@ViewBuilder
// swiftlint:disable:next identifier_name
private func RestorePurchasesActions() -> some View {
Button {
Task {
let alertType = await self.customerCenterViewModel.performRestore()
self.setAlertType(alertType)
}
} label: {
Text(localization.commonLocalizedString(for: .checkPastPurchases))
}

Button(role: .cancel) {
dismissAlert()
} label: {
Text(localization.commonLocalizedString(for: .cancel))
}
}

@ViewBuilder
// swiftlint:disable:next identifier_name
private func PurchasesRecoveredActions() -> some View {
Button(role: .cancel) {
dismissAlert()
} label: {
Text(localization.commonLocalizedString(for: .dismiss))
}
}

@ViewBuilder
// swiftlint:disable:next identifier_name
private func PurchasesNotFoundActions() -> some View {

if let onUpdateAppClick = customerCenterViewModel.onUpdateAppClick,
customerCenterViewModel.shouldShowAppUpdateWarnings {
Button {
onUpdateAppClick()
} label: {
Text(localization.commonLocalizedString(for: .updateWarningUpdate))
.bold()
}
}

if let url = supportURL {
Button {
Task {
openURL(url)
}
} label: {
Text(localization.commonLocalizedString(for: .contactSupport))
}
}

Button(role: .cancel) {
dismissAlert()
} label: {
Text(localization.commonLocalizedString(for: .dismiss))
}
}

// MARK: - Strings
private func alertTitle() -> String {
switch self.alertType {
case .purchasesRecovered:
return localization.commonLocalizedString(for: .purchasesRecovered)
case .purchasesNotFound:
return ""
case .restorePurchases:
return localization.commonLocalizedString(for: .restorePurchases)
}
}

private func alertMessage() -> String {
switch self.alertType {
case .purchasesRecovered:
return localization.commonLocalizedString(for: .purchasesRecoveredExplanation)
case .purchasesNotFound:
var message = localization.commonLocalizedString(for: .purchasesNotRecovered)
if customerCenterViewModel.shouldShowAppUpdateWarnings {
message += "\n\n" + localization.commonLocalizedString(for: .updateWarningDescription)
}
return message
case .restorePurchases:
return localization.commonLocalizedString(for: .goingToCheckPurchases)
}
}

private func dismissAlert() {
self.alertType = .restorePurchases
dismiss()
}
}

@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
Expand Down
3 changes: 3 additions & 0 deletions RevenueCatUI/CustomerCenter/Views/WrongPlatformView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ struct WrongPlatformView: View {
@State
private var purchaseInformation: PurchaseInformation

@EnvironmentObject
private var customerCenterViewModel: CustomerCenterViewModel

private let screen: CustomerCenterConfigData.Screen?

@Environment(\.localization)
Expand Down
8 changes: 7 additions & 1 deletion Sources/CustomerCenter/CustomerCenterConfigData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -412,9 +412,14 @@ public struct CustomerCenterConfigData {
public struct Support {

public let email: String
public let shouldWarnCustomerToUpdate: Bool

public init(email: String) {
public init(
email: String,
shouldWarnCustomerToUpdate: Bool
) {
self.email = email
self.shouldWarnCustomerToUpdate = shouldWarnCustomerToUpdate
}

}
Expand Down Expand Up @@ -550,6 +555,7 @@ extension CustomerCenterConfigData.Support {

init(from response: CustomerCenterConfigResponse.Support) {
self.email = response.email
self.shouldWarnCustomerToUpdate = response.shouldWarnCustomerToUpdate ?? true
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ struct CustomerCenterConfigResponse {
struct Support {

let email: String
let shouldWarnCustomerToUpdate: Bool?

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ import XCTest

class ContactSupportUtilitiesTest: TestCase {

private let support: CustomerCenterConfigData.Support = .init(email: "[email protected]")
private let support: CustomerCenterConfigData.Support = .init(
email: "[email protected]",
shouldWarnCustomerToUpdate: false
)
private let localization: CustomerCenterConfigData.Localization = .init(locale: "en_US", localizedStrings: [:])

func testSupportEmailBodyWithDefaultDataIsCorrect() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import XCTest

#if os(iOS)

// swiftlint:disable file_length
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
@available(macOS, unavailable)
@available(tvOS, unavailable)
Expand Down Expand Up @@ -734,6 +735,55 @@ class CustomerCenterViewModelTests: TestCase {
}
}

func testShouldShowAppUpdateWarningsTrue() {
let mockPurchases = MockCustomerCenterPurchases()
let latestVersion = "3.0.0"
let currentVersion = "2.0.0"
let viewModel = CustomerCenterViewModel(
customerCenterActionHandler: nil,
currentVersionFetcher: { return currentVersion },
purchasesProvider: mockPurchases
)
viewModel.configuration = CustomerCenterConfigTestData.customerCenterData(
lastPublishedAppVersion: latestVersion,
shouldWarnCustomerToUpdate: true
)

expect(viewModel.shouldShowAppUpdateWarnings).to(beTrue())
}

func testShouldShowAppUpdateWarningsFalse() {
let mockPurchases = MockCustomerCenterPurchases()
let latestVersion = "3.0.0"
let viewModel = CustomerCenterViewModel(
customerCenterActionHandler: nil,
currentVersionFetcher: { return latestVersion },
purchasesProvider: mockPurchases
)
viewModel.configuration = CustomerCenterConfigTestData.customerCenterData(
lastPublishedAppVersion: latestVersion,
shouldWarnCustomerToUpdate: true
)

expect(viewModel.shouldShowAppUpdateWarnings).to(beFalse())
}

func testShouldShowAppUpdateWarningsFalseIfBlockedByConfig() {
let mockPurchases = MockCustomerCenterPurchases()
let latestVersion = "3.0.0"
let viewModel = CustomerCenterViewModel(
customerCenterActionHandler: nil,
currentVersionFetcher: { return latestVersion },
purchasesProvider: mockPurchases
)
viewModel.configuration = CustomerCenterConfigTestData.customerCenterData(
lastPublishedAppVersion: latestVersion,
shouldWarnCustomerToUpdate: false
)

expect(viewModel.shouldShowAppUpdateWarnings).to(beFalse())
}

}

@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,10 @@ class CustomerCenterConfigDataTests: TestCase {
)
],
localization: .init(locale: "en_US", localizedStrings: ["key": "value"]),
support: .init(email: "[email protected]")
support: .init(
email: "[email protected]",
shouldWarnCustomerToUpdate: false
)
),
lastPublishedAppVersion: "1.2.3",
itunesTrackId: 123
Expand Down Expand Up @@ -178,6 +181,8 @@ class CustomerCenterConfigDataTests: TestCase {

expect(configData.lastPublishedAppVersion) == "1.2.3"
expect(configData.productId) == 123

expect(configData.support.shouldWarnCustomerToUpdate) == false
}

func testUnknownValuesHandling() throws {
Expand Down

0 comments on commit f87bd03

Please sign in to comment.