Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -23,9 +23,10 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.stripe.android.common.ui.ElementsBottomSheetLayout
import com.stripe.android.paymentsheet.PaymentSheet
import com.stripe.android.paymentsheet.addresselement.AddressElementResultStateHolder.State
import com.stripe.android.paymentsheet.parseAppearance
import com.stripe.android.uicore.StripeTheme
import com.stripe.android.paymentsheet.ui.PaymentElementTheme
import com.stripe.android.uicore.elements.bottomsheet.StripeBottomSheetState
import com.stripe.android.uicore.elements.bottomsheet.rememberStripeBottomSheetState
import com.stripe.android.uicore.utils.fadeOut
Expand Down Expand Up @@ -96,7 +97,7 @@ internal class AddressElementActivity : ComponentActivity() {
bottomSheetState: StripeBottomSheetState,
navController: NavHostController,
) {
StripeTheme {
PaymentElementTheme(appearance = starterArgs?.config?.appearance ?: PaymentSheet.Appearance()) {
ElementsBottomSheetLayout(
state = bottomSheetState,
onDismissed = viewModel.resultStateHolder::onUserCancel,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import androidx.annotation.VisibleForTesting
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
Expand Down Expand Up @@ -47,6 +46,7 @@ import com.stripe.android.uicore.getOuterFormInsets
import com.stripe.android.uicore.shouldUseDarkDynamicColor
import com.stripe.android.uicore.stripeColors
import com.stripe.android.uicore.stripeFormInsets
import com.stripe.android.uicore.stripeThemeIsDark
import com.stripe.android.uicore.text.annotatedStringResource
import com.stripe.android.uicore.utils.collectAsState
import kotlinx.coroutines.flow.collectLatest
Expand Down Expand Up @@ -79,7 +79,7 @@ internal fun AutocompleteScreenUI(
viewModel: AutocompleteViewModel,
navigator: AddressElementNavigator,
attributionDrawable: Int? =
PlacesClientProxy.getPlacesPoweredByGoogleDrawable(isSystemInDarkTheme())
PlacesClientProxy.getPlacesPoweredByGoogleDrawable(MaterialTheme.stripeThemeIsDark)
) {
LaunchedEffect(Unit) {
viewModel.event.collectLatest { event ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,10 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import com.stripe.android.common.ui.PrimaryButton
import com.stripe.android.core.strings.ResolvableString
import com.stripe.android.core.strings.resolvableString
import com.stripe.android.paymentsheet.PaymentSheet
import com.stripe.android.paymentsheet.R
import com.stripe.android.paymentsheet.injection.InputAddressViewModelSubcomponent
import com.stripe.android.paymentsheet.ui.AddressOptionsAppBar
import com.stripe.android.paymentsheet.ui.ErrorMessage
import com.stripe.android.paymentsheet.ui.PaymentElementTheme
import com.stripe.android.ui.core.FormUI
import com.stripe.android.uicore.elements.CheckboxElementUI
import com.stripe.android.uicore.getOuterFormInsets
Expand All @@ -37,7 +35,6 @@ import javax.inject.Provider

@Composable
internal fun InputAddressScreen(
appearance: PaymentSheet.Appearance,
primaryButtonEnabled: Boolean,
primaryButtonLoading: Boolean,
closeButtonEnabled: Boolean,
Expand All @@ -52,63 +49,61 @@ internal fun InputAddressScreen(
saveError: ResolvableString?,
) {
val focusManager = LocalFocusManager.current
PaymentElementTheme(appearance = appearance) {
Scaffold(
modifier = Modifier
.fillMaxHeight()
.imePadding(),
backgroundColor = MaterialTheme.colors.surface,
contentColor = MaterialTheme.colors.onSurface,
topBar = {
AddressOptionsAppBar(
isRootScreen = true,
isEnabled = closeButtonEnabled,
onButtonClick = {
focusManager.clearFocus()
onCloseClick()
}
)
}
Scaffold(
modifier = Modifier
.fillMaxHeight()
.imePadding(),
backgroundColor = MaterialTheme.colors.surface,
contentColor = MaterialTheme.colors.onSurface,
topBar = {
AddressOptionsAppBar(
isRootScreen = true,
isEnabled = closeButtonEnabled,
onButtonClick = {
focusManager.clearFocus()
onCloseClick()
}
)
}
) {
ScrollableColumn(
modifier = Modifier.padding(it)
) {
ScrollableColumn(
modifier = Modifier.padding(it)
Column(
Modifier
.padding(MaterialTheme.stripeFormInsets.getOuterFormInsets())
.padding(top = MaterialTheme.stripeFormInsets.top.dp)
.padding(bottom = MaterialTheme.stripeFormInsets.bottom.dp)
) {
Column(
Modifier
.padding(MaterialTheme.stripeFormInsets.getOuterFormInsets())
.padding(top = MaterialTheme.stripeFormInsets.top.dp)
.padding(bottom = MaterialTheme.stripeFormInsets.bottom.dp)
) {
Text(
title,
style = MaterialTheme.typography.h4,
modifier = Modifier.padding(bottom = 8.dp)
)
topContent()
formContent()
bottomContent()
saveError?.let {
ErrorMessage(
error = it.resolve(),
modifier = Modifier.padding(top = 2.dp),
)
}
PrimaryButton(
isEnabled = primaryButtonEnabled,
isLoading = primaryButtonLoading,
label = primaryButtonText,
onButtonClick = {
focusManager.clearFocus()
onPrimaryButtonClick()
},
canClickWhileDisabled = !primaryButtonLoading,
onDisabledButtonClick = {
focusManager.clearFocus()
onDisabledButtonClick()
},
modifier = Modifier.padding(vertical = 16.dp),
Text(
title,
style = MaterialTheme.typography.h4,
modifier = Modifier.padding(bottom = 8.dp)
)
topContent()
formContent()
bottomContent()
saveError?.let {
ErrorMessage(
error = it.resolve(),
modifier = Modifier.padding(top = 2.dp),
)
}
PrimaryButton(
isEnabled = primaryButtonEnabled,
isLoading = primaryButtonLoading,
label = primaryButtonText,
onButtonClick = {
focusManager.clearFocus()
onPrimaryButtonClick()
},
canClickWhileDisabled = !primaryButtonLoading,
onDisabledButtonClick = {
focusManager.clearFocus()
onDisabledButtonClick()
},
modifier = Modifier.padding(vertical = 16.dp),
)
}
}
}
Expand Down Expand Up @@ -142,7 +137,6 @@ internal fun InputAddressScreen(
val billingSameAsShippingState by viewModel.shippingSameAsBillingState.collectAsState()

InputAddressScreen(
appearance = viewModel.args.config?.appearance ?: PaymentSheet.Appearance(),
primaryButtonEnabled = completeValues != null && formEnabled,
primaryButtonLoading = !formEnabled,
closeButtonEnabled = formEnabled,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,38 @@ import android.app.Activity
import android.app.Application
import android.content.Intent
import android.os.Bundle
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.semantics.SemanticsActions
import androidx.compose.ui.test.junit4.createEmptyComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performSemanticsAction
import androidx.compose.ui.text.TextLayoutResult
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelProvider
import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ApplicationProvider
import com.google.common.truth.Truth.assertThat
import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodMetadataFixtures.DEFAULT_API_CONFIG
import com.stripe.android.networktesting.NetworkRule
import com.stripe.android.paymentelement.AppearanceAPIAdditionsPreview
import com.stripe.android.paymentsheet.PaymentSheet
import com.stripe.android.paymentsheet.R
import com.stripe.android.paymentsheet.parseAppearance
import com.stripe.android.paymentsheet.repositories.CheckoutSessionResponse
import com.stripe.android.paymentsheet.repositories.CheckoutSessionResponseFactory
import com.stripe.android.testing.createComposeCleanupRule
import com.stripe.android.ui.core.elements.autocomplete.PlacesClientProxy
import com.stripe.paymentelementtestpages.AddressElementPage
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config

@RunWith(RobolectricTestRunner::class)
@OptIn(AppearanceAPIAdditionsPreview::class)
internal class AddressElementActivityTest {
private val applicationContext = ApplicationProvider.getApplicationContext<Application>()
private val composeTestRule = createEmptyComposeRule()
Expand Down Expand Up @@ -187,6 +198,48 @@ internal class AddressElementActivityTest {
assertThat(parsed).isEqualTo(AddressElementActivityContract.Result.Canceled)
}

@Test
@Config(qualifiers = "notnight")
fun `always dark appearance is applied to autocomplete text on a light system`() {
val expectedPrimary = Color(0xFF654321)
val appearance = PaymentSheet.Appearance(
colorsDark = PaymentSheet.Colors.configureDefaultDark(primary = expectedPrimary),
themeMode = PaymentSheet.ThemeMode.AlwaysDark,
)
val previousPlacesClientOverride = PlacesClientProxy.override

try {
PlacesClientProxy.override = mock()
runScenario(
args = AddressElementActivityContract.Args.Standalone(
apiConfiguration = DEFAULT_API_CONFIG,
config = AddressLauncher.Configuration.Builder()
.appearance(appearance)
.build(),
),
) {
composeTestRule.waitForIdle()
activityScenario.onActivity { activity ->
ViewModelProvider(activity)[AddressElementViewModel::class.java]
.navigator
.navigateTo(AddressElementScreen.Autocomplete(country = "US"))
}
composeTestRule.waitForIdle()

val textLayoutResults = mutableListOf<TextLayoutResult>()
composeTestRule.onNodeWithText(
applicationContext.getString(R.string.stripe_paymentsheet_enter_address_manually)
).performSemanticsAction(SemanticsActions.GetTextLayoutResult) { action ->
action(textLayoutResults)
}
assertThat(textLayoutResults.single().layoutInput.style.color).isEqualTo(expectedPrimary)
}
} finally {
PlacesClientProxy.override = previousPlacesClientOverride
PaymentSheet.Appearance().parseAppearance()
}
}

@Test
fun `standalone shipping returns the entered address without a tax update`() = runScenario(
args = AddressElementActivityContract.Args.Standalone(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.stripe.android.core.strings.ResolvableString
import com.stripe.android.core.strings.resolvableString
import com.stripe.android.paymentsheet.PaymentSheet
import com.stripe.android.paymentsheet.R
import com.stripe.android.paymentsheet.ui.PaymentElementTheme
import com.stripe.android.screenshottesting.PaparazziRule
import com.stripe.android.screenshottesting.SystemAppearance
import com.stripe.android.ui.core.FormUI
Expand Down Expand Up @@ -137,31 +138,32 @@ internal class InputAddressScreenScreenshotTest {
)
}

InputAddressScreen(
appearance = appearance,
primaryButtonEnabled = addressFormController.completeFormValues.value != null,
primaryButtonLoading = false,
closeButtonEnabled = true,
primaryButtonText = primaryButtonText,
title = title,
onPrimaryButtonClick = {},
onDisabledButtonClick = {},
onCloseClick = {},
topContent = {},
formContent = {
if (scrollToBottom) {
ScrollToBottom()
}
FormUI(
hiddenIdentifiers = emptySet(),
enabled = true,
elements = addressFormController.elements,
lastTextFieldIdentifier = addressFormController.lastTextFieldIdentifier.value,
)
},
bottomContent = {},
saveError = null,
)
PaymentElementTheme(appearance = appearance) {
InputAddressScreen(
primaryButtonEnabled = addressFormController.completeFormValues.value != null,
primaryButtonLoading = false,
closeButtonEnabled = true,
primaryButtonText = primaryButtonText,
title = title,
onPrimaryButtonClick = {},
onDisabledButtonClick = {},
onCloseClick = {},
topContent = {},
formContent = {
if (scrollToBottom) {
ScrollToBottom()
}
FormUI(
hiddenIdentifiers = emptySet(),
enabled = true,
elements = addressFormController.elements,
lastTextFieldIdentifier = addressFormController.lastTextFieldIdentifier.value,
)
},
bottomContent = {},
saveError = null,
)
}
}

@Composable
Expand All @@ -171,28 +173,29 @@ internal class InputAddressScreenScreenshotTest {
) {
val completeValues by formController.completeFormValues.collectAsState()

InputAddressScreen(
appearance = PaymentSheet.Appearance(),
primaryButtonEnabled = completeValues != null,
primaryButtonLoading = false,
closeButtonEnabled = true,
primaryButtonText = stringResource(R.string.stripe_paymentsheet_address_element_primary_button),
title = stringResource(R.string.stripe_paymentsheet_address_element_shipping_address),
onPrimaryButtonClick = {},
onDisabledButtonClick = {},
onCloseClick = {},
topContent = {},
formContent = {
FormUI(
hiddenIdentifiers = emptySet(),
enabled = true,
elements = formController.elements,
lastTextFieldIdentifier = formController.lastTextFieldIdentifier.value,
)
},
bottomContent = {},
saveError = saveError,
)
PaymentElementTheme(appearance = PaymentSheet.Appearance()) {
InputAddressScreen(
primaryButtonEnabled = completeValues != null,
primaryButtonLoading = false,
closeButtonEnabled = true,
primaryButtonText = stringResource(R.string.stripe_paymentsheet_address_element_primary_button),
title = stringResource(R.string.stripe_paymentsheet_address_element_shipping_address),
onPrimaryButtonClick = {},
onDisabledButtonClick = {},
onCloseClick = {},
topContent = {},
formContent = {
FormUI(
hiddenIdentifiers = emptySet(),
enabled = true,
elements = formController.elements,
lastTextFieldIdentifier = formController.lastTextFieldIdentifier.value,
)
},
bottomContent = {},
saveError = saveError,
)
}
}

private fun createFilledFormController() = AddressFormController(
Expand Down
Loading