diff --git a/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/LoadedPaywallComponents.kt b/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/LoadedPaywallComponents.kt index 5c7bae5290..f54ea26894 100644 --- a/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/LoadedPaywallComponents.kt +++ b/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/LoadedPaywallComponents.kt @@ -12,7 +12,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview import androidx.window.core.layout.WindowSizeClass import com.revenuecat.purchases.Offering @@ -47,12 +46,9 @@ import com.revenuecat.purchases.paywalls.components.properties.TwoDimensionalAli import com.revenuecat.purchases.paywalls.components.properties.TwoDimensionalAlignment.BOTTOM import com.revenuecat.purchases.ui.revenuecatui.components.modifier.background import com.revenuecat.purchases.ui.revenuecatui.components.properties.toBackgroundStyle -import com.revenuecat.purchases.ui.revenuecatui.components.state.PackageContext import com.revenuecat.purchases.ui.revenuecatui.components.style.StyleFactory import com.revenuecat.purchases.ui.revenuecatui.data.PaywallState -import com.revenuecat.purchases.ui.revenuecatui.data.processed.VariableDataProvider import com.revenuecat.purchases.ui.revenuecatui.helpers.getOrThrow -import com.revenuecat.purchases.ui.revenuecatui.helpers.toResourceProvider import java.net.URL import java.util.Locale @@ -61,7 +57,6 @@ internal fun LoadedPaywallComponents( state: PaywallState.Loaded.Components, modifier: Modifier = Modifier, ) { - val context = LocalContext.current val configuration = LocalConfiguration.current // Configured locales take precedence over the default one. val preferredIds = configuration.locales.mapToLocaleIds() + state.data.defaultLocaleIdentifier @@ -78,16 +73,7 @@ internal fun LoadedPaywallComponents( windowSize = windowSize, isEligibleForIntroOffer = false, componentState = ComponentViewState.DEFAULT, - packageContext = PackageContext( - initialSelectedPackage = null, - initialVariableContext = PackageContext.VariableContext( - packages = state.offering.availablePackages, - showZeroDecimalPlacePrices = true, - ), - ), localizationDictionary = localizationDictionary, - locale = locale, - variables = VariableDataProvider(context.toResourceProvider()), ) } diff --git a/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/SystemFontFamily.kt b/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/SystemFontFamily.kt new file mode 100644 index 0000000000..b7a070d573 --- /dev/null +++ b/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/SystemFontFamily.kt @@ -0,0 +1,21 @@ +@file:JvmSynthetic + +package com.revenuecat.purchases.ui.revenuecatui.components + +import androidx.compose.ui.text.font.DeviceFontFamilyName +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight + +/** + * Get an Android system-installed font by [familyName]. + */ +@Suppress("FunctionName") +@JvmSynthetic +internal fun SystemFontFamily(familyName: String, weight: FontWeight?): FontFamily = + FontFamily( + Font( + familyName = DeviceFontFamilyName(familyName), + weight = weight ?: FontWeight.Normal, + ), + ) diff --git a/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/button/ButtonComponentView.kt b/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/button/ButtonComponentView.kt index f7fdbcdc92..6dcf526754 100644 --- a/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/button/ButtonComponentView.kt +++ b/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/button/ButtonComponentView.kt @@ -24,6 +24,10 @@ import com.revenuecat.purchases.paywalls.components.properties.Shadow import com.revenuecat.purchases.paywalls.components.properties.Size import com.revenuecat.purchases.paywalls.components.properties.SizeConstraint.Fit import com.revenuecat.purchases.ui.revenuecatui.components.PaywallAction +import com.revenuecat.purchases.ui.revenuecatui.components.ktx.toAlignment +import com.revenuecat.purchases.ui.revenuecatui.components.ktx.toFontWeight +import com.revenuecat.purchases.ui.revenuecatui.components.ktx.toPaddingValues +import com.revenuecat.purchases.ui.revenuecatui.components.ktx.toTextAlign import com.revenuecat.purchases.ui.revenuecatui.components.stack.StackComponentView import com.revenuecat.purchases.ui.revenuecatui.components.style.ButtonComponentStyle import com.revenuecat.purchases.ui.revenuecatui.components.style.StackComponentStyle @@ -58,16 +62,16 @@ private fun previewButtonComponentStyle( light = ColorInfo.Hex(Color.Black.toArgb()), ), fontSize = FontSize.BODY_M, - fontWeight = FontWeight.REGULAR, + fontWeight = FontWeight.REGULAR.toFontWeight(), fontFamily = null, - textAlign = HorizontalAlignment.CENTER, - horizontalAlignment = HorizontalAlignment.CENTER, + textAlign = HorizontalAlignment.CENTER.toTextAlign(), + horizontalAlignment = HorizontalAlignment.CENTER.toAlignment(), backgroundColor = ColorScheme( light = ColorInfo.Hex(Color.Yellow.toArgb()), ), size = Size(width = Fit, height = Fit), - padding = Padding(top = 8.0, bottom = 8.0, leading = 8.0, trailing = 8.0), - margin = Padding(top = 0.0, bottom = 24.0, leading = 0.0, trailing = 24.0), + padding = Padding(top = 8.0, bottom = 8.0, leading = 8.0, trailing = 8.0).toPaddingValues(), + margin = Padding(top = 0.0, bottom = 24.0, leading = 0.0, trailing = 24.0).toPaddingValues(), ), ), dimension = Dimension.Vertical(alignment = HorizontalAlignment.CENTER, distribution = START), diff --git a/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/stack/StackComponentView.kt b/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/stack/StackComponentView.kt index 5433589c15..bdb906f386 100644 --- a/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/stack/StackComponentView.kt +++ b/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/stack/StackComponentView.kt @@ -33,8 +33,11 @@ import com.revenuecat.purchases.paywalls.components.properties.TwoDimensionalAli import com.revenuecat.purchases.paywalls.components.properties.VerticalAlignment import com.revenuecat.purchases.ui.revenuecatui.components.ComponentView import com.revenuecat.purchases.ui.revenuecatui.components.ktx.toAlignment +import com.revenuecat.purchases.ui.revenuecatui.components.ktx.toFontWeight import com.revenuecat.purchases.ui.revenuecatui.components.ktx.toHorizontalAlignmentOrNull import com.revenuecat.purchases.ui.revenuecatui.components.ktx.toHorizontalArrangement +import com.revenuecat.purchases.ui.revenuecatui.components.ktx.toPaddingValues +import com.revenuecat.purchases.ui.revenuecatui.components.ktx.toTextAlign import com.revenuecat.purchases.ui.revenuecatui.components.ktx.toVerticalAlignmentOrNull import com.revenuecat.purchases.ui.revenuecatui.components.ktx.toVerticalArrangement import com.revenuecat.purchases.ui.revenuecatui.components.modifier.background @@ -195,17 +198,17 @@ private fun StackComponentView_Preview_ZLayer() { light = ColorInfo.Hex(Color.Black.toArgb()), ), fontSize = FontSize.BODY_M, - fontWeight = FontWeight.REGULAR, + fontWeight = FontWeight.REGULAR.toFontWeight(), fontFamily = null, - textAlign = HorizontalAlignment.CENTER, - horizontalAlignment = HorizontalAlignment.CENTER, + textAlign = HorizontalAlignment.CENTER.toTextAlign(), + horizontalAlignment = HorizontalAlignment.CENTER.toAlignment(), backgroundColor = ColorScheme( light = ColorInfo.Hex(Color.Yellow.toArgb()), dark = ColorInfo.Hex(Color.Red.toArgb()), ), size = Size(width = Fit, height = Fit), - padding = Padding(top = 8.0, bottom = 8.0, leading = 8.0, trailing = 8.0), - margin = Padding(top = 0.0, bottom = 24.0, leading = 0.0, trailing = 24.0), + padding = Padding(top = 8.0, bottom = 8.0, leading = 8.0, trailing = 8.0).toPaddingValues(), + margin = Padding(top = 0.0, bottom = 24.0, leading = 0.0, trailing = 24.0).toPaddingValues(), ), TextComponentStyle( visible = true, @@ -214,16 +217,16 @@ private fun StackComponentView_Preview_ZLayer() { light = ColorInfo.Hex(Color.Black.toArgb()), ), fontSize = FontSize.BODY_M, - fontWeight = FontWeight.REGULAR, + fontWeight = FontWeight.REGULAR.toFontWeight(), fontFamily = null, - textAlign = HorizontalAlignment.CENTER, - horizontalAlignment = HorizontalAlignment.CENTER, + textAlign = HorizontalAlignment.CENTER.toTextAlign(), + horizontalAlignment = HorizontalAlignment.CENTER.toAlignment(), backgroundColor = ColorScheme( light = ColorInfo.Hex(Color.Blue.toArgb()), ), size = Size(width = Fit, height = Fit), - padding = Padding(top = 8.0, bottom = 8.0, leading = 8.0, trailing = 8.0), - margin = Padding(top = 0.0, bottom = 0.0, leading = 0.0, trailing = 0.0), + padding = Padding(top = 8.0, bottom = 8.0, leading = 8.0, trailing = 8.0).toPaddingValues(), + margin = Padding(top = 0.0, bottom = 0.0, leading = 0.0, trailing = 0.0).toPaddingValues(), ), ), dimension = Dimension.ZLayer(alignment = TwoDimensionalAlignment.BOTTOM_TRAILING), @@ -257,16 +260,16 @@ private fun previewChildren() = listOf( light = ColorInfo.Hex(Color.Black.toArgb()), ), fontSize = FontSize.BODY_M, - fontWeight = FontWeight.REGULAR, + fontWeight = FontWeight.REGULAR.toFontWeight(), fontFamily = null, - textAlign = HorizontalAlignment.CENTER, - horizontalAlignment = HorizontalAlignment.CENTER, + textAlign = HorizontalAlignment.CENTER.toTextAlign(), + horizontalAlignment = HorizontalAlignment.CENTER.toAlignment(), backgroundColor = ColorScheme( light = ColorInfo.Hex(Color.Blue.toArgb()), ), size = Size(width = Fit, height = Fit), - padding = Padding(top = 8.0, bottom = 8.0, leading = 8.0, trailing = 8.0), - margin = Padding(top = 0.0, bottom = 0.0, leading = 0.0, trailing = 0.0), + padding = Padding(top = 8.0, bottom = 8.0, leading = 8.0, trailing = 8.0).toPaddingValues(), + margin = Padding(top = 0.0, bottom = 0.0, leading = 0.0, trailing = 0.0).toPaddingValues(), ), TextComponentStyle( visible = true, @@ -275,15 +278,15 @@ private fun previewChildren() = listOf( light = ColorInfo.Hex(Color.Black.toArgb()), ), fontSize = FontSize.BODY_M, - fontWeight = FontWeight.REGULAR, + fontWeight = FontWeight.REGULAR.toFontWeight(), fontFamily = null, - textAlign = HorizontalAlignment.CENTER, - horizontalAlignment = HorizontalAlignment.CENTER, + textAlign = HorizontalAlignment.CENTER.toTextAlign(), + horizontalAlignment = HorizontalAlignment.CENTER.toAlignment(), backgroundColor = ColorScheme( light = ColorInfo.Hex(Color.Blue.toArgb()), ), size = Size(width = Fit, height = Fit), - padding = Padding(top = 8.0, bottom = 8.0, leading = 8.0, trailing = 8.0), - margin = Padding(top = 0.0, bottom = 0.0, leading = 0.0, trailing = 0.0), + padding = Padding(top = 8.0, bottom = 8.0, leading = 8.0, trailing = 8.0).toPaddingValues(), + margin = Padding(top = 0.0, bottom = 0.0, leading = 0.0, trailing = 0.0).toPaddingValues(), ), ) diff --git a/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/style/StyleFactory.kt b/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/style/StyleFactory.kt index faee8134aa..fc9629bfe6 100644 --- a/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/style/StyleFactory.kt +++ b/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/style/StyleFactory.kt @@ -1,9 +1,5 @@ package com.revenuecat.purchases.ui.revenuecatui.components.style -import androidx.compose.runtime.Composable -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.unit.dp import com.revenuecat.purchases.paywalls.components.ButtonComponent @@ -19,14 +15,15 @@ import com.revenuecat.purchases.ui.revenuecatui.components.ComponentViewState import com.revenuecat.purchases.ui.revenuecatui.components.LocalizedTextPartial import com.revenuecat.purchases.ui.revenuecatui.components.PresentedStackPartial import com.revenuecat.purchases.ui.revenuecatui.components.ScreenCondition +import com.revenuecat.purchases.ui.revenuecatui.components.SystemFontFamily import com.revenuecat.purchases.ui.revenuecatui.components.buildPresentedPartial import com.revenuecat.purchases.ui.revenuecatui.components.ktx.string +import com.revenuecat.purchases.ui.revenuecatui.components.ktx.toAlignment +import com.revenuecat.purchases.ui.revenuecatui.components.ktx.toFontWeight import com.revenuecat.purchases.ui.revenuecatui.components.ktx.toPaddingValues import com.revenuecat.purchases.ui.revenuecatui.components.ktx.toShape -import com.revenuecat.purchases.ui.revenuecatui.components.state.PackageContext +import com.revenuecat.purchases.ui.revenuecatui.components.ktx.toTextAlign import com.revenuecat.purchases.ui.revenuecatui.components.toPresentedOverrides -import com.revenuecat.purchases.ui.revenuecatui.data.processed.VariableDataProvider -import com.revenuecat.purchases.ui.revenuecatui.data.processed.VariableProcessor import com.revenuecat.purchases.ui.revenuecatui.errors.PaywallValidationError import com.revenuecat.purchases.ui.revenuecatui.helpers.NonEmptyList import com.revenuecat.purchases.ui.revenuecatui.helpers.Result @@ -36,17 +33,12 @@ import com.revenuecat.purchases.ui.revenuecatui.helpers.mapOrAccumulate import com.revenuecat.purchases.ui.revenuecatui.helpers.nonEmptyListOf import com.revenuecat.purchases.ui.revenuecatui.helpers.orSuccessfullyNull import com.revenuecat.purchases.ui.revenuecatui.helpers.zipOrAccumulate -import java.util.Locale -@Suppress("LongParameterList") internal class StyleFactory( private val windowSize: ScreenCondition, private val isEligibleForIntroOffer: Boolean, private val componentState: ComponentViewState, - private val packageContext: PackageContext, private val localizationDictionary: LocalizationDictionary, - private val locale: Locale, - private val variables: VariableDataProvider, ) { private companion object { @@ -54,7 +46,6 @@ internal class StyleFactory( private val DEFAULT_SHAPE = RectangleShape } - @Composable fun create(component: PaywallComponent): Result> = when (component) { is ButtonComponent -> TODO("ButtonComponentStyle is not yet implemented.") @@ -66,7 +57,6 @@ internal class StyleFactory( is TextComponent -> createTextComponentStyle(component = component) } - @Composable private fun createStackComponentStyle( component: StackComponent, ): Result> = zipOrAccumulate( @@ -102,7 +92,6 @@ internal class StyleFactory( ) } - @Composable private fun createTextComponentStyle( component: TextComponent, ): Result> = zipOrAccumulate( @@ -118,72 +107,21 @@ internal class StyleFactory( ) { text, presentedPartial -> // Combine the text and PresentedPartial into a TextComponentStyle. val partial = presentedPartial?.partial + val weight = (partial?.fontWeight ?: component.fontWeight).toFontWeight() TextComponentStyle( visible = partial?.visible ?: true, - text = rememberProcessedText( - originalText = presentedPartial?.text ?: text, - packageContext = packageContext, - locale = locale, - variables = variables, - ), + text = presentedPartial?.text ?: text, color = partial?.color ?: component.color, fontSize = partial?.fontSize ?: component.fontSize, - fontWeight = partial?.fontWeight ?: component.fontWeight, - fontFamily = partial?.fontName ?: component.fontName, - textAlign = partial?.horizontalAlignment ?: component.horizontalAlignment, - horizontalAlignment = partial?.horizontalAlignment ?: component.horizontalAlignment, + fontWeight = weight, + fontFamily = (partial?.fontName ?: component.fontName)?.let { SystemFontFamily(it, weight) }, + textAlign = (partial?.horizontalAlignment ?: component.horizontalAlignment).toTextAlign(), + horizontalAlignment = (partial?.horizontalAlignment ?: component.horizontalAlignment).toAlignment(), backgroundColor = partial?.backgroundColor ?: component.backgroundColor, size = partial?.size ?: component.size, - padding = partial?.padding ?: component.padding, - margin = partial?.margin ?: component.margin, + padding = (partial?.padding ?: component.padding).toPaddingValues(), + margin = (partial?.margin ?: component.margin).toPaddingValues(), ) } - - /** - * Replaces any [variables] in the [originalText] with values based on the currently selected - * [package][PackageContext.selectedPackage] and [locale]. - */ - @Composable - private fun rememberProcessedText( - originalText: String, - packageContext: PackageContext, - variables: VariableDataProvider, - locale: Locale, - ): String { - val processedText by remember(packageContext, variables, locale) { - derivedStateOf { - packageContext.selectedPackage?.let { selectedPackage -> - val discount = discountPercentage( - pricePerMonthMicros = selectedPackage.product.pricePerMonth()?.amountMicros, - mostExpensiveMicros = packageContext.variableContext.mostExpensivePricePerMonthMicros, - ) - val variableContext: VariableProcessor.PackageContext = VariableProcessor.PackageContext( - discountRelativeToMostExpensivePerMonth = discount, - showZeroDecimalPlacePrices = packageContext.variableContext.showZeroDecimalPlacePrices, - ) - VariableProcessor.processVariables( - variableDataProvider = variables, - context = variableContext, - originalString = originalText, - rcPackage = selectedPackage, - locale = locale, - ) - } ?: originalText - } - } - - return processedText - } - - private fun discountPercentage(pricePerMonthMicros: Long?, mostExpensiveMicros: Long?): Double? { - if (pricePerMonthMicros == null || - mostExpensiveMicros == null || - mostExpensiveMicros <= pricePerMonthMicros - ) { - return null - } - - return (mostExpensiveMicros - pricePerMonthMicros) / mostExpensiveMicros.toDouble() - } } diff --git a/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/style/TextComponentStyle.kt b/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/style/TextComponentStyle.kt index 2381d1e4ae..ef6c2fb33f 100644 --- a/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/style/TextComponentStyle.kt +++ b/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/style/TextComponentStyle.kt @@ -1,30 +1,18 @@ package com.revenuecat.purchases.ui.revenuecatui.components.style import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.ui.Alignment -import androidx.compose.ui.text.font.DeviceFontFamilyName -import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.TextUnit import com.revenuecat.purchases.paywalls.components.properties.ColorScheme import com.revenuecat.purchases.paywalls.components.properties.FontSize -import com.revenuecat.purchases.paywalls.components.properties.HorizontalAlignment -import com.revenuecat.purchases.paywalls.components.properties.Padding import com.revenuecat.purchases.paywalls.components.properties.Size -import com.revenuecat.purchases.ui.revenuecatui.components.ktx.toAlignment -import com.revenuecat.purchases.ui.revenuecatui.components.ktx.toFontWeight -import com.revenuecat.purchases.ui.revenuecatui.components.ktx.toPaddingValues -import com.revenuecat.purchases.ui.revenuecatui.components.ktx.toTextAlign -import com.revenuecat.purchases.ui.revenuecatui.components.ktx.toTextUnit -import com.revenuecat.purchases.paywalls.components.properties.FontWeight as RcFontWeight @Suppress("LongParameterList") @Immutable -internal class TextComponentStyle private constructor( +internal class TextComponentStyle( @get:JvmSynthetic val visible: Boolean, @get:JvmSynthetic @@ -32,7 +20,7 @@ internal class TextComponentStyle private constructor( @get:JvmSynthetic val color: ColorScheme, @get:JvmSynthetic - val fontSize: TextUnit, + val fontSize: FontSize, @get:JvmSynthetic val fontWeight: FontWeight?, @get:JvmSynthetic @@ -49,52 +37,4 @@ internal class TextComponentStyle private constructor( val padding: PaddingValues, @get:JvmSynthetic val margin: PaddingValues, -) : ComponentStyle { - - companion object { - - @Suppress("LongParameterList") - @JvmSynthetic - @Composable - operator fun invoke( - visible: Boolean, - text: String, - color: ColorScheme, - fontSize: FontSize, - fontWeight: RcFontWeight?, - fontFamily: String?, - textAlign: HorizontalAlignment, - horizontalAlignment: HorizontalAlignment, - backgroundColor: ColorScheme?, - size: Size, - padding: Padding, - margin: Padding, - ): TextComponentStyle { - val weight = fontWeight?.toFontWeight() - - return TextComponentStyle( - visible = visible, - text = text, - color = color, - fontSize = fontSize.toTextUnit(), - fontWeight = weight, - fontFamily = fontFamily?.let { SystemFontFamily(it, weight) }, - textAlign = textAlign.toTextAlign(), - horizontalAlignment = horizontalAlignment.toAlignment(), - backgroundColor = backgroundColor, - size = size, - padding = padding.toPaddingValues(), - margin = margin.toPaddingValues(), - ) - } - - @Suppress("FunctionName") - private fun SystemFontFamily(familyName: String, weight: FontWeight?): FontFamily = - FontFamily( - Font( - familyName = DeviceFontFamilyName(familyName), - weight = weight ?: FontWeight.Normal, - ), - ) - } -} +) : ComponentStyle diff --git a/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/text/TextComponentView.kt b/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/text/TextComponentView.kt index 7d2c5fd44b..e2fafd456f 100644 --- a/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/text/TextComponentView.kt +++ b/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/text/TextComponentView.kt @@ -7,9 +7,14 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.widthIn import androidx.compose.material3.LocalTextStyle import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.revenuecat.purchases.paywalls.components.properties.ColorInfo @@ -23,19 +28,36 @@ 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 com.revenuecat.purchases.paywalls.components.properties.SizeConstraint.Fit +import com.revenuecat.purchases.ui.revenuecatui.components.SystemFontFamily +import com.revenuecat.purchases.ui.revenuecatui.components.ktx.toAlignment +import com.revenuecat.purchases.ui.revenuecatui.components.ktx.toFontWeight +import com.revenuecat.purchases.ui.revenuecatui.components.ktx.toPaddingValues +import com.revenuecat.purchases.ui.revenuecatui.components.ktx.toTextAlign +import com.revenuecat.purchases.ui.revenuecatui.components.ktx.toTextUnit import com.revenuecat.purchases.ui.revenuecatui.components.modifier.background import com.revenuecat.purchases.ui.revenuecatui.components.modifier.size import com.revenuecat.purchases.ui.revenuecatui.components.properties.ColorStyle import com.revenuecat.purchases.ui.revenuecatui.components.properties.rememberColorStyle +import com.revenuecat.purchases.ui.revenuecatui.components.state.PackageContext import com.revenuecat.purchases.ui.revenuecatui.components.style.TextComponentStyle import com.revenuecat.purchases.ui.revenuecatui.composables.Markdown +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.toResourceProvider @Composable internal fun TextComponentView( style: TextComponentStyle, modifier: Modifier = Modifier, + // TODO Remove these default values + packageContext: PackageContext = PackageContext(null, PackageContext.VariableContext(emptyList())), + locale: Locale = Locale.current, ) { + val context = LocalContext.current + val variableDataProvider = remember { VariableDataProvider(context.toResourceProvider()) } + val text = rememberProcessedText(style.text, packageContext, variableDataProvider, locale) + val colorStyle = rememberColorStyle(scheme = style.color) val backgroundColorStyle = style.backgroundColor?.let { rememberColorStyle(scheme = it) } @@ -54,14 +76,14 @@ internal fun TextComponentView( if (style.visible) { Markdown( - text = style.text, + text = text, modifier = modifier .size(style.size, horizontalAlignment = style.horizontalAlignment) .padding(style.margin) .applyIfNotNull(backgroundColorStyle) { background(it) } .padding(style.padding), color = color, - fontSize = style.fontSize, + fontSize = style.fontSize.toTextUnit(), fontWeight = style.fontWeight, fontFamily = style.fontFamily, horizontalAlignment = style.horizontalAlignment, @@ -71,6 +93,49 @@ internal fun TextComponentView( } } +@Composable +private fun rememberProcessedText( + originalText: String, + packageContext: PackageContext, + variables: VariableDataProvider, + locale: Locale, +): String { + val processedText by remember(packageContext, variables, locale) { + derivedStateOf { + packageContext.selectedPackage?.let { selectedPackage -> + val discount = discountPercentage( + pricePerMonthMicros = selectedPackage.product.pricePerMonth()?.amountMicros, + mostExpensiveMicros = packageContext.variableContext.mostExpensivePricePerMonthMicros, + ) + val variableContext: VariableProcessor.PackageContext = VariableProcessor.PackageContext( + discountRelativeToMostExpensivePerMonth = discount, + showZeroDecimalPlacePrices = packageContext.variableContext.showZeroDecimalPlacePrices, + ) + VariableProcessor.processVariables( + variableDataProvider = variables, + context = variableContext, + originalString = originalText, + rcPackage = selectedPackage, + locale = java.util.Locale.forLanguageTag(locale.toLanguageTag()), + ) + } ?: originalText + } + } + + return processedText +} + +private fun discountPercentage(pricePerMonthMicros: Long?, mostExpensiveMicros: Long?): Double? { + if (pricePerMonthMicros == null || + mostExpensiveMicros == null || + mostExpensiveMicros <= pricePerMonthMicros + ) { + return null + } + + return (mostExpensiveMicros - pricePerMonthMicros) / mostExpensiveMicros.toDouble() +} + @Preview(name = "Default") @Composable private fun TextComponentView_Preview_Default() { @@ -79,6 +144,8 @@ private fun TextComponentView_Preview_Default() { text = "Hello, world", color = ColorScheme(light = ColorInfo.Hex(Color.Black.toArgb())), ), + packageContext = previewPackageState(), + locale = Locale.current, ) } @@ -92,6 +159,8 @@ private fun TextComponentView_Preview_SerifFont() { fontFamily = "serif", size = Size(width = Fit, height = Fit), ), + packageContext = previewPackageState(), + locale = Locale.current, ) } @@ -105,6 +174,8 @@ private fun TextComponentView_Preview_SansSerifFont() { fontFamily = "sans-serif", size = Size(width = Fit, height = Fit), ), + packageContext = previewPackageState(), + locale = Locale.current, ) } @@ -118,6 +189,8 @@ private fun TextComponentView_Preview_MonospaceFont() { fontFamily = "monospace", size = Size(width = Fit, height = Fit), ), + packageContext = previewPackageState(), + locale = Locale.current, ) } @@ -131,6 +204,8 @@ private fun TextComponentView_Preview_CursiveFont() { fontFamily = "cursive", size = Size(width = Fit, height = Fit), ), + packageContext = previewPackageState(), + locale = Locale.current, ) } @@ -144,6 +219,8 @@ private fun TextComponentView_Preview_FontSize() { fontSize = FontSize.HEADING_L, size = Size(width = Fit, height = Fit), ), + packageContext = previewPackageState(), + locale = Locale.current, ) } @@ -157,8 +234,10 @@ private fun TextComponentView_Preview_HorizontalAlignment() { size = Size(width = Fit, height = Fit), horizontalAlignment = HorizontalAlignment.TRAILING, ), + packageContext = previewPackageState(), // Our width is Fit, but we are forced to be wider than our contents. modifier = Modifier.widthIn(min = 400.dp), + locale = Locale.current, ) } @@ -177,6 +256,8 @@ private fun TextComponentView_Preview_Customizations() { padding = Padding(top = 10.0, bottom = 10.0, leading = 20.0, trailing = 20.0), margin = Padding(top = 20.0, bottom = 20.0, leading = 10.0, trailing = 10.0), ), + packageContext = previewPackageState(), + locale = Locale.current, ) } @@ -189,6 +270,8 @@ private fun TextComponentView_Preview_Markdown() { "Click [here](https://revenuecat.com)", color = ColorScheme(light = ColorInfo.Hex(Color.Black.toArgb())), ), + packageContext = previewPackageState(), + locale = Locale.current, ) } @@ -225,8 +308,9 @@ private fun TextComponentView_Preview_LinearGradient() { size = Size(width = SizeConstraint.Fixed(200.toUInt()), height = Fit), padding = Padding(top = 10.0, bottom = 10.0, leading = 20.0, trailing = 20.0), margin = Padding(top = 20.0, bottom = 20.0, leading = 10.0, trailing = 10.0), - ), + packageContext = previewPackageState(), + locale = Locale.current, ) } @@ -262,8 +346,9 @@ private fun TextComponentView_Preview_RadialGradient() { size = Size(width = SizeConstraint.Fixed(200.toUInt()), height = Fit), padding = Padding(top = 10.0, bottom = 10.0, leading = 20.0, trailing = 20.0), margin = Padding(top = 20.0, bottom = 20.0, leading = 10.0, trailing = 10.0), - ), + packageContext = previewPackageState(), + locale = Locale.current, ) } @@ -282,17 +367,29 @@ private fun previewTextComponentStyle( size: Size = Size(width = Fill, height = Fit), padding: Padding = zero, margin: Padding = zero, -) = TextComponentStyle( - visible = visible, - text = text, - color = color, - fontSize = fontSize, - fontWeight = fontWeight, - fontFamily = fontFamily, - textAlign = textAlign, - horizontalAlignment = horizontalAlignment, - backgroundColor = backgroundColor, - size = size, - padding = padding, - margin = margin, -) +): TextComponentStyle { + val weight = fontWeight.toFontWeight() + return TextComponentStyle( + visible = visible, + text = text, + color = color, + fontSize = fontSize, + fontWeight = weight, + fontFamily = fontFamily?.let { SystemFontFamily(it, weight) }, + textAlign = textAlign.toTextAlign(), + horizontalAlignment = horizontalAlignment.toAlignment(), + backgroundColor = backgroundColor, + size = size, + padding = padding.toPaddingValues(), + margin = margin.toPaddingValues(), + ) +} + +private fun previewPackageState(): PackageContext = + PackageContext( + initialSelectedPackage = null, + initialVariableContext = PackageContext.VariableContext( + packages = emptyList(), + showZeroDecimalPlacePrices = true, + ), + ) diff --git a/ui/revenuecatui/src/test/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/stack/StackComponentViewTests.kt b/ui/revenuecatui/src/test/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/stack/StackComponentViewTests.kt index 04b1017937..f27f9fb2a9 100644 --- a/ui/revenuecatui/src/test/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/stack/StackComponentViewTests.kt +++ b/ui/revenuecatui/src/test/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/stack/StackComponentViewTests.kt @@ -27,11 +27,8 @@ import com.revenuecat.purchases.ui.revenuecatui.assertions.assertPixelColorEqual import com.revenuecat.purchases.ui.revenuecatui.assertions.assertPixelColorPercentage import com.revenuecat.purchases.ui.revenuecatui.components.ComponentViewState import com.revenuecat.purchases.ui.revenuecatui.components.ScreenCondition -import com.revenuecat.purchases.ui.revenuecatui.components.state.PackageContext import com.revenuecat.purchases.ui.revenuecatui.components.style.StackComponentStyle import com.revenuecat.purchases.ui.revenuecatui.components.style.StyleFactory -import com.revenuecat.purchases.ui.revenuecatui.data.processed.VariableDataProvider -import com.revenuecat.purchases.ui.revenuecatui.data.testdata.MockResourceProvider import com.revenuecat.purchases.ui.revenuecatui.helpers.getOrThrow import com.revenuecat.purchases.ui.revenuecatui.helpers.themeChangingTest import org.junit.Before @@ -41,7 +38,6 @@ import org.junit.runner.RunWith import org.robolectric.annotation.Config import org.robolectric.annotation.GraphicsMode import org.robolectric.shadows.ShadowPixelCopy -import java.util.Locale @GraphicsMode(GraphicsMode.Mode.NATIVE) @Config(shadows = [ShadowPixelCopy::class], sdk = [26]) @@ -59,16 +55,7 @@ class StackComponentViewTests { windowSize = ScreenCondition.COMPACT, isEligibleForIntroOffer = true, componentState = ComponentViewState.DEFAULT, - packageContext = PackageContext( - initialSelectedPackage = null, - initialVariableContext = PackageContext.VariableContext( - packages = emptyList(), - showZeroDecimalPlacePrices = false - ) - ), localizationDictionary = emptyMap(), - locale = Locale.US, - variables = VariableDataProvider(MockResourceProvider()) ) } diff --git a/ui/revenuecatui/src/test/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/style/StyleFactoryTests.kt b/ui/revenuecatui/src/test/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/style/StyleFactoryTests.kt index 4ad130fdba..71e47e17fa 100644 --- a/ui/revenuecatui/src/test/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/style/StyleFactoryTests.kt +++ b/ui/revenuecatui/src/test/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/style/StyleFactoryTests.kt @@ -13,16 +13,12 @@ import com.revenuecat.purchases.paywalls.components.properties.ColorInfo import com.revenuecat.purchases.paywalls.components.properties.ColorScheme import com.revenuecat.purchases.ui.revenuecatui.components.ComponentViewState import com.revenuecat.purchases.ui.revenuecatui.components.ScreenCondition -import com.revenuecat.purchases.ui.revenuecatui.components.state.PackageContext -import com.revenuecat.purchases.ui.revenuecatui.data.processed.VariableDataProvider -import com.revenuecat.purchases.ui.revenuecatui.data.testdata.MockResourceProvider import com.revenuecat.purchases.ui.revenuecatui.helpers.Result import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import java.util.Locale @RunWith(AndroidJUnit4::class) class StyleFactoryTests { @@ -48,16 +44,7 @@ class StyleFactoryTests { windowSize = ScreenCondition.COMPACT, isEligibleForIntroOffer = true, componentState = ComponentViewState.DEFAULT, - packageContext = PackageContext( - initialSelectedPackage = null, - initialVariableContext = PackageContext.VariableContext( - packages = emptyList(), - showZeroDecimalPlacePrices = false - ) - ), localizationDictionary = localizationDictionary, - locale = Locale.US, - variables = VariableDataProvider(MockResourceProvider()) ) } diff --git a/ui/revenuecatui/src/test/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/text/TextComponentViewTests.kt b/ui/revenuecatui/src/test/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/text/TextComponentViewTests.kt index 8c984405bc..dbde2f4d7c 100644 --- a/ui/revenuecatui/src/test/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/text/TextComponentViewTests.kt +++ b/ui/revenuecatui/src/test/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/text/TextComponentViewTests.kt @@ -25,11 +25,8 @@ import com.revenuecat.purchases.ui.revenuecatui.assertions.assertPixelColorEqual import com.revenuecat.purchases.ui.revenuecatui.assertions.assertTextColorEquals import com.revenuecat.purchases.ui.revenuecatui.components.ComponentViewState import com.revenuecat.purchases.ui.revenuecatui.components.ScreenCondition -import com.revenuecat.purchases.ui.revenuecatui.components.state.PackageContext import com.revenuecat.purchases.ui.revenuecatui.components.style.StyleFactory import com.revenuecat.purchases.ui.revenuecatui.components.style.TextComponentStyle -import com.revenuecat.purchases.ui.revenuecatui.data.processed.VariableDataProvider -import com.revenuecat.purchases.ui.revenuecatui.data.testdata.MockResourceProvider import com.revenuecat.purchases.ui.revenuecatui.helpers.getOrThrow import com.revenuecat.purchases.ui.revenuecatui.helpers.themeChangingTest import org.assertj.core.api.Assertions.assertThat @@ -40,7 +37,6 @@ import org.junit.runner.RunWith import org.robolectric.annotation.Config import org.robolectric.annotation.GraphicsMode import org.robolectric.shadows.ShadowPixelCopy -import java.util.Locale @RunWith(AndroidJUnit4::class) class TextComponentViewTests { @@ -59,16 +55,7 @@ class TextComponentViewTests { windowSize = ScreenCondition.COMPACT, isEligibleForIntroOffer = true, componentState = ComponentViewState.DEFAULT, - packageContext = PackageContext( - initialSelectedPackage = null, - initialVariableContext = PackageContext.VariableContext( - packages = emptyList(), - showZeroDecimalPlacePrices = false - ) - ), localizationDictionary = localizationDictionary, - locale = Locale.US, - variables = VariableDataProvider(MockResourceProvider()) ) }