-
Notifications
You must be signed in to change notification settings - Fork 54
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[CustomerCenter] Create
CustomerCenter
composable and view model wi…
…th some initial UI (#1867) ### Description This adds an initial composable and view model with some previews so we can start iterating over the customer center. New composable using it as a bottom sheet and using it from paywall tester will come in follow up PRs. Nothing exposed in this PR yet.
- Loading branch information
Showing
7 changed files
with
263 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
15 changes: 15 additions & 0 deletions
15
...src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/customercenter/CustomerCenter.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package com.revenuecat.purchases.ui.revenuecatui.customercenter | ||
|
||
import androidx.compose.runtime.Composable | ||
import androidx.compose.ui.Modifier | ||
import com.revenuecat.purchases.ui.revenuecatui.ExperimentalPreviewRevenueCatUIPurchasesAPI | ||
|
||
/** | ||
* Composable offering a full screen Customer Center UI configured from the RevenueCat dashboard. | ||
*/ | ||
@Composable | ||
@ExperimentalPreviewRevenueCatUIPurchasesAPI | ||
// CustomerCenter WIP: Make public when ready | ||
internal fun CustomerCenter(modifier: Modifier = Modifier) { | ||
InternalCustomerCenter(modifier) | ||
} |
152 changes: 152 additions & 0 deletions
152
.../kotlin/com/revenuecat/purchases/ui/revenuecatui/customercenter/InternalCustomerCenter.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
package com.revenuecat.purchases.ui.revenuecatui.customercenter | ||
|
||
import androidx.compose.foundation.layout.Arrangement | ||
import androidx.compose.foundation.layout.Column | ||
import androidx.compose.foundation.layout.fillMaxSize | ||
import androidx.compose.foundation.layout.padding | ||
import androidx.compose.material3.Text | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.collectAsState | ||
import androidx.compose.runtime.getValue | ||
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.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.data.PurchasesImpl | ||
import com.revenuecat.purchases.ui.revenuecatui.data.PurchasesType | ||
|
||
@Composable | ||
internal fun InternalCustomerCenter( | ||
modifier: Modifier = Modifier, | ||
viewModel: CustomerCenterViewModel = getCustomerCenterViewModel(), | ||
) { | ||
val state by viewModel.state.collectAsState() | ||
InternalCustomerCenter(state, modifier) | ||
} | ||
|
||
@Composable | ||
private fun InternalCustomerCenter( | ||
state: CustomerCenterState, | ||
modifier: Modifier = Modifier, | ||
) { | ||
CustomerCenterScaffold(modifier) { | ||
when (state) { | ||
is CustomerCenterState.Loading -> CustomerCenterLoading() | ||
is CustomerCenterState.Error -> CustomerCenterError(state) | ||
is CustomerCenterState.Success -> CustomerCenterLoaded(state) | ||
} | ||
} | ||
} | ||
|
||
@Composable | ||
private fun CustomerCenterScaffold( | ||
modifier: Modifier = Modifier, | ||
mainContent: @Composable () -> Unit, | ||
) { | ||
Column( | ||
modifier = modifier, | ||
horizontalAlignment = Alignment.CenterHorizontally, | ||
verticalArrangement = Arrangement.Center, | ||
) { | ||
mainContent() | ||
} | ||
} | ||
|
||
@Composable | ||
private fun CustomerCenterLoading() { | ||
// CustomerCenter WIP: Add proper loading UI | ||
Text("Loading...") | ||
} | ||
|
||
@Composable | ||
private fun CustomerCenterError(state: CustomerCenterState.Error) { | ||
// CustomerCenter WIP: Add proper error UI | ||
Text("Error: ${state.error}") | ||
} | ||
|
||
@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) | ||
} | ||
} | ||
|
||
@Composable | ||
internal fun getCustomerCenterViewModel( | ||
purchases: PurchasesType = PurchasesImpl(), | ||
viewModel: CustomerCenterViewModel = viewModel<CustomerCenterViewModelImpl>( | ||
factory = CustomerCenterViewModelFactory(purchases), | ||
), | ||
): CustomerCenterViewModel { | ||
return viewModel | ||
} | ||
|
||
@Preview | ||
@Composable | ||
internal fun CustomerCenterLoadingPreview() { | ||
InternalCustomerCenter( | ||
modifier = Modifier.fillMaxSize().padding(10.dp), | ||
state = CustomerCenterState.Loading, | ||
) | ||
} | ||
|
||
@Preview | ||
@Composable | ||
internal fun CustomerCenterErrorPreview() { | ||
InternalCustomerCenter( | ||
modifier = Modifier.fillMaxSize().padding(10.dp), | ||
state = CustomerCenterState.Error(PurchasesError(PurchasesErrorCode.UnknownBackendError)), | ||
) | ||
} | ||
|
||
@OptIn(ExperimentalPreviewRevenueCatPurchasesAPI::class) | ||
@Preview | ||
@Composable | ||
internal fun CustomerCenterLoadedPreview() { | ||
InternalCustomerCenter( | ||
modifier = Modifier.fillMaxSize().padding(10.dp), | ||
state = CustomerCenterState.Success(previewConfigData.toString()), | ||
) | ||
} | ||
|
||
@OptIn(ExperimentalPreviewRevenueCatPurchasesAPI::class) | ||
private val previewConfigData = CustomerCenterConfigData( | ||
screens = mapOf( | ||
CustomerCenterConfigData.Screen.ScreenType.MANAGEMENT to CustomerCenterConfigData.Screen( | ||
type = CustomerCenterConfigData.Screen.ScreenType.MANAGEMENT, | ||
title = "Manage Subscription", | ||
subtitle = "Manage subscription subtitle", | ||
paths = listOf( | ||
CustomerCenterConfigData.HelpPath( | ||
id = "path-id-1", | ||
title = "Subscription", | ||
type = CustomerCenterConfigData.HelpPath.PathType.CANCEL, | ||
promotionalOffer = null, | ||
feedbackSurvey = null, | ||
), | ||
), | ||
), | ||
), | ||
appearance = CustomerCenterConfigData.Appearance(), | ||
localization = CustomerCenterConfigData.Localization( | ||
locale = "en_US", | ||
localizedStrings = mapOf( | ||
"cancel" to "Cancel", | ||
"subscription" to "Subscription", | ||
), | ||
), | ||
support = CustomerCenterConfigData.Support(email = "test@revenuecat.com"), | ||
) |
11 changes: 11 additions & 0 deletions
11
...otlin/com/revenuecat/purchases/ui/revenuecatui/customercenter/data/CustomerCenterState.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package com.revenuecat.purchases.ui.revenuecatui.customercenter.data | ||
|
||
import com.revenuecat.purchases.PurchasesError | ||
|
||
internal sealed class CustomerCenterState { | ||
object Loading : CustomerCenterState() | ||
data class Error(val error: PurchasesError) : CustomerCenterState() | ||
|
||
// CustomerCenter WIP: Change to use the actual data the customer center will use. | ||
data class Success(val customerCenterConfigDataString: String) : CustomerCenterState() | ||
} |
38 changes: 38 additions & 0 deletions
38
...n/com/revenuecat/purchases/ui/revenuecatui/customercenter/data/CustomerCenterViewModel.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package com.revenuecat.purchases.ui.revenuecatui.customercenter.data | ||
|
||
import androidx.lifecycle.ViewModel | ||
import androidx.lifecycle.viewModelScope | ||
import com.revenuecat.purchases.ExperimentalPreviewRevenueCatPurchasesAPI | ||
import com.revenuecat.purchases.PurchasesException | ||
import com.revenuecat.purchases.ui.revenuecatui.data.PurchasesType | ||
import kotlinx.coroutines.flow.SharingStarted | ||
import kotlinx.coroutines.flow.StateFlow | ||
import kotlinx.coroutines.flow.flow | ||
import kotlinx.coroutines.flow.stateIn | ||
|
||
internal interface CustomerCenterViewModel { | ||
val state: StateFlow<CustomerCenterState> | ||
} | ||
|
||
@OptIn(ExperimentalPreviewRevenueCatPurchasesAPI::class) | ||
internal class CustomerCenterViewModelImpl( | ||
private val purchases: PurchasesType, | ||
) : ViewModel(), CustomerCenterViewModel { | ||
companion object { | ||
private const val STOP_FLOW_TIMEOUT = 5_000L | ||
} | ||
|
||
// This won't load the state until there is a subscriber | ||
override val state = flow { | ||
try { | ||
val customerCenterConfigData = purchases.awaitCustomerCenterConfigData() | ||
emit(CustomerCenterState.Success(customerCenterConfigData.toString())) | ||
} catch (e: PurchasesException) { | ||
emit(CustomerCenterState.Error(e.error)) | ||
} | ||
}.stateIn( | ||
scope = viewModelScope, | ||
started = SharingStarted.WhileSubscribed(STOP_FLOW_TIMEOUT), | ||
initialValue = CustomerCenterState.Loading, | ||
) | ||
} |
14 changes: 14 additions & 0 deletions
14
...evenuecat/purchases/ui/revenuecatui/customercenter/data/CustomerCenterViewModelFactory.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package com.revenuecat.purchases.ui.revenuecatui.customercenter.data | ||
|
||
import androidx.lifecycle.ViewModel | ||
import androidx.lifecycle.ViewModelProvider | ||
import com.revenuecat.purchases.ui.revenuecatui.data.PurchasesType | ||
|
||
internal class CustomerCenterViewModelFactory( | ||
private val purchases: PurchasesType, | ||
) : ViewModelProvider.NewInstanceFactory() { | ||
@Suppress("UNCHECKED_CAST") | ||
override fun <T : ViewModel> create(modelClass: Class<T>): T { | ||
return CustomerCenterViewModelImpl(purchases) as T | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters