Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import com.revenuecat.paywallstester.paywalls.tabsWithButtons
import com.revenuecat.paywallstester.paywalls.tabsWithToggle
import com.revenuecat.paywallstester.paywalls.webViewSample
import com.revenuecat.purchases.FontAlias
import com.revenuecat.purchases.InternalRevenueCatAPI
import com.revenuecat.purchases.Offering
Expand Down Expand Up @@ -85,6 +86,7 @@ class SamplePaywallsLoader {
)
}

@Suppress("CyclomaticComplexMethod")
private fun paywallForTemplate(template: SamplePaywalls.SampleTemplate): SampleData {
return when (template) {
SamplePaywalls.SampleTemplate.TEMPLATE_1 -> SamplePaywalls.template1()
Expand All @@ -101,6 +103,7 @@ class SamplePaywallsLoader {

SamplePaywalls.SampleTemplate.TABS_BUTTONS -> tabsWithButtons()
SamplePaywalls.SampleTemplate.TABS_TOGGLE -> tabsWithToggle()
SamplePaywalls.SampleTemplate.COMPONENTS_WEB_VIEW -> webViewSample()
SamplePaywalls.SampleTemplate.UNRECOGNIZED_TEMPLATE -> SamplePaywalls.unrecognizedTemplate()
}
}
Expand Down Expand Up @@ -128,6 +131,7 @@ object SamplePaywalls {
COMPONENTS_BLESS_GOOGLE_FONT("#11: Components - bless. - Google font"),
TABS_BUTTONS("#12: Tabs - buttons"),
TABS_TOGGLE("#13 Tabs - toggle"),
COMPONENTS_WEB_VIEW("#14: Components - web_view"),
UNRECOGNIZED_TEMPLATE("Default template"),
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package com.revenuecat.paywallstester.paywalls

import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import com.revenuecat.paywallstester.SampleData
import com.revenuecat.purchases.InternalRevenueCatAPI
import com.revenuecat.purchases.paywalls.components.ButtonComponent
import com.revenuecat.purchases.paywalls.components.StackComponent
import com.revenuecat.purchases.paywalls.components.TextComponent
import com.revenuecat.purchases.paywalls.components.WebViewComponent
import com.revenuecat.purchases.paywalls.components.common.Background
import com.revenuecat.purchases.paywalls.components.common.ComponentsConfig
import com.revenuecat.purchases.paywalls.components.common.LocaleId
import com.revenuecat.purchases.paywalls.components.common.LocalizationData
import com.revenuecat.purchases.paywalls.components.common.LocalizationKey
import com.revenuecat.purchases.paywalls.components.common.PaywallComponentsConfig
import com.revenuecat.purchases.paywalls.components.common.PaywallComponentsData
import com.revenuecat.purchases.paywalls.components.properties.ColorInfo
import com.revenuecat.purchases.paywalls.components.properties.ColorScheme
import com.revenuecat.purchases.paywalls.components.properties.Dimension.Vertical
import com.revenuecat.purchases.paywalls.components.properties.Dimension.ZLayer
import com.revenuecat.purchases.paywalls.components.properties.FlexDistribution.END
import com.revenuecat.purchases.paywalls.components.properties.FontWeight
import com.revenuecat.purchases.paywalls.components.properties.HorizontalAlignment.LEADING
import com.revenuecat.purchases.paywalls.components.properties.Padding
import com.revenuecat.purchases.paywalls.components.properties.Shape
import com.revenuecat.purchases.paywalls.components.properties.Size
import com.revenuecat.purchases.paywalls.components.properties.SizeConstraint.Fill
import com.revenuecat.purchases.paywalls.components.properties.SizeConstraint.Fit
import com.revenuecat.purchases.paywalls.components.properties.SizeConstraint.Fixed
import com.revenuecat.purchases.paywalls.components.properties.TwoDimensionalAlignment
import java.net.URL

private const val PLACEHOLDER_WEB_VIEW_URL = "https://example.com"

@OptIn(InternalRevenueCatAPI::class)
@Suppress("LongMethod")
internal fun webViewSample(): SampleData.Components {
val textColor = ColorScheme(
light = ColorInfo.Hex(Color.Black.toArgb()),
dark = ColorInfo.Hex(Color.White.toArgb()),
)
val backgroundColor = ColorScheme(
light = ColorInfo.Hex(Color.White.toArgb()),
dark = ColorInfo.Hex(Color.Black.toArgb()),
)
val accentColor = ColorScheme(
light = ColorInfo.Hex(Color(red = 5, green = 124, blue = 91).toArgb()),
)

return SampleData.Components(
data = PaywallComponentsData(
id = "sample_web_view_paywall_id",
templateName = "template",
assetBaseURL = URL("https://assets.pawwalls.com"),
componentsConfig = ComponentsConfig(
base = PaywallComponentsConfig(
stack = StackComponent(
components = listOf(
TextComponent(
text = LocalizationKey("title"),
color = textColor,
fontWeight = FontWeight.BOLD,
fontSize = 24,
horizontalAlignment = LEADING,
size = Size(width = Fill, height = Fit),
margin = Padding(top = 16.0, bottom = 8.0, leading = 16.0, trailing = 16.0),
),
WebViewComponent(
id = "sample_web_view",
url = PLACEHOLDER_WEB_VIEW_URL,
size = Size(width = Fill, height = Fixed(400u)),
fallback = StackComponent(
components = listOf(
TextComponent(
text = LocalizationKey("fallback"),
color = textColor,
horizontalAlignment = LEADING,
size = Size(width = Fill, height = Fit),
padding = Padding(
top = 16.0,
bottom = 16.0,
leading = 16.0,
trailing = 16.0,
),
),
),
dimension = ZLayer(alignment = TwoDimensionalAlignment.CENTER),
size = Size(width = Fill, height = Fixed(400u)),
),
),
ButtonComponent(
action = ButtonComponent.Action.RestorePurchases,
stack = StackComponent(
components = listOf(
TextComponent(
text = LocalizationKey("cta"),
color = ColorScheme(light = ColorInfo.Hex(Color.White.toArgb())),
fontWeight = FontWeight.BOLD,
),
),
dimension = ZLayer(alignment = TwoDimensionalAlignment.CENTER),
size = Size(width = Fit, height = Fit),
backgroundColor = accentColor,
padding = Padding(
top = 8.0,
bottom = 8.0,
leading = 32.0,
trailing = 32.0,
),
margin = Padding(top = 16.0, bottom = 16.0, leading = 16.0, trailing = 16.0),
shape = Shape.Pill,
),
),
),
dimension = Vertical(alignment = LEADING, distribution = END),
size = Size(width = Fill, height = Fill),
backgroundColor = backgroundColor,
),
background = Background.Color(backgroundColor),
stickyFooter = null,
),
),
componentsLocalizations = mapOf(
LocaleId("en_US") to mapOf(
LocalizationKey("title") to LocalizationData.Text("Web view placeholder"),
LocalizationKey("fallback") to LocalizationData.Text("Could not load the web view."),
LocalizationKey("cta") to LocalizationData.Text("Restore purchases"),
),
),
defaultLocaleIdentifier = LocaleId("en_US"),
),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,19 @@ import com.revenuecat.purchases.Package
import com.revenuecat.purchases.PurchasesError
import com.revenuecat.purchases.PurchasesErrorCode
import com.revenuecat.purchases.ui.revenuecatui.OriginalTemplatePaywallFooter
import com.revenuecat.purchases.ui.revenuecatui.Paywall
import com.revenuecat.purchases.ui.revenuecatui.PaywallDialog
import com.revenuecat.purchases.ui.revenuecatui.PaywallDialogOptions
import com.revenuecat.purchases.ui.revenuecatui.PaywallOptions
import com.revenuecat.purchases.ui.revenuecatui.PaywallWebViewMessageHandler
import com.revenuecat.purchases.ui.revenuecatui.PurchaseLogic
import com.revenuecat.purchases.ui.revenuecatui.PurchaseLogicResult
import com.revenuecat.purchases.ui.revenuecatui.PurchaseLogicWithCallback
import com.revenuecat.purchases.ui.revenuecatui.fonts.CustomFontProvider
import com.revenuecat.purchases.ui.revenuecatui.fonts.FontProvider

private const val TAG = "PaywallTester"

private class TestAppPurchaseLogicSuspend : PurchaseLogic {

companion object {
Expand Down Expand Up @@ -127,6 +131,16 @@ fun PaywallsScreen(
}
}

val webViewMessageHandler = remember {
PaywallWebViewMessageHandler { message, _ ->
Log.d(
TAG,
"web_view message: type=${message.type} componentId=${message.componentId} " +
"error=${message.error}",
)
}
}

LazyColumn(
modifier = modifier.testTag("paywall_screen"),
) {
Expand All @@ -144,6 +158,11 @@ fun PaywallsScreen(
displayPaywallState = DisplayPaywallState.FullScreen(
offering,
purchaseLogic = myAppPurchaseLogic,
webViewMessageHandler = if (template == SamplePaywalls.SampleTemplate.COMPONENTS_WEB_VIEW) {
webViewMessageHandler
} else {
null
},
)
},
emoji = "\uD83D\uDCF1",
Expand Down Expand Up @@ -207,15 +226,32 @@ fun PaywallsScreen(

@Composable
private fun FullScreenDialog(currentState: DisplayPaywallState.FullScreen, onDismiss: () -> Unit) {
PaywallDialog(
PaywallDialogOptions.Builder()
.setDismissRequest(onDismiss)
.setOffering(currentState.offering)
.setFontProvider(currentState.fontProvider)
.setCustomVariables(CustomVariablesHolder.customVariables)
.setCustomPurchaseLogic(currentState.purchaseLogic)
.build(),
)
val paywallOptions = PaywallOptions.Builder(onDismiss)
.setOffering(currentState.offering)
.setFontProvider(currentState.fontProvider)
.setCustomVariables(CustomVariablesHolder.customVariables)
.setPurchaseLogic(currentState.purchaseLogic)
.setWebViewMessageHandler(currentState.webViewMessageHandler)
.build()

if (currentState.webViewMessageHandler != null) {
Dialog(
onDismissRequest = onDismiss,
properties = DialogProperties(usePlatformDefaultWidth = false),
) {
Paywall(paywallOptions)
}
} else {
PaywallDialog(
PaywallDialogOptions.Builder()
.setDismissRequest(onDismiss)
.setOffering(currentState.offering)
.setFontProvider(currentState.fontProvider)
.setCustomVariables(CustomVariablesHolder.customVariables)
.setCustomPurchaseLogic(currentState.purchaseLogic)
.build(),
)
}
}

@Composable
Expand Down Expand Up @@ -251,6 +287,7 @@ private sealed class DisplayPaywallState {
val offering: Offering? = null,
val fontProvider: FontProvider? = null,
var purchaseLogic: PurchaseLogic? = null,
val webViewMessageHandler: PaywallWebViewMessageHandler? = null,
) : DisplayPaywallState()

data class Footer(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ internal class PaywallComponentSerializer : KSerializer<PaywallComponent> {
"tabs" -> jsonDecoder.json.decodeFromJsonElement<TabsComponent>(json)
"video" -> jsonDecoder.json.decodeFromJsonElement<VideoComponent>(json)
"countdown" -> jsonDecoder.json.decodeFromJsonElement<CountdownComponent>(json)
"web_view" -> jsonDecoder.json.decodeFromJsonElement<WebViewComponent>(json)
"fallback_header" -> FallbackHeaderComponent
else -> json["fallback"]
?.let { it as? JsonObject }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.revenuecat.purchases.paywalls.components

import androidx.compose.runtime.Immutable
import com.revenuecat.purchases.InternalRevenueCatAPI
import com.revenuecat.purchases.paywalls.components.properties.Size
import com.revenuecat.purchases.paywalls.components.properties.SizeConstraint
import com.revenuecat.purchases.paywalls.components.properties.SizeConstraint.Fill
import dev.drewhamilton.poko.Poko
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

/**
* A Paywalls V2 component that renders generic hosted web content (a web bundle entrypoint) inside a
* web view.
*
* Only [protocolVersion] `1` is currently supported. When [protocolVersion] is present the SDK
* isolates the web content from external sources (see the content blocking applied in the UI module):
* the bundle must be fully self-contained.
*/
@InternalRevenueCatAPI
@Poko
@Serializable
@SerialName("web_view")
@Immutable
public class WebViewComponent(
@get:JvmSynthetic
public val url: String,
@get:JvmSynthetic
public val id: String? = null,
@get:JvmSynthetic
public val name: String? = null,
@get:JvmSynthetic
public val visible: Boolean? = null,
@SerialName("protocol_version")
@get:JvmSynthetic
public val protocolVersion: Int? = null,
@get:JvmSynthetic
public val size: Size = Size(width = Fill, height = SizeConstraint.Fit),
@get:JvmSynthetic
public val fallback: StackComponent? = null,
) : PaywallComponent
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import com.revenuecat.purchases.paywalls.components.TabsComponent
import com.revenuecat.purchases.paywalls.components.TextComponent
import com.revenuecat.purchases.paywalls.components.TimelineComponent
import com.revenuecat.purchases.paywalls.components.VideoComponent
import com.revenuecat.purchases.paywalls.components.WebViewComponent

/**
* Returns all PaywallComponent that satisfy the predicate.
Expand Down Expand Up @@ -74,6 +75,7 @@ internal fun PaywallComponent.filter(predicate: (PaywallComponent) -> Boolean):
is ImageComponent,
is IconComponent,
is TextComponent,
is WebViewComponent,
-> {
// These don't have child components.
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import com.revenuecat.purchases.paywalls.components.TabsComponent
import com.revenuecat.purchases.paywalls.components.TextComponent
import com.revenuecat.purchases.paywalls.components.TimelineComponent
import com.revenuecat.purchases.paywalls.components.VideoComponent
import com.revenuecat.purchases.paywalls.components.WebViewComponent
import com.revenuecat.purchases.paywalls.components.common.Background
import com.revenuecat.purchases.paywalls.components.common.ComponentOverride
import com.revenuecat.purchases.paywalls.components.common.PaywallComponentsConfig
Expand Down Expand Up @@ -115,6 +116,7 @@ internal class PaywallComponentsImagePreDownloader(
is TabControlToggleComponent,
is TextComponent,
is TimelineComponent,
is WebViewComponent,
-> emptySet()
}
}
Expand Down
Loading