Skip to content
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: created LoadingPaywallView #1282

Merged
merged 6 commits into from
Oct 4, 2023
Merged

Conversation

NachoSoto
Copy link
Contributor

This uses PaywallData.default (from #1261) to be able to display a loading view just like in iOS.

@NachoSoto NachoSoto requested a review from a team September 21, 2023 16:48
import java.net.URL

@Composable
internal fun LoadingPaywallView(mode: PaywallViewMode) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whole implementation is pretty much the same as in iOS, creating fake data and reusing the rest of the code! :D

@@ -283,7 +284,7 @@ private object Constants {
val assetBaseURL = URL("https://assets.pawwalls.com")
}

private data class TestStoreProduct(
internal data class TestStoreProduct(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might want to move this to the main SDK for everyone to use? It's been super helpful in iOS.

Comment on lines 15 to 25
val paywallData = this.paywall
?: return PaywallViewState.Error("No paywall data for offering: $identifier")
return try {
val templateConfiguration = TemplateConfigurationFactory.create(
variableDataProvider = variableDataProvider,
mode = mode,
paywallData = paywallData,
packages = availablePackages,
activelySubscribedProductIdentifiers = emptySet(), // TODO-PAYWALLS: Check for active subscriptions
)
PaywallViewState.Loaded(
templateConfiguration = templateConfiguration,
selectedPackage = templateConfiguration.packages.default,
)
} catch (e: Exception) {
PaywallViewState.Error("Error creating paywall: ${e.message}")
}

return toPaywallViewState(variableDataProvider, paywallData, mode)
}

internal fun Offering.toPaywallViewState(
variableDataProvider: VariableDataProvider,
paywallData: PaywallData,
mode: PaywallViewMode,
): PaywallViewState.Loaded {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vegaro this is what I was telling you about :) thanks to your refactor in #1273 (I'll wait for that to be finished before merging this) we can create a PaywallViewState.Loaded here since we can guarantee we have everything ready (useful for the default / loading paywalls).

templateConfiguration = templateConfiguration,
selectedPackage = templateConfiguration.packages.default,
)
} catch (e: Exception) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed this to compile for now. #1273 is also getting rid of it.

val provider = VariableDataProvider(LocalContext.current.applicationContext.toAndroidContext())
val offering = LoadingPaywallConstants.offering

Template2(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An upcoming PR will actually make this look like a "placeholder" like in iOS.

@NachoSoto NachoSoto force-pushed the paywalls-inject-mode branch 2 times, most recently from 3aeb2c8 to 3d705c3 Compare September 22, 2023 14:50
Base automatically changed from paywalls-inject-mode to paywalls September 22, 2023 15:06
@NachoSoto NachoSoto changed the title [WIP] Paywalls: created LoadingPaywallView Paywalls: created LoadingPaywallView Oct 3, 2023
@NachoSoto
Copy link
Contributor Author

@RevenueCat/cashnip this is ready for review!

@codecov
Copy link

codecov bot commented Oct 3, 2023

Codecov Report

All modified lines are covered by tests ✅

Comparison is base (d6b1c7c) 85.06% compared to head (6cf9ae9) 85.06%.

❗ Current head 6cf9ae9 differs from pull request most recent head 0524300. Consider uploading reports for the commit 0524300 to get more accurate results

Additional details and impacted files
@@            Coverage Diff            @@
##           paywalls    #1282   +/-   ##
=========================================
  Coverage     85.06%   85.06%           
=========================================
  Files           193      193           
  Lines          6475     6475           
  Branches        942      942           
=========================================
  Hits           5508     5508           
  Misses          600      600           
  Partials        367      367           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Contributor

@tonidero tonidero left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a few comments but looking good!

)

override fun selectPackage(packageToSelect: TemplateConfiguration.PackageInfo) {
error("Not supported")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm what would happen if people tapped on these buttons while they are loading? Would it crash? I think we probably want to do nothing on these.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh thanks for the reminder! Fixed by making it not clickable.

paywall = paywallData,
)

val viewModel = LoadingViewModel(mode, offering, paywallData)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Classes that inherit from ViewModel should not be instantiated using the constructor. Instead you could use the viewModel function to get an instance. However, you would need to create a factory to pass this data to the VM, similar to how we create the main view model currently

Alternatively, we could just make this VM not inherit from ViewModel. The main advantage of using that is that you get a data container that survives configuration changes automatically... But I don't think that's that important for this. If we see performance issues at some point, we can reconsider.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL, thanks! I removed the ViewModel parent class.

