Skip to content

Commit

Permalink
dismiss promotional offer sheet after successful purchase
Browse files Browse the repository at this point in the history
  • Loading branch information
fire-at-will authored Nov 14, 2024
1 parent d107fcd commit 02dece8
Show file tree
Hide file tree
Showing 8 changed files with 194 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,9 @@ protocol CustomerCenterPurchasesType: Sendable {
func promotionalOffer(forProductDiscount discount: StoreProductDiscount,
product: StoreProduct) async throws -> PromotionalOffer

func purchase(
product: StoreProduct,
promotionalOffer: PromotionalOffer
) async throws -> PurchaseResultData

}
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,13 @@ final class CustomerCenterPurchases: CustomerCenterPurchasesType {
product: product)
}

func purchase(
product: StoreProduct,
promotionalOffer: PromotionalOffer
) async throws -> PurchaseResultData {
try await Purchases.shared.purchase(
product: product,
promotionalOffer: promotionalOffer
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ class FeedbackSurveyViewModel: ObservableObject {
var feedbackSurveyData: FeedbackSurveyData

@Published
var loadingState: String?
var loadingOption: String?

@Published
var promotionalOfferData: PromotionalOfferData?

Expand All @@ -54,32 +55,54 @@ class FeedbackSurveyViewModel: ObservableObject {
self.customerCenterActionHandler = customerCenterActionHandler
}

func handleAction(for option: CustomerCenterConfigData.HelpPath.FeedbackSurvey.Option) async {
func handleAction(
for option: CustomerCenterConfigData.HelpPath.FeedbackSurvey.Option,
dismissView: () -> Void
) async {
if let customerCenterActionHandler = self.customerCenterActionHandler {
customerCenterActionHandler(.feedbackSurveyCompleted(option.id))
}

if let promotionalOffer = option.promotionalOffer,
promotionalOffer.eligible {
self.loadingState = option.id
self.loadingOption = option.id
let result = await loadPromotionalOfferUseCase.execute(promoOfferDetails: promotionalOffer)
switch result {
case .success(let promotionalOfferData):
self.promotionalOfferData = promotionalOfferData
case .failure:
self.feedbackSurveyData.onOptionSelected()
self.loadingState = nil
self.loadingOption = nil
}
} else {
self.feedbackSurveyData.onOptionSelected()
dismissView()
}
}
}

func handleSheetDismiss() {
self.feedbackSurveyData.onOptionSelected()
self.loadingState = nil
}
// MARK: - Promotional Offer Sheet Dismissal Handling
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
@available(macOS, unavailable)
@available(tvOS, unavailable)
@available(watchOS, unavailable)
extension FeedbackSurveyViewModel {

/// Function responsible for handling the user's action on the PromotionalOfferView
func handleDismissPromotionalOfferView(
_ userAction: PromotionalOfferViewAction,
dismissView: () -> Void
) async {
// Clear the promotional offer data to dismiss the sheet
self.promotionalOfferData = nil
self.loadingOption = nil

if !userAction.shouldTerminateCurrentPathFlow {
self.feedbackSurveyData.onOptionSelected()
}

dismissView()
}
}

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,29 @@ struct IdentifiableURL: Identifiable {

}

// MARK: - Promotional Offer Sheet Dismissal Handling
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
@available(macOS, unavailable)
@available(tvOS, unavailable)
@available(watchOS, unavailable)
extension ManageSubscriptionsViewModel {

/// Function responsible for handling the user's action on the PromotionalOfferView
func handleDismissPromotionalOfferView(_ userAction: PromotionalOfferViewAction) async {
// Clear the promotional offer data to dismiss the sheet
self.promotionalOfferData = nil

if userAction.shouldTerminateCurrentPathFlow {
self.loadingPath = nil
} else {
if let loadingPath = loadingPath {
await self.onPathSelected(path: loadingPath)
self.loadingPath = nil
}
}
}
}

@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
@available(macOS, unavailable)
@available(tvOS, unavailable)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
//

import Foundation
import SwiftUI

import RevenueCat

#if os(iOS)
Expand All @@ -33,14 +35,18 @@ class PromotionalOfferViewModel: ObservableObject {
private var purchasesProvider: CustomerCenterPurchasesType
private let loadPromotionalOfferUseCase: LoadPromotionalOfferUseCase

convenience init() {
self.init(promotionalOfferData: nil)
}
/// Callback to be called when the promotional offer is purchased
internal var onPromotionalOfferPurchaseFlowComplete: ((PromotionalOfferViewAction) -> Void)?

init(promotionalOfferData: PromotionalOfferData?) {
init(
promotionalOfferData: PromotionalOfferData?,
purchasesProvider: CustomerCenterPurchasesType = CustomerCenterPurchases(),
onPromotionalOfferPurchaseFlowComplete: ((PromotionalOfferViewAction) -> Void)? = nil
) {
self.promotionalOfferData = promotionalOfferData
self.purchasesProvider = CustomerCenterPurchases()
self.purchasesProvider = purchasesProvider
self.loadPromotionalOfferUseCase = LoadPromotionalOfferUseCase()
self.onPromotionalOfferPurchaseFlowComplete = onPromotionalOfferPurchaseFlowComplete
}

func purchasePromo() async {
Expand All @@ -51,12 +57,18 @@ class PromotionalOfferViewModel: ObservableObject {
}

do {
let result = try await Purchases.shared.purchase(product: product, promotionalOffer: promotionalOffer)
// swiftlint:disable:next todo
// TODO: do something with result
let result = try await self.purchasesProvider.purchase(
product: product,
promotionalOffer: promotionalOffer
)

Logger.debug("Purchased promotional offer: \(result)")
self.onPromotionalOfferPurchaseFlowComplete?(.successfullyRedeemedPromotionalOffer(result))
} catch {
// swiftlint:disable:next todo
// TODO: Log error message
self.error = error
self.onPromotionalOfferPurchaseFlowComplete?(.promotionalCodeRedemptionFailed(error))
}
}

Expand Down
52 changes: 36 additions & 16 deletions RevenueCatUI/CustomerCenter/Views/FeedbackSurveyView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ struct FeedbackSurveyView: View {

@StateObject
private var viewModel: FeedbackSurveyViewModel

@Environment(\.localization)
private var localization: CustomerCenterConfigData.Localization
@Environment(\.appearance)
Expand All @@ -36,9 +37,11 @@ struct FeedbackSurveyView: View {
@Binding
private var isPresented: Bool

init(feedbackSurveyData: FeedbackSurveyData,
customerCenterActionHandler: CustomerCenterActionHandler?,
isPresented: Binding<Bool>) {
init(
feedbackSurveyData: FeedbackSurveyData,
customerCenterActionHandler: CustomerCenterActionHandler?,
isPresented: Binding<Bool>
) {
self._viewModel = StateObject(wrappedValue: FeedbackSurveyViewModel(
feedbackSurveyData: feedbackSurveyData,
customerCenterActionHandler: customerCenterActionHandler
Expand All @@ -49,26 +52,43 @@ struct FeedbackSurveyView: View {
var body: some View {
ZStack {
List {
FeedbackSurveyButtonsView(options: self.viewModel.feedbackSurveyData.configuration.options,
onOptionSelected: { option in
await self.viewModel.handleAction(for: option)
self.isPresented = false
},
loadingState: self.$viewModel.loadingState)
FeedbackSurveyButtonsView(
options: self.viewModel.feedbackSurveyData.configuration.options,
onOptionSelected: { option in
await self.viewModel.handleAction(
for: option,
dismissView: self.dismissView
)
},
loadingOption: self.$viewModel.loadingOption
)
}
.sheet(
item: self.$viewModel.promotionalOfferData,
onDismiss: { self.viewModel.handleSheetDismiss() },
content: { promotionalOfferData in
PromotionalOfferView(promotionalOffer: promotionalOfferData.promotionalOffer,
product: promotionalOfferData.product,
promoOfferDetails: promotionalOfferData.promoOfferDetails)
PromotionalOfferView(
promotionalOffer: promotionalOfferData.promotionalOffer,
product: promotionalOfferData.product,
promoOfferDetails: promotionalOfferData.promoOfferDetails,
onDismissPromotionalOfferView: { userAction in
Task(priority: .userInitiated) {
await viewModel.handleDismissPromotionalOfferView(
userAction,
dismissView: self.dismissView
)
}
}
)
.interactiveDismissDisabled()
})
}
.navigationTitle(self.viewModel.feedbackSurveyData.configuration.title)
.navigationBarTitleDisplayMode(.inline)
}

private func dismissView() {
self.isPresented = false
}
}

@available(iOS 15.0, *)
Expand All @@ -83,20 +103,20 @@ struct FeedbackSurveyButtonsView: View {
@Environment(\.appearance) private var appearance: CustomerCenterConfigData.Appearance

@Binding
var loadingState: String?
var loadingOption: String?

var body: some View {
ForEach(options, id: \.id) { option in
AsyncButton {
await self.onOptionSelected(option)
} label: {
if self.loadingState == option.id {
if self.loadingOption == option.id {
TintedProgressView()
} else {
Text(option.title)
}
}
.disabled(self.loadingState != nil)
.disabled(self.loadingOption != nil)
}

}
Expand Down
26 changes: 15 additions & 11 deletions RevenueCatUI/CustomerCenter/Views/ManageSubscriptionsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,17 +111,21 @@ struct ManageSubscriptionsView: View {
await loadInformationIfNeeded()
}
.restorePurchasesAlert(isPresented: self.$viewModel.showRestoreAlert)
.sheet(item: self.$viewModel.promotionalOfferData,
onDismiss: {
Task {
await self.viewModel.handleSheetDismiss()
}
},
content: { promotionalOfferData in
PromotionalOfferView(promotionalOffer: promotionalOfferData.promotionalOffer,
product: promotionalOfferData.product,
promoOfferDetails: promotionalOfferData.promoOfferDetails)
})
.sheet(
item: self.$viewModel.promotionalOfferData,
content: { promotionalOfferData in
PromotionalOfferView(
promotionalOffer: promotionalOfferData.promotionalOffer,
product: promotionalOfferData.product,
promoOfferDetails: promotionalOfferData.promoOfferDetails,
onDismissPromotionalOfferView: { userAction in
Task(priority: .userInitiated) {
await self.viewModel.handleDismissPromotionalOfferView(userAction)
}
}
)
.interactiveDismissDisabled()
})
.sheet(item: self.$viewModel.inAppBrowserURL,
onDismiss: {
self.viewModel.onDismissInAppBrowser()
Expand Down
Loading

0 comments on commit 02dece8

Please sign in to comment.