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: Add API to display paywall as a composable dialog #1297

Merged

Conversation

tonidero
Copy link
Contributor

Description

This adds a new composable PaywallDialog that can be used to display the paywall as a dialog. It also modifies paywall tester to provide options on how we want to display the paywall from the offerings tab.

@codecov
Copy link

codecov bot commented Sep 28, 2023

Codecov Report

All modified lines are covered by tests ✅

Comparison is base (4885224) 85.46% compared to head (84fb593) 85.46%.
Report is 18 commits behind head on paywalls.

❗ Current head 84fb593 differs from pull request most recent head fc5704f. Consider uploading reports for the commit fc5704f to get more accurate results

Additional details and impacted files
@@            Coverage Diff            @@
##           paywalls    #1297   +/-   ##
=========================================
  Coverage     85.46%   85.46%           
=========================================
  Files           190      190           
  Lines          6385     6385           
  Branches        930      930           
=========================================
  Hits           5457     5457           
  Misses          568      568           
  Partials        360      360           

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

shouldDisplayDialog = try {
// TODO-PAYWALLS: This won't receive updates in case the customer info changes and starts/stops
// being eligible to display the paywall dialog. We would need to support multiple customer info
// listeners to refresh this.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

As mentioned, we would need to support multiple customer info listeners if we wanted to display this paywall after the fact... But I think it might be ok, at least for V1?

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 how it works in iOS. We just want to do one initial check. I'd remove the TODO, since we wouldn't want to get notified of customer info changes and hide the paywall.

// TODO-PAYWALLS: This won't receive updates in case the customer info changes and starts/stops
// being eligible to display the paywall dialog. We would need to support multiple customer info
// listeners to refresh this.
val customerInfo = Purchases.sharedInstance.awaitCustomerInfo()
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 didn't fully like this here so I was thinking about moving it to the view model. But then, I didn't want to have to create the view model at this point (since we might not even display the dialog)... I thought it was ok for now to keep this. Lmk if you would prefer me to move it.

Copy link
Contributor

Choose a reason for hiding this comment

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

This is fine. That's exactly how PresentingPaywallModifier works.


@Composable
@ReadOnlyComposable
private fun shouldUsePlatformDefaultWidth(): Boolean {
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 basically will display a full screen dialog for phones and a normal dialog for tablets and foldables. I think it looks ok for now but we can revisit later and polish this logic.

@@ -12,5 +12,5 @@ interface PaywallViewListener {
fun onRestoreStarted() {}
fun onRestoreCompleted(customerInfo: CustomerInfo) {}
fun onRestoreError(error: PurchasesError) {}
fun onDismissed() {}
fun onCloseButtonPressed() {}
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'm changing this listener method, since the onDismissed only made sense when displaying as a dialog and the dialog options will already have a separate callback for that.
I'm making showing the close button optional (currently not implemented) and having a callback here for when it's tapped.

Template1MainContent(state)
Column(modifier = Modifier.weight(1f)) {
Template1MainContent(state)
}
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 fixes an issue that happened in some devices, specially in landscape where the purchase button and footer weren't visible. By adding a weight to the main content, it has less priority than the button/footer so those will always show.

Copy link
Contributor

Choose a reason for hiding this comment

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

Would adding the weight to the outer colum work too?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, if we apply the weight to the outer column, that means that it won't apply to the inner views. Weights work with siblings by allowing you to set a relative weight between them to use the available space. In case one or more of the siblings don't have a weight attribute, those will have priority over those that do have it, which is what I'm doing here (give priority to the button + footer over the main content to display on the screen)

Template2MainContent(state, viewModel)
Spacer(modifier = Modifier.weight(1f))
Box(
modifier = Modifier.weight(1f).padding(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Similar to the above, but for template 2.

@tonidero tonidero marked this pull request as ready for review September 28, 2023 10:43
@tonidero tonidero requested a review from a team September 28, 2023 10:43
@@ -55,6 +55,9 @@ private fun getPaywallViewModel(
): PaywallViewModel {
val applicationContext = LocalContext.current.applicationContext
return viewModel<PaywallViewModelImpl>(
// We need to pass the key in order to create different view models for different offerings when
// trying to load different paywalls for the same view model store owner.
key = offering?.identifier,
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 has an important drawback which is that, if a user displays multiple different offerings as dialogs, the viewmodels for all of them will remain in memory... I'm not sure if this is the best way to fix this, but it should work for now.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think that makes sense. It's also probably extremely uncommon.

@tonidero tonidero requested a review from a team September 28, 2023 11:07
@tonidero tonidero force-pushed the toniricodiez/pwl-286-api-for-easily-displaying-paywalls branch from 3736875 to 84fb593 Compare September 29, 2023 12:54
Copy link
Contributor

@NachoSoto NachoSoto left a comment

Choose a reason for hiding this comment

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

Looks great!

@@ -55,6 +55,9 @@ private fun getPaywallViewModel(
): PaywallViewModel {
val applicationContext = LocalContext.current.applicationContext
return viewModel<PaywallViewModelImpl>(
// We need to pass the key in order to create different view models for different offerings when
// trying to load different paywalls for the same view model store owner.
key = offering?.identifier,
Copy link
Contributor

Choose a reason for hiding this comment

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

I think that makes sense. It's also probably extremely uncommon.

shouldDisplayDialog = try {
// TODO-PAYWALLS: This won't receive updates in case the customer info changes and starts/stops
// being eligible to display the paywall dialog. We would need to support multiple customer info
// listeners to refresh this.
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 how it works in iOS. We just want to do one initial check. I'd remove the TODO, since we wouldn't want to get notified of customer info changes and hide the paywall.

// TODO-PAYWALLS: This won't receive updates in case the customer info changes and starts/stops
// being eligible to display the paywall dialog. We would need to support multiple customer info
// listeners to refresh this.
val customerInfo = Purchases.sharedInstance.awaitCustomerInfo()
Copy link
Contributor

Choose a reason for hiding this comment

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

This is fine. That's exactly how PresentingPaywallModifier works.

}
if (shouldDisplayDialog) {
Logger.d("Displaying paywall dialog according to display logic")
} else Logger.d("Not displaying paywall dialog according to display logic")
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: weird that you have {} for the if but not the else.

import kotlinx.coroutines.launch

@Composable
fun PaywallDialog(
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add a docstring to this?

Template1MainContent(state)
Column(modifier = Modifier.weight(1f)) {
Template1MainContent(state)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Would adding the weight to the outer colum work too?

@tonidero tonidero enabled auto-merge (squash) October 3, 2023 10:11
@tonidero tonidero merged commit 13af808 into paywalls Oct 3, 2023
@tonidero tonidero deleted the toniricodiez/pwl-286-api-for-easily-displaying-paywalls branch October 3, 2023 10:27
tonidero added a commit that referenced this pull request Oct 31, 2023
### Description
This adds a new composable `PaywallDialog` that can be used to display
the paywall as a dialog. It also modifies paywall tester to provide
options on how we want to display the paywall from the offerings tab.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants