Commit Graph
133 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
JayShortway f9708b61fc Adds a note on publishing to the missing paywall error. (#4817) 2025-02-25 12:38:57 +01:00
Antonio Pallares 24ada6079b Add Internal support for draft paywall previews (#4761)
* feat: add support for presenting draft paywalls under `@_spi(Internal)` attribute

* fix: initialize `useDraftPaywall` property of `PaywallView`

* feat: have `PaywallView` actually show draft paywall when `useDraftPaywall` is `true`

* remove lint warning

* silence linter warning

* tests: add unit tests for `paywallComponents` and `draftPaywallComponents` in `OfferingsResponse`

* tests: move test offerings with v2 paywalls into a new file not to affect existing decode + encode test

* remove unnecessary property wrapper

* silence lint warning
2025-02-13 18:37:04 +01:00
Facundo Menzella 5354cbe62c feat: Dont show cancel path if lifetime subscription (#4755)
* feat: Dont show cancel path if lifetime subscription

* added variable + tests

* add lifetime sub check

* nits

* remove extra loding path

* Renamed isLifetime

* Added docs
2025-02-07 11:14:37 +01:00
Josh Holtz fc2138249a [Paywalls V2] Implement V2 variables and functions (#4633)
* This mostly works

* This mostly works

* Better tests and logic

* More tests

* Fixed onemore

* lint

* Fix tests

* Actually use this

* Fix for all quantity translation things

* More minor fixes

* Fix tests
2025-01-08 09:17:34 -06:00
Cesar de la Vega 936d66affe Fix loading Customer Center when entitlement is granted by another Apple app (#4603) 2024-12-17 16:05:59 +01:00
Cesar de la Vega 24d66605ea Refactors the creation of the subscription details in Customer Center (#4515) 2024-12-11 20:55:22 +01:00
Cesar de la Vega 3f4c4eb6c5 Customer Center deeplinks should always be opened externally (#4533)
* check if it's a deeplink

* match http or https

* add log
2024-12-04 18:20:55 +01:00
Cesar de la Vega 4a0156e1f6 Add lifetime support to the Customer Center (#4503) 2024-11-22 14:42:48 +01:00
Cesar de la VegaandDan Pannasch 7e8bb3627c Refactor SubscriptionDetailsView and better WrongPlatformView (#4410)
Co-authored-by: Dan Pannasch <104692013+dpannasch@users.noreply.github.com>
2024-11-06 09:47:28 +01:00
Josh Holtz ee3fb188f3 Remove PackageGroup (#4413)
* Improved JSON format for ButtonComponent codables

* Started to remove PackageGroup

* Removed PackageGroup and added Template 5 SwiftUI preview

* Remove PackageGroup

* Fixed some thread, lint, and selection stuff

* Made renames and some code improvements
2024-10-31 10:46:23 -05:00
Josh Holtz 74447a2292 Select package and purchase (#4332)
* Added scaffolding for paywall components, view models, and views

* Fixed lint

* Improvements from PR review

* Fixed lint

* Made intro offer optional

* Views be viewing

* Fixed lint

* Add swift flag check

* Fixed compile issue from rebase

* Made view models internal

* Added new individual corner radius and border modifier

* Select package and purchase

* Better validation in models

* Fix paywallstester compile errors

* Fix paywalls tester issues

* Fix more paywall tester issues

* Remove swift flags on paywalls tester

* Make thing not public

* Fix one typo
2024-10-17 08:43:20 -05:00
JayShortway a2036ae7de ButtonComponent can handle URL destinations (#4360) 2024-10-14 15:54:34 +02:00
Cesar de la Vega 5c079d7634 Better logs for promotional offer view (#4336) 2024-10-02 14:20:46 +02:00
JayShortway 136639e4f1 Fixes a Paywall Template 7 crash when none of the tiers have any available products. (#4243) 2024-09-09 17:00:51 +02:00
James Borthwick edeff27548 Paywall Components Localized Strings (#4237)
- Adds localized string support to paywall components
-This is primarily done in `TemplateComponentsView`'s static function
`chooseLocalization`.

- Validates that promised localized strings are actually available.
-This is done as component view models are created (see
`TextComponentViewModel`), by throwing if an expected key is not
present:
`self.text = try localizedStrings.string(key: component.textLid)`
-This pattern should be followed for every property that represents a
localized string id, which ensures a non-nil value for every value.
2024-09-06 09:35:05 -07:00
James BorthwickandJosh Holtz cad537e109 Paywalls Components Viewmodels + partial localization support (#4230)
This PR makes all paywall component views rely on intermediate view
models for better separation of concerns. The Component is decoded from
the server response, the ComponentViewModel organizes the component's
data into a view-friendly format, including translating "raw" types into
SwiftUI types when needed, and the ComponentView renders the component.

[ComponentView] --owns---> [ComponentViewModel]  --owns---> [Component].

There has been file re-organization, including grouping all of our model
type extensions and the code that transforms them into SwiftUI
equivalents into their own files. Colors are now decoded without relying
on template paywall code.

---------

Co-authored-by: Josh Holtz <me@joshholtz.com>
2024-09-04 12:32: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
James Borthwick 22ff468e88 Support to round+truncate prices displayed on paywalls for backend-specified countries (#4132)
## Rounds prices for certain locales.

The server now returns a list of storefront country codes where paywalls
should not display cents. This part of paywall data, in
`zero_decimal_place_countries`.

Prices are only truncated if the price ends in .00.
2024-08-16 11:00:38 -05:00
dd0ee262ea [Customer Center] Initial implementation (#3967)
Alpha of the Customer Center. In this branch, it's been disabled behind
a compilation flag.

---------

Co-authored-by: Will Taylor <wtaylor151@gmail.com>
Co-authored-by: James Borthwick <109382862+jamesrb1@users.noreply.github.com>
Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
Co-authored-by: JayShortway <29483617+JayShortway@users.noreply.github.com>
Co-authored-by: Toni Rico <toni.rico.diez@revenuecat.com>
Co-authored-by: Toni Rico <antonio.rico.diez@revenuecat.com>
Co-authored-by: Cody Kerns <44073103+codykerns@users.noreply.github.com>
Co-authored-by: Andy Boedo <andresboedo@gmail.com>
2024-08-09 15:26:55 +02:00
James Borthwick 3c084753c8 Observe PurchaseHandler when owned externally (#4097)
Josh kindly [fixed a
problem](https://github.com/RevenueCat/purchases-ios/pull/4035) while I
was away where the purchase handler would get clobbered when the paywall
was re-rendered. This PR restores avoiding having one struct owned by
two @StateObjects at the same time.
2024-08-08 09:36:03 -07:00
Cesar de la Vega fb7ea428fa Fix PaywallsTester by changing TestData DEBUG checks (#4092)
Round 2 on https://github.com/RevenueCat/purchases-ios/pull/4087. There
were still some stuff not being available to `PaywallsTester` on archive
2024-07-19 16:33:51 +02:00
Cesar de la Vega 0de331f5b6 Add missing @PublicForExternalTesting that broke PaywallsTester (#4087) 2024-07-19 14:18:17 +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
Josh Holtz dbcfe0ceb5 Prevent paywall PurchaseHandler from being cleared on rerender (#4035)
### Motivation

Fixes #4034

`PurchaseHandler` was being cleared/reset when a paywall was being
rendered (ex: if an overlay was applied to it)

### Description

- Restores `PurchaseHandler` back to being a `@StateObject` from #3187
- `checkForConfigurationConsistency()` has a few changes
- Takes a `PurchaseHandler` as a parameter so it doesn't try to access
the `StateObject`
- This was fixes the purple Xcode warning of being access wrongly by it
being a `StateObject` again
- Removes `mutating` keyword and returns an `NSError?` which it now sets
in the constructor

### Demo

#### Using RevenueCat


https://github.com/RevenueCat/purchases-ios/assets/401294/7e6430ca-6243-49ad-b7c0-8a90d90894f8

#### Using MyApp


https://github.com/RevenueCat/purchases-ios/assets/401294/829f12c3-a11a-4952-883c-7ae7addd9c1d
2024-07-10 10:45:55 -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 BorthwickandCesar de la Vega 1499538d33 Xcode project with RevenueCatUI + Tests (#3960)
### RevenueCatUI and its tests are now accessible in the files
navigator:

<img width="555" alt="image"
src="https://github.com/RevenueCat/purchases-ios/assets/109382862/a4067d0a-62e5-43b2-bf77-a8a7ee1dd16e">

### It add two new schemes:

<img width="384" alt="image"
src="https://github.com/RevenueCat/purchases-ios/assets/109382862/94b80848-5cc3-4abc-b132-345d79a6f143">

Use RevenueCatUIDev to build RevenueCatUI, and RevenueCatUITests to run
its tests.

### Linting works:

<img width="716" alt="image"
src="https://github.com/RevenueCat/purchases-ios/assets/109382862/aacfa0bf-84af-494e-a4b1-9434de69b4f8">

### Individual tests can be run from the UI:

<img width="999" alt="image"
src="https://github.com/RevenueCat/purchases-ios/assets/109382862/902c5167-d352-4e6d-a848-c5d6c0e988de">

## N.B.

- Tests must be run via the RevenueCatUITest scheme.
- Xcode sometimes has difficulties switching schemes. If you see the
errors below, **clean the build folder** and try again and it will work
<img width="822" alt="image"
src="https://github.com/RevenueCat/purchases-ios/assets/109382862/0ac8dd77-676c-46e4-bc76-f8edf86f5a79">
- Building with the RevenueCatUIDev scheme creates some warnings related
to switches. Adding in an `@unknown default:` resolves the warning but
causes an error in other configurations ("Default will never be
executed").
<img width="801" alt="image"
src="https://github.com/RevenueCat/purchases-ios/assets/109382862/dcba6e0c-5755-4e5a-b940-ca270242b6bb">
- Because the root Package file was not altered, this PR won't have any
affect on how our package works when integrated via SPM.

---------

Co-authored-by: Cesar de la Vega <cesarvegaro@gmail.com>
2024-06-18 13:14:46 -07: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
James Borthwick 478dd26e64 Paywalls Tester: Enable Example Paywalls in Release Mode (#3885)
The paywalls examples weren't showing up when compiled in release mode.
Enabling it meant making a bunch of more internal things public via the
preprocessing script. `TestData` is no longer `#ifdef DEBUG`ed out, but
most of its data is still `#ifdef DEBUG`, and when it's compiled with an
`internal` modifier and thus nothing uses it, I'd expect what remains to
be stripped away during linking.
2024-05-15 12:06:05 -07:00
James Borthwick 62a8c42a10 Preprocessor to make select constructs public (#3880)
This PR allows the Paywalls Tester app to access some items internal to
purchases-ios by temporarily marking them public during complication.

Items that need to be public have been annotated with
`\\@PublicForExternalTesting`.

The Paywalls Tester project runs a script, Preprocessor.sh, as a
pre-action to add `public` to all classes/structs/enums/functions/inits
that have been annotated.

After compilation it runs a second script, Postprocessor.sh, as a
post-action to undo these changes so that they don't accidentally get
checked in.

The first script is run as a scheme pre-action rather than as a run
script build phase, because when run as a build phase the changes to the
files aren't picked up until the next compilation attempt.

<img width="669" alt="image"
src="https://github.com/RevenueCat/purchases-ios/assets/109382862/0b0bacdb-79de-441d-8d61-2a5dd03b1b53">

It also changes the archive step to be built with a release
configuration, and removes the use of `@testable import` for debug
builds.

resolves PWL-459
2024-05-09 11:37:11 -07:00
NachoSoto e5b5bb0a4d Paywalls: add 4 new variables (#3629)
### Added:
- `price_per_period_full`
- `total_price_and_per_month_full`
- `sub_period_length`
- `sub_period_abbreviated`
2024-02-05 09:18:34 -08:00
NachoSoto 9b683b0b87 Paywalls: improve "offering has no configured paywall" error (#3625)
I was debugging this problem (turned out to be an unknown targeting
rule), and was confused why I didn't have a paywall.

This will indicate exactly what offering has no paywall:

![image](https://github.com/RevenueCat/purchases-ios/assets/685609/d8d66e5c-6fd0-48ea-bcbe-3cf4b7e655d1)
2024-02-02 08:55:19 -08: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 2f1a4e0be8 PaywallViewController: new initializer with Offering identifier (#3587)
Depends on #3586.

This will be used by the hybrid SDKs to be able to load paywalls with an
offering identifier.
2024-01-22 09:00:36 -08:00
NachoSoto 25008b0e68 Paywalls: extracted PaywallViewConfiguration (#3586) 2024-01-22 07:12:46 -08:00
NachoSoto 56741f0a92 Paywalls: improve error display (#3577)
### Before:

This prevents "leaking" _RevenueCat_ in the error dialogs:

![Simulator Screenshot - iPhone 15 Pro Max - 2024-01-16 at 12 45
13](https://github.com/RevenueCat/purchases-ios/assets/685609/a59bce50-a470-44fb-9d36-1553abcc8488)

### After:
With this change, users still get some information about the problem,
and we get enough context to be able to diagnose it:

![Simulator Screenshot - iPhone 15 Pro Max - 2024-01-16 at 12 57
15](https://github.com/RevenueCat/purchases-ios/assets/685609/721d0197-5507-4618-9fe1-e0e748b0bc32)
2024-01-16 16:27:32 -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 59a877a699 Paywalls: improve image caching (#3498) 2023-12-12 07:24:12 -08:00
Andy BoedoandNachoSoto 56f7178145 Paywalls: fix empty description when using custom package type and {{ sub_period }} (#3495)
If you select a custom package type and set Offer Period, the paywall
would have an empty entry for that package.


<img width="802" alt="image"
src="https://github.com/RevenueCat/purchases-ios/assets/3922667/fa50e15b-b122-4caa-816e-3250f10d5d89">

| Before | After |
| :-: | :-: |
| ![IMG_908CED5B663B-1
(1)](https://github.com/RevenueCat/purchases-ios/assets/3922667/2519c74e-c0d2-44c1-a126-64bd78808384)
|
![IMG_B1CED574DF20-1](https://github.com/RevenueCat/purchases-ios/assets/3922667/1eaf17d6-75fd-4e20-86cf-ef93de2d74d3)
|

---------

Co-authored-by: NachoSoto <ignaciosoto90@gmail.com>
2023-12-08 07:17:49 +08:00
NachoSoto 56daf52f63 Optimization: changed first to first(where:) (#3467)
See https://github.com/apple/swift/issues/55374

`.first` has been broken since 2020.
2023-12-06 11:08:00 -08:00
Tensei 5e9c4f7493 Paywalls: adjusted German translations (#3476)
Special abbreviation style for German and some corrections to the German
translation

The updated translations are more precise. For instance, "Datenschutz"
is a more commonly used term for "Privacy" in German. Similarly,
changing "Lebensdauer" to "Lebenslang" for "Lifetime" is more accurate,
as "Lebenslang" specifically implies 'for life'. The other changes
follow a similar pattern of refining the language for better
comprehension and context-appropriate usage in German.
2023-12-06 11:04:04 -08:00
NachoSoto e24f8ad72d Paywalls: remove empty space when template 4 has no offer details (#3469)
### Before:
<img width="333" alt="Screenshot 2023-11-19 at 16 01 15"
src="https://github.com/RevenueCat/purchases-ios/assets/685609/4286c281-b2e9-463b-a368-726ae44bdc69">

### After:
<img width="328" alt="Screenshot 2023-11-19 at 16 01 06"
src="https://github.com/RevenueCat/purchases-ios/assets/685609/234b8c8d-f2ec-4ff4-8153-2c461dc92987">
2023-11-22 18:43:07 -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 c0120a653e Paywalls: simplified PaywallViewMode logic (#3470)
This brings the implementations closer to Android, removing the
unnecessary `PaywallViewMode` properties.
2023-11-22 17:18:39 -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
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 0035d18ef5 Fix deprecation warning (#3371)
We normally don't have to handle this because of our lower deployment
target, except we do for `visionOS 1.0`, so this updates it for all.

```
▸ ⚠️  Localization.swift:265:25: 'languageCode' was deprecated in visionOS 1.0: renamed to 'language.languageCode.identifier'
▸ return self.languageCode
▸                         ^
```
2023-11-02 06:43:10 -07:00
NachoSoto c203198973 Paywalls: improve period abbreviations in Japanese (#3367)
This mainly fixes `{{ price_per_period }}` showing `¥1/y` for year.

I fixed it by forcing a single abbreviation style for Japanese.
2023-11-01 09:57:47 -07:00