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 V2] Moves validation logic to the Loading phase #2007

Merged
merged 119 commits into from
Dec 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
119 commits
Select commit Hold shift + click to select a range
15ee579
Adds PaywallComponentsData to Offering, and has PaywallViewModel use …
JayShortway Dec 11, 2024
97a6d2a
Renames PaywallDataValidationTest to LegacyPaywallDataValidationTest.
JayShortway Dec 12, 2024
f039302
LoadingPaywall calls toLegacyPaywallState.
JayShortway Dec 12, 2024
9153fc0
OfferingParser parses paywall_components.
JayShortway Dec 12, 2024
0a07fd5
Adds Paywalls V2 support to PaywallsTester.
JayShortway Dec 12, 2024
8616a26
Adds missing circle MaskShape.
JayShortway Dec 12, 2024
bbe72eb
LoadedPaywallComponents will always fill the maximum available size.
JayShortway Dec 12, 2024
27837e2
ImageUrls width and height are optional.
JayShortway Dec 12, 2024
0e60bd3
Merge branch 'main' into pw2-offerings-data
JayShortway Dec 12, 2024
3d6fd64
Merge branch 'pw2-offerings-data' into pw2-tester
JayShortway Dec 12, 2024
da94c38
Reverts Constants.
JayShortway Dec 12, 2024
f849520
Merge branch 'pw2-tester' into pw2-various-fixes
JayShortway Dec 12, 2024
b669421
Fixes lint.
JayShortway Dec 12, 2024
07cfc1c
Merge branch 'pw2-offerings-data' into pw2-tester
JayShortway Dec 12, 2024
442b479
Merge branch 'pw2-tester' into pw2-various-fixes
JayShortway Dec 12, 2024
49e604a
StackComponentView changes background color when the theme changes.
JayShortway Dec 12, 2024
2b5574d
Adds a failing test to StackComponentViewTests.
JayShortway Dec 12, 2024
7271506
Adds 2 previews.
JayShortway Dec 12, 2024
dc58101
Ensures MDParagraph uses the correct fontSize.
JayShortway Dec 12, 2024
4f4bc41
Merge branch 'main' into pw2-offerings-data
JayShortway Dec 12, 2024
c7657da
Merge branch 'pw2-offerings-data' into pw2-tester
JayShortway Dec 12, 2024
4b54f33
Merge branch 'pw2-tester' into pw2-various-fixes
JayShortway Dec 12, 2024
831854a
Merge branch 'pw2-various-fixes' into pw2-fix-fontsize
JayShortway Dec 12, 2024
7bc2786
Adds another failing test to StackComponentViewTests.
JayShortway Dec 12, 2024
38401ed
Removing some redundant parameters.
JayShortway Dec 12, 2024
65affb8
Fixes the border test.
JayShortway Dec 12, 2024
453b5cf
Fixes the shadow test.
JayShortway Dec 12, 2024
4c189ee
Some cleanup.
JayShortway Dec 12, 2024
7952c6a
Merge branch 'pw2-fix-fontsize' into pw2-stack-tests
JayShortway Dec 12, 2024
a342ed8
Merge branch 'main' into pw2-tester
JayShortway Dec 13, 2024
2cc6c97
Merge branch 'pw2-tester' into pw2-various-fixes
JayShortway Dec 13, 2024
7cca77b
Merge branch 'pw2-various-fixes' into pw2-fix-fontsize
JayShortway Dec 13, 2024
c43525e
Merge branch 'pw2-fix-fontsize' into pw2-stack-tests
JayShortway Dec 13, 2024
914d9da
Adds a regression test.
JayShortway Dec 13, 2024
9abcb6a
Excludes Paywalls V1 from new font size behavior.
JayShortway Dec 13, 2024
870c577
Merge branch 'main' into pw2-fix-fontsize
JayShortway Dec 13, 2024
d5c4ae9
Merge branch 'pw2-fix-fontsize' into pw2-stack-tests
JayShortway Dec 13, 2024
43f1ecf
TextComponentStyle no longer needs a Composable context to be created.
JayShortway Dec 13, 2024
412312e
TextComponentStyle just has a single constructor now.
JayShortway Dec 13, 2024
9a6bab4
Merge branch 'main' into pw2-stack-tests
JayShortway Dec 16, 2024
8bcb10d
Moves rememberProcessedText from StyleFactory to TextComponentView.
JayShortway Dec 16, 2024
524ae3b
Removes unused StyleFactory parameters.
JayShortway Dec 16, 2024
7adc5d3
StyleFactory no longer needs a Composable context.
JayShortway Dec 16, 2024
d57b87a
Merge branch 'pw2-stack-tests' into pw2-simplify-textstyle
JayShortway Dec 16, 2024
7ef7d46
Locale and LocalizationDictionary are part of PaywallState now.
JayShortway Dec 16, 2024
ea77f24
Adds LoadedPaywallComponentsLocaleTests.
JayShortway Dec 16, 2024
8237323
Adds isEligibleForIntroOffer to PaywallState.
JayShortway Dec 16, 2024
68f3c38
Removes braces from a Preview annotation to please lint.
JayShortway Dec 16, 2024
f18ea32
Moves PackageContext to PaywallState.
JayShortway Dec 16, 2024
81bb7a6
Renames VariableContextTests to MostExpensivePricePerMonthMicrosTests.
JayShortway Dec 16, 2024
d7a1367
Moves MostExpensivePricePerMonthMicrosTests to the data package.
JayShortway Dec 16, 2024
dfc5024
TextComponentView uses PaywallState.
JayShortway Dec 16, 2024
f69c7b9
MostExpensivePricePerMonthMicrosTests uses FakePaywallState.
JayShortway Dec 16, 2024
2fdc87c
Merge branch 'main' into pw2-more-paywallstate
JayShortway Dec 17, 2024
5544518
Reorders imports in ButtonComponentView.
JayShortway Dec 17, 2024
cb25d3a
Adds TextComponentState.
JayShortway Dec 17, 2024
895f085
Adds PresentedOverrides to TextComponentStyle, and makes use of TextC…
JayShortway Dec 17, 2024
fb75780
Fixes ButtonComponentViewTests.
JayShortway Dec 17, 2024
7a7e944
Merge branch 'pw2-more-paywallstate' into pw2-component-state
JayShortway Dec 17, 2024
d1373ba
Fixes ButtonComponentViewTests once more.
JayShortway Dec 17, 2024
900a58d
Adds TextComponentViewWindowTests.
JayShortway Dec 17, 2024
0fe9f49
Adds windowChangingTest.
JayShortway Dec 17, 2024
77efa21
Adds WithoutActivityRecreationTests to TextComponentViewWindowTests.
JayShortway Dec 17, 2024
ce73645
Updates a comment.
JayShortway Dec 17, 2024
ececc0e
Adds a selected overrides test.
JayShortway Dec 17, 2024
c2bbc4f
Adds an intro offer overrides test.
JayShortway Dec 17, 2024
0d49f83
Fixes LoadedPaywallComponentsLocaleTests.
JayShortway Dec 17, 2024
7023710
Merge branch 'main' into pw2-component-state
JayShortway Dec 18, 2024
f94592a
Adds StackComponentState.
JayShortway Dec 18, 2024
1319e15
Removes unused properties from StyleFactory.
JayShortway Dec 18, 2024
5e02c05
StackComponentView uses StackComponentState.
JayShortway Dec 18, 2024
e19390d
Moves assertSquareBorderColor to assertPixelColorEquals.kt and rename…
JayShortway Dec 18, 2024
063307b
Adds assertApproximatePixelColorPercentage().
JayShortway Dec 18, 2024
f9cae41
Adds StackComponentViewWindowTests.
JayShortway Dec 18, 2024
cc60503
Merge branch 'main' into pw2-component-state
JayShortway Dec 18, 2024
4032112
Merge branch 'pw2-component-state' into pw2-stack-component-state
JayShortway Dec 18, 2024
ad96d64
Fixes StackComponentView.
JayShortway Dec 18, 2024
5cc8e1e
Adds 2 tests to StackComponentViewTests.
JayShortway Dec 18, 2024
cd5984e
Removes unused PackageContext.kt.
JayShortway Dec 18, 2024
b3ee332
Aligns shadow assertions.
JayShortway Dec 18, 2024
036d745
Merge branch 'main' into pw2-stack-component-state
JayShortway Dec 19, 2024
bf27e45
Removes `visible` property from TextComponentStyle and StackComponent…
JayShortway Dec 19, 2024
c96beb4
TextComponentStyle contains all translations for its text. TextCompon…
JayShortway Dec 19, 2024
581168e
Fixes LoadedPaywallComponents.
JayShortway Dec 19, 2024
23afaed
Updates a property name.
JayShortway Dec 19, 2024
5ce1f5d
Merge branch 'main' into pw2-stack-component-state
JayShortway Dec 19, 2024
7b51b52
Merge branch 'pw2-stack-component-state' into pw2-minor-cleanup
JayShortway Dec 19, 2024
1810317
Merge branch 'pw2-minor-cleanup' into pw2-localization-state
JayShortway Dec 19, 2024
575ff12
Adds NonEmptyMap.
JayShortway Dec 19, 2024
7ce9006
Localizations are now a NonEmptyMap.
JayShortway Dec 19, 2024
e6e6a77
Moves the LocalizationDictionary typealias to revenuecatui.
JayShortway Dec 19, 2024
08f8e8d
LocalizationDictionary is a NonEmptyMap now.
JayShortway Dec 19, 2024
eb54346
Adds locale to missing localization errors.
JayShortway Dec 19, 2024
657170a
Merge branch 'pw2-localization-state' into pw2-nonemptymap
JayShortway Dec 19, 2024
a2a031b
Adds a few more Result extensions.
JayShortway Dec 23, 2024
a4d350a
Adds NonEmptySet.
JayShortway Dec 23, 2024
49bf6ab
Adds AllLocalizationsMissing error.
JayShortway Dec 23, 2024
eb7b728
Adds validation of PaywallComponentsData.
JayShortway Dec 23, 2024
bba53e2
Adds PaywallComponentDataValidationTests.
JayShortway Dec 23, 2024
455183c
Merge branch 'main' into pw2-stack-component-state
JayShortway Dec 23, 2024
4f973ec
Merge branch 'pw2-stack-component-state' into pw2-minor-cleanup
JayShortway Dec 23, 2024
78370f5
Merge branch 'pw2-minor-cleanup' into pw2-localization-state
JayShortway Dec 23, 2024
383d721
Merge branch 'pw2-localization-state' into pw2-nonemptymap
JayShortway Dec 23, 2024
b5a1364
Merge branch 'pw2-nonemptymap' into pw2-validate-loading-phase
JayShortway Dec 23, 2024
86eff62
Fixes some compiler errors.
JayShortway Dec 23, 2024
55b8781
Fixes some compiler errors.
JayShortway Dec 23, 2024
1d8b505
Merge branch 'pw2-stack-component-state' into pw2-minor-cleanup
JayShortway Dec 23, 2024
495b996
Merge branch 'pw2-minor-cleanup' into pw2-localization-state
JayShortway Dec 23, 2024
18470a9
Merge branch 'pw2-localization-state' into pw2-nonemptymap
JayShortway Dec 23, 2024
1aed5d9
Merge branch 'pw2-nonemptymap' into pw2-validate-loading-phase
JayShortway Dec 23, 2024
444aec5
Merge branch 'main' into pw2-localization-state
JayShortway Dec 23, 2024
b230d9e
Merge branch 'pw2-localization-state' into pw2-nonemptymap
JayShortway Dec 23, 2024
6066ec6
Merge branch 'pw2-nonemptymap' into pw2-validate-loading-phase
JayShortway Dec 23, 2024
5f703c3
Merge branch 'main' into pw2-nonemptymap
JayShortway Dec 23, 2024
2bb0b1d
Merge branch 'pw2-nonemptymap' into pw2-validate-loading-phase
JayShortway Dec 23, 2024
ab86ede
Fixes tests.
JayShortway Dec 24, 2024
7fb11a9
Merge branch 'pw2-nonemptymap' into pw2-validate-loading-phase
JayShortway Dec 24, 2024
80a2917
Merge branch 'main' into pw2-validate-loading-phase
JayShortway Dec 24, 2024
7755855
Merge branch 'main' into pw2-validate-loading-phase
JayShortway Dec 27, 2024
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

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ import com.revenuecat.purchases.ui.revenuecatui.components.style.ButtonComponent
import com.revenuecat.purchases.ui.revenuecatui.components.style.StackComponentStyle
import com.revenuecat.purchases.ui.revenuecatui.components.style.TextComponentStyle
import com.revenuecat.purchases.ui.revenuecatui.data.PaywallState
import com.revenuecat.purchases.ui.revenuecatui.helpers.getOrThrow
import com.revenuecat.purchases.ui.revenuecatui.helpers.nonEmptyMapOf
import com.revenuecat.purchases.ui.revenuecatui.helpers.toComponentsPaywallState
import com.revenuecat.purchases.ui.revenuecatui.helpers.validate
import kotlinx.coroutines.launch
import java.net.URL

