`PaywallView` and `PaywallViewController` (along with the extensions)
still require iOS 16, but this paves the way to support iOS 15 down the
road if we need to.
### Main Changes:
- Changed all `@available` annotations
- Added CI tests
- Replaced `ViewThatFits` with the "biggest" version
- Because of that, `scrollableIfNeeded` is always scrollable
- Double `Regex` implementation (keeping the modern one), so once we
drop support for iOS 15 we can just keep the modern one.
### TODO:
- [x] Add snapshots
- [x] Test paywalls
- [x] Updated APITester to verify this
This allows `PaywallView` to handle state handling during a purchase:
instead of the previous behavior where only `PurchaseButton` would
disable itself, now the entire `PaywallView` is disabled.
I extracted `PackageButtonStyle` so any template with multi-package
selection can get this same behavior: the package selection also becomes
disabled, as well as any other button (like restore purchases).
I've also cleaned up the templates since we no longer needed the
"Content" inside view, and they can be simplified with a single type.
By making `PurchaseHandler` `@MainActor` we also ensure that all these
state transitions lead to UI changes happening exclusively on the main
thread.
- 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
Another small refactor, follow up to #2837.
Slowly taking implementation details from `Example1Template` to make it
easier to implement more templates.
This adds a new `PurchaseHandler` type, which can be instantiated with
`Purchases` or with a mock implementation for previews. That gets
injected, and templates can now purchase and dismiss themselves.
I've improved the `SimpleApp` setup to better work with the lifetime of
purchases. Now it only displays the paywall if the user doesn't have an
entitlement.
I've also added some basic error handling to the new `AsyncButton`:
