Skip to content

Commit

Permalink
[connect] Init Account Onboarding settings; XML layout POC (#9769)
Browse files Browse the repository at this point in the history
* init onboarding settings; refactor settings nav graph

* init full terms of service saving

* add more fields

* pass props to webview

* prove XML layout usage

* lint

* resource lint

* tweak AppearanceView UI

* add previews

* cleanup

* rm unused

* refactor API to prevent updating props after init

* string resources

* cleanup and comments

* fix tests

* lint
  • Loading branch information
lng-stripe authored Dec 13, 2024
1 parent 3d66b3c commit e1b1316
Show file tree
Hide file tree
Showing 24 changed files with 766 additions and 98 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.stripe.android.connect.EmbeddedComponentManager
import com.stripe.android.connect.PrivateBetaConnectSDK
import com.stripe.android.connect.example.core.safeNavigateUp
import com.stripe.android.connect.example.ui.common.ConnectSdkExampleTheme
import com.stripe.android.connect.example.ui.componentpicker.ComponentPickerContent
import com.stripe.android.connect.example.ui.embeddedcomponentmanagerloader.EmbeddedComponentLoaderViewModel
import com.stripe.android.connect.example.ui.settings.SettingsView
import com.stripe.android.connect.example.ui.settings.SettingsViewModel
import com.stripe.android.connect.example.ui.settings.SettingsDestination
import com.stripe.android.connect.example.ui.settings.settingsComposables
import dagger.hilt.android.AndroidEntryPoint

@OptIn(PrivateBetaConnectSDK::class)
Expand All @@ -27,24 +26,17 @@ class MainActivity : ComponentActivity() {
EmbeddedComponentManager.onActivityCreate(this@MainActivity)

setContent {
val viewModel = hiltViewModel<EmbeddedComponentLoaderViewModel>()
val viewModel = hiltViewModel<EmbeddedComponentLoaderViewModel>(this@MainActivity)
val navController = rememberNavController()
ConnectSdkExampleTheme {
NavHost(navController = navController, startDestination = MainDestination.ComponentPicker) {
composable(MainDestination.ComponentPicker) {
ComponentPickerContent(
viewModel = viewModel,
openSettings = { navController.navigate(route = MainDestination.Settings) },
)
}
composable(MainDestination.Settings) {
val settingsViewModel = hiltViewModel<SettingsViewModel>()
SettingsView(
viewModel = settingsViewModel,
onDismiss = { navController.safeNavigateUp() },
onReloadRequested = viewModel::reload,
openSettings = { navController.navigate(SettingsDestination.Settings) },
)
}
settingsComposables(this@MainActivity, navController)
}
}
}
Expand All @@ -54,5 +46,4 @@ class MainActivity : ComponentActivity() {
@Suppress("ConstPropertyName")
private object MainDestination {
const val ComponentPicker = "ComponentPicker"
const val Settings = "Settings"
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ class SettingsService @Inject constructor(@ApplicationContext context: Context)
return PresentationSettings(
presentationStyleIsPush = !sharedPreferences.getBoolean(PRESENTATION_IS_MODAL, false),
embedInTabBar = sharedPreferences.getBoolean(EMBED_IN_TABBAR, false),
embedInNavBar = !sharedPreferences.getBoolean(DISABLE_EMBED_IN_NAVBAR, false)
embedInNavBar = !sharedPreferences.getBoolean(DISABLE_EMBED_IN_NAVBAR, false),
useXmlViews = sharedPreferences.getBoolean(USE_XML_VIEWS, false)
)
}

Expand All @@ -97,6 +98,7 @@ class SettingsService @Inject constructor(@ApplicationContext context: Context)
putBoolean(PRESENTATION_IS_MODAL, !value.presentationStyleIsPush)
putBoolean(EMBED_IN_TABBAR, value.embedInTabBar)
putBoolean(DISABLE_EMBED_IN_NAVBAR, !value.embedInNavBar)
putBoolean(USE_XML_VIEWS, value.useXmlViews)
}
}

Expand Down Expand Up @@ -140,6 +142,7 @@ class SettingsService @Inject constructor(@ApplicationContext context: Context)
private const val PRESENTATION_IS_MODAL = "PresentationIsModal"
private const val DISABLE_EMBED_IN_NAVBAR = "DisableEmbedInNavbar"
private const val EMBED_IN_TABBAR = "EmbedInTabbar"
private const val USE_XML_VIEWS = "UseXmlViews"
private const val ONBOARDING_TERMS_OF_SERVICE_URL = "OnboardingTermsOfServiceURL"
private const val ONBOARDING_RECIPIENT_TERMS_OF_SERVICE_STRING = "OnboardingRecipientTermsOfServiceString"
private const val ONBOARDING_PRIVACY_POLICY_STRING = "OnboardingPrivacyPolicyString"
Expand All @@ -150,18 +153,19 @@ class SettingsService @Inject constructor(@ApplicationContext context: Context)
}