Expand Down Expand Up @@ -148,5 +151,5 @@ private fun previewEmptyState(): PaywallState.Loaded.Components {
paywallComponents = data,
)

return PaywallState.Loaded.Components(offering, data)
return offering.toComponentsPaywallState(data.validate().getOrThrow())
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ import com.revenuecat.purchases.ui.revenuecatui.components.style.StackComponentS
import com.revenuecat.purchases.ui.revenuecatui.components.style.TextComponentStyle
import com.revenuecat.purchases.ui.revenuecatui.data.PaywallState
import com.revenuecat.purchases.ui.revenuecatui.extensions.applyIfNotNull
import com.revenuecat.purchases.ui.revenuecatui.helpers.getOrThrow
import com.revenuecat.purchases.ui.revenuecatui.helpers.nonEmptyMapOf
import com.revenuecat.purchases.ui.revenuecatui.helpers.toComponentsPaywallState
import com.revenuecat.purchases.ui.revenuecatui.helpers.validate
import java.net.URL

@Suppress("LongMethod")
Expand Down Expand Up @@ -336,5 +339,5 @@ private fun previewEmptyState(): PaywallState.Loaded.Components {
paywallComponents = data,
)

return PaywallState.Loaded.Components(offering, data)
return offering.toComponentsPaywallState(data.validate().getOrThrow())
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,11 @@ import com.revenuecat.purchases.ui.revenuecatui.data.PaywallState
import com.revenuecat.purchases.ui.revenuecatui.data.processed.VariableDataProvider
import com.revenuecat.purchases.ui.revenuecatui.data.processed.VariableProcessor
import com.revenuecat.purchases.ui.revenuecatui.extensions.applyIfNotNull
import com.revenuecat.purchases.ui.revenuecatui.helpers.getOrThrow
import com.revenuecat.purchases.ui.revenuecatui.helpers.nonEmptyMapOf
import com.revenuecat.purchases.ui.revenuecatui.helpers.toComponentsPaywallState
import com.revenuecat.purchases.ui.revenuecatui.helpers.toResourceProvider
import com.revenuecat.purchases.ui.revenuecatui.helpers.validate
import java.net.URL

