-
Notifications
You must be signed in to change notification settings - Fork 315
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Paywalls
: handle missing paywalls gracefully
#2855
Conversation
@@ -19,6 +19,7 @@ struct DebugErrorView: View { | |||
|
|||
case emptyView | |||
case fatalError | |||
case replacement(AnyView) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💥
cabb034
to
7debf7a
Compare
860e972
to
bcb6446
Compare
7debf7a
to
333be7a
Compare
Paywalls
: handle missing paywall gracefullyPaywalls
: handle missing paywall gracefully
bcb6446
to
0c15257
Compare
Paywalls
: handle missing paywall gracefullyPaywalls
: handle missing paywalls gracefully
} | ||
} | ||
} label: { | ||
self.label | ||
} | ||
.disabled(self.inProgress) | ||
.alert(isPresented: self.isShowingError, error: self.error) { _ in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All this is extracted to ErrorDisplay
|
||
var recoverySuggestion: String? { | ||
self.underlyingError.localizedRecoverySuggestion | ||
.displayError(self.$error) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same thing in just one line.
identifier: Self.offeringIdentifier, | ||
serverDescription: "", | ||
metadata: [:], | ||
paywall: .default, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The loading view reuses the same .default
paywall.
purchaseHandler: Self.purchaseHandler | ||
) | ||
.disabled(true) | ||
.redacted(reason: .placeholder) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😍
purchase: { _ in | ||
fatalError("Should not be able to purchase") | ||
}, | ||
restorePurchases: { | ||
fatalError("Should not be able to purchase") | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Important to create these so we don't attempt to purchase fake packages.
The view is marked as .disabled()
to make this impossible.
Group { | ||
if let aspectRatio { | ||
self.placeholderView | ||
.aspectRatio(aspectRatio, contentMode: .fit) | ||
} else { | ||
self.placeholderView | ||
} | ||
} | ||
.frame(maxWidth: self.maxWidth) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This ensures consistent layout when transitioning from loading to loaded.
@@ -54,40 +50,6 @@ struct SimpleApp: App { | |||
} | |||
} | |||
|
|||
@ViewBuilder | |||
private var paywallView: some View { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All this boilerplate is gone and no longer necessary for our users either 🎉
.task { | ||
// Fix-me: better error handling |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
TODO: expose |
048a341
to
08839fc
Compare
bfdecc5
to
8348c66
Compare
08839fc
to
d06b303
Compare
8348c66
to
422fa87
Compare
See #2711. This is needed for #2855. In order to be able to create test products for the paywall loading screen, we need to be able to do this in release builds as well. Another benefit of exposing `TestStoreProduct` in release builds is that it's also usable for pre-built versions of the SDK (like Carthage).
d06b303
to
cc15734
Compare
### Changes: - Improved `PaywallView` API: now there's only 2 constructors (with optional `Mode` parameters): - `PaywallView()` - `PaywallView(offering:)` - New `PaywallData.default` as a fallback when trying to present a paywall with missing data (either because it failed to decode, or it's missing) - Extracted error state handling to `ErrorDisplay` (used by `PaywallView` and `AsyncButton` now). It can optionally dismiss the presenting sheet. - Handling offering loading errors in `PaywallView` - Improved `DebugErrorView` to allow displaying a fallback view: ```swift DebugErrorView( "Offering '\(offering.identifier)' has no configured paywall.\n" + "The displayed paywall contains default configuration.\n" + "This error will be hidden in production.", releaseBehavior: .replacement( AnyView( LoadedOfferingPaywallView( offering: offering, paywall: .default, mode: mode, introEligibility: checker, purchaseHandler: purchaseHandler ) ) ) ) ``` - Added `LoadingPaywallView` as a placeholder view during loading - Improved `MultiPackageTemplate` and `RemoteImage` to fix some layout issues during transitions - Added transition animations between loading state and loaded paywall
### Changes: - Improved `PaywallView` API: now there's only 2 constructors (with optional `Mode` parameters): - `PaywallView()` - `PaywallView(offering:)` - New `PaywallData.default` as a fallback when trying to present a paywall with missing data (either because it failed to decode, or it's missing) - Extracted error state handling to `ErrorDisplay` (used by `PaywallView` and `AsyncButton` now). It can optionally dismiss the presenting sheet. - Handling offering loading errors in `PaywallView` - Improved `DebugErrorView` to allow displaying a fallback view: ```swift DebugErrorView( "Offering '\(offering.identifier)' has no configured paywall.\n" + "The displayed paywall contains default configuration.\n" + "This error will be hidden in production.", releaseBehavior: .replacement( AnyView( LoadedOfferingPaywallView( offering: offering, paywall: .default, mode: mode, introEligibility: checker, purchaseHandler: purchaseHandler ) ) ) ) ``` - Added `LoadingPaywallView` as a placeholder view during loading - Improved `MultiPackageTemplate` and `RemoteImage` to fix some layout issues during transitions - Added transition animations between loading state and loaded paywall
### Changes: - Improved `PaywallView` API: now there's only 2 constructors (with optional `Mode` parameters): - `PaywallView()` - `PaywallView(offering:)` - New `PaywallData.default` as a fallback when trying to present a paywall with missing data (either because it failed to decode, or it's missing) - Extracted error state handling to `ErrorDisplay` (used by `PaywallView` and `AsyncButton` now). It can optionally dismiss the presenting sheet. - Handling offering loading errors in `PaywallView` - Improved `DebugErrorView` to allow displaying a fallback view: ```swift DebugErrorView( "Offering '\(offering.identifier)' has no configured paywall.\n" + "The displayed paywall contains default configuration.\n" + "This error will be hidden in production.", releaseBehavior: .replacement( AnyView( LoadedOfferingPaywallView( offering: offering, paywall: .default, mode: mode, introEligibility: checker, purchaseHandler: purchaseHandler ) ) ) ) ``` - Added `LoadingPaywallView` as a placeholder view during loading - Improved `MultiPackageTemplate` and `RemoteImage` to fix some layout issues during transitions - Added transition animations between loading state and loaded paywall
### Changes: - Improved `PaywallView` API: now there's only 2 constructors (with optional `Mode` parameters): - `PaywallView()` - `PaywallView(offering:)` - New `PaywallData.default` as a fallback when trying to present a paywall with missing data (either because it failed to decode, or it's missing) - Extracted error state handling to `ErrorDisplay` (used by `PaywallView` and `AsyncButton` now). It can optionally dismiss the presenting sheet. - Handling offering loading errors in `PaywallView` - Improved `DebugErrorView` to allow displaying a fallback view: ```swift DebugErrorView( "Offering '\(offering.identifier)' has no configured paywall.\n" + "The displayed paywall contains default configuration.\n" + "This error will be hidden in production.", releaseBehavior: .replacement( AnyView( LoadedOfferingPaywallView( offering: offering, paywall: .default, mode: mode, introEligibility: checker, purchaseHandler: purchaseHandler ) ) ) ) ``` - Added `LoadingPaywallView` as a placeholder view during loading - Improved `MultiPackageTemplate` and `RemoteImage` to fix some layout issues during transitions - Added transition animations between loading state and loaded paywall
### Changes: - Improved `PaywallView` API: now there's only 2 constructors (with optional `Mode` parameters): - `PaywallView()` - `PaywallView(offering:)` - New `PaywallData.default` as a fallback when trying to present a paywall with missing data (either because it failed to decode, or it's missing) - Extracted error state handling to `ErrorDisplay` (used by `PaywallView` and `AsyncButton` now). It can optionally dismiss the presenting sheet. - Handling offering loading errors in `PaywallView` - Improved `DebugErrorView` to allow displaying a fallback view: ```swift DebugErrorView( "Offering '\(offering.identifier)' has no configured paywall.\n" + "The displayed paywall contains default configuration.\n" + "This error will be hidden in production.", releaseBehavior: .replacement( AnyView( LoadedOfferingPaywallView( offering: offering, paywall: .default, mode: mode, introEligibility: checker, purchaseHandler: purchaseHandler ) ) ) ) ``` - Added `LoadingPaywallView` as a placeholder view during loading - Improved `MultiPackageTemplate` and `RemoteImage` to fix some layout issues during transitions - Added transition animations between loading state and loaded paywall
### Changes: - Improved `PaywallView` API: now there's only 2 constructors (with optional `Mode` parameters): - `PaywallView()` - `PaywallView(offering:)` - New `PaywallData.default` as a fallback when trying to present a paywall with missing data (either because it failed to decode, or it's missing) - Extracted error state handling to `ErrorDisplay` (used by `PaywallView` and `AsyncButton` now). It can optionally dismiss the presenting sheet. - Handling offering loading errors in `PaywallView` - Improved `DebugErrorView` to allow displaying a fallback view: ```swift DebugErrorView( "Offering '\(offering.identifier)' has no configured paywall.\n" + "The displayed paywall contains default configuration.\n" + "This error will be hidden in production.", releaseBehavior: .replacement( AnyView( LoadedOfferingPaywallView( offering: offering, paywall: .default, mode: mode, introEligibility: checker, purchaseHandler: purchaseHandler ) ) ) ) ``` - Added `LoadingPaywallView` as a placeholder view during loading - Improved `MultiPackageTemplate` and `RemoteImage` to fix some layout issues during transitions - Added transition animations between loading state and loaded paywall
### Changes: - Improved `PaywallView` API: now there's only 2 constructors (with optional `Mode` parameters): - `PaywallView()` - `PaywallView(offering:)` - New `PaywallData.default` as a fallback when trying to present a paywall with missing data (either because it failed to decode, or it's missing) - Extracted error state handling to `ErrorDisplay` (used by `PaywallView` and `AsyncButton` now). It can optionally dismiss the presenting sheet. - Handling offering loading errors in `PaywallView` - Improved `DebugErrorView` to allow displaying a fallback view: ```swift DebugErrorView( "Offering '\(offering.identifier)' has no configured paywall.\n" + "The displayed paywall contains default configuration.\n" + "This error will be hidden in production.", releaseBehavior: .replacement( AnyView( LoadedOfferingPaywallView( offering: offering, paywall: .default, mode: mode, introEligibility: checker, purchaseHandler: purchaseHandler ) ) ) ) ``` - Added `LoadingPaywallView` as a placeholder view during loading - Improved `MultiPackageTemplate` and `RemoteImage` to fix some layout issues during transitions - Added transition animations between loading state and loaded paywall
### Changes: - Improved `PaywallView` API: now there's only 2 constructors (with optional `Mode` parameters): - `PaywallView()` - `PaywallView(offering:)` - New `PaywallData.default` as a fallback when trying to present a paywall with missing data (either because it failed to decode, or it's missing) - Extracted error state handling to `ErrorDisplay` (used by `PaywallView` and `AsyncButton` now). It can optionally dismiss the presenting sheet. - Handling offering loading errors in `PaywallView` - Improved `DebugErrorView` to allow displaying a fallback view: ```swift DebugErrorView( "Offering '\(offering.identifier)' has no configured paywall.\n" + "The displayed paywall contains default configuration.\n" + "This error will be hidden in production.", releaseBehavior: .replacement( AnyView( LoadedOfferingPaywallView( offering: offering, paywall: .default, mode: mode, introEligibility: checker, purchaseHandler: purchaseHandler ) ) ) ) ``` - Added `LoadingPaywallView` as a placeholder view during loading - Improved `MultiPackageTemplate` and `RemoteImage` to fix some layout issues during transitions - Added transition animations between loading state and loaded paywall
### Changes: - Improved `PaywallView` API: now there's only 2 constructors (with optional `Mode` parameters): - `PaywallView()` - `PaywallView(offering:)` - New `PaywallData.default` as a fallback when trying to present a paywall with missing data (either because it failed to decode, or it's missing) - Extracted error state handling to `ErrorDisplay` (used by `PaywallView` and `AsyncButton` now). It can optionally dismiss the presenting sheet. - Handling offering loading errors in `PaywallView` - Improved `DebugErrorView` to allow displaying a fallback view: ```swift DebugErrorView( "Offering '\(offering.identifier)' has no configured paywall.\n" + "The displayed paywall contains default configuration.\n" + "This error will be hidden in production.", releaseBehavior: .replacement( AnyView( LoadedOfferingPaywallView( offering: offering, paywall: .default, mode: mode, introEligibility: checker, purchaseHandler: purchaseHandler ) ) ) ) ``` - Added `LoadingPaywallView` as a placeholder view during loading - Improved `MultiPackageTemplate` and `RemoteImage` to fix some layout issues during transitions - Added transition animations between loading state and loaded paywall
- Improved `PaywallView` API: now there's only 2 constructors (with optional `Mode` parameters): - `PaywallView()` - `PaywallView(offering:)` - New `PaywallData.default` as a fallback when trying to present a paywall with missing data (either because it failed to decode, or it's missing) - Extracted error state handling to `ErrorDisplay` (used by `PaywallView` and `AsyncButton` now). It can optionally dismiss the presenting sheet. - Handling offering loading errors in `PaywallView` - Improved `DebugErrorView` to allow displaying a fallback view: ```swift DebugErrorView( "Offering '\(offering.identifier)' has no configured paywall.\n" + "The displayed paywall contains default configuration.\n" + "This error will be hidden in production.", releaseBehavior: .replacement( AnyView( LoadedOfferingPaywallView( offering: offering, paywall: .default, mode: mode, introEligibility: checker, purchaseHandler: purchaseHandler ) ) ) ) ``` - Added `LoadingPaywallView` as a placeholder view during loading - Improved `MultiPackageTemplate` and `RemoteImage` to fix some layout issues during transitions - Added transition animations between loading state and loaded paywall
- Improved `PaywallView` API: now there's only 2 constructors (with optional `Mode` parameters): - `PaywallView()` - `PaywallView(offering:)` - New `PaywallData.default` as a fallback when trying to present a paywall with missing data (either because it failed to decode, or it's missing) - Extracted error state handling to `ErrorDisplay` (used by `PaywallView` and `AsyncButton` now). It can optionally dismiss the presenting sheet. - Handling offering loading errors in `PaywallView` - Improved `DebugErrorView` to allow displaying a fallback view: ```swift DebugErrorView( "Offering '\(offering.identifier)' has no configured paywall.\n" + "The displayed paywall contains default configuration.\n" + "This error will be hidden in production.", releaseBehavior: .replacement( AnyView( LoadedOfferingPaywallView( offering: offering, paywall: .default, mode: mode, introEligibility: checker, purchaseHandler: purchaseHandler ) ) ) ) ``` - Added `LoadingPaywallView` as a placeholder view during loading - Improved `MultiPackageTemplate` and `RemoteImage` to fix some layout issues during transitions - Added transition animations between loading state and loaded paywall
- Improved `PaywallView` API: now there's only 2 constructors (with optional `Mode` parameters): - `PaywallView()` - `PaywallView(offering:)` - New `PaywallData.default` as a fallback when trying to present a paywall with missing data (either because it failed to decode, or it's missing) - Extracted error state handling to `ErrorDisplay` (used by `PaywallView` and `AsyncButton` now). It can optionally dismiss the presenting sheet. - Handling offering loading errors in `PaywallView` - Improved `DebugErrorView` to allow displaying a fallback view: ```swift DebugErrorView( "Offering '\(offering.identifier)' has no configured paywall.\n" + "The displayed paywall contains default configuration.\n" + "This error will be hidden in production.", releaseBehavior: .replacement( AnyView( LoadedOfferingPaywallView( offering: offering, paywall: .default, mode: mode, introEligibility: checker, purchaseHandler: purchaseHandler ) ) ) ) ``` - Added `LoadingPaywallView` as a placeholder view during loading - Improved `MultiPackageTemplate` and `RemoteImage` to fix some layout issues during transitions - Added transition animations between loading state and loaded paywall
- Improved `PaywallView` API: now there's only 2 constructors (with optional `Mode` parameters): - `PaywallView()` - `PaywallView(offering:)` - New `PaywallData.default` as a fallback when trying to present a paywall with missing data (either because it failed to decode, or it's missing) - Extracted error state handling to `ErrorDisplay` (used by `PaywallView` and `AsyncButton` now). It can optionally dismiss the presenting sheet. - Handling offering loading errors in `PaywallView` - Improved `DebugErrorView` to allow displaying a fallback view: ```swift DebugErrorView( "Offering '\(offering.identifier)' has no configured paywall.\n" + "The displayed paywall contains default configuration.\n" + "This error will be hidden in production.", releaseBehavior: .replacement( AnyView( LoadedOfferingPaywallView( offering: offering, paywall: .default, mode: mode, introEligibility: checker, purchaseHandler: purchaseHandler ) ) ) ) ``` - Added `LoadingPaywallView` as a placeholder view during loading - Improved `MultiPackageTemplate` and `RemoteImage` to fix some layout issues during transitions - Added transition animations between loading state and loaded paywall
- Improved `PaywallView` API: now there's only 2 constructors (with optional `Mode` parameters): - `PaywallView()` - `PaywallView(offering:)` - New `PaywallData.default` as a fallback when trying to present a paywall with missing data (either because it failed to decode, or it's missing) - Extracted error state handling to `ErrorDisplay` (used by `PaywallView` and `AsyncButton` now). It can optionally dismiss the presenting sheet. - Handling offering loading errors in `PaywallView` - Improved `DebugErrorView` to allow displaying a fallback view: ```swift DebugErrorView( "Offering '\(offering.identifier)' has no configured paywall.\n" + "The displayed paywall contains default configuration.\n" + "This error will be hidden in production.", releaseBehavior: .replacement( AnyView( LoadedOfferingPaywallView( offering: offering, paywall: .default, mode: mode, introEligibility: checker, purchaseHandler: purchaseHandler ) ) ) ) ``` - Added `LoadingPaywallView` as a placeholder view during loading - Improved `MultiPackageTemplate` and `RemoteImage` to fix some layout issues during transitions - Added transition animations between loading state and loaded paywall
- Improved `PaywallView` API: now there's only 2 constructors (with optional `Mode` parameters): - `PaywallView()` - `PaywallView(offering:)` - New `PaywallData.default` as a fallback when trying to present a paywall with missing data (either because it failed to decode, or it's missing) - Extracted error state handling to `ErrorDisplay` (used by `PaywallView` and `AsyncButton` now). It can optionally dismiss the presenting sheet. - Handling offering loading errors in `PaywallView` - Improved `DebugErrorView` to allow displaying a fallback view: ```swift DebugErrorView( "Offering '\(offering.identifier)' has no configured paywall.\n" + "The displayed paywall contains default configuration.\n" + "This error will be hidden in production.", releaseBehavior: .replacement( AnyView( LoadedOfferingPaywallView( offering: offering, paywall: .default, mode: mode, introEligibility: checker, purchaseHandler: purchaseHandler ) ) ) ) ``` - Added `LoadingPaywallView` as a placeholder view during loading - Improved `MultiPackageTemplate` and `RemoteImage` to fix some layout issues during transitions - Added transition animations between loading state and loaded paywall
- Improved `PaywallView` API: now there's only 2 constructors (with optional `Mode` parameters): - `PaywallView()` - `PaywallView(offering:)` - New `PaywallData.default` as a fallback when trying to present a paywall with missing data (either because it failed to decode, or it's missing) - Extracted error state handling to `ErrorDisplay` (used by `PaywallView` and `AsyncButton` now). It can optionally dismiss the presenting sheet. - Handling offering loading errors in `PaywallView` - Improved `DebugErrorView` to allow displaying a fallback view: ```swift DebugErrorView( "Offering '\(offering.identifier)' has no configured paywall.\n" + "The displayed paywall contains default configuration.\n" + "This error will be hidden in production.", releaseBehavior: .replacement( AnyView( LoadedOfferingPaywallView( offering: offering, paywall: .default, mode: mode, introEligibility: checker, purchaseHandler: purchaseHandler ) ) ) ) ``` - Added `LoadingPaywallView` as a placeholder view during loading - Improved `MultiPackageTemplate` and `RemoteImage` to fix some layout issues during transitions - Added transition animations between loading state and loaded paywall
- Improved `PaywallView` API: now there's only 2 constructors (with optional `Mode` parameters): - `PaywallView()` - `PaywallView(offering:)` - New `PaywallData.default` as a fallback when trying to present a paywall with missing data (either because it failed to decode, or it's missing) - Extracted error state handling to `ErrorDisplay` (used by `PaywallView` and `AsyncButton` now). It can optionally dismiss the presenting sheet. - Handling offering loading errors in `PaywallView` - Improved `DebugErrorView` to allow displaying a fallback view: ```swift DebugErrorView( "Offering '\(offering.identifier)' has no configured paywall.\n" + "The displayed paywall contains default configuration.\n" + "This error will be hidden in production.", releaseBehavior: .replacement( AnyView( LoadedOfferingPaywallView( offering: offering, paywall: .default, mode: mode, introEligibility: checker, purchaseHandler: purchaseHandler ) ) ) ) ``` - Added `LoadingPaywallView` as a placeholder view during loading - Improved `MultiPackageTemplate` and `RemoteImage` to fix some layout issues during transitions - Added transition animations between loading state and loaded paywall
- Improved `PaywallView` API: now there's only 2 constructors (with optional `Mode` parameters): - `PaywallView()` - `PaywallView(offering:)` - New `PaywallData.default` as a fallback when trying to present a paywall with missing data (either because it failed to decode, or it's missing) - Extracted error state handling to `ErrorDisplay` (used by `PaywallView` and `AsyncButton` now). It can optionally dismiss the presenting sheet. - Handling offering loading errors in `PaywallView` - Improved `DebugErrorView` to allow displaying a fallback view: ```swift DebugErrorView( "Offering '\(offering.identifier)' has no configured paywall.\n" + "The displayed paywall contains default configuration.\n" + "This error will be hidden in production.", releaseBehavior: .replacement( AnyView( LoadedOfferingPaywallView( offering: offering, paywall: .default, mode: mode, introEligibility: checker, purchaseHandler: purchaseHandler ) ) ) ) ``` - Added `LoadingPaywallView` as a placeholder view during loading - Improved `MultiPackageTemplate` and `RemoteImage` to fix some layout issues during transitions - Added transition animations between loading state and loaded paywall
- Improved `PaywallView` API: now there's only 2 constructors (with optional `Mode` parameters): - `PaywallView()` - `PaywallView(offering:)` - New `PaywallData.default` as a fallback when trying to present a paywall with missing data (either because it failed to decode, or it's missing) - Extracted error state handling to `ErrorDisplay` (used by `PaywallView` and `AsyncButton` now). It can optionally dismiss the presenting sheet. - Handling offering loading errors in `PaywallView` - Improved `DebugErrorView` to allow displaying a fallback view: ```swift DebugErrorView( "Offering '\(offering.identifier)' has no configured paywall.\n" + "The displayed paywall contains default configuration.\n" + "This error will be hidden in production.", releaseBehavior: .replacement( AnyView( LoadedOfferingPaywallView( offering: offering, paywall: .default, mode: mode, introEligibility: checker, purchaseHandler: purchaseHandler ) ) ) ) ``` - Added `LoadingPaywallView` as a placeholder view during loading - Improved `MultiPackageTemplate` and `RemoteImage` to fix some layout issues during transitions - Added transition animations between loading state and loaded paywall
- Improved `PaywallView` API: now there's only 2 constructors (with optional `Mode` parameters): - `PaywallView()` - `PaywallView(offering:)` - New `PaywallData.default` as a fallback when trying to present a paywall with missing data (either because it failed to decode, or it's missing) - Extracted error state handling to `ErrorDisplay` (used by `PaywallView` and `AsyncButton` now). It can optionally dismiss the presenting sheet. - Handling offering loading errors in `PaywallView` - Improved `DebugErrorView` to allow displaying a fallback view: ```swift DebugErrorView( "Offering '\(offering.identifier)' has no configured paywall.\n" + "The displayed paywall contains default configuration.\n" + "This error will be hidden in production.", releaseBehavior: .replacement( AnyView( LoadedOfferingPaywallView( offering: offering, paywall: .default, mode: mode, introEligibility: checker, purchaseHandler: purchaseHandler ) ) ) ) ``` - Added `LoadingPaywallView` as a placeholder view during loading - Improved `MultiPackageTemplate` and `RemoteImage` to fix some layout issues during transitions - Added transition animations between loading state and loaded paywall
- Improved `PaywallView` API: now there's only 2 constructors (with optional `Mode` parameters): - `PaywallView()` - `PaywallView(offering:)` - New `PaywallData.default` as a fallback when trying to present a paywall with missing data (either because it failed to decode, or it's missing) - Extracted error state handling to `ErrorDisplay` (used by `PaywallView` and `AsyncButton` now). It can optionally dismiss the presenting sheet. - Handling offering loading errors in `PaywallView` - Improved `DebugErrorView` to allow displaying a fallback view: ```swift DebugErrorView( "Offering '\(offering.identifier)' has no configured paywall.\n" + "The displayed paywall contains default configuration.\n" + "This error will be hidden in production.", releaseBehavior: .replacement( AnyView( LoadedOfferingPaywallView( offering: offering, paywall: .default, mode: mode, introEligibility: checker, purchaseHandler: purchaseHandler ) ) ) ) ``` - Added `LoadingPaywallView` as a placeholder view during loading - Improved `MultiPackageTemplate` and `RemoteImage` to fix some layout issues during transitions - Added transition animations between loading state and loaded paywall
- Improved `PaywallView` API: now there's only 2 constructors (with optional `Mode` parameters): - `PaywallView()` - `PaywallView(offering:)` - New `PaywallData.default` as a fallback when trying to present a paywall with missing data (either because it failed to decode, or it's missing) - Extracted error state handling to `ErrorDisplay` (used by `PaywallView` and `AsyncButton` now). It can optionally dismiss the presenting sheet. - Handling offering loading errors in `PaywallView` - Improved `DebugErrorView` to allow displaying a fallback view: ```swift DebugErrorView( "Offering '\(offering.identifier)' has no configured paywall.\n" + "The displayed paywall contains default configuration.\n" + "This error will be hidden in production.", releaseBehavior: .replacement( AnyView( LoadedOfferingPaywallView( offering: offering, paywall: .default, mode: mode, introEligibility: checker, purchaseHandler: purchaseHandler ) ) ) ) ``` - Added `LoadingPaywallView` as a placeholder view during loading - Improved `MultiPackageTemplate` and `RemoteImage` to fix some layout issues during transitions - Added transition animations between loading state and loaded paywall
- Improved `PaywallView` API: now there's only 2 constructors (with optional `Mode` parameters): - `PaywallView()` - `PaywallView(offering:)` - New `PaywallData.default` as a fallback when trying to present a paywall with missing data (either because it failed to decode, or it's missing) - Extracted error state handling to `ErrorDisplay` (used by `PaywallView` and `AsyncButton` now). It can optionally dismiss the presenting sheet. - Handling offering loading errors in `PaywallView` - Improved `DebugErrorView` to allow displaying a fallback view: ```swift DebugErrorView( "Offering '\(offering.identifier)' has no configured paywall.\n" + "The displayed paywall contains default configuration.\n" + "This error will be hidden in production.", releaseBehavior: .replacement( AnyView( LoadedOfferingPaywallView( offering: offering, paywall: .default, mode: mode, introEligibility: checker, purchaseHandler: purchaseHandler ) ) ) ) ``` - Added `LoadingPaywallView` as a placeholder view during loading - Improved `MultiPackageTemplate` and `RemoteImage` to fix some layout issues during transitions - Added transition animations between loading state and loaded paywall
**This is an automatic release.** ### New Features #### ✨ Introducing RevenueCatUI 📱 RevenueCat's Paywalls allow you to to remotely configure your entire paywall view without any code changes or app updates. Our paywall templates use native code to deliver smooth, intuitive experiences to your customers when you’re ready to deliver them an Offering; and you can use our Dashboard to pick the right template and configuration to meet your needs. To use RevenueCat Paywalls on iOS, simply: 1. Create a Paywall on the Dashboard for the `Offering` you intend to serve to your customers 2. Add the `RevenueCatUI` SPM dependency to your project 3. `import RevenueCatUI` at the point in the user experience when you want to display a paywall: ```swift import RevenueCatUI import SwiftUI struct YourApp: View { var body: some View { YourContent() .presentPaywallIfNeeded( requiredEntitlementIdentifier: "pro", purchaseCompleted: { customerInfo in print("Purchase completed: \(customerInfo)") }, restoreCompleted: { customerInfo in print("Purchases restored: \(customerInfo)") } ) } } ``` You can find more information in [our documentation](https://rev.cat/paywalls). <details> <summary>List of changes</summary> * NachoSoto: `Paywalls`: renamed `PaywallEvent.view` to `.impression` (#3212) * NachoSoto: `Paywalls`: loading indicator for in-progress purchases (#3217) * NachoSoto: `Paywalls`: fixed template 4 bottom padding (#3211) * NachoSoto: `Paywalls`: only pre-warm images/intro-eligibility for `Offerings.current` (#3210) * NachoSoto: `Paywalls`: fixed mock intro eligibility on snapshot tests (#3205) * NachoSoto: `Paywalls`: fixed SimpleApp release build (#3203) * NachoSoto: `Paywalls`: improved `DebugErrorView` layout (#3204) * NachoSoto: `Paywalls`: refactored `PurchaseHandler` extracting protocol (#3196) * NachoSoto: `Paywalls`: automatically flush events (#3177) * NachoSoto: `Paywalls`: fixed `TemplateBackgroundImageView` aspect ratio (#3201) * NachoSoto: `Paywalls`: fixed broken layout on template 4 (#3202) * NachoSoto: `Paywalls`: events unit and integration tests (#3169) * NachoSoto: `Paywalls`: send events to `Purchases` (#3164) * NachoSoto: `Paywalls`: convert empty images into `nil` (#3195) * NachoSoto: `Paywalls`: new `onRestoreCompleted` handler (#3190) * NachoSoto: `Paywalls`: fixed `IntroEligibilityViewModel` data lifetime (#3194) * NachoSoto: `Paywalls`: test plan for running non-snapshot tests (#3188) * NachoSoto: `Paywalls`: polish template 4 (#3183) * NachoSoto: `Paywalls`: fixed data flow resulting in multiple `PurchaseHandler` instances (#3187) * Cesar de la Vega: `Paywalls`: update `blurred_background_image` key in `PaywallData` test fixture (#3186) * NachoSoto: `Paywalls`: added `Purchases.track(paywallEvent:)` (#3160) * NachoSoto: `Paywalls`: don't apply dark appearance with no dark mode colors (#3184) * NachoSoto: `Paywalls`: fixed template 2 + `.condensedFooter` + iPad (#3185) * NachoSoto: `Paywalls`: new `{{ sub_duration_in_months }}` variable (#3173) * NachoSoto: `Paywalls`: created `PaywallEventsManager` (#3159) * NachoSoto: `Paywalls`: implemented `PostPaywallEventsOperation` (#3158) * NachoSoto: `Paywalls`: new `{{ sub_relative_discount }}` variable (#3131) * Charlie Chapman: `Paywalls`: improved `FooterView` (#3171) * NachoSoto: `Paywalls`: fixed `FooterView` horizontal centering (#3172) * NachoSoto: `Paywalls`: created `PaywallEventStore` (#3157) * NachoSoto: `Paywalls`: add `PaywallEvent` model (#3156) * NachoSoto: `Paywalls`: add `PaywallData.revision` (#3155) * NachoSoto: `Paywalls`: support fuzzy-Locale search in `iOS 15` (#3162) * NachoSoto: `PaywallData`: added `@NonEmptyString` to `subtitle` and `offerName` (#3150) * NachoSoto: `Paywalls`: add paywall for Load Shedder integration tests (#3151) * NachoSoto: `Paywalls`: fixed error view being displayed on release builds (#3141) * NachoSoto: `Paywalls`: improved `{{ total_price_and_per_month }}` to include period (#3136) * NachoSoto: `Paywalls`: `{{ price_per_period }}` now takes `SubscriptionPeriod.value` into account (#3133) * NachoSoto: `Paywalls`: add Arabic to SimpleApp for testing (#3132) * NachoSoto: `Paywalls`: update snapshot generation with new separate git repo (#3116) * NachoSoto: `Paywalls`: add support for CTA button gradients (#3121) * NachoSoto: `Paywalls`: template 5 (#3095) * NachoSoto: `Paywalls`: replaced submodule with `gitignore`d reference (#3125) * NachoSoto: `Catalyst`: fixed a couple of Catalyst build warnings (#3120) * NachoSoto: `Paywalls`: reference test snapshots from submodule (#3115) * NachoSoto: `Paywalls`: removed `presentedPaywallViewMode` (#3109) * NachoSoto: `Paywalls`: remove duplicate `RevenueCat` scheme to fix Carthage (#3105) * NachoSoto: `Paywalls`: fixed iOS 12 build (#3104) * NachoSoto: `Paywalls`: fixed template 2 inconsistent spacing (#3091) * NachoSoto: `Paywalls`: improved test custom paywall (#3089) * NachoSoto: `Paywalls`: avoid warming up cache multiple times (#3068) * NachoSoto: `Paywalls`: added all localization (#3080) * NachoSoto: `Paywalls`: temporarily disable `PaywallTemplate.template4` (#3088) * NachoSoto: `Paywalls`: enabled `Catalyst` support (#3087) * NachoSoto: `Paywalls`: iPad polish (#3061) * NachoSoto: `Paywalls`: added MIT license to all headers (#3084) * NachoSoto: `Paywalls`: improved unselected package background color (#3079) * NachoSoto: `Paywalls`: handle already purchased state (#3046) * NachoSoto: `Paywalls`: only dismiss `PaywallView` when explicitly presenting it with `.presentPaywallIfNeeded` (#3075) * NachoSoto: `Paywalls`: add support for generating snapshots on CI (#3055) * NachoSoto: `Paywalls`: removed unnecessary `PaywallFooterView` (#3064) * Josh Holtz: `Paywalls`: new `PaywallFooterView` to replace `modes` (#3051) * Josh Holtz: `Paywalls`: rename card to footer (#3049) * NachoSoto: `Paywalls`: changed `total_price_and_per_month` to include period (#3044) * NachoSoto: `Paywalls`: internal documentation for implementing templates (#3053) * NachoSoto: `Paywalls`: finished `iOS 15` support (#3043) * NachoSoto: `Paywalls`: validate `PaywallData` to ensure displayed data is always correct (#3019) * NachoSoto: `Paywalls`: fixed `total_price_and_per_month` for custom monthly packages (#3027) * NachoSoto: `Paywalls`: tweaking colors on template 2&3 (#3011) * NachoSoto: `Paywalls`: changed snapshots to scale 1 (#3016) * NachoSoto: `Paywalls`: replaced `defaultLocale` with `preferredLocales` (#3003) * NachoSoto: `Paywalls`: improved `PaywallDisplayMode.condensedCard` layout (#3001) * NachoSoto: `Paywalls`: `.card` and `.condensedCard` modes (#2995) * NachoSoto: `Paywalls`: prevent multiple concurrent purchases (#2991) * NachoSoto: `Paywalls`: improved variable warning (#2984) * NachoSoto: `Paywalls`: fixed horizontal padding on template 1 (#2987) * NachoSoto: `Paywalls`: changed `FooterView` to always use `text1` color (#2992) * NachoSoto: `Paywalls`: retry test failures (#2985) * NachoSoto: `Paywalls`: send presented `PaywallViewMode` with purchases (#2859) * NachoSoto: `Paywalls`: added support for custom fonts (#2988) * NachoSoto: `Paywalls`: improved template 2 unselected packages (#2982) * Josh Holtz: `Paywalls`: fix template 2 selected text offer details color (#2975) * NachoSoto: `Paywalls`: warm-up image cache (#2978) * NachoSoto: `Paywalls`: extracted `PaywallCacheWarming` (#2977) * NachoSoto: `Paywalls`: fixed color in template 3 (#2980) * NachoSoto: `Paywalls`: improved default template (#2973) * NachoSoto: `Paywalls`: added links to documentation (#2974) * NachoSoto: `Paywalls`: updated template names (#2971) * NachoSoto: `Paywalls`: updated variable names (#2970) * NachoSoto: `Paywalls`: added JSON debug screen to `debugRevenueCatOverlay` (#2972) * NachoSoto: `Paywalls`: multi-package horizontal template (#2949) * NachoSoto: `Paywalls`: fixed template 3 icon aspect ratio (#2969) * NachoSoto: `Paywalls`: iOS 17 tests on CI (#2955) * NachoSoto: `Paywalls`: deploy `debug` sample app (#2966) * NachoSoto: `Paywalls`: sort offerings list in sample app (#2965) * NachoSoto: `Paywalls`: initial iOS 15 support (#2933) * NachoSoto: `Paywalls`: changed default `PaywallData` to display available packages (#2964) * NachoSoto: `Paywalls`: changed `offerDetails` to be optional (#2963) * NachoSoto: `Paywalls`: markdown support (#2961) * NachoSoto: `Paywalls`: updated icon set to match frontend (#2962) * NachoSoto: `Paywalls`: added support for `PackageType.custom` (#2959) * NachoSoto: `Paywalls`: fixed `tvOS` compilation by making it explicitly unavailable (#2956) * NachoSoto: `Paywalls`: fix crash when computing localization with duplicate packages (#2958) * NachoSoto: `Paywalls`: UIKit `PaywallViewController` (#2934) * NachoSoto: `Paywalls`: `presentPaywallIfNecessary` -> `presentPaywallIfNeeded` (#2953) * NachoSoto: `Paywalls`: added support for custom and lifetime products (#2941) * NachoSoto: `Paywalls`: changed `SamplePaywallsList` to work offline (#2937) * NachoSoto: `Paywalls`: fixed header image mask on first template (#2936) * NachoSoto: `Paywalls`: new `subscription_duration` variable (#2942) * NachoSoto: `Paywalls`: removed `mode` parameter from `presentPaywallIfNecessary` (#2940) * NachoSoto: `Paywalls`: improved `RemoteImage` error layout (#2939) * NachoSoto: `Paywalls`: added default close button when using `presentPaywallIfNecessary` (#2935) * NachoSoto: `Paywalls`: added ability to preview templates in a `.sheet` (#2938) * NachoSoto: `Paywalls`: avoid recomputing variable `Regex` (#2944) * NachoSoto: `Paywalls`: improved `FooterView` scaling (#2948) * NachoSoto: `Paywalls`: added ability to calculate and localize subscription discounts (#2943) * NachoSoto: `Offering`: improved description (#2912) * NachoSoto: `Paywalls`: fixed `FooterView` color in template 1 (#2951) * NachoSoto: `Paywalls`: fixed `View.scrollableIfNecessary` (#2947) * NachoSoto: `Paywalls`: improved `IntroEligibilityStateView` to avoid layout changes (#2946) * NachoSoto: `Paywalls`: updated offerings snapshot with new asset base URL (#2950) * NachoSoto: `Paywalls`: extracted `TemplateBackgroundImageView` (#2945) * NachoSoto: `Paywalls`: more polish from design feedback (#2932) * NachoSoto: `Paywalls`: more unit tests for purchasing state (#2931) * NachoSoto: `Paywalls`: new `.onPurchaseCompleted` modifier (#2930) * NachoSoto: `Paywalls`: fixed `LoadingPaywallView` displaying a progress view (#2929) * NachoSoto: `Paywalls`: added default template to `SamplePaywallsList` (#2928) * NachoSoto: `Paywalls`: added a few more logs (#2927) * NachoSoto: `Paywalls` added individual previews for templates (#2924) * NachoSoto: `Paywalls`: improved default paywall configuration (#2926) * NachoSoto: `Paywalls`: moved purchasing state to `PurchaseHandler` (#2923) * NachoSoto: `Paywalls`: updated Integration Test snapshot (#2921) * NachoSoto: `Paywalls`: pre-warm intro eligibility in background thread (#2925) * NachoSoto: `Paywalls`: removed "couldn't find package" log (#2922) * NachoSoto: `Paywalls`: SimpleApp reads API key from Xcode Cloud environment (#2919) * NachoSoto: `Paywalls`: improved template accessibility support (#2920) * NachoSoto: `Paywalls`: work around SwiftUI bug to allow embedding `PaywallView` inside `NavigationStack` (#2918) * NachoSoto: `Paywalls`: some basic polish from design feedback (#2917) * NachoSoto: `Paywalls`: added `OfferingsList` to preview all paywalls (#2916) * NachoSoto: `Paywalls`: fixed tappable area for a couple of buttons (#2915) * NachoSoto: `Paywalls`: new `text1` and `text2` colors (#2903) * NachoSoto: `Paywalls`: updated multi-package bold template design (#2908) * NachoSoto: `Paywalls`: added sample paywalls to `SimpleApp` (#2907) * NachoSoto: `Paywalls`: one package with features template (#2902) * NachoSoto: `Paywalls`: initial support for icons (#2882) * NachoSoto: `Paywalls`: extracted intro eligibility out of templates (#2901) * NachoSoto: `Paywalls`: changed `subtitle` to be optional (#2900) * NachoSoto: `Paywalls`: added "features" to `LocalizedConfiguration` (#2899) * NachoSoto: `Paywalls`: fixed `{{ total_price_and_per_month }}` (#2881) * NachoSoto: `Paywalls`: updated template names (#2878) * NachoSoto: `Paywalls`: added accent colors (#2883) * NachoSoto: `Paywalls`: changed images representation to an object (#2875) * NachoSoto: `Paywalls`: added `offerName` parameter (#2877) * NachoSoto: `Paywalls`: new `{{ period }}` variable (#2876) * NachoSoto: `Paywalls`: disabled `PaywallViewMode`s for now (#2874) * NachoSoto: `Paywalls`: added new `defaultPackage` configuration (#2871) * NachoSoto: `Paywalls`: fixed tests on CI (#2872) * NachoSoto: `Paywalls`: pre-fetch intro eligibility for paywalls (#2860) * Andy Boedo: `Paywalls`: clean up the error view (#2873) * NachoSoto: `Paywalls`: new API for easily displaying `PaywallView` with just one line (#2869) * NachoSoto: `Paywalls`: handle missing paywalls gracefully (#2855) * NachoSoto: `Paywalls`: temporarily disable non-fullscreen `PaywallView`s (#2868) * NachoSoto: `Paywalls`: added test to ensure package selection maintains order (#2853) * NachoSoto: `Paywalls`: added new `blurredBackgroundImage` configuration (#2852) * NachoSoto: `Paywalls`: fuzzy `Locale` lookups (#2847) * NachoSoto: `Paywalls`: basic localization support (#2851) * NachoSoto: `Paywalls`: added `FooterView` (#2850) * NachoSoto: `Paywalls`: multi-package template (#2840) * NachoSoto: `Paywalls`: disable animations during unit tests (#2848) * NachoSoto: `Paywalls`: `TrialOrIntroEligibilityChecker.eligibility(for packages:)` (#2846) * NachoSoto: `Paywalls`: added new `total_price_and_per_month` variable (#2845) * NachoSoto: `Paywalls`: extracted `PurchaseButton` (#2839) * NachoSoto: `Paywalls`: extracted `IntroEligibilityStateView` (#2837) * NachoSoto: `Paywalls`: support for multiple `PaywallViewMode`s (#2834) * NachoSoto: `Paywalls`: add support for multiple images in template configuration (#2832) * NachoSoto: `Paywalls`: extracted configuration processing into a new `TemplateViewConfiguration` (#2830) * NachoSoto: `Paywalls`: improved support for dynamic type with snapshots (#2827) * NachoSoto: `Paywalls`: disable `macOS`/`macCatalyst`/`watchOS` for now (#2821) * NachoSoto: `Paywalls`: using new color information in template (#2823) * NachoSoto: `Paywalls`: set up CI tests and API Tester (#2816) * NachoSoto: `Paywalls`: added support for decoding colors (#2822) * NachoSoto: `Paywalls`: ignore empty strings in `LocalizedConfiguration` (#2818) * NachoSoto: `Paywalls`: updated `PaywallData` field names (#2817) * NachoSoto: `Paywalls`: added support for purchasing (#2812) * NachoSoto: `Paywalls`: added tests for `PackageType` filtering (#2810) * Andy Boedo: `Paywalls`: changed variable handling to use Swift `Regex` (#2811) * NachoSoto: `Paywalls`: added `price` variable (#2809) * NachoSoto: `Paywalls`: determine intro eligibility (#2808) * NachoSoto: `Paywalls`: added header image to configuration (#2800) * NachoSoto: `Paywalls`: added `packages` to configuration (#2798) * NachoSoto: `Paywalls`: add support for displaying `StoreProductDiscount`s (#2796) * NachoSoto: `Paywalls`: added support for variables (#2793) * NachoSoto: `Paywalls`: using `PaywallData` and setting up basic template loading (#2781) * NachoSoto: `Paywalls`: initial configuration types (#2780) * NachoSoto: `Paywalls`: initial `RevenueCatUI` target setup (#2776) </details> ### Other Changes * `Debug`: add `Offering` metadata to debug screen (#3137) via NachoSoto (@NachoSoto) * `TestStoreProduct`: new `locale` parameter (#3134) via NachoSoto (@NachoSoto) * `Integration Tests`: fixed more flaky failures (#3218) via NachoSoto (@NachoSoto) --------- Co-authored-by: NachoSoto <[email protected]> Co-authored-by: NachoSoto <[email protected]>
Changes:
PaywallView
API: now there's only 2 constructors (with optionalMode
parameters):PaywallView()
PaywallView(offering:)
PaywallData.default
as a fallback when trying to present a paywall with missing data (either because it failed to decode, or it's missing)ErrorDisplay
(used byPaywallView
andAsyncButton
now). It can optionally dismiss the presenting sheet.PaywallView
DebugErrorView
to allow displaying a fallback view:LoadingPaywallView
as a placeholder view during loadingMultiPackageTemplate
andRemoteImage
to fix some layout issues during transitions