Skip to content

Commit

Permalink
[Paywalls V2] Parses UiConfig (#2068)
Browse files Browse the repository at this point in the history
  • Loading branch information
JayShortway authored Jan 22, 2025
1 parent 12569a6 commit 208bcd8
Show file tree
Hide file tree
Showing 27 changed files with 945 additions and 61 deletions.
3 changes: 3 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,9 @@ jobs:
- run:
name: Verify purchases-ui target SDK compatibility (currently 34)
command: ./gradlew :test-apps:testpurchasesuiandroidcompatibility:assembleDebug
- run:
name: Enable the Paywall Components build flag
command: echo 'revenuecat.flag.paywallComponents=true' >> local.properties
- run:
name: Run Tests
command: ./gradlew lint test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.revenuecat.purchases.InternalRevenueCatAPI
import com.revenuecat.purchases.Offering
import com.revenuecat.purchases.Package
import com.revenuecat.purchases.PackageType
import com.revenuecat.purchases.UiConfig
import com.revenuecat.purchases.models.Period
import com.revenuecat.purchases.models.Price
import com.revenuecat.purchases.models.TestStoreProduct
Expand Down Expand Up @@ -46,7 +47,12 @@ class SamplePaywallsLoader {
emptyMap(),
SamplePaywalls.packages,
paywall = (paywallForTemplate(template) as? SampleData.Legacy)?.data,
paywallComponents = (paywallForTemplate(template) as? SampleData.Components)?.data,
paywallComponents = (paywallForTemplate(template) as? SampleData.Components)?.data?.let { data ->
Offering.PaywallComponents(
uiConfig = UiConfig(),
data = data,
)
},
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ private fun OfferingsListScreen(
offering.paywall?.also {
Text("Template ${it.templateName}")
} ?: offering.paywallComponents?.also {
Text("Components ${it.templateName}")
Text("Components ${it.data.templateName}")
} ?: Text("No paywall")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package com.revenuecat.purchases

import com.revenuecat.purchases.paywalls.PaywallData
import com.revenuecat.purchases.paywalls.components.common.PaywallComponentsData
import dev.drewhamilton.poko.Poko

/**
* An offering is a collection of [Package] available for the user to purchase.
Expand All @@ -27,8 +28,14 @@ constructor(
val availablePackages: List<Package>,
val paywall: PaywallData? = null,
@InternalRevenueCatAPI
val paywallComponents: PaywallComponentsData? = null,
val paywallComponents: PaywallComponents? = null,
) {
@InternalRevenueCatAPI
@Poko
class PaywallComponents(
val uiConfig: UiConfig,
val data: PaywallComponentsData,
)

/**
* Lifetime package type configured in the RevenueCat dashboard, if available.
Expand Down
81 changes: 81 additions & 0 deletions purchases/src/main/kotlin/com/revenuecat/purchases/UiConfig.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package com.revenuecat.purchases

import com.revenuecat.purchases.paywalls.components.common.LocaleId
import com.revenuecat.purchases.paywalls.components.common.LocalizedVariableLocalizationKeyMapSerializer
import com.revenuecat.purchases.paywalls.components.common.VariableLocalizationKey
import com.revenuecat.purchases.paywalls.components.properties.ColorScheme
import dev.drewhamilton.poko.Poko
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@InternalRevenueCatAPI
@Serializable
@JvmInline
value class ColorAlias(@get:JvmSynthetic val value: String)

@InternalRevenueCatAPI
@Serializable
@JvmInline
value class FontAlias(@get:JvmSynthetic val value: String)

@InternalRevenueCatAPI
@Poko
@Serializable
class UiConfig(
@get:JvmSynthetic
val app: AppConfig = AppConfig(),
@Serializable(with = LocalizedVariableLocalizationKeyMapSerializer::class)
@get:JvmSynthetic
val localizations: Map<LocaleId, Map<VariableLocalizationKey, String>> = emptyMap(),
@SerialName("variable_config")
@get:JvmSynthetic
val variableConfig: VariableConfig = VariableConfig(),
) {

@InternalRevenueCatAPI
@Poko
@Serializable
class AppConfig(
@get:JvmSynthetic
val colors: Map<ColorAlias, ColorScheme> = emptyMap(),
@get:JvmSynthetic
val fonts: Map<FontAlias, FontsConfig> = emptyMap(),
) {
@InternalRevenueCatAPI
@Poko
@Serializable
class FontsConfig(
@get:JvmSynthetic
val android: FontInfo,
) {

@InternalRevenueCatAPI
@Serializable
sealed interface FontInfo {
@InternalRevenueCatAPI
@Poko
@Serializable
@SerialName("name")
class Name(@get:JvmSynthetic val value: String) : FontInfo

@InternalRevenueCatAPI
@Poko
@Serializable
@SerialName("google_fonts")
class GoogleFonts(@get:JvmSynthetic val value: String) : FontInfo
}
}
}

@InternalRevenueCatAPI
@Poko
@Serializable
class VariableConfig(
@SerialName("variable_compatibility_map")
@get:JvmSynthetic
val variableCompatibilityMap: Map<String, String> = emptyMap(),
@SerialName("function_compatibility_map")
@get:JvmSynthetic
val functionCompatibilityMap: Map<String, String> = emptyMap(),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.revenuecat.purchases.Offerings
import com.revenuecat.purchases.Package
import com.revenuecat.purchases.PackageType
import com.revenuecat.purchases.PresentedOfferingContext
import com.revenuecat.purchases.UiConfig
import com.revenuecat.purchases.api.BuildConfig
import com.revenuecat.purchases.models.StoreProduct
import com.revenuecat.purchases.paywalls.PaywallData
Expand Down Expand Up @@ -38,16 +39,29 @@ internal abstract class OfferingParser {
/**
* Note: this may return an empty Offerings.
*/
@OptIn(InternalRevenueCatAPI::class)
fun createOfferings(offeringsJson: JSONObject, productsById: Map<String, List<StoreProduct>>): Offerings {
log(LogIntent.DEBUG, OfferingStrings.BUILDING_OFFERINGS.format(productsById.size))

val jsonOfferings = offeringsJson.getJSONArray("offerings")
val currentOfferingID = offeringsJson.getString("current_offering_id")

val uiConfigJson = offeringsJson.optJSONObject("ui_config")

@Suppress("TooGenericExceptionCaught")
val uiConfig: UiConfig? = uiConfigJson?.let {
try {
json.decodeFromString<UiConfig>(it.toString())
} catch (e: Throwable) {
errorLog("Error deserializing ui_config", e)
null
}
}

val offerings = mutableMapOf<String, Offering>()
for (i in 0 until jsonOfferings.length()) {
val offeringJson = jsonOfferings.getJSONObject(i)
createOffering(offeringJson, productsById)?.let {
createOffering(offeringJson, productsById, uiConfig)?.let {
offerings[it.identifier] = it

if (it.availablePackages.isEmpty()) {
Expand Down Expand Up @@ -94,7 +108,11 @@ internal abstract class OfferingParser {

@OptIn(InternalRevenueCatAPI::class)
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun createOffering(offeringJson: JSONObject, productsById: Map<String, List<StoreProduct>>): Offering? {
fun createOffering(
offeringJson: JSONObject,
productsById: Map<String, List<StoreProduct>>,
uiConfig: UiConfig?,
): Offering? {
val offeringIdentifier = offeringJson.getString("identifier")
val metadata = offeringJson.optJSONObject("metadata")?.toMap<Any>(deep = true) ?: emptyMap()
val jsonPackages = offeringJson.getJSONArray("packages")
Expand Down Expand Up @@ -135,14 +153,20 @@ internal abstract class OfferingParser {
null
}

val paywallComponents = if (paywallComponentsData != null && uiConfig != null) {
Offering.PaywallComponents(uiConfig, paywallComponentsData)
} else {
null
}

return if (availablePackages.isNotEmpty()) {
Offering(
offeringIdentifier,
offeringJson.getString("description"),
metadata,
availablePackages,
paywallData,
paywallComponentsData,
paywallComponents,
)
} else {
null
Expand Down
Loading

0 comments on commit 208bcd8

Please sign in to comment.