@Composable
Expand Down Expand Up @@ -414,5 +417,5 @@ private fun previewEmptyState(): PaywallState.Loaded.Components {
paywallComponents = data,
)

return PaywallState.Loaded.Components(offering, data)
return offering.toComponentsPaywallState(data.validate().getOrThrow())
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.text.intl.LocaleList
import com.revenuecat.purchases.Offering
import com.revenuecat.purchases.Package
import com.revenuecat.purchases.paywalls.components.common.Background
import com.revenuecat.purchases.paywalls.components.common.LocaleId
import com.revenuecat.purchases.paywalls.components.common.PaywallComponentsData
import com.revenuecat.purchases.ui.revenuecatui.components.ktx.toComposeLocale
import com.revenuecat.purchases.ui.revenuecatui.components.ktx.toLocaleId
import com.revenuecat.purchases.ui.revenuecatui.components.style.ComponentStyle
import com.revenuecat.purchases.ui.revenuecatui.data.processed.ProcessedLocalizedConfiguration
import com.revenuecat.purchases.ui.revenuecatui.data.processed.TemplateConfiguration
import com.revenuecat.purchases.ui.revenuecatui.helpers.Logger
import com.revenuecat.purchases.ui.revenuecatui.helpers.NonEmptySet
import com.revenuecat.purchases.ui.revenuecatui.isFullScreen
import android.os.LocaleList as FrameworkLocaleList

