Skip to content

Commit

Permalink
Build Customer Center from JSON (#1998)
Browse files Browse the repository at this point in the history
  • Loading branch information
vegaro authored Dec 20, 2024
1 parent fe5ac73 commit b831972
Show file tree
Hide file tree
Showing 13 changed files with 569 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ data class CustomerCenterConfigData(
val eligible: Boolean,
val title: String,
val subtitle: String,
@SerialName("product_mapping") val productMapping: Map<String, String>,
) : PathDetail()

@Serializable
Expand Down Expand Up @@ -270,6 +271,17 @@ data class CustomerCenterConfigData(

@Serializable
data class Support(
@Serializable(with = EmptyStringToNullSerializer::class) val email: String? = null,
@Serializable(with = EmptyStringToNullSerializer::class)
val email: String? = null,
@SerialName("should_warn_customer_to_update")
val shouldWarnCustomerToUpdate: Boolean? = null,
)

fun getManagementScreen(): CustomerCenterConfigData.Screen? {
return screens[CustomerCenterConfigData.Screen.ScreenType.MANAGEMENT]
}

fun getNoActiveScreen(): CustomerCenterConfigData.Screen? {
return screens[CustomerCenterConfigData.Screen.ScreenType.NO_ACTIVE]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ class BackendGetCustomerCenterConfigTest {
androidOfferId = "rc-refund-offer",
eligible = true,
title = "Wait!",
subtitle = "Before you go, here's a one-time offer to continue at a discount."
subtitle = "Before you go, here's a one-time offer to continue at a discount.",
productMapping = mapOf("monthly_subscription" to "rc-refund-offer")
)
),
HelpPath(
Expand All @@ -88,7 +89,8 @@ class BackendGetCustomerCenterConfigTest {
androidOfferId = "rc-cancel-offer",
eligible = true,
title = "Wait!",
subtitle = "Before you go, here's a one-time offer to continue at a discount."
subtitle = "Before you go, here's a one-time offer to continue at a discount.",
productMapping = mapOf("monthly_subscription" to "rc-cancel-offer")
)
),
HelpPath.PathDetail.FeedbackSurvey.Option(
Expand All @@ -98,7 +100,8 @@ class BackendGetCustomerCenterConfigTest {
androidOfferId = "rc-cancel-offer",
eligible = true,
title = "Wait!",
subtitle = "Before you go, here's a one-time offer to continue at a discount."
subtitle = "Before you go, here's a one-time offer to continue at a discount.",
productMapping = mapOf("monthly_subscription" to "rc-cancel-offer")
)
),
HelpPath.PathDetail.FeedbackSurvey.Option(
Expand Down Expand Up @@ -180,7 +183,8 @@ class BackendGetCustomerCenterConfigTest {
)
),
support = CustomerCenterConfigData.Support(
email = "support@revenuecat.com"
email = "support@revenuecat.com",
shouldWarnCustomerToUpdate = true
),
lastPublishedAppVersion = null
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ class CustomerCenterConfigDataTest {
androidOfferId = "offer_id",
eligible = true,
title = "Offer Title",
subtitle = "Offer Subtitle"
subtitle = "Offer Subtitle",
productMapping = mapOf("monthly_subscription" to "rc-refund-offer")
)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,10 @@
"eligible":true,
"android_offer_id":"rc-refund-offer",
"subtitle":"Before you go, here's a one-time offer to continue at a discount.",
"title":"Wait!"
"title":"Wait!",
"product_mapping":{
"monthly_subscription":"rc-refund-offer"
}
},
"title":"Request a refund",
"type":"REFUND_REQUEST"
Expand All @@ -89,7 +92,10 @@
"eligible":true,
"android_offer_id":"rc-cancel-offer",
"subtitle":"Before you go, here's a one-time offer to continue at a discount.",
"title":"Wait!"
"title":"Wait!",
"product_mapping":{
"monthly_subscription":"rc-cancel-offer"
}
},
"title":"Too expensive"
},
Expand All @@ -99,7 +105,10 @@
"eligible":true,
"android_offer_id":"rc-cancel-offer",
"subtitle":"Before you go, here's a one-time offer to continue at a discount.",
"title":"Wait!"
"title":"Wait!",
"product_mapping":{
"monthly_subscription":"rc-cancel-offer"
}
},
"title":"Don't use the app"
},
Expand All @@ -113,6 +122,13 @@
"id":"jnkasldfhas",
"title":"Cancel subscription",
"type":"CANCEL"
},
{
"id": "path_ZD-yiHSBN",
"open_method": "EXTERNAL",
"title": "RevenueCat",
"type": "CUSTOM_URL",
"url": "https://revenuecat.com"
}
],
"title":"How can we help?",
Expand All @@ -124,6 +140,13 @@
"id":"9q9719171o",
"title":"Check purchases",
"type":"MISSING_PURCHASE"
},
{
"id": "path_ZD-yiHSDN",
"open_method": "EXTERNAL",
"title": "RevenueCat",
"type": "CUSTOM_URL",
"url": "https://revenuecat.com"
}
],
"subtitle":"You currently have no active subscriptions",
Expand All @@ -132,7 +155,8 @@
}
},
"support":{
"email":"support@revenuecat.com"
"email":"support@revenuecat.com",
"should_warn_customer_to_update": true
}
},
"itunes_track_id":null,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@file:OptIn(ExperimentalPreviewRevenueCatPurchasesAPI::class)

