Skip to content

Commit

Permalink
Add PlaygroundConfigurationData for configuring known playground op…
Browse files Browse the repository at this point in the history
…tions.
  • Loading branch information
samer-stripe committed May 15, 2024
1 parent 861f779 commit 59d7977
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ import com.stripe.android.paymentsheet.example.playground.settings.CheckoutMode
import com.stripe.android.paymentsheet.example.playground.settings.CheckoutModeSettingsDefinition
import com.stripe.android.paymentsheet.example.playground.settings.CustomerSettingsDefinition
import com.stripe.android.paymentsheet.example.playground.settings.CustomerType
import com.stripe.android.paymentsheet.example.playground.settings.IntegrationType
import com.stripe.android.paymentsheet.example.playground.settings.IntegrationTypeSettingsDefinition
import com.stripe.android.paymentsheet.example.playground.settings.PlaygroundConfigurationData
import com.stripe.android.paymentsheet.ui.PAYMENT_SHEET_ERROR_TEXT_TEST_TAG
import com.stripe.android.test.core.ui.BrowserUI
import com.stripe.android.test.core.ui.ComposeButton
Expand Down Expand Up @@ -181,7 +180,11 @@ internal class PlaygroundTestDriver(
): PlaygroundState? {
setup(
testParameters.copyPlaygroundSettings { settings ->
settings[IntegrationTypeSettingsDefinition] = IntegrationType.FlowController
settings.updateConfigurationData { configurationData ->
configurationData.copy(
integrationType = PlaygroundConfigurationData.IntegrationType.FlowController
)
}
}
)
launchCustom()
Expand Down Expand Up @@ -229,7 +232,11 @@ internal class PlaygroundTestDriver(
): PlaygroundState? {
setup(
testParameters.copyPlaygroundSettings { settings ->
settings[IntegrationTypeSettingsDefinition] = IntegrationType.FlowController
settings.updateConfigurationData { configurationData ->
configurationData.copy(
integrationType = PlaygroundConfigurationData.IntegrationType.FlowController
)
}

customerId?.let { id ->
settings[CustomerSettingsDefinition] = CustomerType.Existing(id)
Expand Down Expand Up @@ -283,7 +290,12 @@ internal class PlaygroundTestDriver(

setup(
testParameters.copyPlaygroundSettings { settings ->
settings[IntegrationTypeSettingsDefinition] = IntegrationType.FlowController
settings.updateConfigurationData { configurationData ->
configurationData.copy(
integrationType = PlaygroundConfigurationData.IntegrationType.FlowController
)
}

settings[CustomerSettingsDefinition] = CustomerType.Existing(customerId)
}
)
Expand Down Expand Up @@ -605,7 +617,11 @@ internal class PlaygroundTestDriver(
) {
setup(
testParameters.copyPlaygroundSettings { settings ->
settings[IntegrationTypeSettingsDefinition] = IntegrationType.FlowController
settings.updateConfigurationData { configurationData ->
configurationData.copy(
integrationType = PlaygroundConfigurationData.IntegrationType.FlowController
)
}
}
)
launchCustom()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import com.stripe.android.paymentsheet.example.playground.activity.FawryActivity
import com.stripe.android.paymentsheet.example.playground.activity.QrCodeActivity
import com.stripe.android.paymentsheet.example.playground.settings.CheckoutMode
import com.stripe.android.paymentsheet.example.playground.settings.InitializationType
import com.stripe.android.paymentsheet.example.playground.settings.IntegrationType
import com.stripe.android.paymentsheet.example.playground.settings.PlaygroundConfigurationData
import com.stripe.android.paymentsheet.example.playground.settings.PlaygroundSettings
import com.stripe.android.paymentsheet.example.playground.settings.SettingsUi
import com.stripe.android.paymentsheet.example.samples.ui.shared.BuyButton
Expand Down Expand Up @@ -223,14 +223,14 @@ internal class PaymentSheetPlaygroundActivity : AppCompatActivity(), ExternalPay
)

when (playgroundState.integrationType) {
IntegrationType.PaymentSheet -> {
PlaygroundConfigurationData.IntegrationType.PaymentSheet -> {
PaymentSheetUi(
paymentSheet = paymentSheet,
playgroundState = playgroundState,
)
}

IntegrationType.FlowController -> {
PlaygroundConfigurationData.IntegrationType.FlowController -> {
FlowControllerUi(
flowController = flowController,
playgroundState = playgroundState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import com.stripe.android.paymentsheet.example.playground.settings.CheckoutModeS
import com.stripe.android.paymentsheet.example.playground.settings.CountrySettingsDefinition
import com.stripe.android.paymentsheet.example.playground.settings.CurrencySettingsDefinition
import com.stripe.android.paymentsheet.example.playground.settings.InitializationTypeSettingsDefinition
import com.stripe.android.paymentsheet.example.playground.settings.IntegrationTypeSettingsDefinition
import com.stripe.android.paymentsheet.example.playground.settings.PaymentMethodConfigurationSettingsDefinition
import com.stripe.android.paymentsheet.example.playground.settings.PlaygroundSettings

Expand All @@ -24,7 +23,7 @@ internal data class PlaygroundState(
val currencyCode = snapshot[CurrencySettingsDefinition]
val countryCode = snapshot[CountrySettingsDefinition]
val checkoutMode = snapshot[CheckoutModeSettingsDefinition]
val integrationType = snapshot[IntegrationTypeSettingsDefinition]
val integrationType = snapshot.configurationData.integrationType
val paymentMethodConfigurationId: String? = snapshot[PaymentMethodConfigurationSettingsDefinition].ifEmpty { null }

val stripeIntentId: String
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.stripe.android.paymentsheet.example.playground.settings

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class PlaygroundConfigurationData(
val integrationType: IntegrationType = IntegrationType.PaymentSheet,
) {
@Serializable
enum class IntegrationType {
@SerialName("paymentSheet")
PaymentSheet,

@SerialName("flowController")
FlowController,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ internal interface PlaygroundSettingDefinition<T> {

fun valueUpdated(value: T, playgroundSettings: PlaygroundSettings) {}

fun applicable(configurationData: PlaygroundConfigurationData): Boolean = true

fun saveable(): Saveable<T>? {
@Suppress("UNCHECKED_CAST")
return this as? Saveable<T>?
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,35 @@
package com.stripe.android.paymentsheet.example.playground.settings

import android.content.Context
import android.util.Log
import androidx.compose.runtime.Stable
import androidx.core.content.edit
import com.stripe.android.paymentsheet.PaymentSheet
import com.stripe.android.paymentsheet.example.playground.PlaygroundState
import com.stripe.android.paymentsheet.example.playground.model.CheckoutRequest
import com.stripe.android.uicore.utils.mapAsStateFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationException
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive

internal class PlaygroundSettings private constructor(
initialConfigurationData: PlaygroundConfigurationData,
private val settings: MutableMap<PlaygroundSettingDefinition<*>, MutableStateFlow<Any?>>
) {
private val _configurationData = MutableStateFlow(initialConfigurationData)
val configurationData = _configurationData.asStateFlow()

val displayableDefinitions = _configurationData.mapAsStateFlow { data ->
settings
.filterKeys { it.applicable(data) }
.map { (definition, _) -> definition }
.filterIsInstance<PlaygroundSettingDefinition.Displayable<*>>()
}

operator fun <T> get(settingsDefinition: PlaygroundSettingDefinition<T>): StateFlow<T> {
@Suppress("UNCHECKED_CAST")
return settings[settingsDefinition]?.asStateFlow() as StateFlow<T>
Expand All @@ -31,15 +44,23 @@ internal class PlaygroundSettings private constructor(
settingsDefinition.valueUpdated(value, this)
}

fun updateConfigurationData(
updater: (PlaygroundConfigurationData) -> PlaygroundConfigurationData
) {
_configurationData.value = updater(_configurationData.value)
}

fun snapshot(): Snapshot {
return Snapshot(this)
}

@Stable
class Snapshot private constructor(
val configurationData: PlaygroundConfigurationData,
private val settings: Map<PlaygroundSettingDefinition<*>, Any?>
) {
constructor(playgroundSettings: PlaygroundSettings) : this(
playgroundSettings.configurationData.value,
playgroundSettings.settings.map { it.key to it.value.value }.toMap()
)

Expand All @@ -52,17 +73,19 @@ internal class PlaygroundSettings private constructor(
val mutableSettings = settings.map {
it.key to MutableStateFlow(it.value)
}.toMap().toMutableMap()
return PlaygroundSettings(mutableSettings)
return PlaygroundSettings(configurationData, mutableSettings)
}

fun paymentSheetConfiguration(
playgroundState: PlaygroundState
): PaymentSheet.Configuration {
val builder = PaymentSheet.Configuration.Builder("Example, Inc.")
val configurationData =
val paymentSheetConfigurationData =
PlaygroundSettingDefinition.PaymentSheetConfigurationData(builder)
settings.onEach { (settingDefinition, value) ->
settingDefinition.configure(value, builder, playgroundState, configurationData)
settings.filter { (definition, _) ->
definition.applicable(configurationData)
}.onEach { (settingDefinition, value) ->
settingDefinition.configure(value, builder, playgroundState, paymentSheetConfigurationData)
}
return builder.build()
}
Expand All @@ -84,7 +107,9 @@ internal class PlaygroundSettings private constructor(

fun checkoutRequest(): CheckoutRequest {
val builder = CheckoutRequest.Builder()
settings.onEach { (settingDefinition, value) ->
settings.filter { (definition, _) ->
definition.applicable(configurationData)
}.onEach { (settingDefinition, value) ->
settingDefinition.configure(builder, value)
}
return builder.build()
Expand All @@ -102,12 +127,17 @@ internal class PlaygroundSettings private constructor(
val settingsMap = settings.filterKeys(filter).map {
val saveable = it.key.saveable()
if (saveable != null) {
saveable.key to JsonPrimitive(saveable.convertToString(it.value))
saveable.key to saveable.convertToString(it.value)
} else {
null
}
}.filterNotNull().toMap()
return Json.encodeToString(JsonObject(settingsMap))
return Json.encodeToString(
SerializableSettings(
configurationData = configurationData,
settings = settingsMap,
)
)
}

fun saveToSharedPreferences(context: Context) {
Expand Down Expand Up @@ -136,38 +166,48 @@ internal class PlaygroundSettings private constructor(
}
}

@Serializable
private class SerializableSettings(
val configurationData: PlaygroundConfigurationData,
val settings: Map<String, String>,
)

companion object {
private const val sharedPreferencesName = "PlaygroundSettings"
private const val sharedPreferencesKey = "json"

fun createFromDefaults(): PlaygroundSettings {
val defaultConfigurationData = PlaygroundConfigurationData()
val settings = allSettingDefinitions.associateWith { settingDefinition ->
MutableStateFlow(settingDefinition.defaultValue)
}.toMutableMap()
return PlaygroundSettings(settings)
return PlaygroundSettings(defaultConfigurationData, settings)
}

fun createFromJsonString(jsonString: String): PlaygroundSettings {
val settings: MutableMap<PlaygroundSettingDefinition<*>, MutableStateFlow<Any?>> =
mutableMapOf()
val jsonObject = Json.decodeFromString(JsonObject.serializer(), jsonString)
val settings: MutableMap<PlaygroundSettingDefinition<*>, MutableStateFlow<Any?>> = mutableMapOf()

val unserializedSettings = try {
Json.decodeFromString(SerializableSettings.serializer(), jsonString)
} catch (exception: SerializationException) {
Log.e("PlaygroundParsingError", exception.message ?: exception::class.java.name)

return createFromDefaults()
}

for (settingDefinition in allSettingDefinitions) {
val saveable = settingDefinition.saveable()
if (saveable != null) {
val jsonPrimitive = jsonObject[saveable.key] as? JsonPrimitive?
if (jsonPrimitive?.isString == true) {
settings[settingDefinition] =
MutableStateFlow(saveable.convertToValue(jsonPrimitive.content))
} else {
settings[settingDefinition] = MutableStateFlow(settingDefinition.defaultValue)
}
} else {
settingDefinition.saveable()?.let { saveable ->
val value = unserializedSettings.settings[saveable.key]?.let { stringValue ->
saveable.convertToValue(stringValue)
} ?: saveable.defaultValue

settings[settingDefinition] = MutableStateFlow(value)
} ?: run {
settings[settingDefinition] = MutableStateFlow(settingDefinition.defaultValue)
}
}

return PlaygroundSettings(settings)
return PlaygroundSettings(unserializedSettings.configurationData, settings)
}

fun createFromSharedPreferences(context: Context): PlaygroundSettings {
Expand All @@ -182,7 +222,7 @@ internal class PlaygroundSettings private constructor(
return createFromJsonString(jsonString)
}

val uiSettingDefinitions: List<PlaygroundSettingDefinition.Displayable<*>> = listOf(
private val uiSettingDefinitions: List<PlaygroundSettingDefinition.Displayable<*>> = listOf(
InitializationTypeSettingsDefinition,
CustomerSessionSettingsDefinition,
CustomerSettingsDefinition,
Expand All @@ -208,7 +248,6 @@ internal class PlaygroundSettings private constructor(
PaymentMethodOrderSettingsDefinition,
ExternalPaymentMethodSettingsDefinition,
LayoutSettingsDefinition,
IntegrationTypeSettingsDefinition,
)

private val nonUiSettingDefinitions: List<PlaygroundSettingDefinition<*>> = listOf(
Expand Down
Loading

0 comments on commit 59d7977

Please sign in to comment.