Expand Down Expand Up @@ -58,10 +60,17 @@ internal sealed interface PaywallState {
}
}

@Suppress("LongParameterList")
@Stable
class Components(
val stack: ComponentStyle,
val stickyFooter: ComponentStyle?,
val background: Background,
override val offering: Offering,
val data: PaywallComponentsData,
/**
* All locales that this paywall supports, with `locales.head` being the default one.
*/
private val locales: NonEmptySet<LocaleId>,
initialLocaleList: LocaleList = LocaleList.current,
initialIsEligibleForIntroOffer: Boolean = false,
initialSelectedPackage: Package? = null,
Expand Down Expand Up @@ -90,9 +99,9 @@ internal sealed interface PaywallState {

private fun LocaleList.toLocaleId(): LocaleId =
// Configured locales take precedence over the default one.
map { it.toLocaleId() }.plus(data.defaultLocaleIdentifier)
map { it.toLocaleId() }.plus(locales.head)
// Find the first locale we have a LocalizationDictionary for.
.first { id -> data.componentsLocalizations.containsKey(id) }
.first { id -> locales.contains(id) }

private fun List<Package>.mostExpensivePricePerMonthMicros(): Long? =
asSequence()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import com.revenuecat.purchases.ui.revenuecatui.helpers.PaywallValidationResult
import com.revenuecat.purchases.ui.revenuecatui.helpers.ResourceProvider
import com.revenuecat.purchases.ui.revenuecatui.helpers.toComponentsPaywallState
import com.revenuecat.purchases.ui.revenuecatui.helpers.toLegacyPaywallState
import com.revenuecat.purchases.ui.revenuecatui.helpers.validatedLegacyPaywall
import com.revenuecat.purchases.ui.revenuecatui.helpers.validatedPaywall
import com.revenuecat.purchases.ui.revenuecatui.strings.PaywallValidationErrorStrings
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
Expand Down Expand Up @@ -374,13 +374,12 @@ internal class PaywallViewModelImpl(
return PaywallState.Error("No packages available")
}

val validationResult = offering.paywallComponents
// TODO Actually validate the PaywallComponentsData
?.let { PaywallValidationResult.Components(displayablePaywall = it) }
?: offering.validatedLegacyPaywall(colorScheme, resourceProvider)
val validationResult = offering.validatedPaywall(colorScheme, resourceProvider)

validationResult.error?.let { validationError ->
Logger.w(validationError.associatedErrorString(offering))
validationResult.errors?.let { validationErrors ->
validationErrors.forEach { error ->
Logger.w(error.associatedErrorString(offering))
}
Logger.w(PaywallValidationErrorStrings.DISPLAYING_DEFAULT)
}

Expand All @@ -398,7 +397,7 @@ internal class PaywallViewModelImpl(
storefrontCountryCode = storefrontCountryCode,
)
is PaywallValidationResult.Components -> offering.toComponentsPaywallState(
validatedPaywallData = validationResult.displayablePaywall,
validationResult = validationResult,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ internal sealed class PaywallValidationError : Throwable() {
}
is MissingStringLocalization -> message
is MissingImageLocalization -> message
is AllLocalizationsMissing -> message
}
}

Expand All @@ -53,4 +54,10 @@ internal sealed class PaywallValidationError : Throwable() {
PaywallValidationErrorStrings.MISSING_IMAGE_LOCALIZATION_WITH_LOCALE.format(key.value, locale.value)
} ?: PaywallValidationErrorStrings.MISSING_IMAGE_LOCALIZATION.format(key.value)
}
data class AllLocalizationsMissing(
val locale: LocaleId,
) : PaywallValidationError() {
override val message: String =
PaywallValidationErrorStrings.ALL_LOCALIZATIONS_MISSING_FOR_LOCALE.format(locale.value)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ internal class NonEmptyMap<K, out V> private constructor(

constructor(entry: Pair<K, V>, tail: Map<K, V>) : this(entry = mapOf(entry).entries.first(), all = tail + entry)

override val keys: NonEmptySet<K> = NonEmptySet(entry.key, all.keys)

@JvmSynthetic
fun toMap(): Map<K, V> = all

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
@file:JvmSynthetic

package com.revenuecat.purchases.ui.revenuecatui.helpers

/**
* A Set that is guaranteed to have at least 1 element. Inspired by Arrow. Use [nonEmptySetOf] or
* [toNonEmptySetOrNull] to construct.
*/
internal class NonEmptySet<out A> private constructor(
@get:JvmSynthetic
val head: A,
private val all: Set<A>,
) : Set<A> by all {

constructor(head: A, rest: Iterable<A>) : this(head, all = rest.toSet() + head)

@JvmSynthetic
fun toSet(): Set<A> = all

@JvmSynthetic
override fun isEmpty(): Boolean = false

override fun equals(other: Any?): Boolean = when (other) {
is NonEmptySet<*> -> this.all == other.all
else -> this.all == other
}

override fun hashCode(): Int = all.hashCode()

override fun toString(): String =
"NonEmptySet(${all.joinToString()})"
}

@JvmSynthetic
internal fun <A> nonEmptySetOf(head: A, vararg t: A): NonEmptySet<A> =
NonEmptySet(head, t.asIterable())

@JvmSynthetic
internal fun <A> Iterable<A>.toNonEmptySetOrNull(): NonEmptySet<A>? {
val iterator = iterator()
if (!iterator.hasNext()) return null
return NonEmptySet(iterator.next(), Iterable { iterator })
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
@file:Suppress("TooManyFunctions")

package com.revenuecat.purchases.ui.revenuecatui.helpers

import androidx.compose.material3.ColorScheme
import com.revenuecat.purchases.Offering
import com.revenuecat.purchases.paywalls.PaywallData
import com.revenuecat.purchases.paywalls.components.common.LocalizationData
import com.revenuecat.purchases.paywalls.components.common.LocalizationKey
import com.revenuecat.purchases.paywalls.components.common.PaywallComponentsData
import com.revenuecat.purchases.ui.revenuecatui.PaywallMode
import com.revenuecat.purchases.ui.revenuecatui.components.PaywallAction
import com.revenuecat.purchases.ui.revenuecatui.components.style.StyleFactory
import com.revenuecat.purchases.ui.revenuecatui.composables.PaywallIconName
import com.revenuecat.purchases.ui.revenuecatui.data.PaywallState
import com.revenuecat.purchases.ui.revenuecatui.data.processed.PackageConfigurationType
Expand All @@ -17,27 +23,30 @@ import com.revenuecat.purchases.ui.revenuecatui.extensions.createDefault
import com.revenuecat.purchases.ui.revenuecatui.extensions.createDefaultForIdentifiers
import com.revenuecat.purchases.ui.revenuecatui.extensions.defaultTemplate
import kotlin.Result
import com.revenuecat.purchases.ui.revenuecatui.helpers.Result as RcResult

@Suppress("ReturnCount")
internal fun Offering.validatedLegacyPaywall(
internal fun Offering.validatedPaywall(
currentColorScheme: ColorScheme,
resourceProvider: ResourceProvider,
): PaywallValidationResult.Legacy {
val paywallData = this.paywall
?: return PaywallValidationResult.Legacy(
PaywallData.createDefault(
availablePackages,
currentColorScheme,
resourceProvider,
),
PaywallData.defaultTemplate,
PaywallValidationError.MissingPaywall,
)
): PaywallValidationResult =
paywallComponents?.validate()?.let { result ->
// We need to either unwrap the success value, or wrap the errors in a fallback Paywall.
when (result) {
is RcResult.Success -> result.value
is RcResult.Error -> fallbackPaywall(currentColorScheme, resourceProvider, errors = result.value)
}
} ?: paywall?.validate(currentColorScheme, resourceProvider)
?: fallbackPaywall(currentColorScheme, resourceProvider, error = PaywallValidationError.MissingPaywall)

val template = paywallData.validate().getOrElse {
internal fun PaywallData.validate(
currentColorScheme: ColorScheme,
resourceProvider: ResourceProvider,
): PaywallValidationResult.Legacy {
val template = validate().getOrElse {
return PaywallValidationResult.Legacy(
PaywallData.createDefaultForIdentifiers(
paywallData.config.packageIds,
config.packageIds,
currentColorScheme,
resourceProvider,
),
Expand All @@ -46,11 +55,72 @@ internal fun Offering.validatedLegacyPaywall(
)
}
return PaywallValidationResult.Legacy(
paywallData,
this,
template,
)
}

private fun Offering.fallbackPaywall(
currentColorScheme: ColorScheme,
resourceProvider: ResourceProvider,
error: PaywallValidationError,
): PaywallValidationResult.Legacy =
fallbackPaywall(
currentColorScheme = currentColorScheme,
resourceProvider = resourceProvider,
errors = nonEmptyListOf(error),
)

private fun Offering.fallbackPaywall(
currentColorScheme: ColorScheme,
resourceProvider: ResourceProvider,
errors: NonEmptyList<PaywallValidationError>,
): PaywallValidationResult.Legacy =
PaywallValidationResult.Legacy(
PaywallData.createDefault(
availablePackages,
currentColorScheme,
resourceProvider,
),
PaywallData.defaultTemplate,
errors,
)

@Suppress("MaxLineLength")
internal fun PaywallComponentsData.validate(): RcResult<PaywallValidationResult.Components, NonEmptyList<PaywallValidationError>> =
defaultLocalization
// Check that the default localization is present in the localizations map.
.errorIfNull(PaywallValidationError.AllLocalizationsMissing(defaultLocaleIdentifier))
.mapError { nonEmptyListOf(it) }
.map { defaultLocalization ->
// Build a NonEmptyMap, ensuring that we always have the default localization as fallback.
nonEmptyMapOf(defaultLocaleIdentifier to defaultLocalization, componentsLocalizations)
}
.flatMap { localizations ->
// We need to turn our NonEmptyMap<LocaleId, Map> into NonEmptyMap<LocaleId, NonEmptyMap>.
localizations.mapValues { (locale, map) ->
map.toNonEmptyMapOrNull()
.errorIfNull(PaywallValidationError.AllLocalizationsMissing(locale))
.mapError { nonEmptyListOf(it) }
}.mapValuesOrAccumulate { it }
}.flatMap { localizations ->
// Use the StyleFactory to recursively create and validate all ComponentStyles.
val styleFactory = StyleFactory(localizations)
val actionHandler: suspend (PaywallAction) -> Unit = { /* TODO Move the action handler to the UI layer. */ }
val config = componentsConfig.base
zipOrAccumulate(
styleFactory.create(config.stack, actionHandler),
config.stickyFooter?.let { styleFactory.create(it, actionHandler) }.orSuccessfullyNull(),
) { stack, stickyFooter ->
PaywallValidationResult.Components(
stack = stack,
stickyFooter = stickyFooter,
background = config.background,
locales = localizations.keys,
)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This chain is not complex, but it's a bit hard to read, it takes some time to fully understand what's going on. I think extracting into some private functions could help

Copy link
Member Author

Choose a reason for hiding this comment

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

Yea I agree. And we'll have to spend that time every time we need to read this code. Private functions is a good idea. I was also thinking of adding some early returns. I'll have a look.

Copy link
Member Author

Choose a reason for hiding this comment

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

Decided to do this separately to minimize the conflicts, as I ended up making some changes to these lines in follow-up PRs. Have a look here: #2017


@Suppress("ReturnCount")
private fun PaywallData.validate(): Result<PaywallTemplate> {
val template = validateTemplate() ?: return Result.failure(PaywallValidationError.InvalidTemplate(templateName))
Expand Down Expand Up @@ -162,10 +232,15 @@ internal fun Offering.toLegacyPaywallState(
)
}

internal fun Offering.toComponentsPaywallState(validatedPaywallData: PaywallComponentsData): PaywallState =
internal fun Offering.toComponentsPaywallState(
validationResult: PaywallValidationResult.Components,
): PaywallState.Loaded.Components =
PaywallState.Loaded.Components(
stack = validationResult.stack,
stickyFooter = validationResult.stickyFooter,
background = validationResult.background,
offering = this,
data = validatedPaywallData,
locales = validationResult.locales,
)

/**
Expand Down Expand Up @@ -222,3 +297,6 @@ private fun PaywallData.LocalizedConfiguration.validateIcons(): PaywallValidatio
private fun PaywallData.validateTemplate(): PaywallTemplate? {
return PaywallTemplate.fromId(templateName)
}

private val PaywallComponentsData.defaultLocalization: Map<LocalizationKey, LocalizationData>?
get() = componentsLocalizations[defaultLocaleIdentifier]
Loading