package com.revenuecat.purchases.ui.revenuecatui.customercenter

import androidx.compose.foundation.layout.Arrangement
Expand All @@ -8,43 +10,55 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.revenuecat.purchases.ExperimentalPreviewRevenueCatPurchasesAPI
import com.revenuecat.purchases.PurchasesError
import com.revenuecat.purchases.PurchasesErrorCode
import com.revenuecat.purchases.customercenter.CustomerCenterConfigData
import com.revenuecat.purchases.ui.revenuecatui.customercenter.data.CustomerCenterConfigTestData
import com.revenuecat.purchases.ui.revenuecatui.customercenter.data.CustomerCenterState
import com.revenuecat.purchases.ui.revenuecatui.customercenter.data.CustomerCenterViewModel
import com.revenuecat.purchases.ui.revenuecatui.customercenter.data.CustomerCenterViewModelFactory
import com.revenuecat.purchases.ui.revenuecatui.customercenter.data.CustomerCenterViewModelImpl
import com.revenuecat.purchases.ui.revenuecatui.customercenter.viewmodel.CustomerCenterViewModel
import com.revenuecat.purchases.ui.revenuecatui.customercenter.viewmodel.CustomerCenterViewModelFactory
import com.revenuecat.purchases.ui.revenuecatui.customercenter.viewmodel.CustomerCenterViewModelImpl
import com.revenuecat.purchases.ui.revenuecatui.customercenter.views.ManageSubscriptionsView
import com.revenuecat.purchases.ui.revenuecatui.data.PurchasesImpl
import com.revenuecat.purchases.ui.revenuecatui.data.PurchasesType
import kotlinx.coroutines.launch

@Composable
internal fun InternalCustomerCenter(
modifier: Modifier = Modifier,
viewModel: CustomerCenterViewModel = getCustomerCenterViewModel(),
) {
val state by viewModel.state.collectAsState()
InternalCustomerCenter(state, modifier)
val coroutineScope = rememberCoroutineScope()
InternalCustomerCenter(
state,
modifier,
onDetermineFlow = { path ->
coroutineScope.launch {
viewModel.determineFlow(path)
}
},
)
}

@Composable
private fun InternalCustomerCenter(
state: CustomerCenterState,
modifier: Modifier = Modifier,
onDetermineFlow: (CustomerCenterConfigData.HelpPath) -> Unit,
) {
CustomerCenterScaffold(modifier) {
when (state) {
is CustomerCenterState.Loading -> CustomerCenterLoading()
is CustomerCenterState.Error -> CustomerCenterError(state)
is CustomerCenterState.Success -> CustomerCenterLoaded(state)
is CustomerCenterState.Success -> CustomerCenterLoaded(state, onDetermineFlow)
}
}
}
Expand Down Expand Up @@ -75,12 +89,34 @@ private fun CustomerCenterError(state: CustomerCenterState.Error) {
Text("Error: ${state.error}")
}

@OptIn(ExperimentalPreviewRevenueCatPurchasesAPI::class)
@Composable
private fun CustomerCenterLoaded(state: CustomerCenterState.Success) {
// CustomerCenter WIP: Add proper success UI
Column {
Text("Customer Center config:", fontWeight = FontWeight.Bold, fontSize = 20.sp)
Text(state.customerCenterConfigDataString)
private fun CustomerCenterLoaded(
state: CustomerCenterState.Success,
onDetermineFlow: (CustomerCenterConfigData.HelpPath) -> Unit,
) {
val configuration = state.customerCenterConfigData
if (state.purchaseInformation != null) {
configuration.getManagementScreen()?.let { managementScreen ->
ManageSubscriptionsView(
screen = managementScreen,
purchaseInformation = state.purchaseInformation,
onDetermineFlow = onDetermineFlow,
)
} ?: run {
// Handle missing management screen
// WrongPlatformView
}
} else {
configuration.getNoActiveScreen()?.let { noActiveScreen ->
ManageSubscriptionsView(
screen = noActiveScreen,
onDetermineFlow = onDetermineFlow,
)
} ?: run {
// Fallback with a restore button
// NoSubscriptionsView(configuration = configuration)
}
}
}

Expand All @@ -98,17 +134,23 @@ internal fun getCustomerCenterViewModel(
@Composable
internal fun CustomerCenterLoadingPreview() {
InternalCustomerCenter(
modifier = Modifier.fillMaxSize().padding(10.dp),
state = CustomerCenterState.Loading,
modifier = Modifier
.fillMaxSize()
.padding(10.dp),
onDetermineFlow = {},
)
}

@Preview
@Composable
internal fun CustomerCenterErrorPreview() {
InternalCustomerCenter(
modifier = Modifier.fillMaxSize().padding(10.dp),
state = CustomerCenterState.Error(PurchasesError(PurchasesErrorCode.UnknownBackendError)),
modifier = Modifier
.fillMaxSize()
.padding(10.dp),
onDetermineFlow = {},
)
}

Expand All @@ -117,8 +159,14 @@ internal fun CustomerCenterErrorPreview() {
@Composable
internal fun CustomerCenterLoadedPreview() {
InternalCustomerCenter(
modifier = Modifier.fillMaxSize().padding(10.dp),
state = CustomerCenterState.Success(previewConfigData.toString()),
state = CustomerCenterState.Success(
customerCenterConfigData = previewConfigData,
purchaseInformation = CustomerCenterConfigTestData.purchaseInformationMonthlyRenewing,
),
modifier = Modifier
.fillMaxSize()
.padding(10.dp),
onDetermineFlow = {},
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.revenuecat.purchases.ui.revenuecatui.customercenter.data.SubscriptionInformation
import com.revenuecat.purchases.ui.revenuecatui.customercenter.data.PurchaseInformation
import com.revenuecat.purchases.ui.revenuecatui.icons.CalendarMonth
import com.revenuecat.purchases.ui.revenuecatui.icons.CurrencyExchange
import com.revenuecat.purchases.ui.revenuecatui.icons.UniversalCurrencyAlt

@Composable
internal fun SubscriptionDetailsView(
details: SubscriptionInformation,
details: PurchaseInformation,
modifier: Modifier = Modifier,
) {
Surface(
Expand Down Expand Up @@ -139,25 +139,25 @@ private val PaddingHorizontal = 8.dp
private val PaddingVertical = 8.dp
private const val SizeIconDp = 22

private class SubscriptionInformationProvider : PreviewParameterProvider<SubscriptionInformation> {
override val values: Sequence<SubscriptionInformation> = sequenceOf(
SubscriptionInformation(
private class SubscriptionInformationProvider : PreviewParameterProvider<PurchaseInformation> {
override val values: Sequence<PurchaseInformation> = sequenceOf(
PurchaseInformation(
title = "Basic",
durationTitle = "Monthly",
price = "$4.99",
expirationDateString = "June 1st, 2024",
willRenew = true,
active = true,
),
SubscriptionInformation(
PurchaseInformation(
title = "Basic",
durationTitle = "Yearly",
price = "$49.99",
expirationDateString = "June 1st, 2024",
willRenew = false,
active = true,
),
SubscriptionInformation(
PurchaseInformation(
title = "Basic",
durationTitle = "Weekly",
price = "$1.99",
Expand Down
Loading

0 comments on commit b831972

Please sign in to comment.