private val _state =
MutableStateFlow(
offering.toPaywallViewState(
VariableDataProvider(MockApplicationContext()),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I don't think we should use MockApplicationContext in production, even if it's not really going to be visible... Maybe we can pass the real application context to the view model?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

packageType = PackageType.WEEKLY,
offering = this.offeringIdentifier,
product = TestStoreProduct(
id = "com.revenuecat.weekly",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder how many devs have a weekly option... Just wondering whether we should just have 2 options for the loading screen, and remove the weekly one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ultimately you won't see them. You think having 2 in the loading screen makes more sense than 3?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's what I was thinking... But maybe we can leave the 3 for now, if it's what we do in iOS.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. Ultimately there's no right answer though.

identifier = "weekly",
packageType = PackageType.WEEKLY,
offering = this.offeringIdentifier,
product = TestStoreProduct(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm not a fan of using TestStoreProduct in production... But I guess we would have to do something similar in any case for the loading screen following this route...

I'm wondering whether we would like to have a completely separate loading screen which doesn't need actual data... But I guess that would mean duplicating a lot of the UI layout, so probably not be worth it. I think we can go with this for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it's slightly weird, but are you worried about something in particulaar?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, mostly worried about using these test models in production... I think it's a bit of a smell TBH... But yeah, as long as we make sure it's limited to the LoadingPaywallView, it should be ok for now I think. We can reconsider about adding a separate layout for the loading view later if needed.

Copy link
Contributor

@tonidero tonidero left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good for now. I'm not completely sold on using TestStoreProduct in production, but I think we can iterate on that later if needed.

@NachoSoto
Copy link
Contributor Author

Thanks, yeah. I'm thinking we could have a separate PackageConfiguration that allows us to represent these fake packages instead.

@NachoSoto NachoSoto enabled auto-merge (squash) October 4, 2023 16:48
@NachoSoto NachoSoto merged commit 220631f into paywalls Oct 4, 2023
4 of 5 checks passed
@NachoSoto NachoSoto deleted the paywalls-loading-state branch October 4, 2023 16:52
tonidero pushed a commit that referenced this pull request Oct 31, 2023
This uses `PaywallData.default` (from #1261) to be able to display a
loading view just like in iOS.
NachoSoto added a commit that referenced this pull request Oct 31, 2023
### New Features
#### ✨📱 Introducing Android Paywalls 🐾🧱

RevenueCat's Paywalls allow you 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 Android, simply:

1. Create a Paywall on the Dashboard for the `Offering` you intend to
serve to your customers
2. Add the `RevenueCatUI` dependency to your project:
```groovy build.gradle
implementation 'com.revenuecat.purchases:purchases:7.1.0'
implementation 'com.revenuecat.purchases:purchases-ui:7.1.0'
```
3. Display a paywall:
```kotlin
@OptIn(ExperimentalPreviewRevenueCatUIPurchasesAPI::class)
@composable
private fun LockedScreen() {
    YourContent()

    PaywallDialog(
        PaywallDialogOptions.Builder()
            .setRequiredEntitlementIdentifier(Constants.ENTITLEMENT_ID)
            .build()
    )
}
```

> **Note**
> Android paywalls is currently behind an experimental flag:
`ExperimentalPreviewRevenueCatUIPurchasesAPI`.
> It is safe to release app updates with it. We guarantee that paywalls
will continue to work and any changes will always be backwards
compatible.
> They are stable, but migration steps may be required in the future.
We'll do our best to minimize any changes you have to make.

You can find more information in [our
documentation](https://rev.cat/paywalls).

<details>

<summary>List of changes</summary>

* `Paywalls`: Improve PaywallDialog look on tablets (#1419) via Toni
Rico (@tonidero)
* `Paywalls`: disallow Markdown links in PurchaseButton (#1421) via
NachoSoto (@NachoSoto)
* `Paywalls`: implemented template 5 (#1412) via NachoSoto (@NachoSoto)
* `Paywalls`: Fix wrong view model data after configuration changes
(#1417) via Toni Rico (@tonidero)
* `Paywalls`: update template 4 colors (#1410) via NachoSoto
(@NachoSoto)
* `Paywalls`: Add bundled font to paywall tester (#1415) via Toni Rico
(@tonidero)
* `Paywalls`: Fix purchase button text flashing when changing selected
package (#1416) via Toni Rico (@tonidero)
* `Paywalls`: support text3 and accent3 colors (#1409) via NachoSoto
(@NachoSoto)
* `Paywalls`: PaywallData errors shouldn't make Offerings fail to decode
(#1402) via NachoSoto (@NachoSoto)
* `Paywalls`: convert empty strings to null via NachoSoto (@NachoSoto)
* `Paywalls`: footer view should not render background image via
NachoSoto (@NachoSoto)
* `Paywalls`: improve template 4 A11y support via NachoSoto (@NachoSoto)
* `Paywalls`: Add close button option (#1390) via Toni Rico (@tonidero)
* `Paywalls`: fix MarkdownText stealing touches from package buttons
(#1411) via NachoSoto (@NachoSoto)
* Fix onDismiss getting called twice in PaywallDialogs (#1408) via Cesar
de la Vega (@vegaro)
* `Paywalls`: ignore URL deserialization errors (#1399) via NachoSoto
(@NachoSoto)
* Animate transition between loading and loaded paywall (#1404) via
Cesar de la Vega (@vegaro)
* Fix button padding in Loading paywall (#1405) via Cesar de la Vega
(@vegaro)
* Add test for `packageInfo.localizedDiscount` (#1407) via Cesar de la
Vega (@vegaro)
* `Paywalls`: RemoteImage always renders a placeholder on previews via
NachoSoto (@NachoSoto)
* `Paywalls`: decode empty images as null via NachoSoto (@NachoSoto)
* `Paywalls`: Allow trigger manual paywall tester release (#1406) via
Toni Rico (@tonidero)
* Fix navigation after closing paywall in Paywalls screen (#1403) via
Cesar de la Vega (@vegaro)
* `Paywalls`: Add experimental annotation to all public APIs in
RevenueCat UI (#1400) via Toni Rico (@tonidero)
* `Paywalls`: improve template 2 layout (#1396) via NachoSoto
(@NachoSoto)
* `Paywalls`: template 4 (#1349) via NachoSoto (@NachoSoto)
* `Paywalls`: fix template 3 offer details color (#1394) via NachoSoto
(@NachoSoto)
* `Paywalls`: PaywallDialogOptions no longer requires dismissRequest
(#1386) via NachoSoto (@NachoSoto)
* `Paywalls`: fixed accessibility across templates (#1392) via NachoSoto
(@NachoSoto)
* `Paywalls`: increase PaywallBackground blur radius to match iOS via
NachoSoto (@NachoSoto)
* `Paywalls`: use LocalUriHandler for opening links (#1388) via
NachoSoto (@NachoSoto)
* `Paywalls`: support for Markdown links (#1387) via NachoSoto
(@NachoSoto)
* `Paywalls`: display purchase/restore errors (#1384) via NachoSoto
(@NachoSoto)
* `Paywalls`: improve error handling (#1383) via NachoSoto (@NachoSoto)
* `Paywalls`: fixed incorrect background on footer (#1382) via NachoSoto
(@NachoSoto)
* `Paywalls`: fix backwards-compatible blurring of default paywall
background (#1380) via NachoSoto (@NachoSoto)
* `Paywalls`: polish template 2 spacing (#1381) via NachoSoto
(@NachoSoto)
* `Paywalls`: optimize backwards compatible blurring (#1379) via
NachoSoto (@NachoSoto)
* `Paywalls`: Predownload offering images if paywalls sdk exists (#1372)
via Toni Rico (@tonidero)
* `Paywalls`: PurchaseButton supports gradients (#1378) via NachoSoto
(@NachoSoto)
* `Paywalls`: optimize AdaptiveComposable (#1377) via NachoSoto
(@NachoSoto)
* `Paywalls`: improve FooterDialog corner radius (#1374) via NachoSoto
(@NachoSoto)
* `Paywalls`: optimize PurchaseButton (#1376) via NachoSoto (@NachoSoto)
* `Paywalls`: Enable footer modes in paywall tester paywalls tab (#1368)
via Toni Rico (@tonidero)
* `Paywalls`: Support footer in template 3 (#1367) via Toni Rico
(@tonidero)
* `Paywalls`: calculate discountRelativeToMostExpensivePerMonth (#1370)
via NachoSoto (@NachoSoto)
* `Paywalls`: Add offer details in template 2 when in condensed form
(#1371) via Toni Rico (@tonidero)
* `Paywalls`: improve handling of lifetime/custom packages (#1363) via
NachoSoto (@NachoSoto)
* `Paywalls`: Support footer in template 1 (#1366) via Toni Rico
(@tonidero)
* Add `StoreProduct.pricePerMonth` (#1369) via NachoSoto (@NachoSoto)
* `Paywalls`: Support condensed footer presentation in template 2
(#1365) via Toni Rico (@tonidero)
* `Paywalls`: finished localization support (#1362) via NachoSoto
(@NachoSoto)
* `Paywalls`: backwards compatible blurring (#1327) via Andy Boedo
(@AndyBoedo)
* `Paywalls`: PaywallViewModel tests (#1357) via NachoSoto (@NachoSoto)
* `Paywalls`: improve LoadingPaywall (#1364) via NachoSoto (@NachoSoto)
* `Paywalls`: Fix paywall compose previews (#1360) via Toni Rico
(@tonidero)
* `Paywalls`: Fix proguard rules kotlinx serialization (#1356) via Toni
Rico (@tonidero)
* `Paywalls`: Add custom font example to paywalls screen (#1358) via
Toni Rico (@tonidero)
* `Paywalls`: Support Google fonts and font families with multiple fonts
(#1338) via Toni Rico (@tonidero)
* `Paywalls`: Support custom fonts through FontProvider (#1328) via Toni
Rico (@tonidero)
* `Paywalls`: fixed Footer padding (#1354) via NachoSoto (@NachoSoto)
* `Paywalls`: Rename PaywallView to Paywall (#1351) via Toni Rico
(@tonidero)
* `Paywalls`: disable PurchaseButton during purchases (#1352) via
NachoSoto (@NachoSoto)
* `Paywalls`: enable library publishing (#1353) via NachoSoto
(@NachoSoto)
* `Paywalls`: handle "action in progress" state (#1346) via NachoSoto
(@NachoSoto)
* `Paywalls`: support {{ sub_duration_in_months }} (#1348) via NachoSoto
(@NachoSoto)
* `Paywalls`: disallow purchasing currently subscribed products (#1334)
via NachoSoto (@NachoSoto)
* `Paywalls`: new PaywallActivityLauncher.launchIfNeeded methods (#1335)
via NachoSoto (@NachoSoto)
* `Paywalls`: PaywallColor supports RGBA (#1332) via NachoSoto
(@NachoSoto)
* `Paywalls`: Make DialogScaffold private (#1329) via Toni Rico
(@tonidero)
* Add `revenuecatui` `gradle.properties` to specify name of dependency
(#1324) via Toni Rico (@tonidero)
* `Paywalls`: log error when failing to load images (#1321) via
NachoSoto (@NachoSoto)
* Log errors when displaying default paywall (#1318) via Cesar de la
Vega (@vegaro)
* Rename packages to packageIds in `PaywallData` (#1309) via Cesar de la
Vega (@vegaro)
* Add support for multiple intro offers in `IntroEligibilityStateView`
(#1319) via Cesar de la Vega (@vegaro)
* Fix material theme references to use Material3 versions (#1326) via
Toni Rico (@tonidero)
* `Paywalls`: Add support to launch paywall as activity (#1317) via Toni
Rico (@tonidero)
* Parse `{{ sub_offer_price_2 }}` and `{{ sub_offer_duration_2 }}`
variables (#1313) via Cesar de la Vega (@vegaro)
* `Paywalls`: changed PaywallsTester app icon (#1323) via NachoSoto
(@NachoSoto)
* `Paywalls`: fixed PaywallDialog.setRequiredEntitlementIdentifier
(#1322) via NachoSoto (@NachoSoto)
* `Paywalls`: Markdown support (#1312) via NachoSoto (@NachoSoto)
* `PaywallsTester`: added template name to offerings list (#1316) via
NachoSoto (@NachoSoto)
* `Paywalls`: Update paywall tester to be able to display paywall footer
(#1315) via Toni Rico (@tonidero)
* `Paywalls`: Add PaywallFooter composable to present a minified paywall
UI that allows for custom paywalls (#1314) via Toni Rico (@tonidero)
* `Paywalls`: use IntroEligibilityStateView (#1311) via NachoSoto
(@NachoSoto)
* `PaywallData` validation tests (#1310) via Cesar de la Vega (@vegaro)
* `Paywalls`: implemented LoadingPaywallView with placeholder (#1284)
via NachoSoto (@NachoSoto)
* `Paywalls`: created LoadingPaywallView (#1282) via NachoSoto
(@NachoSoto)
* Fix template test data (#1308) via Cesar de la Vega (@vegaro)
</details>

### Other Changes
Add `purchases.setOnesignalUserID` (#1304) via Raquel Diez
(@Raquel10-RevenueCat)

---------

Co-authored-by: revenuecat-ops <[email protected]>
Co-authored-by: NachoSoto <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants