Commit Graph
114 Commits
Author SHA1 Message Date
Facundo Menzella 6b5c430082 chore: Enable force_unwrapping for SwiftLint (#4820)
* enable force_unwrapping

* avoid force unwrap

* fixed all errors

* remove extra disable
2025-02-26 12:40:49 +01:00
Mark Villacampa 8d0475842a Use CSS linear-gradient spec to compute gradient start/end points (#4789) 2025-02-24 16:27:32 +01:00
Will Taylor fcd71e1cf8 fix GradientView preview on iOS 15 2025-02-05 11:41:38 -06:00
Mark Villacampa 55dedb701d add extra gradient previews (#4750) 2025-01-30 07:30:23 -06:00
Josh Holtz 3d86d7dbbb [Paywalls V2] Remove/replace PAYWALL_COMPONENTS compiler flag and fix OS/platform compile issues (#4727)
* We can't just remove PAYWALL_COMPONENT because we don't support macos and tv and things were failing

* Move PartialComponent outside of PaywallComponent extension

* Fix some lint errors

* Wrong thing

* Undid things i shouldn't have done

* Removed more

* Added comment

* Removed/fix some places for the check thingies

* RevenueCat module compiles on all the things

* RevenueCatUI compiles in all the platforms now

* Make all the things compile again

* Make work for visionOS

* Fix some platform and xcode version issues

* Fix failing spm ios 15 test

* Undo circleci changes

* Replace smooth with easeInOut

* Only run the thing i want

* Attempt to remove circular

* Explicit returns

* Does this pass

* This is it

* Swift 5.9 for this

* All tests again

* Undid public testing processor stuff

* Undo this too

* Swift compiler check out pill style circular

* Fixes from PR review

* Fix compile for mac
2025-01-29 11:53:25 -06:00
Mark Villacampa c4fb5483a6 [Paywalls] Add Timeline component (#4713)
* Add Timeline component

* rename text -> title

* struct -> class

* lint
2025-01-28 14:51:35 +01:00
Mark Villacampa 816f4caa55 [Paywalls] Fix shadow orientation by shifing initial position by 90º and making it rotate clockwise (#4634) 2025-01-07 16:38:44 +01:00
Mark Villacampa 9e60fd27f9 [Paywalls] Add previews for different combinations of vertical/horizontal alignment and flex distributions (#4538)
* [Paywalls] Add previews for different combinations of vertical/horizontal alignment and flex distributions

* Make icon colors in the preview always the same
2024-12-05 05:21:10 -06:00
Mark Villacampa 21a6df8d20 [Paywalls] Add support for gradient backgrounds (#4522)
* [Paywalls] Add support for gradient backgrounds

* [Paywalls] Add support for gradients as text foreground style, and support mixed solid and gradient light/dark colors in ColorScheme
2024-11-28 23:53:21 +01:00
Josh Holtz 91e695869a [Paywalls V2] Add root paywall background (color and image) (#4502)
* Added root paywall background

* Added some previews

* Undo comment about fallback

* Fix some lint

* Fit lint... again

* Added backgroud style to text and stack and fixed preview
2024-11-21 10:46:15 -06:00
Josh Holtz 8f71dd9199 [Paywalls V2] Process variables in the text component (#4490)
* Start of processing variables in text

* Moved some files

* Fixed lint and added logic for zero decimal place

* Fix test

* Apply new PackageContext in package and better preview

* Fixed lint
2024-11-19 05:55:37 -06:00
Toni Rico 95aaf74888 Support custom URL paths in ManageSubscriptionsView (#4382)
* Support custom URL paths in ManageSubscriptionsView

* Fix tests

* More test fixes

* Add parsing and processing tests

* Consolidate SafariView

* Rename method

* Another rename

* Fix import issue with SafariView not being compatible to some platforms

* Style fix
2024-10-21 15:47:16 +02:00
JayShortway c4d50d3195 Adds an onDismiss callback to ErrorDisplay. (#4312) 2024-09-27 16:51:14 +02:00
Josh Holtz cc12dd93f8 [Paywall Components] Match properties and behaviors from dashboard (#4261)
* A mixture of changes while putting the demo together

* Doing some manual fixes for max image height

* Removed unused file and renamed struct

* Fixed lint

* Fix tests

* Image default value on component and not localization

* Fixed sample paywall

* A few more small fixes

* Fix lint
2024-09-26 14:43:11 -05:00
James BorthwickandJosh Holtz d40eb1157b Paywall Components Initial Commit (#4224)
Initial components merge. Notes:

- Linting enabled except for public documentation which will come as the
API matures and stabilizes.
- Took out unused files (tier-related ones), which will come back in a
future version. The comments were useful and will be referenced when
they come back so thank you for the comments on these.
- Left in tvOS and watchOS availability checks for now - may revisit
this later.
- Everything is codable.
- Features added:
- Layout components allow other components to be laid out along x/y/z
axes.
- Text component supports font weight. (Changing actual font will come
later.)
  - LinkButton component links out to external website of choice.
- RemoteImage allows the image to be arbitrarily styled by the calling
code by passing the image back in a closure.
- Image component supports corner rounding an overlaid gradient. (These
will likely be pulled out into common code somewhere in a future PR.)
- Support for OfferingResponse that includes a separate
`PaywallComponentsData` structure, which is separate from `PaywallData`
used for template-based paywalls.

---------

Co-authored-by: Josh Holtz <me@joshholtz.com>
2024-08-28 17:11:42 -07:00
Josh Holtz 1fa69d42f4 [Paywalls] Improve locale consistency (#4158)
⚠️ Waiting for #4188 to be merged first (because needs all the tests to
pass)

## Motivation

Fix cases where paywall was showing multiple languages at once and
random languages.

## Description

👉 For a demo of the problem and solution, look in Slack in the paywalls
or SDK channel for the latest Loom that I posted

### Flow of paywall localization logic (since it can be confusing)

1.
[PaywallView](https://github.com/RevenueCat/purchases-ios/pull/4158/files#diff-ea75fe7280803e15b975d702a16c6851ffc2bbed01b7b196bf0fb80d75b4b332)
starts with `Locale.current` but then calls
`offering.validatedPaywall()` which will return the new
`displayedLocale: Locale` variable
2.
[PaywallData+Validation](https://github.com/RevenueCat/purchases-ios/pull/4158/files#diff-452790cf5287a44f75a4b2260254db2281a066faeea2b3e441a007ec60522b6b)
returns `displayedLocale` if its a valid paywall, otherwise it return
`Locale.current` for fallback paywall
3.
[PaywallData+Localization](https://github.com/RevenueCat/purchases-ios/pull/4158/files#diff-e620baead94f7acad56d931c485d7691c4f909be3fe625e104528a2702cca2a0)
has a lot of the new logic
- New `locale: Locale` computer property that it determines the locale
from the `localizedConfiguration` or `localizedConfigurationsByTier`
- New ` defaultLocalizedConfiguration` that uses the developer set
`defaultLocale` (from `PaywallData`) to find the default localization
- Updated `localizedConfiguration<Value>()` to take a new
`defaultLocalization` to put in its priority (and log it if its used).
If for some reason that's not found, it will use the legacy
`fallbackLocalization` (which is the first localization it finds or
english)
4. After all of that, the `displayedLocale` is passed into the actually
SwiftUI `LoadedOfferingPaywallView` to show this locale everywhere

### Problem 1 - Inconsistent languages displayed at once

The user defined localizations and the pre-localized content in the SDK
(like links in the footer and package duration names) could should
different localizations. The choice of which locale was happening in two
different spots.

Since the SDK has all possible localizations that the dashboard offers,
the locale SDK displays from the dashboard will be chosen for the
pre-localized content.

### Problem 2 - Showing random languages if not supported

We decided early on to not use a `default_locale` but that has hurt us.
The solution to use was use `localizations.first` to show the first
locale in the list and the intention that this was the first locale the
user did in the paywall. That was not the case, however. The order of
the localizations list was indeterministic so it should should a random
language (even to the point where different random languages would show
the the same user).

We are now introducing a `default_locale` that the developer can set in
the paywall. If the locale the user is requesting is not translated by
the developer, it will use the `default_locale`

### Problem 3 - Footer not using correct localization from the bundle

There were times where some parts of the footer itself (mainly the
restore button) would be a different language than the terms of service
and privacy policy buttons. This was due to a code issue where we were
using `Bundle.module`. Instead, we needed to specify a localization
bundle specifically that we wanted to use.
2024-08-20 09:44:27 -05:00
Andy Boedo 37f3fb1752 export RevenueCatUI xcframework (#4172)
We don't currently export RevenueCatUI as an xcframework, which can be a
source of confusion and a dealbreaker for customers who need a pre-built
framework.
This adds RevenueCatUI's xcframework into the `export_xcframeworks`
lane, and includes the output in the `github_release` lane.


Should help address: 
- https://github.com/RevenueCat/purchases-ios/issues/4168
- https://github.com/RevenueCat/purchases-ios/issues/4146
- https://github.com/RevenueCat/purchases-ios/issues/4056
2024-08-15 18:18:45 -03:00
Andy Boedo d0dbbdce9e Fix tests in main (#4174)
Fixes the tests that were failing in `main`, which you can see in
https://github.com/RevenueCat/purchases-ios/pull/4172
2024-08-13 15:59:29 -03:00
Mark Villacampa 2bc647e77e Use newer Alert API for showing customer restored alert (#4078)
This was causing issues for some developers, because the old, deprecated
Alert API would fail silently if there was a view higher up in the view
hierarchy which already had an Alert modifier

This in turn caused the `onRestoreCompleted` callback not to be called.


https://community.revenuecat.com/sdks-51/restore-completion-handlers-not-firing-in-presentpaywallifneeded-4674

The newer Alert API seems to not suffer from the same issue.

### Checklist
- [ ] If applicable, unit tests
- [ ] If applicable, create follow-up issues for `purchases-android` and
hybrids

### Motivation
<!-- Why is this change required? What problem does it solve? -->
<!-- Please link to issues following this format: Resolves #999999 -->

### Description
<!-- Describe your changes in detail -->
<!-- Please describe in detail how you tested your changes -->
2024-07-18 15:19:52 +02:00
Josh Holtz 363bb0ce09 Added new paywall template to support multiple tiered subscriptions (#4022)
### Motivation

Template 5 but with multiple tiers 😊

### Description

Enabling Template 7 by changing `7_disabled` to `7`

- Changed a `header.jpg` to `header.heic` to try and fix issue with
weirdly failing snapshot UI test
- Changed visibility on `TestData` from `public` to `internal`
- Hides tier selector if only one tier
- Changed `color_overrides_by_tier` to `colors_by_tier` and
`image_overrides_by_tier` to `images_by_tier`
- The original implementation was going to default to existing color and
images configurations and then override by tier
- However, this turned out to be harder to implement on the
configuration side
- `Colors` got four new optional values
  - `tier_control_background`
  - `tier_control_foreground`
  - `tier_control_selected_background`
  - `tier_control_selected_foreground`

### Demo

#### Full Screen


https://github.com/RevenueCat/purchases-ios/assets/401294/e5317a53-428c-415e-a1a1-b4231d32a776

### Footer


https://github.com/RevenueCat/purchases-ios/assets/401294/8905b55e-0fa2-49f0-aa1c-d4c1e694fb5b

### Footer Condensed


https://github.com/RevenueCat/purchases-ios/assets/401294/311d78fb-bdeb-43cd-b601-9c60a8a54b6f
2024-07-17 07:04:42 -05:00
41d3541451 Paywalls with custom purchase and restore logic handlers (#3973)
## tl;dr

PaywallView has a new constructor that takes `performPurchase` and
`performRestore` blocks, which are called to preform purchasing/restore
directly by the customer's app when the purchase/restore buttons are
tapped on the paywall. This makes it possible to use RC Paywalls when
`Purchases` has been configured `.with(purchasesAreCompletedBy:
.myApp)`.

Example usage:
```swift
PaywallView(performPurchase: {
    var userCancelled = false
    var error: Error?
    
    // use StoreKit to perform purchase

    return (userCancelled: userCancelled, error: error)
}, performRestore: {
    var success = false
    var error: Error?

    // use StoreKit to perform restore

    return (success: success, error: error)
})
```

## Description

When a `PaywallView` is constructed, a new `PurchaseHandler` is created.
The `PurchaseHandler` (an internal RevenueCatUI class) is owned by the
`PaywallView`, and it is responsible for executing new purchases and
restores.

When a `PaywallView` is constructed **without** `performPurchase` and
`performRestore` blocks, the `PaywallView` creates a `PurchaseHandler`
capable of preforming purchases using RevenueCat. When a `PaywallView`
is constructed with `performPurchase` and `performRestore` blocks, it
can also make purchases using the customer-supplied closures.

The `PurchaseHandler` is invoked when the user taps the
`PurchaseButton`, calling `purchaseHandler.purchase(package:
self.selectedPackage.content)`, which branches to either the internal or
external purchase code, as defined by `purchasesAreCompletedBy`:

```swift
@MainActor
func purchase(package: Package) async throws {
    switch self.purchases.purchasesAreCompletedBy {
    case .revenueCat:
        try await performPurchase(package: package)
    case .myApp:
        try await performExternalPurchaseLogic(package: package)
    }
}
```


Purchase and Restore blocks can also be assigned for paywall footers:

```swift
MyAppDefinedPaywall()
    .paywallFooter(myAppPurchaseLogic: MyAppPurchaseLogic(performPurchase: { packageToPurchase in
        var userCancelled = false
        var error: Error?

        // use StoreKit to perform purchase

        return (userCancelled: userCancelled, error: error)
    }, performRestore: {
        var success = false
        var error: Error?

        // use StoreKit to perform restore

        return (success: success, error: error)
    }))
 ```

and via `.presentPaywallIfNeeded`:

```swift
MyAppDefinedPaywal()
.presentPaywallIfNeeded(requiredEntitlementIdentifier: "test",
myAppPurchaseLogic: MyAppPurchaseLogic(performPurchase: {
packageToPurchase in
        return (userCancelled: false, error: nil)
    }, performRestore: {
        return (success: true, error: nil)
    }))
```



## Notes

### Testing

A lot needs to be mocked, the purchase/restore blocks that are passed in need to be assigned directly to the `PurchaseHandler`, which is then passed in as part of a `PaywallConfiguration`, both of which are normally constructed internally in the PaywallView constructor.

```swift
    func testHandleExternalRestoreWithPurchaaseHandlers() throws {
        var completed = false
        var customRestoreCodeExecuted = false

        let purchasHandler = Self.externalPurchaseHandler { _ in
            return (userCancelled: true, error: nil)
        } performRestore: {
            customRestoreCodeExecuted = true
            return (success: true, error: nil)
        }

let config = PaywallViewConfiguration(purchaseHandler: purchasHandler)

        try PaywallView(configuration: config).addToHierarchy()

        Task {
            _ = try await purchasHandler.restorePurchases()
            completed = true
        }

        expect(completed).toEventually(beTrue())
        expect(customRestoreCodeExecuted) == true
    }
```

### Error Handling

For the external code blocks to be called, `purchasesAreCompletedBy` needs to be set to `.myApp` AND the blocks need to be defined. So we need to handle cases when these are not consistent:

1. If someone configures purchasesAreCompletedBy to `.myApp`, and then displays a PaywallView **without** purchase/restore handlers, the SDK will:
* Log an error when the PaywallView is constructed
* When the PaywallView is displayed, replace it with a big red banner when in debug mode, fatalError() in release mode.
* If you purchase directly via the purchase handler (which is an internal class but this is done for testing), it will throw.

2. If someone configures purchases to use `.revenueCat`, and then displays a PaywallView **with** purchase/restore handler, the SDK will:

* Log a warning when the PaywallView is constructed

Rationale:
In case 1, there could be no (acceptable) way to make a purchase, so this is very bad and it needs to be dealt with.

In case 2, they've over-specified the paywall, and while this might be confusing (why isn't my code being called??), it's not as problematic, and we want to make it easy for people to switch from using their purchase logic to our purchase logic.

These checks are made in PaywallView.swift, method `checkForConfigurationConsitency()`.

### UIKit

Not yet supported, will add in a follow-up PR, unlikely to be complicated.

### Paywall Footer and Paywall If Needed
The `performPurchase` and `performRestore` parameters for the paywall footer are contained in a struct rather than as loose parameters of closure types, because doing the latter would create a very poor experience with regards to code completion, where Xcode will always offer complete your code as a trailing closure, but will always use the first closure where the function signature matches.

The following illustrates the problem of using loose closure parameters:

The trailing closure that Xcode auto-creates here does _not_ get called for `performPurchase`, but rather `purchaseStarted`, because it also has a `package` as its input parameter, and comes first in the parameter list 😱.

https://github.com/RevenueCat/purchases-ios/assets/109382862/1f809e7c-b661-4844-89e3-fd846a029531

This is the problematic function signature:

```swift
public func paywallFooter(
        offering: Offering,
        condensed: Bool = false,
        fonts: PaywallFontProvider = DefaultPaywallFontProvider(),
        purchaseStarted: PurchaseOfPackageStartedHandler? = nil,
        purchaseCompleted: PurchaseOrRestoreCompletedHandler? = nil,
        purchaseCancelled: PurchaseCancelledHandler? = nil,
        restoreStarted: RestoreStartedHandler? = nil,
        restoreCompleted: PurchaseOrRestoreCompletedHandler? = nil,
        purchaseFailure: PurchaseFailureHandler? = nil,
        restoreFailure: PurchaseFailureHandler? = nil,
        performPurchase: PerformPurchase? = nil,
        performRestore: PerformRestore? = nil
    ) -> some View
 ```

To fix this, get rid of the two new parameters: 
```swift
        performPurchase: PerformPurchase? = nil,
        performRestore: PerformRestore? = nil
```
 
and embed them in a struct:

```swift
public struct MyAppPurchaseLogic {
    public let performPurchase: PerformPurchase
    public let performRestore: PerformRestore

    public init(performPurchase: @escaping PerformPurchase, performRestore: @escaping PerformRestore) {
        self.performPurchase = performPurchase
        self.performRestore = performRestore
    }
}
```

which we use as a last parameter:

```swift
        myAppPurchaseLogic: MyAppPurchaseLogic? = nil
```

 Now the code completes as so:


https://github.com/RevenueCat/purchases-ios/assets/109382862/332ac42d-ccba-42bc-bfc3-702d7870c99c

The `MyAppPurchaseLogic` doesn't complete in perfectly, but once you
initialize one of those it goes nicely the rest of the way, including
the error messages that instruct you on what you need to return.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Josh Holtz <me@joshholtz.com>
Co-authored-by: Cesar de la Vega <cesarvegaro@gmail.com>
Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
Co-authored-by: Toni Rico <toni.rico.diez@revenuecat.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Will Taylor <wtaylor151@gmail.com>
Co-authored-by: Andy Boedo <andresboedo@gmail.com>
2024-07-05 12:05:44 -07:00
NachoSoto 2bcd0ce328 5.0.0 changes: support SK2 by default 2024-07-01 14:37:21 -05:00
James BorthwickandJosh Holtz 0a627d7db7 RemoteImage Low Res Image support (#3906)
`RemoteImage` now uses two images: a high res image (as before), and a
new low res image which will be used if it is available before the high
res image has been loaded.

To enable this, it now uses two `ImageLoader` instances, one for to
fetch the low res image, a second for high res image.

It assumes that the low res image url is the same as the high res image
url, with a suffix appended (default `_low_res`) to the file name, eg
`/images/myImage.jpg` is assumed to have a low res equivalent at
`/images/myImage_low_res.jpg`.

It requests the high and low res images concurrently, and displays an
error when both requests fail. A low res image will never replace a high
res image even if it is received last.

Here is a video of it loading a "low res" image, following by high res
image. For this video the low-res image was intentionally set to a
completely different image to make the change easy to see. If you move
the slider manually you can control the speed.


https://github.com/RevenueCat/purchases-ios/assets/109382862/97521803-5c12-43d0-92a8-596b23cdcff6

---------

Co-authored-by: Josh Holtz <me@joshholtz.com>
2024-05-27 12:39:18 -07:00
Toni RicoandChris Wu be25350259 [EXTERNAL] Hide decorative Paywall images from accessibility (#3886) via @shiftingsand (#3892)
### Description

Added .accessibilityHidden(true) to the two images in RemoteImage. I
initially was hesitant to change RemoteImage itself, but went ahead and
did so since it seems to be only used by the Paywall templates.

Contributed by @shiftingsand in #3886

Co-authored-by: Chris Wu <31398736+shiftingsand@users.noreply.github.com>
2024-05-15 18:23:28 +02:00
NachoSoto 9d1772a85f Paywalls: .onRestoreCompleted is invoked after the restore dialog is dismissed (#3620)
Fixes #3617.

This allows users to handle that notification only once the user has
dismissed the "purchases restored" dialog.
2024-02-01 08:44:05 -08:00
NachoSoto d672daef7f Fix RevenueCatUI snapshot tests (#3526) 2023-12-20 15:26:41 -08:00
NachoSoto c5e039a6a0 Paywalls: improve landscape support of all templates (#3471)
### Template 2
#### Before
![Simulator Screenshot - iPhone 15 Pro Max - 2023-11-22 at 07 29
06](https://github.com/RevenueCat/purchases-ios/assets/685609/371a5170-f63c-41e3-a5c6-476e918b1f8c)

#### After

![simulator_screenshot_171E3E61-7182-4B4A-80C5-770F9B0803E6](https://github.com/RevenueCat/purchases-ios/assets/685609/8ee42629-9be9-4541-b0d5-a8e7821601ae)

### Template 3
#### Before

![simulator_screenshot_528FF616-C89A-4F58-9983-732AE4D57A40](https://github.com/RevenueCat/purchases-ios/assets/685609/e20019af-1fa1-41ac-ba63-fe63cf83f0f3)

#### After

![simulator_screenshot_8A95B6D9-2C44-41F4-ACAB-ADD5DE0663EE](https://github.com/RevenueCat/purchases-ios/assets/685609/f148cf5e-9f01-4626-9f18-8ed961b45433)

### Template 4
#### Before

![simulator_screenshot_71F4CF71-A49C-4252-AB17-87D39B94CE9F](https://github.com/RevenueCat/purchases-ios/assets/685609/d5942d2f-b6bd-433c-8d46-45ad6a023fdc)

#### After

![simulator_screenshot_55A7A235-DF58-40FC-96E4-01B1F0070F39](https://github.com/RevenueCat/purchases-ios/assets/685609/20774b1a-42ea-4c58-a639-678cacfea332)

### Template 5
#### Before

![simulator_screenshot_30C0580D-D2D7-42C1-930E-D95D0C000115](https://github.com/RevenueCat/purchases-ios/assets/685609/6b7aff05-4d88-4c91-8bb4-b5453a7beb41)

#### After

![simulator_screenshot_C75156C2-5237-4D22-920F-61A4D0452780](https://github.com/RevenueCat/purchases-ios/assets/685609/44f7b4ac-e899-4dce-a9bf-6cbcbea71899)
2023-12-19 12:24:52 -08:00
NachoSoto 84b12db8c5 Paywalls: ensure footer links open in full-screen sheets (#3524)
Fixes #3518.
Follow up to #3475 and #3508.
2023-12-19 08:40:47 -08:00
NachoSoto 76e27809f4 Paywalls: improve FooterView text alignment (#3525)
These seemed to be the default before, but it broke with #3475.

Difference (before and after):
![Screenshot 2023-12-18 at 15 09
51](https://github.com/RevenueCat/purchases-ios/assets/685609/1e2e8c69-51b0-4500-8e30-28e4c357907d)

This restores the layout shown on the left side.
2023-12-19 08:33:56 -08:00
NachoSoto 34e00451c4 Paywalls: always dismiss paywalls automatically after a purchase (#3517)
Fixes #3516.

This logic only existed for `.presentPaywallIfNeeded`. However, it
should apply to `PaywallView` when presented manually as well.

This is also consistent with how Android works, reduces boilerplate code
required by users, and removes the possibility that a paywall is still
presented after a purchase, which doesn't make sense since our paywalls
don't have a mode for "already subscribed".
2023-12-14 09:29:44 -08:00
NachoSoto 59a877a699 Paywalls: improve image caching (#3498) 2023-12-12 07:24:12 -08:00
NachoSoto ebcaba356c Paywalls: change style of CTA button to be consistent with other platforms (#3507)
This is what it looked like before:

![Screenshot 2023-12-08 at 15 31
10](https://github.com/RevenueCat/purchases-ios/assets/685609/59a4591e-9ae9-4e9e-a399-fa80043fe424)

I chose that on purpose to provide a "native" look to paywalls. However,
as others have reported, it has several issues:
- It's too small. The button should be the most obvious action to take
on the paywall.
- It doesn't respect the configuration for the paywall (background and
foreground color).

This looked like a "bug", so the new implementation will provide
consistency with what users probably expect:

![Screenshot 2023-12-08 at 15 27
17](https://github.com/RevenueCat/purchases-ios/assets/685609/44403529-6931-43fb-99ae-ce922cc9e47f)
2023-12-11 19:15:31 -08:00
NachoSoto 75155183de Paywalls: open footer links on Safari on Catalyst (#3508) 2023-12-11 08:20:09 -08:00
Andy BoedoandNachoSoto 2a9e190ce9 Paywalls: open Terms and Privacy Policy links in-app (#3475)
Updated the terms and Privacy Policy links to open within the app,
without exiting to Safari


https://github.com/RevenueCat/purchases-ios/assets/685609/e10df16d-1016-4271-9270-35c4ae8a87d4

---------

Co-authored-by: NachoSoto <ignaciosoto90@gmail.com>
2023-12-09 05:46:28 +08:00
NachoSoto b62650a1dc Paywalls: tests for PurchaseButton layout logic (#3468)
This adds test coverage for the logic added in #3361, now extracted from
`PurchaseButton` into a `TemplateViewConfiguration` extension.
2023-11-22 17:37:06 -08:00
NachoSoto 6317f096cf Paywalls: watchOS support (#3291)
![simulator_screenshot_38A61F03-8048-40E2-AD3E-384DF4112771](https://github.com/RevenueCat/purchases-ios/assets/685609/b74e2c9d-ece1-4f00-920b-7bccb68acd82)

![simulator_screenshot_ADD7F6B5-8A39-473B-85E2-5175027F641C](https://github.com/RevenueCat/purchases-ios/assets/685609/6531d598-b859-4482-a669-34d2eaf5b8f1)
2023-11-16 12:49:53 -08:00
NachoSoto d3866a28e4 visionOS: fix support for Xcode 15.1 beta 3 (#3409)
Apple changed `xrOS` to `visionOS` on the latest beta. Looks like this
was already supported in prior `Xcode 15.1` betas, so this doesn't break
compatibility with them.
2023-11-14 12:51:39 -08:00
NachoSotoandAndy Boedo af08736775 RevenueCatUI: added support to other deployment targets (#3392)
Follow up to #3378.

This allows us to link both `RevenueCat` and `RevenueCatUI` from
`PurchasesHybridCommon` supporting the same platforms.
The actual types might not be available in some platforms / versions,
but we can detect that at compile time.

See
https://app.circleci.com/pipelines/github/RevenueCat/purchases-hybrid-common/2564/workflows/ea6a4e24-e1eb-4c7a-a930-173945732999/jobs/10352

---------

Co-authored-by: Andy Boedo <andresboedo@gmail.com>
2023-11-08 07:35:19 -08:00
NachoSoto 822bebc69e Paywalls: log warning when attempting to purchase already-subscribed product (#3366)
Until we improve the presentation of already-subscribed products this is
just a no-op. This will at least help developers know why nothing is
happening.
2023-10-31 14:15:47 -07:00
NachoSoto 485bd71bf8 Paywalls: simplify PurchaseButton (#3362)
This moves some of the modifications outside of
`ConsistentPackageContentView` so they're not duplicated in the
hierarchy for each package.
2023-10-31 10:41:06 -07:00
NachoSoto f16c3744fa Paywalls: avoid animating PurchaseButton labels when text does not change (#3361)
Equivalent to https://github.com/RevenueCat/purchases-android/pull/1416
2023-10-31 07:39:58 -07:00
NachoSoto 8bde604a6e Paywalls: refactored IntroEligibilityStateView (#3360)
This will be used to implement a similar fix to
https://github.com/RevenueCat/purchases-android/pull/1416
2023-10-31 07:38:15 -07:00
NachoSoto 9472ed7cfc Paywalls: new optional displayCloseButton parameter (#3359)
The
[documentation](https://www.revenuecat.com/docs/displaying-paywalls#close-button)
points out that this is only automatic when using
`presentPaywallIfNeeded`, but it can also be useful to have
`RevenueCatUI` do this automatically.
2023-10-30 15:46:36 -07:00
NachoSoto 64a00274a1 Paywalls: improve FooterView accessibility (#3349)
This overrides the `accessibilityLabel` to always read the longest text,
even if it didn't fit on the screen.

I thought of this while working on [Android
A11y](https://github.com/RevenueCat/purchases-android/pull/1392).
2023-10-26 09:16:59 -07:00
NachoSoto fc72c2a6b0 Paywalls: disable shimmering on footer loading view (#3324) 2023-10-24 07:56:45 -07:00
NachoSoto eccddc0361 Paywalls: localize default template (#3295)
![Screenshot 2023-10-09 at 14 00
55](https://github.com/RevenueCat/purchases-ios/assets/685609/4e766486-c082-452d-9abf-22d3d0080812)

_Note: the package names in this screenshot aren't localized because
they're hardcoded in these `TestStoreProduct`._
2023-10-10 16:25:26 -07:00
NachoSoto 7368d7de50 Paywalls: created ConsistentPackageContentView to improve package change transitions (#3246)
This abstracts away the logic to ensure layouts are consistent when
selecting different packages in multi-package templates, in a way that
the content doesn't move around.
2023-10-10 15:30:09 -07:00
NachoSoto af9634db73 Paywalls: visionOS support (#3293) 2023-10-10 07:32:41 -07:00
NachoSoto 2206f5ad89 Paywalls: improved purchase-in-progress UI (#3279) 2023-10-06 09:29:19 -05:00
NachoSoto 31a411347c Paywalls: added shimmer effect to LoadingPaywallView (#3267) 2023-10-03 22:09:06 -05:00