-
Notifications
You must be signed in to change notification settings - Fork 54
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Paywalls: Add API to display paywall as a composable dialog (#1297)
### 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.
- Loading branch information
Showing
11 changed files
with
280 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
87 changes: 87 additions & 0 deletions
87
ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/PaywallDialog.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package com.revenuecat.purchases.ui.revenuecatui | ||
|
||
import androidx.compose.foundation.layout.Box | ||
import androidx.compose.foundation.layout.fillMaxSize | ||
import androidx.compose.foundation.layout.padding | ||
import androidx.compose.material3.Scaffold | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.LaunchedEffect | ||
import androidx.compose.runtime.ReadOnlyComposable | ||
import androidx.compose.runtime.getValue | ||
import androidx.compose.runtime.mutableStateOf | ||
import androidx.compose.runtime.remember | ||
import androidx.compose.runtime.setValue | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.window.Dialog | ||
import androidx.compose.ui.window.DialogProperties | ||
import androidx.window.core.layout.WindowWidthSizeClass | ||
import com.revenuecat.purchases.Purchases | ||
import com.revenuecat.purchases.PurchasesException | ||
import com.revenuecat.purchases.awaitCustomerInfo | ||
import com.revenuecat.purchases.ui.revenuecatui.helpers.Logger | ||
import com.revenuecat.purchases.ui.revenuecatui.helpers.computeWindowWidthSizeClass | ||
import kotlinx.coroutines.launch | ||
|
||
/** | ||
* Composable offering a dialog screen Paywall UI configured from the RevenueCat dashboard. | ||
* This dialog will be shown as a full screen dialog in compact devices and a normal dialog othewise. | ||
* @param paywallDialogOptions The options to configure the PaywallDialog and what to do on dismissal. | ||
*/ | ||
@Composable | ||
fun PaywallDialog( | ||
paywallDialogOptions: PaywallDialogOptions, | ||
) { | ||
val shouldDisplayBlock = paywallDialogOptions.shouldDisplayBlock | ||
var shouldDisplayDialog by remember { mutableStateOf(shouldDisplayBlock == null) } | ||
if (shouldDisplayBlock != null) { | ||
LaunchedEffect(paywallDialogOptions) { | ||
launch { | ||
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. | ||
val customerInfo = Purchases.sharedInstance.awaitCustomerInfo() | ||
shouldDisplayBlock.invoke(customerInfo) | ||
} catch (e: PurchasesException) { | ||
Logger.e("Error fetching customer info to display paywall dialog", e) | ||
false | ||
} | ||
if (shouldDisplayDialog) { | ||
Logger.d("Displaying paywall dialog according to display logic") | ||
} else { | ||
Logger.d("Not displaying paywall dialog according to display logic") | ||
} | ||
} | ||
} | ||
} | ||
if (shouldDisplayDialog) { | ||
Dialog( | ||
onDismissRequest = paywallDialogOptions.dismissRequest, | ||
properties = DialogProperties(usePlatformDefaultWidth = shouldUsePlatformDefaultWidth()), | ||
) { | ||
DialogScaffold(paywallDialogOptions) | ||
} | ||
} | ||
} | ||
|
||
@Composable | ||
fun DialogScaffold(paywallDialogOptions: PaywallDialogOptions) { | ||
Scaffold(modifier = Modifier.fillMaxSize()) { paddingValues -> | ||
Box( | ||
modifier = Modifier | ||
.fillMaxSize() | ||
.padding(paddingValues), | ||
) { | ||
PaywallView(paywallDialogOptions.toPaywallViewOptions()) | ||
} | ||
} | ||
} | ||
|
||
@Composable | ||
@ReadOnlyComposable | ||
private fun shouldUsePlatformDefaultWidth(): Boolean { | ||
return when (computeWindowWidthSizeClass()) { | ||
WindowWidthSizeClass.MEDIUM, WindowWidthSizeClass.EXPANDED -> true | ||
else -> false | ||
} | ||
} |
72 changes: 72 additions & 0 deletions
72
...nuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/PaywallDialogOptions.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package com.revenuecat.purchases.ui.revenuecatui | ||
|
||
import com.revenuecat.purchases.CustomerInfo | ||
import com.revenuecat.purchases.Offering | ||
|
||
class PaywallDialogOptions(builder: Builder) { | ||
|
||
val dismissRequest: () -> Unit | ||
val shouldDisplayBlock: ((CustomerInfo) -> Boolean)? | ||
val offering: Offering? | ||
val shouldDisplayDismissButton: Boolean | ||
val listener: PaywallViewListener? | ||
|
||
init { | ||
this.shouldDisplayBlock = builder.shouldDisplayBlock | ||
this.dismissRequest = builder.dismissRequest | ||
this.offering = builder.offering | ||
this.shouldDisplayDismissButton = builder.shouldDisplayDismissButton | ||
this.listener = builder.listener | ||
} | ||
|
||
fun toPaywallViewOptions(): PaywallViewOptions { | ||
return PaywallViewOptions.Builder() | ||
.setOffering(offering) | ||
.setShouldDisplayDismissButton(shouldDisplayDismissButton) | ||
.setListener(listener) | ||
.build() | ||
} | ||
|
||
class Builder( | ||
val dismissRequest: () -> Unit, | ||
) { | ||
internal var shouldDisplayBlock: ((CustomerInfo) -> Boolean)? = null | ||
internal var offering: Offering? = null | ||
internal var shouldDisplayDismissButton: Boolean = true | ||
internal var listener: PaywallViewListener? = null | ||
|
||
/** | ||
* Allows to configure whether to display the paywall dialog depending on operations on the CustomerInfo | ||
*/ | ||
fun setShouldDisplayBlock(shouldDisplayBlock: ((CustomerInfo) -> Boolean)?) = apply { | ||
this.shouldDisplayBlock = shouldDisplayBlock | ||
} | ||
|
||
/** | ||
* Allows to configure whether to display the paywall dialog depending on the presence of a specific entitlement | ||
*/ | ||
fun setRequiredEntitlementIdentifier(requiredEntitlementIdentifier: String?) = apply { | ||
requiredEntitlementIdentifier?.let { requiredEntitlementIdentifier -> | ||
this.shouldDisplayBlock = { customerInfo -> | ||
customerInfo.entitlements[requiredEntitlementIdentifier]?.isActive == true | ||
} | ||
} | ||
} | ||
|
||
fun setOffering(offering: Offering?) = apply { | ||
this.offering = offering | ||
} | ||
|
||
fun setShouldDisplayDismissButton(shouldDisplayDismissButton: Boolean) = apply { | ||
this.shouldDisplayDismissButton = shouldDisplayDismissButton | ||
} | ||
|
||
fun setListener(listener: PaywallViewListener?) = apply { | ||
this.listener = listener | ||
} | ||
|
||
fun build(): PaywallDialogOptions { | ||
return PaywallDialogOptions(this) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
20 changes: 20 additions & 0 deletions
20
...nuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/helpers/WindowHelper.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package com.revenuecat.purchases.ui.revenuecatui.helpers | ||
|
||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.ReadOnlyComposable | ||
import androidx.compose.ui.platform.LocalContext | ||
import androidx.window.core.layout.WindowSizeClass | ||
import androidx.window.core.layout.WindowWidthSizeClass | ||
import androidx.window.layout.WindowMetricsCalculator | ||
import com.revenuecat.purchases.ui.revenuecatui.extensions.getActivity | ||
|
||
@Composable | ||
@ReadOnlyComposable | ||
internal fun computeWindowWidthSizeClass(): WindowWidthSizeClass? { | ||
val activity = LocalContext.current.getActivity() ?: return null | ||
val metrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(activity) | ||
val width = metrics.bounds.width() | ||
val height = metrics.bounds.height() | ||
val density = LocalContext.current.resources.displayMetrics.density | ||
return WindowSizeClass.compute(width / density, height / density).windowWidthSizeClass | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.