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

Add support for multiple intro offers in IntroEligibilityStateView #1319

Merged
Show file tree
Hide file tree
Changes from 4 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 @@ -16,19 +16,21 @@ import androidx.compose.ui.tooling.preview.Preview
internal fun IntroEligibilityStateView(
textWithNoIntroOffer: String?,
textWithIntroOffer: String?,
textWithMultipleIntroOffers: String?,
eligibility: IntroOfferEligibility,
color: Color = Color.Unspecified,
style: TextStyle = TextStyle.Default,
fontWeight: FontWeight? = null,
textAlign: TextAlign? = null,
) {
val text: String = if (textWithIntroOffer != null && eligibility == IntroOfferEligibility.ELIGIBLE) {
textWithIntroOffer
} else {
// Display text with intro offer as a backup to ensure layout does not change
// when switching states.
textWithNoIntroOffer ?: textWithIntroOffer ?: ""
}
val text: String = when (eligibility) {
IntroOfferEligibility.SINGLE_OFFER_ELIGIBLE -> textWithIntroOffer
IntroOfferEligibility.MULTIPLE_OFFER_ELIGIBLE -> textWithMultipleIntroOffers
else -> textWithNoIntroOffer
} // Display text with intro offer as a backup to ensure layout does not change when switching states.
?: textWithNoIntroOffer
?: textWithIntroOffer
?: ""
vegaro marked this conversation as resolved.
Show resolved Hide resolved

Text(
text,
Expand All @@ -41,7 +43,8 @@ internal fun IntroEligibilityStateView(

internal enum class IntroOfferEligibility {
INELIGIBLE,
ELIGIBLE,
SINGLE_OFFER_ELIGIBLE,
MULTIPLE_OFFER_ELIGIBLE,
vegaro marked this conversation as resolved.
Show resolved Hide resolved
}

@Preview(showBackground = true)
Expand All @@ -50,7 +53,8 @@ private fun IntroEligibilityPreviewNoOffer() {
IntroEligibilityStateView(
textWithNoIntroOffer = "$3.99/mo",
textWithIntroOffer = null,
eligibility = IntroOfferEligibility.ELIGIBLE,
textWithMultipleIntroOffers = null,
eligibility = IntroOfferEligibility.SINGLE_OFFER_ELIGIBLE,
color = Color.Black,
)
}
Expand All @@ -61,18 +65,32 @@ private fun IntroEligibilityPreviewBothTextsIneligible() {
IntroEligibilityStateView(
textWithNoIntroOffer = "$3.99/mo",
textWithIntroOffer = "7 day trial, then $3.99/mo",
textWithMultipleIntroOffers = "7 days for free, then $1.99 for your first month, and just $4.99/mo thereafter.",
eligibility = IntroOfferEligibility.INELIGIBLE,
color = Color.Black,
)
}

@Preview(showBackground = true)
@Composable
private fun IntroEligibilityPreviewBothTextsEligible() {
private fun IntroEligibilityPreviewBothTextsEligibleSingleOffer() {
IntroEligibilityStateView(
textWithNoIntroOffer = "$3.99/mo",
textWithIntroOffer = "7 day trial, then $3.99/mo",
eligibility = IntroOfferEligibility.ELIGIBLE,
textWithMultipleIntroOffers = "7 days for free, then $1.99 for your first month, and just $3.99/mo thereafter.",
eligibility = IntroOfferEligibility.SINGLE_OFFER_ELIGIBLE,
color = Color.Black,
)
}

@Preview(showBackground = true)
@Composable
private fun IntroEligibilityPreviewBothTextsEligibleMultipleOffers() {
IntroEligibilityStateView(
textWithNoIntroOffer = "$3.99/mo",
textWithIntroOffer = "7 day trial, then $3.99/mo",
textWithMultipleIntroOffers = "7 days for free, then $1.99 for your first month, and just $4.99/mo thereafter.",
eligibility = IntroOfferEligibility.MULTIPLE_OFFER_ELIGIBLE,
color = Color.Black,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ internal fun PurchaseButton(
IntroEligibilityStateView(
textWithNoIntroOffer = state.selectedLocalization.callToAction,
textWithIntroOffer = state.selectedLocalization.callToActionWithIntroOffer,
textWithMultipleIntroOffers = state.selectedLocalization.offerDetailsWithMultipleIntroOffers,
vegaro marked this conversation as resolved.
Show resolved Hide resolved
eligibility = state.selectedPackage.introEligibility,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,15 @@ val Package.isMonthly: Boolean
get() = product.period?.let { it.unit == Period.Unit.MONTH && it.value == 1 } ?: false

internal val Package.introEligibility: IntroOfferEligibility
get() = if (product.defaultOption?.isBasePlan == true) {
IntroOfferEligibility.INELIGIBLE
} else {
IntroOfferEligibility.ELIGIBLE
}
get() = product.defaultOption?.let { defaultOption ->
when {
defaultOption.isBasePlan -> IntroOfferEligibility.INELIGIBLE
(defaultOption.freePhase != null && defaultOption.introPhase == null) ||
(defaultOption.freePhase == null && defaultOption.introPhase != null) ->
IntroOfferEligibility.SINGLE_OFFER_ELIGIBLE
else -> IntroOfferEligibility.MULTIPLE_OFFER_ELIGIBLE
}
} ?: IntroOfferEligibility.INELIGIBLE

internal val TemplateConfiguration.PackageInfo.introEligibility: IntroOfferEligibility
get() = this.rcPackage.introEligibility
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ private fun ColumnScope.Template1MainContent(state: PaywallViewState.Loaded) {
IntroEligibilityStateView(
textWithNoIntroOffer = localizedConfig.offerDetails,
textWithIntroOffer = localizedConfig.offerDetailsWithIntroOffer,
textWithMultipleIntroOffers = localizedConfig.offerDetailsWithMultipleIntroOffers,
vegaro marked this conversation as resolved.
Show resolved Hide resolved
eligibility = state.selectedPackage.introEligibility,
color = colors.text1,
style = MaterialTheme.typography.bodyLarge,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ private fun SelectPackageButton(
IntroEligibilityStateView(
textWithNoIntroOffer = packageInfo.localization.offerDetails,
textWithIntroOffer = packageInfo.localization.offerDetailsWithIntroOffer,
textWithMultipleIntroOffers = packageInfo.localization.offerDetailsWithMultipleIntroOffers,
eligibility = packageInfo.introEligibility,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ internal fun Template3(
IntroEligibilityStateView(
textWithNoIntroOffer = state.selectedLocalization.offerDetails,
textWithIntroOffer = state.selectedLocalization.offerDetailsWithIntroOffer,
textWithMultipleIntroOffers = state.selectedLocalization.offerDetailsWithMultipleIntroOffers,
eligibility = state.selectedPackage.introEligibility,
color = state.templateConfiguration.getCurrentColors().text1,
textAlign = TextAlign.Center,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package com.revenuecat.purchases.ui.revenuecatui.extensions

import androidx.test.ext.junit.runners.AndroidJUnit4
import com.revenuecat.purchases.Package
import com.revenuecat.purchases.models.PricingPhase
import com.revenuecat.purchases.models.PurchasingData
import com.revenuecat.purchases.models.StoreProduct
import com.revenuecat.purchases.models.SubscriptionOption
import com.revenuecat.purchases.ui.revenuecatui.composables.IntroOfferEligibility
Expand All @@ -15,24 +17,40 @@ import org.junit.runner.RunWith
class PackageExtensionsTest {

@Test
fun `introEligibility calculation is true if defaultOption is not base plan`() {
val rcPackage = createPackage(basePlan = false)
fun `introEligibility calculation is SINGLE_OFFER_ELIGIBLE if defaultOption only has a free trial`() {
val rcPackage = createPackage(numberOfOffers = 1)

assertThat(rcPackage.introEligibility).isEqualTo(IntroOfferEligibility.ELIGIBLE)
assertThat(rcPackage.introEligibility).isEqualTo(IntroOfferEligibility.SINGLE_OFFER_ELIGIBLE)
}

@Test
fun `introEligibility calculation is false if defaultOption is base plan`() {
val rcPackage = createPackage(basePlan = true)
fun `introEligibility calculation is MULTIPLE_OFFER_ELIGIBLE if defaultOption has trial and discounted price`() {
val rcPackage = createPackage(numberOfOffers = 2)

assertThat(rcPackage.introEligibility).isEqualTo(IntroOfferEligibility.MULTIPLE_OFFER_ELIGIBLE)
vegaro marked this conversation as resolved.
Show resolved Hide resolved
}

@Test
fun `introEligibility calculation is INELIGIBLE if defaultOption is base plan`() {
val rcPackage = createPackage(numberOfOffers = 0)

assertThat(rcPackage.introEligibility).isEqualTo(IntroOfferEligibility.INELIGIBLE)
}

private fun createPackage(basePlan: Boolean): Package {
private fun createPackage(numberOfOffers: Int): Package {
return mockk<Package>().apply {
every { product } returns mockk<StoreProduct>().apply {
every { defaultOption } returns mockk<SubscriptionOption>().apply {
every { isBasePlan } returns basePlan
every { defaultOption } returns object : SubscriptionOption {
override val id: String
get() = "id"
override val pricingPhases: List<PricingPhase>
get() = List(numberOfOffers + 1) { mockk() } // Base plans have one pricing phase
override val tags: List<String>
get() = listOf("tag_1")
override val presentedOfferingIdentifier: String?
get() = "offering_id"
override val purchasingData: PurchasingData
get() = mockk()
}
}
}
Expand Down