data class OnboardingSettings(
val fullTermsOfServiceString: String?,
val recipientTermsOfServiceString: String?,
val privacyPolicyString: String?,
val skipTermsOfService: SkipTermsOfService,
val fieldOption: FieldOption,
val futureRequirement: FutureRequirement
val fullTermsOfServiceString: String? = null,
val recipientTermsOfServiceString: String? = null,
val privacyPolicyString: String? = null,
val skipTermsOfService: SkipTermsOfService = SkipTermsOfService.DEFAULT,
val fieldOption: FieldOption = FieldOption.DEFAULT,
val futureRequirement: FutureRequirement = FutureRequirement.DEFAULT,
)

data class PresentationSettings(
val presentationStyleIsPush: Boolean,
val embedInTabBar: Boolean,
val embedInNavBar: Boolean
val presentationStyleIsPush: Boolean = false,
val embedInTabBar: Boolean = false,
val embedInNavBar: Boolean = false,
val useXmlViews: Boolean = false,
)

enum class SkipTermsOfService { DEFAULT, SKIP, SHOW }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.stripe.android.connect.example.ui.appearance

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
Expand Down Expand Up @@ -70,24 +69,24 @@ private fun SelectAnAppearance(
onAppearanceSelected: (AppearanceInfo.AppearanceId) -> Unit,
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
modifier = Modifier.padding(vertical = 16.dp),
) {
appearances.forEach { appearance ->
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier
.clickable { onAppearanceSelected(appearance) }
.fillMaxWidth()
.clickable { onAppearanceSelected(appearance) },
.padding(vertical = 8.dp, horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
RadioButton(
selected = appearance == selectedAppearance,
onClick = null, // onClick handled by row
)
Column {
Text(text = stringResource(appearance.displayNameRes))
}
Text(
modifier = Modifier.padding(start = 16.dp),
text = stringResource(appearance.displayNameRes)
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,12 @@ import androidx.navigation.compose.rememberNavController
import com.stripe.android.connect.EmbeddedComponentManager
import com.stripe.android.connect.PrivateBetaConnectSDK
import com.stripe.android.connect.example.core.Success
import com.stripe.android.connect.example.core.safeNavigateUp
import com.stripe.android.connect.example.core.then
import com.stripe.android.connect.example.ui.appearance.AppearanceView
import com.stripe.android.connect.example.ui.appearance.AppearanceViewModel
import com.stripe.android.connect.example.ui.embeddedcomponentmanagerloader.EmbeddedComponentLoaderViewModel
import com.stripe.android.connect.example.ui.embeddedcomponentmanagerloader.EmbeddedComponentManagerLoader
import com.stripe.android.connect.example.ui.settings.SettingsView
import com.stripe.android.connect.example.ui.settings.SettingsViewModel
import com.stripe.android.connect.example.ui.settings.settingsComposables
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch

Expand All @@ -59,7 +57,7 @@ abstract class BasicExampleComponentActivity : FragmentActivity() {

setContent {
BackHandler(onBack = ::finish)
val viewModel = hiltViewModel<EmbeddedComponentLoaderViewModel>()
val viewModel = hiltViewModel<EmbeddedComponentLoaderViewModel>(this@BasicExampleComponentActivity)
val navController = rememberNavController()
ConnectSdkExampleTheme {
NavHost(navController = navController, startDestination = BasicComponentExampleDestination.Component) {
Expand All @@ -69,14 +67,7 @@ abstract class BasicExampleComponentActivity : FragmentActivity() {
openSettings = { navController.navigate(BasicComponentExampleDestination.Settings) },
)
}
composable(BasicComponentExampleDestination.Settings) {
val settingsViewModel = hiltViewModel<SettingsViewModel>()
SettingsView(
viewModel = settingsViewModel,
onDismiss = { navController.safeNavigateUp() },
onReloadRequested = viewModel::reload,
)
}
settingsComposables(this@BasicExampleComponentActivity, navController)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,52 @@
package com.stripe.android.connect.example.ui.features.accountonboarding

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import androidx.activity.viewModels
import com.stripe.android.connect.AccountOnboardingProps
import com.stripe.android.connect.EmbeddedComponentManager
import com.stripe.android.connect.PrivateBetaConnectSDK
import com.stripe.android.connect.example.R
import com.stripe.android.connect.example.databinding.ViewAccountOnboardingExampleBinding
import com.stripe.android.connect.example.ui.common.BasicExampleComponentActivity
import com.stripe.android.connect.example.ui.settings.SettingsViewModel
import dagger.hilt.android.AndroidEntryPoint

@OptIn(PrivateBetaConnectSDK::class)
@AndroidEntryPoint
class AccountOnboardingExampleActivity : BasicExampleComponentActivity() {
override val titleRes: Int = R.string.account_onboarding

private val settingsViewModel by viewModels<SettingsViewModel>()

override fun createComponentView(context: Context, embeddedComponentManager: EmbeddedComponentManager): View {
return embeddedComponentManager.createAccountOnboardingView(context)
val settings = settingsViewModel.state.value
val onboardingSettings = settings.onboardingSettings
val settingsProps = AccountOnboardingProps(
fullTermsOfServiceUrl = onboardingSettings.fullTermsOfServiceString,
recipientTermsOfServiceUrl = onboardingSettings.recipientTermsOfServiceString,
privacyPolicyUrl = onboardingSettings.privacyPolicyString,
)
return if (settings.presentationSettings.useXmlViews) {
ViewAccountOnboardingExampleBinding.inflate(LayoutInflater.from(context)).root
.apply {
val props = AccountOnboardingProps(
fullTermsOfServiceUrl = settingsProps.fullTermsOfServiceUrl,
recipientTermsOfServiceUrl = settingsProps.recipientTermsOfServiceUrl,
privacyPolicyUrl = settingsProps.privacyPolicyUrl,
)
initialize(
embeddedComponentManager = embeddedComponentManager,
listener = null,
props = props
)
}
} else {
embeddedComponentManager.createAccountOnboardingView(
context = context,
props = settingsProps
)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,45 @@
package com.stripe.android.connect.example.ui.features.payouts

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.widget.Toast
import androidx.activity.viewModels
import com.stripe.android.connect.EmbeddedComponentManager
import com.stripe.android.connect.EmptyProps
import com.stripe.android.connect.PayoutsListener
import com.stripe.android.connect.PrivateBetaConnectSDK
import com.stripe.android.connect.example.R
import com.stripe.android.connect.example.databinding.ViewPayoutsExampleBinding
import com.stripe.android.connect.example.ui.common.BasicExampleComponentActivity
import com.stripe.android.connect.example.ui.settings.SettingsViewModel
import dagger.hilt.android.AndroidEntryPoint

@OptIn(PrivateBetaConnectSDK::class)
@AndroidEntryPoint
class PayoutsExampleActivity : BasicExampleComponentActivity() {
override val titleRes: Int = R.string.payouts

private val settingsViewModel by viewModels<SettingsViewModel>()

override fun createComponentView(context: Context, embeddedComponentManager: EmbeddedComponentManager): View {
return embeddedComponentManager.createPayoutsView(
context = context,
listener = Listener(),
)
val settings = settingsViewModel.state.value
val listener = Listener()
return if (settings.presentationSettings.useXmlViews) {
ViewPayoutsExampleBinding.inflate(LayoutInflater.from(context)).root
.apply {
initialize(
embeddedComponentManager = embeddedComponentManager,
listener = listener,
props = EmptyProps
)
}
} else {
embeddedComponentManager.createPayoutsView(
context = context,
listener = listener,
)
}
}

private inner class Listener : PayoutsListener {
Expand Down
Loading

0 comments on commit e1b1316

Please sign in to comment.