-
Notifications
You must be signed in to change notification settings - Fork 319
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Paywalls tester with sandbox purchases (#4024)
Restore's Paywall Tester's ability to make sandbox purchases. You must include the Paywalls test app's API key in `LocalConfigItems.swift`. If it doesn't work initially try deleting the app from your simulator (or device) and try again. ![image](https://github.com/RevenueCat/purchases-ios/assets/109382862/c3e81929-64f0-4ffc-8570-bc94c57951cf)
- Loading branch information
Showing
4 changed files
with
214 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
199 changes: 199 additions & 0 deletions
199
...TestingApps/PaywallsTester/PaywallsTester/UI/Views/OfferingList/APIKeyDashboardList.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
// | ||
// APIKeyDashboardList.swift | ||
// SimpleApp | ||
// | ||
// Created by Nacho Soto on 7/27/23. | ||
// | ||
|
||
import RevenueCat | ||
#if DEBUG | ||
@testable import RevenueCatUI | ||
#else | ||
import RevenueCatUI | ||
#endif | ||
import SwiftUI | ||
|
||
struct APIKeyDashboardList: View { | ||
|
||
fileprivate struct Template: Hashable { | ||
var name: String? | ||
} | ||
|
||
fileprivate struct Data: Hashable { | ||
var sections: [Template] | ||
var offeringsBySection: [Template: [Offering]] | ||
} | ||
|
||
fileprivate struct PresentedPaywall: Hashable { | ||
var offering: Offering | ||
var mode: PaywallViewMode | ||
} | ||
|
||
@State | ||
private var offerings: Result<Data, NSError>? | ||
|
||
@State | ||
private var presentedPaywall: PresentedPaywall? | ||
|
||
var body: some View { | ||
NavigationView { | ||
self.content | ||
.navigationTitle("Live Paywalls") | ||
} | ||
.task { | ||
do { | ||
let offerings = try await Purchases.shared.offerings() | ||
.all | ||
.map(\.value) | ||
.sorted { $0.serverDescription > $1.serverDescription } | ||
|
||
let offeringsBySection = Dictionary( | ||
grouping: offerings, | ||
by: { Template(name: $0.paywall?.templateName) } | ||
) | ||
|
||
self.offerings = .success( | ||
.init( | ||
sections: Array(offeringsBySection.keys).sorted { $0.description < $1.description }, | ||
offeringsBySection: offeringsBySection | ||
) | ||
) | ||
} catch let error as NSError { | ||
self.offerings = .failure(error) | ||
} | ||
} | ||
} | ||
|
||
@ViewBuilder | ||
private var content: some View { | ||
switch self.offerings { | ||
case let .success(data): | ||
VStack { | ||
Text(Self.modesInstructions) | ||
.font(.footnote) | ||
self.list(with: data) | ||
} | ||
|
||
case let .failure(error): | ||
Text(error.description) | ||
|
||
case .none: | ||
SwiftUI.ProgressView() | ||
} | ||
} | ||
|
||
@ViewBuilder | ||
private func list(with data: Data) -> some View { | ||
List { | ||
ForEach(data.sections, id: \.self) { template in | ||
Section { | ||
ForEach(data.offeringsBySection[template]!, id: \.id) { offering in | ||
if let paywall = offering.paywall { | ||
#if targetEnvironment(macCatalyst) | ||
NavigationLink( | ||
destination: PaywallPresenter(offering: offering, | ||
mode: .default, | ||
displayCloseButton: false), | ||
tag: PresentedPaywall(offering: offering, mode: .default), | ||
selection: self.$presentedPaywall | ||
) { | ||
OfferButton(offering: offering, paywall: paywall) {} | ||
.contextMenu { | ||
self.contextMenu(for: offering) | ||
} | ||
} | ||
#else | ||
OfferButton(offering: offering, paywall: paywall) { | ||
self.presentedPaywall = .init(offering: offering, mode: .default) | ||
} | ||
#if !os(watchOS) | ||
.contextMenu { | ||
self.contextMenu(for: offering) | ||
} | ||
#endif | ||
#endif | ||
} else { | ||
Text(offering.serverDescription) | ||
} | ||
} | ||
} header: { | ||
Text(verbatim: template.description) | ||
} | ||
} | ||
} | ||
.sheet(item: self.$presentedPaywall) { paywall in | ||
PaywallPresenter(offering: paywall.offering, mode: paywall.mode, introEligility: .eligible) | ||
.onRestoreCompleted { _ in | ||
self.presentedPaywall = nil | ||
} | ||
} | ||
} | ||
|
||
#if !os(watchOS) | ||
@ViewBuilder | ||
private func contextMenu(for offering: Offering) -> some View { | ||
ForEach(PaywallViewMode.allCases, id: \.self) { mode in | ||
self.button(for: mode, offering: offering) | ||
} | ||
} | ||
#endif | ||
|
||
@ViewBuilder | ||
private func button(for selectedMode: PaywallViewMode, offering: Offering) -> some View { | ||
Button { | ||
self.presentedPaywall = .init(offering: offering, mode: selectedMode) | ||
} label: { | ||
Text(selectedMode.name) | ||
Image(systemName: selectedMode.icon) | ||
} | ||
} | ||
|
||
private struct OfferButton: View { | ||
let offering: Offering | ||
let paywall: PaywallData | ||
let action: () -> Void | ||
|
||
var body: some View { | ||
Button(action: action) { | ||
Text(self.offering.serverDescription) | ||
} | ||
.buttonStyle(.plain) | ||
.contentShape(Rectangle()) | ||
} | ||
} | ||
|
||
#if targetEnvironment(macCatalyst) | ||
private static let modesInstructions = "Right click or ⌘ + click to open in different modes." | ||
#else | ||
private static let modesInstructions = "Press and hold to open in different modes." | ||
#endif | ||
|
||
} | ||
|
||
extension APIKeyDashboardList.Template: CustomStringConvertible { | ||
|
||
var description: String { | ||
if let name = self.name { | ||
#if DEBUG | ||
if let template = PaywallTemplate(rawValue: name) { | ||
return template.name | ||
} else { | ||
return "Unrecognized template" | ||
} | ||
#else | ||
return name | ||
#endif | ||
} else { | ||
return "No paywall" | ||
} | ||
} | ||
|
||
} | ||
|
||
extension APIKeyDashboardList.PresentedPaywall: Identifiable { | ||
|
||
var id: String { | ||
return "\(self.offering.id)-\(self.mode.name)" | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters