From 616f488307a16eeba4d9dd806a3cb06309e0f304 Mon Sep 17 00:00:00 2001
From: jameswoo-stripe <99316447+jameswoo-stripe@users.noreply.github.com>
Date: Thu, 30 Jun 2022 13:54:45 -0700
Subject: [PATCH 1/2] Add Autocomplete address screen
---
payments-ui-core/api/payments-ui-core.api | 67 ++++++
payments-ui-core/res/values/colors.xml | 2 +
payments-ui-core/res/values/totranslate.xml | 4 +
.../autocomplete/PlacesClientProxy.kt | 7 +-
.../core/elements/autocomplete/model/Place.kt | 16 +-
.../model/TransformGoogleToStripeAddress.kt | 4 +-
paymentsheet-example/build.gradle | 1 +
paymentsheet/api/paymentsheet.api | 35 ++-
paymentsheet/build.gradle | 1 +
.../addresselement/AutocompleteScreenTest.kt | 174 ++++++++++++++
.../android/paymentsheet/PaymentSheet.kt | 9 +-
.../addresselement/AddressElementActivity.kt | 10 +-
.../addresselement/AddressElementNavigator.kt | 2 +-
.../addresselement/AddressElementViewModel.kt | 2 +-
.../addresselement/AutoCompleteScreen.kt | 44 ----
.../addresselement/AutoCompleteViewModel.kt | 31 ---
.../addresselement/AutocompleteScreen.kt | 219 +++++++++++++++++
.../addresselement/AutocompleteViewModel.kt | 220 ++++++++++++++++++
.../addresselement/InputAddressScreen.kt | 4 +-
.../addresselement/InputAddressViewModel.kt | 18 +-
...AddressElementViewModelFactoryComponent.kt | 4 +-
.../AutoCompleteViewModelSubcomponent.kt | 8 +-
.../AutocompleteViewModelTest.kt | 198 ++++++++++++++++
.../InputAddressViewModelTest.kt | 15 +-
24 files changed, 973 insertions(+), 122 deletions(-)
create mode 100644 paymentsheet/src/androidTest/java/com/stripe/android/paymentsheet/addresselement/AutocompleteScreenTest.kt
delete mode 100644 paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AutoCompleteScreen.kt
delete mode 100644 paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AutoCompleteViewModel.kt
create mode 100644 paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AutocompleteScreen.kt
create mode 100644 paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AutocompleteViewModel.kt
create mode 100644 paymentsheet/src/test/java/com/stripe/android/paymentsheet/addresselement/AutocompleteViewModelTest.kt
diff --git a/payments-ui-core/api/payments-ui-core.api b/payments-ui-core/api/payments-ui-core.api
index bbf447e2cd1..962e999cc17 100644
--- a/payments-ui-core/api/payments-ui-core.api
+++ b/payments-ui-core/api/payments-ui-core.api
@@ -584,6 +584,73 @@ public final class com/stripe/android/ui/core/elements/TranslationId$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
+public final class com/stripe/android/ui/core/elements/autocomplete/PlacesClientProxy$Companion {
+ public final fun create (Landroid/content/Context;Ljava/lang/String;Lcom/stripe/android/ui/core/elements/autocomplete/IsPlacesAvailable;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;)Lcom/stripe/android/ui/core/elements/autocomplete/PlacesClientProxy;
+ public static synthetic fun create$default (Lcom/stripe/android/ui/core/elements/autocomplete/PlacesClientProxy$Companion;Landroid/content/Context;Ljava/lang/String;Lcom/stripe/android/ui/core/elements/autocomplete/IsPlacesAvailable;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lcom/stripe/android/ui/core/elements/autocomplete/PlacesClientProxy;
+ public final fun getPlacesPoweredByGoogleDrawable (ZLcom/stripe/android/ui/core/elements/autocomplete/IsPlacesAvailable;)Ljava/lang/Integer;
+ public static synthetic fun getPlacesPoweredByGoogleDrawable$default (Lcom/stripe/android/ui/core/elements/autocomplete/PlacesClientProxy$Companion;ZLcom/stripe/android/ui/core/elements/autocomplete/IsPlacesAvailable;ILjava/lang/Object;)Ljava/lang/Integer;
+}
+
+public final class com/stripe/android/ui/core/elements/autocomplete/model/AddressComponent$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field $stable I
+ public static final field INSTANCE Lcom/stripe/android/ui/core/elements/autocomplete/model/AddressComponent$$serializer;
+ public static final synthetic field descriptor Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/stripe/android/ui/core/elements/autocomplete/model/AddressComponent;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/stripe/android/ui/core/elements/autocomplete/model/AddressComponent;)V
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class com/stripe/android/ui/core/elements/autocomplete/model/AddressComponent$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class com/stripe/android/ui/core/elements/autocomplete/model/Place$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field $stable I
+ public static final field INSTANCE Lcom/stripe/android/ui/core/elements/autocomplete/model/Place$$serializer;
+ public static final synthetic field descriptor Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/stripe/android/ui/core/elements/autocomplete/model/Place;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/stripe/android/ui/core/elements/autocomplete/model/Place;)V
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class com/stripe/android/ui/core/elements/autocomplete/model/Place$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class com/stripe/android/ui/core/elements/autocomplete/model/Place$Type : java/lang/Enum {
+ public static final field ADMINISTRATIVE_AREA_LEVEL_1 Lcom/stripe/android/ui/core/elements/autocomplete/model/Place$Type;
+ public static final field ADMINISTRATIVE_AREA_LEVEL_2 Lcom/stripe/android/ui/core/elements/autocomplete/model/Place$Type;
+ public static final field ADMINISTRATIVE_AREA_LEVEL_3 Lcom/stripe/android/ui/core/elements/autocomplete/model/Place$Type;
+ public static final field ADMINISTRATIVE_AREA_LEVEL_4 Lcom/stripe/android/ui/core/elements/autocomplete/model/Place$Type;
+ public static final field COUNTRY Lcom/stripe/android/ui/core/elements/autocomplete/model/Place$Type;
+ public static final field LOCALITY Lcom/stripe/android/ui/core/elements/autocomplete/model/Place$Type;
+ public static final field NEIGHBORHOOD Lcom/stripe/android/ui/core/elements/autocomplete/model/Place$Type;
+ public static final field POSTAL_CODE Lcom/stripe/android/ui/core/elements/autocomplete/model/Place$Type;
+ public static final field POSTAL_TOWN Lcom/stripe/android/ui/core/elements/autocomplete/model/Place$Type;
+ public static final field PREMISE Lcom/stripe/android/ui/core/elements/autocomplete/model/Place$Type;
+ public static final field ROUTE Lcom/stripe/android/ui/core/elements/autocomplete/model/Place$Type;
+ public static final field STREET_NUMBER Lcom/stripe/android/ui/core/elements/autocomplete/model/Place$Type;
+ public static final field SUBLOCALITY Lcom/stripe/android/ui/core/elements/autocomplete/model/Place$Type;
+ public static final field SUBLOCALITY_LEVEL_1 Lcom/stripe/android/ui/core/elements/autocomplete/model/Place$Type;
+ public static final field SUBLOCALITY_LEVEL_2 Lcom/stripe/android/ui/core/elements/autocomplete/model/Place$Type;
+ public static final field SUBLOCALITY_LEVEL_3 Lcom/stripe/android/ui/core/elements/autocomplete/model/Place$Type;
+ public static final field SUBLOCALITY_LEVEL_4 Lcom/stripe/android/ui/core/elements/autocomplete/model/Place$Type;
+ public final fun getValue ()Ljava/lang/String;
+ public static fun valueOf (Ljava/lang/String;)Lcom/stripe/android/ui/core/elements/autocomplete/model/Place$Type;
+ public static fun values ()[Lcom/stripe/android/ui/core/elements/autocomplete/model/Place$Type;
+}
+
+public final class com/stripe/android/ui/core/elements/autocomplete/model/TransformGoogleToStripeAddressKt {
+}
+
public final class com/stripe/android/ui/core/elements/menu/CheckboxKt {
}
diff --git a/payments-ui-core/res/values/colors.xml b/payments-ui-core/res/values/colors.xml
index f18761190cd..f963a0a1a88 100644
--- a/payments-ui-core/res/values/colors.xml
+++ b/payments-ui-core/res/values/colors.xml
@@ -49,4 +49,6 @@
+ #1A1A1A0D
+
diff --git a/payments-ui-core/res/values/totranslate.xml b/payments-ui-core/res/values/totranslate.xml
index da7c99a2030..5416b57db03 100644
--- a/payments-ui-core/res/values/totranslate.xml
+++ b/payments-ui-core/res/values/totranslate.xml
@@ -7,4 +7,8 @@
Something went wrong when linking your account.\nPlease try again later.
Pay with your bank account in just a few steps.
Remove bank account
+
+
+ Enter address manually
+ No results found
diff --git a/payments-ui-core/src/main/java/com/stripe/android/ui/core/elements/autocomplete/PlacesClientProxy.kt b/payments-ui-core/src/main/java/com/stripe/android/ui/core/elements/autocomplete/PlacesClientProxy.kt
index 683c15455ae..b922a663ffc 100644
--- a/payments-ui-core/src/main/java/com/stripe/android/ui/core/elements/autocomplete/PlacesClientProxy.kt
+++ b/payments-ui-core/src/main/java/com/stripe/android/ui/core/elements/autocomplete/PlacesClientProxy.kt
@@ -3,6 +3,7 @@ package com.stripe.android.ui.core.elements.autocomplete
import android.content.Context
import android.graphics.Typeface
import android.text.style.StyleSpan
+import androidx.annotation.RestrictTo
import com.google.android.libraries.places.api.Places
import com.google.android.libraries.places.api.model.AutocompleteSessionToken
import com.google.android.libraries.places.api.model.TypeFilter
@@ -18,7 +19,8 @@ import com.stripe.android.ui.core.elements.autocomplete.model.FindAutocompletePr
import com.stripe.android.ui.core.elements.autocomplete.model.Place
import kotlinx.coroutines.tasks.await
-internal interface PlacesClientProxy {
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+interface PlacesClientProxy {
suspend fun findAutocompletePredictions(
query: String?,
country: String,
@@ -166,7 +168,8 @@ internal class UnsupportedPlacesClientProxy : PlacesClientProxy {
}
}
-internal interface IsPlacesAvailable {
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+interface IsPlacesAvailable {
operator fun invoke(): Boolean
}
diff --git a/payments-ui-core/src/main/java/com/stripe/android/ui/core/elements/autocomplete/model/Place.kt b/payments-ui-core/src/main/java/com/stripe/android/ui/core/elements/autocomplete/model/Place.kt
index 8d9967b91c5..4797c041d1b 100644
--- a/payments-ui-core/src/main/java/com/stripe/android/ui/core/elements/autocomplete/model/Place.kt
+++ b/payments-ui-core/src/main/java/com/stripe/android/ui/core/elements/autocomplete/model/Place.kt
@@ -1,25 +1,30 @@
package com.stripe.android.ui.core.elements.autocomplete.model
import android.text.SpannableString
+import androidx.annotation.RestrictTo
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
-internal data class FindAutocompletePredictionsResponse(
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+data class FindAutocompletePredictionsResponse(
val autocompletePredictions: List
)
-internal data class AutocompletePrediction(
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+data class AutocompletePrediction(
val primaryText: SpannableString,
val secondaryText: SpannableString,
val placeId: String
)
-internal data class FetchPlaceResponse(
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+data class FetchPlaceResponse(
val place: Place
)
@Serializable
-internal data class Place(
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+data class Place(
@SerialName("address_components") val addressComponents: List?
) {
enum class Type(val value: String) {
@@ -44,7 +49,8 @@ internal data class Place(
}
@Serializable
-internal data class AddressComponent(
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+data class AddressComponent(
@SerialName("short_name") val shortName: String?,
@SerialName("long_name") val longName: String,
@SerialName("types") val types: List
diff --git a/payments-ui-core/src/main/java/com/stripe/android/ui/core/elements/autocomplete/model/TransformGoogleToStripeAddress.kt b/payments-ui-core/src/main/java/com/stripe/android/ui/core/elements/autocomplete/model/TransformGoogleToStripeAddress.kt
index efc4a765908..e171ca1a0d4 100644
--- a/payments-ui-core/src/main/java/com/stripe/android/ui/core/elements/autocomplete/model/TransformGoogleToStripeAddress.kt
+++ b/payments-ui-core/src/main/java/com/stripe/android/ui/core/elements/autocomplete/model/TransformGoogleToStripeAddress.kt
@@ -2,6 +2,7 @@ package com.stripe.android.ui.core.elements.autocomplete.model
import android.content.Context
import android.os.Build
+import androidx.annotation.RestrictTo
import java.util.Locale
// Largely duplicated from
@@ -176,7 +177,8 @@ internal fun Address.modifyStripeAddressByCountry(place: Place): Address {
return newAddress
}
-internal fun Place.transformGoogleToStripeAddress(
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+fun Place.transformGoogleToStripeAddress(
context: Context
): com.stripe.android.model.Address {
var address = Address()
diff --git a/paymentsheet-example/build.gradle b/paymentsheet-example/build.gradle
index 49facefccdb..f7c8684d888 100644
--- a/paymentsheet-example/build.gradle
+++ b/paymentsheet-example/build.gradle
@@ -16,6 +16,7 @@ dependencies {
implementation project(':stripecardscan')
implementation project(':financial-connections')
+ implementation "com.google.android.libraries.places:places:$placesVersion"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$androidxLifecycleVersion"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$androidxLifecycleVersion"
implementation "androidx.preference:preference-ktx:$androidxPreferenceVersion"
diff --git a/paymentsheet/api/paymentsheet.api b/paymentsheet/api/paymentsheet.api
index b08b6d21dd0..51ffa7952e2 100644
--- a/paymentsheet/api/paymentsheet.api
+++ b/paymentsheet/api/paymentsheet.api
@@ -266,7 +266,8 @@ public final class com/stripe/android/paymentsheet/PaymentSheet$Configuration :
public fun (Ljava/lang/String;Lcom/stripe/android/paymentsheet/PaymentSheet$CustomerConfiguration;Lcom/stripe/android/paymentsheet/PaymentSheet$GooglePayConfiguration;Landroid/content/res/ColorStateList;Lcom/stripe/android/paymentsheet/PaymentSheet$BillingDetails;)V
public fun (Ljava/lang/String;Lcom/stripe/android/paymentsheet/PaymentSheet$CustomerConfiguration;Lcom/stripe/android/paymentsheet/PaymentSheet$GooglePayConfiguration;Landroid/content/res/ColorStateList;Lcom/stripe/android/paymentsheet/PaymentSheet$BillingDetails;Z)V
public fun (Ljava/lang/String;Lcom/stripe/android/paymentsheet/PaymentSheet$CustomerConfiguration;Lcom/stripe/android/paymentsheet/PaymentSheet$GooglePayConfiguration;Landroid/content/res/ColorStateList;Lcom/stripe/android/paymentsheet/PaymentSheet$BillingDetails;ZLcom/stripe/android/paymentsheet/PaymentSheet$Appearance;)V
- public synthetic fun (Ljava/lang/String;Lcom/stripe/android/paymentsheet/PaymentSheet$CustomerConfiguration;Lcom/stripe/android/paymentsheet/PaymentSheet$GooglePayConfiguration;Landroid/content/res/ColorStateList;Lcom/stripe/android/paymentsheet/PaymentSheet$BillingDetails;ZLcom/stripe/android/paymentsheet/PaymentSheet$Appearance;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public fun (Ljava/lang/String;Lcom/stripe/android/paymentsheet/PaymentSheet$CustomerConfiguration;Lcom/stripe/android/paymentsheet/PaymentSheet$GooglePayConfiguration;Landroid/content/res/ColorStateList;Lcom/stripe/android/paymentsheet/PaymentSheet$BillingDetails;ZLcom/stripe/android/paymentsheet/PaymentSheet$Appearance;Ljava/lang/String;)V
+ public synthetic fun (Ljava/lang/String;Lcom/stripe/android/paymentsheet/PaymentSheet$CustomerConfiguration;Lcom/stripe/android/paymentsheet/PaymentSheet$GooglePayConfiguration;Landroid/content/res/ColorStateList;Lcom/stripe/android/paymentsheet/PaymentSheet$BillingDetails;ZLcom/stripe/android/paymentsheet/PaymentSheet$Appearance;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()Lcom/stripe/android/paymentsheet/PaymentSheet$CustomerConfiguration;
public final fun component3 ()Lcom/stripe/android/paymentsheet/PaymentSheet$GooglePayConfiguration;
@@ -274,8 +275,9 @@ public final class com/stripe/android/paymentsheet/PaymentSheet$Configuration :
public final fun component5 ()Lcom/stripe/android/paymentsheet/PaymentSheet$BillingDetails;
public final fun component6 ()Z
public final fun component7 ()Lcom/stripe/android/paymentsheet/PaymentSheet$Appearance;
- public final fun copy (Ljava/lang/String;Lcom/stripe/android/paymentsheet/PaymentSheet$CustomerConfiguration;Lcom/stripe/android/paymentsheet/PaymentSheet$GooglePayConfiguration;Landroid/content/res/ColorStateList;Lcom/stripe/android/paymentsheet/PaymentSheet$BillingDetails;ZLcom/stripe/android/paymentsheet/PaymentSheet$Appearance;)Lcom/stripe/android/paymentsheet/PaymentSheet$Configuration;
- public static synthetic fun copy$default (Lcom/stripe/android/paymentsheet/PaymentSheet$Configuration;Ljava/lang/String;Lcom/stripe/android/paymentsheet/PaymentSheet$CustomerConfiguration;Lcom/stripe/android/paymentsheet/PaymentSheet$GooglePayConfiguration;Landroid/content/res/ColorStateList;Lcom/stripe/android/paymentsheet/PaymentSheet$BillingDetails;ZLcom/stripe/android/paymentsheet/PaymentSheet$Appearance;ILjava/lang/Object;)Lcom/stripe/android/paymentsheet/PaymentSheet$Configuration;
+ public final fun component8 ()Ljava/lang/String;
+ public final fun copy (Ljava/lang/String;Lcom/stripe/android/paymentsheet/PaymentSheet$CustomerConfiguration;Lcom/stripe/android/paymentsheet/PaymentSheet$GooglePayConfiguration;Landroid/content/res/ColorStateList;Lcom/stripe/android/paymentsheet/PaymentSheet$BillingDetails;ZLcom/stripe/android/paymentsheet/PaymentSheet$Appearance;Ljava/lang/String;)Lcom/stripe/android/paymentsheet/PaymentSheet$Configuration;
+ public static synthetic fun copy$default (Lcom/stripe/android/paymentsheet/PaymentSheet$Configuration;Ljava/lang/String;Lcom/stripe/android/paymentsheet/PaymentSheet$CustomerConfiguration;Lcom/stripe/android/paymentsheet/PaymentSheet$GooglePayConfiguration;Landroid/content/res/ColorStateList;Lcom/stripe/android/paymentsheet/PaymentSheet$BillingDetails;ZLcom/stripe/android/paymentsheet/PaymentSheet$Appearance;Ljava/lang/String;ILjava/lang/Object;)Lcom/stripe/android/paymentsheet/PaymentSheet$Configuration;
public fun describeContents ()I
public fun equals (Ljava/lang/Object;)Z
public final fun getAllowsDelayedPaymentMethods ()Z
@@ -798,20 +800,24 @@ public final class com/stripe/android/paymentsheet/addresselement/AddressElement
public static fun injectViewModel (Lcom/stripe/android/paymentsheet/addresselement/AddressElementViewModel$Factory;Lcom/stripe/android/paymentsheet/addresselement/AddressElementViewModel;)V
}
-public final class com/stripe/android/paymentsheet/addresselement/AutoCompleteViewModel_Factory : dagger/internal/Factory {
- public fun (Ljavax/inject/Provider;Ljavax/inject/Provider;)V
- public static fun create (Ljavax/inject/Provider;Ljavax/inject/Provider;)Lcom/stripe/android/paymentsheet/addresselement/AutoCompleteViewModel_Factory;
- public fun get ()Lcom/stripe/android/paymentsheet/addresselement/AutoCompleteViewModel;
+public final class com/stripe/android/paymentsheet/addresselement/AutocompleteScreenKt {
+ public static final field TEST_TAG_ATTRIBUTION_DRAWABLE Ljava/lang/String;
+}
+
+public final class com/stripe/android/paymentsheet/addresselement/AutocompleteViewModel_Factory : dagger/internal/Factory {
+ public fun (Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;)V
+ public static fun create (Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;)Lcom/stripe/android/paymentsheet/addresselement/AutocompleteViewModel_Factory;
+ public fun get ()Lcom/stripe/android/paymentsheet/addresselement/AutocompleteViewModel;
public synthetic fun get ()Ljava/lang/Object;
- public static fun newInstance (Lcom/stripe/android/paymentsheet/addresselement/AddressElementActivityContract$Args;Lcom/stripe/android/paymentsheet/addresselement/AddressElementNavigator;)Lcom/stripe/android/paymentsheet/addresselement/AutoCompleteViewModel;
+ public static fun newInstance (Lcom/stripe/android/paymentsheet/addresselement/AddressElementActivityContract$Args;Lcom/stripe/android/paymentsheet/addresselement/AddressElementNavigator;Landroid/app/Application;)Lcom/stripe/android/paymentsheet/addresselement/AutocompleteViewModel;
}
-public final class com/stripe/android/paymentsheet/addresselement/AutoCompleteViewModel_Factory_MembersInjector : dagger/MembersInjector {
+public final class com/stripe/android/paymentsheet/addresselement/AutocompleteViewModel_Factory_MembersInjector : dagger/MembersInjector {
public fun (Ljavax/inject/Provider;)V
public static fun create (Ljavax/inject/Provider;)Ldagger/MembersInjector;
- public fun injectMembers (Lcom/stripe/android/paymentsheet/addresselement/AutoCompleteViewModel$Factory;)V
+ public fun injectMembers (Lcom/stripe/android/paymentsheet/addresselement/AutocompleteViewModel$Factory;)V
public synthetic fun injectMembers (Ljava/lang/Object;)V
- public static fun injectSubComponentBuilderProvider (Lcom/stripe/android/paymentsheet/addresselement/AutoCompleteViewModel$Factory;Ljavax/inject/Provider;)V
+ public static fun injectSubComponentBuilderProvider (Lcom/stripe/android/paymentsheet/addresselement/AutocompleteViewModel$Factory;Ljavax/inject/Provider;)V
}
public final class com/stripe/android/paymentsheet/addresselement/ComposableSingletons$AddressElementActivityKt {
@@ -821,13 +827,6 @@ public final class com/stripe/android/paymentsheet/addresselement/ComposableSing
public final fun getLambda-1$paymentsheet_release ()Lkotlin/jvm/functions/Function2;
}
-public final class com/stripe/android/paymentsheet/addresselement/ComposableSingletons$AutoCompleteScreenKt {
- public static final field INSTANCE Lcom/stripe/android/paymentsheet/addresselement/ComposableSingletons$AutoCompleteScreenKt;
- public static field lambda-1 Lkotlin/jvm/functions/Function3;
- public fun ()V
- public final fun getLambda-1$paymentsheet_release ()Lkotlin/jvm/functions/Function3;
-}
-
public final class com/stripe/android/paymentsheet/addresselement/ComposableSingletons$InputAddressScreenKt {
public static final field INSTANCE Lcom/stripe/android/paymentsheet/addresselement/ComposableSingletons$InputAddressScreenKt;
public static field lambda-1 Lkotlin/jvm/functions/Function3;
diff --git a/paymentsheet/build.gradle b/paymentsheet/build.gradle
index 8f94030aea4..b3454c7287f 100644
--- a/paymentsheet/build.gradle
+++ b/paymentsheet/build.gradle
@@ -81,6 +81,7 @@ dependencies {
testImplementation "androidx.arch.core:core-testing:$androidxArchCoreVersion"
testImplementation "androidx.fragment:fragment-testing:$androidxFragmentVersion"
+ androidTestImplementation "com.google.android.libraries.places:places:$placesVersion"
androidTestImplementation "androidx.test.ext:junit:$androidTestJunitVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
androidTestImplementation ("androidx.test.espresso:espresso-contrib:$espressoVersion") {
diff --git a/paymentsheet/src/androidTest/java/com/stripe/android/paymentsheet/addresselement/AutocompleteScreenTest.kt b/paymentsheet/src/androidTest/java/com/stripe/android/paymentsheet/addresselement/AutocompleteScreenTest.kt
new file mode 100644
index 00000000000..9e9196f7706
--- /dev/null
+++ b/paymentsheet/src/androidTest/java/com/stripe/android/paymentsheet/addresselement/AutocompleteScreenTest.kt
@@ -0,0 +1,174 @@
+package com.stripe.android.paymentsheet.addresselement
+
+import android.app.Application
+import android.text.SpannableString
+import androidx.activity.ComponentActivity
+import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.onAllNodesWithTag
+import androidx.compose.ui.test.onAllNodesWithText
+import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performTextInput
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.stripe.android.model.parsers.PaymentIntentJsonParser
+import com.stripe.android.paymentsheet.PaymentSheet
+import com.stripe.android.ui.core.DefaultPaymentsTheme
+import com.stripe.android.ui.core.elements.autocomplete.PlacesClientProxy
+import com.stripe.android.ui.core.elements.autocomplete.model.AddressComponent
+import com.stripe.android.ui.core.elements.autocomplete.model.AutocompletePrediction
+import com.stripe.android.ui.core.elements.autocomplete.model.FetchPlaceResponse
+import com.stripe.android.ui.core.elements.autocomplete.model.FindAutocompletePredictionsResponse
+import com.stripe.android.ui.core.elements.autocomplete.model.Place
+import org.json.JSONObject
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@ExperimentalAnimationApi
+@RunWith(AndroidJUnit4::class)
+class AutocompleteScreenTest {
+ @get:Rule
+ val composeTestRule = createAndroidComposeRule()
+
+ private val paymentIntent = requireNotNull(
+ PaymentIntentJsonParser().parse(
+ JSONObject(
+ """
+ {
+ "id": "pi_1IRg6VCRMbs6F",
+ "object": "payment_intent",
+ "amount": 1099,
+ "canceled_at": null,
+ "cancellation_reason": null,
+ "capture_method": "automatic",
+ "client_secret": "pi_1IRg6VCRMbs6F_secret_7oH5g4v8GaCrHfsGYS6kiSnwF",
+ "confirmation_method": "automatic",
+ "created": 1614960135,
+ "currency": "usd",
+ "description": "Example PaymentIntent",
+ "last_payment_error": null,
+ "livemode": false,
+ "next_action": null,
+ "payment_method": "pm_1IJs3ZCRMbs",
+ "payment_method_types": ["card"],
+ "receipt_email": null,
+ "setup_future_usage": null,
+ "shipping": null,
+ "source": null,
+ "status": "succeeded"
+ }
+ """.trimIndent()
+ )
+ )
+ )
+
+ private val args = AddressElementActivityContract.Args(
+ paymentIntent,
+ PaymentSheet.Configuration(
+ merchantDisplayName = "Merchant, Inc.",
+ customer = PaymentSheet.CustomerConfiguration(
+ "customer_id",
+ "ephemeral_key"
+ )
+ ),
+ AddressElementActivityContract.Args.InjectionParams(
+ "injectorKey",
+ setOf("Product Usage"),
+ true
+ )
+ )
+ private val application = ApplicationProvider.getApplicationContext()
+
+ @Test
+ fun ensure_elements_exist() {
+ setContent()
+ onAddressOptionsAppBar().assertExists()
+ onQueryField().assertExists()
+ onEnterAddressManually().assertExists()
+ }
+
+ @Test
+ fun no_results_found_should_appear() {
+ setContent()
+ onQueryField().performTextInput("Some text")
+ composeTestRule.waitUntil {
+ composeTestRule
+ .onAllNodesWithText("No results found")
+ .fetchSemanticsNodes().size == 1
+ }
+ composeTestRule.waitUntil {
+ composeTestRule
+ .onAllNodesWithTag(TEST_TAG_ATTRIBUTION_DRAWABLE)
+ .fetchSemanticsNodes().size == 1
+ }
+ }
+
+ @Test
+ fun results_found_should_appear() {
+ setContent(
+ mockClient = FakeGooglePlacesClient(
+ predictions = listOf(
+ AutocompletePrediction(
+ SpannableString("primaryText"),
+ SpannableString("secondaryText"),
+ "placeId"
+ )
+ )
+ )
+ )
+ onQueryField().performTextInput("Some text")
+ composeTestRule.waitUntil {
+ composeTestRule
+ .onAllNodesWithText("primaryText")
+ .fetchSemanticsNodes().size == 1
+ }
+ }
+
+ private fun setContent(
+ mockClient: FakeGooglePlacesClient = FakeGooglePlacesClient()
+ ) =
+ composeTestRule.setContent {
+ DefaultPaymentsTheme {
+ AutocompleteTextField(
+ viewModel = AutocompleteViewModel(
+ args,
+ AddressElementNavigator(),
+ application
+ ).apply {
+ initialize {
+ mockClient
+ }
+ }
+ )
+ }
+ }
+
+ private fun onAddressOptionsAppBar() = composeTestRule.onNodeWithContentDescription("Back")
+ private fun onQueryField() = composeTestRule.onNodeWithText("Address")
+ private fun onEnterAddressManually() = composeTestRule.onNodeWithText("Enter address manually")
+
+ private class FakeGooglePlacesClient(
+ private val predictions: List = listOf(),
+ private val addressComponents: List = listOf()
+ ) : PlacesClientProxy {
+ override suspend fun findAutocompletePredictions(
+ query: String?,
+ country: String,
+ limit: Int
+ ): Result {
+ return Result.success(
+ FindAutocompletePredictionsResponse(predictions)
+ )
+ }
+
+ override suspend fun fetchPlace(placeId: String): Result {
+ return Result.success(
+ FetchPlaceResponse(
+ Place(addressComponents)
+ )
+ )
+ }
+ }
+}
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheet.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheet.kt
index ba8ec99d2f0..3cf0e5f75e0 100644
--- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheet.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheet.kt
@@ -6,6 +6,7 @@ import android.os.Parcelable
import androidx.activity.ComponentActivity
import androidx.annotation.ColorInt
import androidx.annotation.FontRes
+import androidx.annotation.RestrictTo
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.fragment.app.Fragment
@@ -141,7 +142,13 @@ class PaymentSheet internal constructor(
/**
* Describes the appearance of Payment Sheet.
*/
- val appearance: Appearance = Appearance()
+ val appearance: Appearance = Appearance(),
+
+ /**
+ * Google Places API key used for autocomplete addresses.
+ */
+ @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ val googlePlacesApiKey: String? = null
) : Parcelable {
/**
* [Configuration] builder for cleaner object creation from Java.
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AddressElementActivity.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AddressElementActivity.kt
index 0417df0ae1c..79a98a0e8a6 100644
--- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AddressElementActivity.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AddressElementActivity.kt
@@ -8,6 +8,7 @@ import androidx.activity.viewModels
import androidx.annotation.VisibleForTesting
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.ModalBottomSheetLayout
import androidx.compose.material.ModalBottomSheetValue
@@ -17,7 +18,9 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
+import androidx.core.view.WindowCompat
import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import com.google.accompanist.navigation.animation.composable
import com.google.accompanist.navigation.animation.AnimatedNavHost
@@ -47,6 +50,8 @@ internal class AddressElementActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ WindowCompat.setDecorFitsSystemWindows(window, false)
+
// set a default result in case the user closes the sheet manually
setResult()
@@ -92,13 +97,14 @@ internal class AddressElementActivity : ComponentActivity() {
InputAddressScreen(viewModel.injector)
}
composable(AddressElementScreen.Autocomplete.route) {
- AutoCompleteScreen(viewModel.injector)
+ AutocompleteScreen(viewModel.injector)
}
}
}
}
},
- content = {}
+ content = {},
+ modifier = Modifier.navigationBarsPadding()
)
}
}
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AddressElementNavigator.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AddressElementNavigator.kt
index dfdaebde9dc..071fc7a68bb 100644
--- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AddressElementNavigator.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AddressElementNavigator.kt
@@ -18,7 +18,7 @@ internal class AddressElementNavigator @Inject constructor() {
target: AddressElementScreen
) = navigationController?.navigate(target.route)
- fun setResult(key: String, value: Any) =
+ fun setResult(key: String, value: Any?) =
navigationController?.previousBackStackEntry?.savedStateHandle?.set(key, value)
fun getResultFlow(key: String) =
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AddressElementViewModel.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AddressElementViewModel.kt
index 70a4116ee3b..760f1dfdc4a 100644
--- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AddressElementViewModel.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AddressElementViewModel.kt
@@ -85,7 +85,7 @@ internal class AddressElementViewModel @Inject internal constructor(
when (injectable) {
is Factory -> viewModelComponent.inject(injectable)
is InputAddressViewModel.Factory -> viewModelComponent.inject(injectable)
- is AutoCompleteViewModel.Factory -> viewModelComponent.inject(injectable)
+ is AutocompleteViewModel.Factory -> viewModelComponent.inject(injectable)
else -> {
throw IllegalArgumentException(
"invalid Injectable $injectable requested in $this"
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AutoCompleteScreen.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AutoCompleteScreen.kt
deleted file mode 100644
index 16786f83b18..00000000000
--- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AutoCompleteScreen.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-package com.stripe.android.paymentsheet.addresselement
-
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material.Button
-import androidx.compose.material.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
-import androidx.lifecycle.viewmodel.compose.viewModel
-import com.stripe.android.paymentsheet.ui.AddressOptionsAppBar
-import com.stripe.android.ui.core.injection.NonFallbackInjector
-
-@Composable
-internal fun AutoCompleteScreen(
- injector: NonFallbackInjector
-) {
- val viewModel: AutoCompleteViewModel = viewModel(
- factory = AutoCompleteViewModel.Factory(
- injector
- )
- )
-
- Column {
- AddressOptionsAppBar(
- isRootScreen = false,
- onButtonClick = {
- viewModel.navigator.onBack()
- }
- )
- Column(Modifier.padding(horizontal = 20.dp)) {
- Text("AutoComplete Screen")
- Button(
- onClick = {
- // TODO implement this screen
- val dummyAddress = ShippingAddress(name = "Skyler")
- viewModel.navigator.setResult(ShippingAddress.KEY, dummyAddress)
- viewModel.navigator.onBack()
- },
- content = { Text(text = "Input Autocompleted Address") }
- )
- }
- }
-}
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AutoCompleteViewModel.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AutoCompleteViewModel.kt
deleted file mode 100644
index d7627e17351..00000000000
--- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AutoCompleteViewModel.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.stripe.android.paymentsheet.addresselement
-
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import com.stripe.android.ui.core.injection.NonFallbackInjectable
-import com.stripe.android.paymentsheet.injection.AutoCompleteViewModelSubcomponent
-import com.stripe.android.ui.core.injection.NonFallbackInjector
-import javax.inject.Inject
-import javax.inject.Provider
-
-internal class AutoCompleteViewModel @Inject constructor(
- val args: AddressElementActivityContract.Args,
- val navigator: AddressElementNavigator
-) : ViewModel() {
-
- internal class Factory(
- private val injector: NonFallbackInjector
- ) : ViewModelProvider.Factory, NonFallbackInjectable {
-
- @Inject
- lateinit var subComponentBuilderProvider:
- Provider
-
- @Suppress("UNCHECKED_CAST")
- override fun create(modelClass: Class): T {
- injector.inject(this)
- return subComponentBuilderProvider.get()
- .build().autoCompleteViewModel as T
- }
- }
-}
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AutocompleteScreen.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AutocompleteScreen.kt
new file mode 100644
index 00000000000..4853f4cd623
--- /dev/null
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AutocompleteScreen.kt
@@ -0,0 +1,219 @@
+package com.stripe.android.paymentsheet.addresselement
+
+import android.app.Application
+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
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.imePadding
+import androidx.compose.foundation.layout.navigationBarsPadding
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.systemBarsPadding
+import androidx.compose.foundation.text.ClickableText
+import androidx.compose.material.CircularProgressIndicator
+import androidx.compose.material.Divider
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Scaffold
+import androidx.compose.material.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.platform.LocalContext
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.stripe.android.paymentsheet.R
+import com.stripe.android.paymentsheet.ui.AddressOptionsAppBar
+import com.stripe.android.ui.core.elements.TextFieldSection
+import com.stripe.android.ui.core.elements.annotatedStringResource
+import com.stripe.android.ui.core.elements.autocomplete.PlacesClientProxy
+import com.stripe.android.ui.core.injection.NonFallbackInjector
+import com.stripe.android.ui.core.paymentsColors
+
+@VisibleForTesting
+const val TEST_TAG_ATTRIBUTION_DRAWABLE = "AutocompleteAttributionDrawable"
+
+@Composable
+internal fun AutocompleteScreen(injector: NonFallbackInjector) {
+ val application = LocalContext.current.applicationContext as Application
+ val viewModel: AutocompleteViewModel =
+ viewModel(
+ factory = AutocompleteViewModel.Factory(
+ injector
+ ) { application }
+ ).also {
+ it.initialize()
+ }
+
+ AutocompleteTextField(viewModel = viewModel)
+}
+
+@Composable
+internal fun AutocompleteTextField(viewModel: AutocompleteViewModel) {
+ val predictions by viewModel.predictions.collectAsState()
+ val loading by viewModel.loading.collectAsState(initial = false)
+ val query = viewModel.textFieldController.fieldValue.collectAsState(initial = "")
+ val attributionDrawable =
+ PlacesClientProxy.getPlacesPoweredByGoogleDrawable(isSystemInDarkTheme())
+
+ Scaffold(
+ bottomBar = {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Center,
+ modifier = Modifier
+ .background(
+ color = colorResource(
+ id = R.color.stripe_paymentsheet_shipping_address_background
+ )
+ )
+ .fillMaxWidth()
+ .imePadding()
+ .navigationBarsPadding()
+ .padding(vertical = 8.dp)
+ ) {
+ ClickableText(
+ text = buildAnnotatedString {
+ append(
+ stringResource(
+ id = R.string.stripe_paymentsheet_enter_address_manually
+ )
+ )
+ },
+ style = TextStyle.Default.copy(
+ color = MaterialTheme.paymentsColors.materialColors.primary
+ )
+ ) {
+ viewModel.onEnterAddressManually()
+ }
+ }
+ }
+ ) { paddingValues ->
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .fillMaxHeight()
+ .systemBarsPadding()
+ .padding(paddingValues)
+ ) {
+ Column(
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ AddressOptionsAppBar(false) {
+ viewModel.setResultAndGoBack()
+ }
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp)
+ ) {
+ TextFieldSection(
+ textFieldController = viewModel.textFieldController,
+ imeAction = ImeAction.Done,
+ enabled = true,
+ modifier = Modifier.fillMaxWidth()
+ )
+ }
+ if (loading) {
+ Row(
+ horizontalArrangement = Arrangement.Center,
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ CircularProgressIndicator()
+ }
+ } else if (query.value.isNotBlank()) {
+ predictions?.let {
+ if (it.isNotEmpty()) {
+ Divider(
+ modifier = Modifier.padding(vertical = 8.dp)
+ )
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp)
+ ) {
+ it.forEach { prediction ->
+ val primaryText = prediction.primaryText
+ val secondaryText = prediction.secondaryText
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable {
+ viewModel.selectPrediction(prediction)
+ }
+ ) {
+ val regex = query.value
+ .replace(" ", "|")
+ .toRegex(RegexOption.IGNORE_CASE)
+ val matches = regex.findAll(primaryText).toList()
+ val values = matches.map {
+ it.value
+ }.filter { it.isNotBlank() }
+ var text = primaryText.toString()
+ values.forEach {
+ text = text.replace(it, "$it")
+ }
+ Text(
+ text = annotatedStringResource(text = text),
+ color = MaterialTheme.paymentsColors.onComponent,
+ style = MaterialTheme.typography.body1
+ )
+ Text(
+ text = secondaryText.toString(),
+ color = MaterialTheme.paymentsColors.onComponent,
+ style = MaterialTheme.typography.body1
+ )
+ }
+ Divider(
+ modifier = Modifier.padding(vertical = 8.dp)
+ )
+ }
+ }
+ } else {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp)
+ ) {
+ Text(
+ text = stringResource(
+ R.string.stripe_paymentsheet_autocomplete_no_results_found
+ ),
+ color = MaterialTheme.paymentsColors.onComponent,
+ style = MaterialTheme.typography.body1
+ )
+ }
+ }
+ attributionDrawable?.let { drawable ->
+ Image(
+ painter = painterResource(
+ id = drawable
+ ),
+ contentDescription = null,
+ modifier = Modifier
+ .padding(top = 8.dp)
+ .padding(horizontal = 16.dp)
+ .testTag(TEST_TAG_ATTRIBUTION_DRAWABLE)
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AutocompleteViewModel.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AutocompleteViewModel.kt
new file mode 100644
index 00000000000..46f64d24526
--- /dev/null
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AutocompleteViewModel.kt
@@ -0,0 +1,220 @@
+package com.stripe.android.paymentsheet.addresselement
+
+import android.app.Application
+import androidx.annotation.VisibleForTesting
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
+import com.stripe.android.model.Address
+import com.stripe.android.paymentsheet.R
+import com.stripe.android.ui.core.injection.NonFallbackInjectable
+import com.stripe.android.paymentsheet.injection.AutoCompleteViewModelSubcomponent
+import com.stripe.android.ui.core.elements.SimpleTextFieldConfig
+import com.stripe.android.ui.core.elements.SimpleTextFieldController
+import com.stripe.android.ui.core.elements.TextFieldIcon
+import com.stripe.android.ui.core.elements.autocomplete.PlacesClientProxy
+import com.stripe.android.ui.core.elements.autocomplete.model.AutocompletePrediction
+import com.stripe.android.ui.core.elements.autocomplete.model.transformGoogleToStripeAddress
+import com.stripe.android.ui.core.injection.NonFallbackInjector
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.isActive
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+import javax.inject.Provider
+
+internal class AutocompleteViewModel @Inject constructor(
+ val args: AddressElementActivityContract.Args,
+ val navigator: AddressElementNavigator,
+ application: Application
+) : AndroidViewModel(application) {
+ private var client: PlacesClientProxy? = null
+
+ private val _predictions = MutableStateFlow?>(null)
+ val predictions: StateFlow?>
+ get() = _predictions
+
+ private val _loading = MutableStateFlow(false)
+ val loading: StateFlow
+ get() = _loading
+
+ @VisibleForTesting
+ val addressResult = MutableStateFlow?>(null)
+
+ val textFieldController = SimpleTextFieldController(
+ SimpleTextFieldConfig(
+ label = R.string.address_label_address,
+ trailingIcon = MutableStateFlow(
+ TextFieldIcon.Trailing(
+ idRes = R.drawable.stripe_ic_clear,
+ isTintable = true,
+ onClick = { clearQuery() }
+ )
+ )
+ )
+ )
+
+ private val queryFlow = textFieldController.fieldValue
+ .map { it }
+ .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), "")
+
+ private val debouncer = Debouncer()
+
+ fun initialize(
+ clientProvider: () -> PlacesClientProxy? = {
+ args.config?.googlePlacesApiKey?.let {
+ PlacesClientProxy.create(getApplication(), it)
+ }
+ }
+ ) {
+ client = clientProvider()
+ debouncer.startWatching(
+ coroutineScope = viewModelScope,
+ queryFlow = queryFlow,
+ onValidQuery = {
+ viewModelScope.launch {
+ client?.findAutocompletePredictions(
+ query = it,
+ country = "US",
+ limit = MAX_DISPLAYED_RESULTS
+ )?.fold(
+ onSuccess = {
+ _loading.value = false
+ _predictions.value = it.autocompletePredictions
+ },
+ onFailure = {
+ _loading.value = false
+ addressResult.value = Result.failure(it)
+ }
+ )
+ }
+ }
+ )
+ }
+
+ fun selectPrediction(prediction: AutocompletePrediction) {
+ viewModelScope.launch {
+ _loading.value = true
+ client?.fetchPlace(
+ placeId = prediction.placeId
+ )?.fold(
+ onSuccess = {
+ _loading.value = false
+ addressResult.value = Result.success(
+ it.place.transformGoogleToStripeAddress(getApplication())
+ )
+ setResultAndGoBack()
+ },
+ onFailure = {
+ _loading.value = false
+ addressResult.value = Result.failure(it)
+ setResultAndGoBack()
+ }
+ )
+ }
+ }
+
+ fun onEnterAddressManually() {
+ setResultAndGoBack()
+ }
+
+ fun setResultAndGoBack() {
+ addressResult.value?.fold(
+ onSuccess = {
+ navigator.setResult(ShippingAddress.KEY, it)
+ },
+ onFailure = {
+ navigator.setResult(ShippingAddress.KEY, null)
+ }
+ )
+ navigator.onBack()
+ }
+
+ private fun clearQuery() {
+ textFieldController.onRawValueChange("")
+ }
+
+ internal class Debouncer {
+ private var searchJob: Job? = null
+
+ fun startWatching(
+ coroutineScope: CoroutineScope,
+ queryFlow: StateFlow,
+ onValidQuery: (String) -> Unit
+ ) {
+ coroutineScope.launch {
+ queryFlow.collect { query ->
+ query?.let {
+ searchJob?.cancel()
+ if (query.length > MIN_CHARS_AUTOCOMPLETE) {
+ searchJob = launch {
+ delay(SEARCH_DEBOUNCE_MS)
+ if (isActive) {
+ onValidQuery(it)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ internal class Factory(
+ private val injector: NonFallbackInjector,
+ private val applicationSupplier: () -> Application
+ ) : ViewModelProvider.Factory, NonFallbackInjectable {
+
+ @Inject
+ lateinit var subComponentBuilderProvider:
+ Provider
+
+ @Suppress("UNCHECKED_CAST")
+ override fun create(modelClass: Class): T {
+ injector.inject(this)
+ return subComponentBuilderProvider.get()
+ .application(applicationSupplier())
+ .build().autoCompleteViewModel as T
+ }
+ }
+
+ companion object {
+ const val SEARCH_DEBOUNCE_MS = 1000L
+ const val MAX_DISPLAYED_RESULTS = 4
+ const val MIN_CHARS_AUTOCOMPLETE = 3
+ val autocompleteSupportedCountries = setOf(
+ "AU",
+ "BE",
+ "BR",
+ "CA",
+ "CH",
+ "DE",
+ "ES",
+ "FR",
+ "GB",
+ "IE",
+ "IN",
+ "IT",
+ "JP",
+ "MX",
+ "MY",
+ "NO",
+ "NL",
+ "PH",
+ "PL",
+ "RU",
+ "SE",
+ "SG",
+ "TR",
+ "US",
+ "ZA"
+ )
+ }
+}
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/InputAddressScreen.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/InputAddressScreen.kt
index bac78581a5b..e224013a9c4 100644
--- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/InputAddressScreen.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/InputAddressScreen.kt
@@ -34,9 +34,7 @@ internal fun InputAddressScreen(
Column(Modifier.padding(horizontal = 20.dp)) {
Text("BaseAddress Screen")
collectedAddress?.let { address ->
- address.name?.let {
- Text(it)
- }
+ Text(address.toString())
}
if (collectedAddress == null) {
Button(
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/InputAddressViewModel.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/InputAddressViewModel.kt
index 03f1d2fcd1e..38c80668659 100644
--- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/InputAddressViewModel.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/InputAddressViewModel.kt
@@ -3,6 +3,7 @@ package com.stripe.android.paymentsheet.addresselement
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
+import com.stripe.android.model.Address
import com.stripe.android.ui.core.injection.NonFallbackInjectable
import com.stripe.android.paymentsheet.injection.InputAddressViewModelSubcomponent
import com.stripe.android.ui.core.injection.FormControllerSubcomponent
@@ -24,8 +25,21 @@ internal class InputAddressViewModel @Inject constructor(
init {
viewModelScope.launch {
- navigator.getResultFlow(ShippingAddress.KEY)?.collect {
- _collectedAddress.value = it
+ navigator.getResultFlow(ShippingAddress.KEY)?.collect {
+ val oldShippingAddress = _collectedAddress.value
+ _collectedAddress.emit(
+ ShippingAddress(
+ name = oldShippingAddress?.name,
+ company = oldShippingAddress?.company,
+ phoneNumber = oldShippingAddress?.phoneNumber,
+ city = it?.city,
+ country = it?.country,
+ line1 = it?.line1,
+ line2 = it?.line2,
+ state = it?.state,
+ postalCode = it?.postalCode
+ )
+ )
}
}
}
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/injection/AddressElementViewModelFactoryComponent.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/injection/AddressElementViewModelFactoryComponent.kt
index b918a8673c5..11dd23c8ddd 100644
--- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/injection/AddressElementViewModelFactoryComponent.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/injection/AddressElementViewModelFactoryComponent.kt
@@ -8,7 +8,7 @@ import com.stripe.android.payments.core.injection.PRODUCT_USAGE
import com.stripe.android.payments.core.injection.StripeRepositoryModule
import com.stripe.android.paymentsheet.addresselement.AddressElementActivityContract
import com.stripe.android.paymentsheet.addresselement.AddressElementViewModel
-import com.stripe.android.paymentsheet.addresselement.AutoCompleteViewModel
+import com.stripe.android.paymentsheet.addresselement.AutocompleteViewModel
import com.stripe.android.paymentsheet.addresselement.InputAddressViewModel
import com.stripe.android.ui.core.forms.resources.injection.ResourceRepositoryModule
import com.stripe.android.ui.core.injection.FormControllerModule
@@ -32,7 +32,7 @@ import javax.inject.Singleton
internal interface AddressElementViewModelFactoryComponent {
fun inject(factory: AddressElementViewModel.Factory)
fun inject(factory: InputAddressViewModel.Factory)
- fun inject(factory: AutoCompleteViewModel.Factory)
+ fun inject(factory: AutocompleteViewModel.Factory)
@Component.Builder
interface Builder {
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/injection/AutoCompleteViewModelSubcomponent.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/injection/AutoCompleteViewModelSubcomponent.kt
index ee8f7fd44c0..82d23e755bf 100644
--- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/injection/AutoCompleteViewModelSubcomponent.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/injection/AutoCompleteViewModelSubcomponent.kt
@@ -1,14 +1,18 @@
package com.stripe.android.paymentsheet.injection
-import com.stripe.android.paymentsheet.addresselement.AutoCompleteViewModel
+import android.app.Application
+import com.stripe.android.paymentsheet.addresselement.AutocompleteViewModel
+import dagger.BindsInstance
import dagger.Subcomponent
@Subcomponent
internal interface AutoCompleteViewModelSubcomponent {
- val autoCompleteViewModel: AutoCompleteViewModel
+ val autoCompleteViewModel: AutocompleteViewModel
@Subcomponent.Builder
interface Builder {
+ @BindsInstance
+ fun application(application: Application): Builder
fun build(): AutoCompleteViewModelSubcomponent
}
diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/addresselement/AutocompleteViewModelTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/addresselement/AutocompleteViewModelTest.kt
new file mode 100644
index 00000000000..0fff29ecae9
--- /dev/null
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/addresselement/AutocompleteViewModelTest.kt
@@ -0,0 +1,198 @@
+package com.stripe.android.paymentsheet.addresselement
+
+import android.app.Application
+import android.text.SpannableString
+import androidx.lifecycle.viewModelScope
+import androidx.test.core.app.ApplicationProvider
+import com.google.common.truth.Truth.assertThat
+import com.stripe.android.model.Address
+import com.stripe.android.ui.core.elements.TextFieldIcon
+import com.stripe.android.ui.core.elements.autocomplete.PlacesClientProxy
+import com.stripe.android.ui.core.elements.autocomplete.model.AutocompletePrediction
+import com.stripe.android.ui.core.elements.autocomplete.model.FetchPlaceResponse
+import com.stripe.android.ui.core.elements.autocomplete.model.FindAutocompletePredictionsResponse
+import com.stripe.android.ui.core.elements.autocomplete.model.Place
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+import org.robolectric.RobolectricTestRunner
+import kotlin.test.AfterTest
+import kotlin.test.BeforeTest
+
+@ExperimentalCoroutinesApi
+@RunWith(RobolectricTestRunner::class)
+class AutocompleteViewModelTest {
+ private val args = mock()
+ private val navigator = mock()
+ private val application = ApplicationProvider.getApplicationContext()
+ private val mockClient = mock()
+
+ private fun createViewModel() =
+ AutocompleteViewModel(
+ args,
+ navigator,
+ application
+ ).apply {
+ initialize {
+ mockClient
+ }
+ }
+
+ @BeforeTest
+ fun setUp() {
+ Dispatchers.setMain(UnconfinedTestDispatcher())
+ }
+
+ @AfterTest
+ fun tearDown() {
+ Dispatchers.resetMain()
+ }
+
+ @Test
+ fun `selectPrediction emits successful result`() = runTest(UnconfinedTestDispatcher()) {
+ val viewModel = createViewModel()
+ val fetchPlaceResponse = Result.success(
+ FetchPlaceResponse(
+ Place(
+ listOf()
+ )
+ )
+ )
+ val expectedResult = Result.success(
+ Address(
+ city = null,
+ country = null,
+ line1 = "",
+ line2 = null,
+ postalCode = null,
+ state = null
+ )
+ )
+ whenever(mockClient.fetchPlace(any())).thenReturn(fetchPlaceResponse)
+
+ viewModel.selectPrediction(
+ AutocompletePrediction(
+ SpannableString("primaryText"),
+ SpannableString("secondaryText"),
+ "placeId"
+ )
+ )
+
+ assertThat(viewModel.loading.value).isEqualTo(false)
+ assertThat(viewModel.addressResult.value)
+ .isEqualTo(expectedResult)
+
+ verify(navigator).setResult(anyOrNull(), eq(expectedResult.getOrNull()))
+ verify(navigator).onBack()
+ }
+
+ @Test
+ fun `selectPrediction emits failure result`() = runTest(UnconfinedTestDispatcher()) {
+ val viewModel = createViewModel()
+ val exception = Exception("fake exception")
+ val result = Result.failure(exception)
+
+ mockClient.stub {
+ onBlocking { fetchPlace(any()) }.thenReturn(result)
+ }
+
+ viewModel.selectPrediction(
+ AutocompletePrediction(
+ SpannableString("primaryText"),
+ SpannableString("secondaryText"),
+ "placeId"
+ )
+ )
+
+ assertThat(viewModel.loading.value).isEqualTo(false)
+ assertThat(viewModel.addressResult.value)
+ .isEqualTo(Result.failure(exception))
+
+ verify(navigator).setResult(anyOrNull(), eq(null))
+ verify(navigator).onBack()
+ }
+
+ @Test
+ fun `onEnterAddressManually sets the current address and navigates back`() = runTest(UnconfinedTestDispatcher()) {
+ val viewModel = createViewModel()
+ val expectedResult = Result.success(
+ Address(
+ city = "city",
+ country = null,
+ line1 = "",
+ line2 = null,
+ postalCode = null,
+ state = null
+ )
+ )
+
+ viewModel.addressResult.value = expectedResult
+ viewModel.onEnterAddressManually()
+
+ verify(navigator).setResult(anyOrNull(), eq(expectedResult.getOrNull()))
+ verify(navigator).onBack()
+ }
+
+ @Test
+ fun `when user presses clear text field is cleared`() = runTest {
+ val viewModel = createViewModel()
+ val trailingIcon = viewModel.textFieldController.trailingIcon.stateIn(viewModel.viewModelScope)
+
+ (trailingIcon.value as? TextFieldIcon.Trailing)?.onClick?.invoke()
+
+ assertThat(viewModel.textFieldController.rawFieldValue.stateIn(viewModel.viewModelScope).value)
+ .isEqualTo("")
+ }
+
+ @Test
+ fun `when query is valid then search is triggered with delay`() = runTest(UnconfinedTestDispatcher()) {
+ val viewModel = createViewModel()
+
+ viewModel.textFieldController.onRawValueChange("Some valid query")
+
+ whenever(mockClient.findAutocompletePredictions(any(), any(), any())).thenReturn(
+ Result.success(
+ FindAutocompletePredictionsResponse(
+ listOf(
+ AutocompletePrediction(
+ SpannableString("primaryText"),
+ SpannableString("secondaryText"),
+ "placeId"
+ )
+ )
+ )
+ )
+ )
+
+ // Advance past search debounce delay
+ advanceTimeBy(AutocompleteViewModel.SEARCH_DEBOUNCE_MS + 1)
+
+ assertThat(viewModel.predictions.value?.size).isEqualTo(1)
+ }
+
+ @Test
+ fun `when query is invalid then search is not triggered`() = runTest(UnconfinedTestDispatcher()) {
+ val viewModel = createViewModel()
+
+ viewModel.textFieldController.onRawValueChange("a")
+
+ // Advance past search debounce delay
+ advanceTimeBy(AutocompleteViewModel.SEARCH_DEBOUNCE_MS + 1)
+
+ assertThat(viewModel.predictions.value?.size).isEqualTo(null)
+ }
+}
diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/addresselement/InputAddressViewModelTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/addresselement/InputAddressViewModelTest.kt
index 120069ed59f..c24f894a18d 100644
--- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/addresselement/InputAddressViewModelTest.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/addresselement/InputAddressViewModelTest.kt
@@ -1,6 +1,7 @@
package com.stripe.android.paymentsheet.addresselement
import com.google.common.truth.Truth.assertThat
+import com.stripe.android.model.Address
import com.stripe.android.ui.core.injection.FormControllerSubcomponent
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@@ -29,20 +30,20 @@ class InputAddressViewModelTest {
@Test
fun `no autocomplete address passed has an empty address to start`() = runTest {
- val flow = MutableStateFlow(null)
- whenever(navigator.getResultFlow(any())).thenReturn(flow)
+ val flow = MutableStateFlow(null)
+ whenever(navigator.getResultFlow(any())).thenReturn(flow)
val viewModel = createViewModel()
- assertThat(viewModel.collectedAddress.value).isEqualTo(null)
+ assertThat(viewModel.collectedAddress.value).isEqualTo(ShippingAddress())
}
@Test
fun `autocomplete address passed is collected to start`() = runTest {
- val expectedAddress = ShippingAddress(name = "skyler", company = "stripe")
- val flow = MutableStateFlow(expectedAddress)
- whenever(navigator.getResultFlow(any())).thenReturn(flow)
+ val expectedAddress = Address(city = "Seattle")
+ val flow = MutableStateFlow(expectedAddress)
+ whenever(navigator.getResultFlow(any())).thenReturn(flow)
val viewModel = createViewModel()
- assertThat(viewModel.collectedAddress.value).isEqualTo(expectedAddress)
+ assertThat(viewModel.collectedAddress.value).isEqualTo(ShippingAddress(city = "Seattle"))
}
}
From e6c211a9d2dfa6ea970d840394f6f73fb4e69a0a Mon Sep 17 00:00:00 2001
From: jameswoo-stripe <99316447+jameswoo-stripe@users.noreply.github.com>
Date: Wed, 6 Jul 2022 11:37:56 -0700
Subject: [PATCH 2/2] Pairing with skyler
---
paymentsheet/api/paymentsheet.api | 8 +++----
.../addresselement/AutocompleteScreenTest.kt | 2 +-
.../android/paymentsheet/PaymentSheet.kt | 9 +-------
.../addresselement/AutocompleteScreen.kt | 9 ++++----
.../addresselement/AutocompleteViewModel.kt | 21 +++++++++++++------
.../addresselement/InputAddressViewModel.kt | 9 ++++----
.../AutocompleteViewModelTest.kt | 5 ++---
.../InputAddressViewModelTest.kt | 13 ++++++------
8 files changed, 36 insertions(+), 40 deletions(-)
diff --git a/paymentsheet/api/paymentsheet.api b/paymentsheet/api/paymentsheet.api
index 51ffa7952e2..072dddb49a3 100644
--- a/paymentsheet/api/paymentsheet.api
+++ b/paymentsheet/api/paymentsheet.api
@@ -266,8 +266,7 @@ public final class com/stripe/android/paymentsheet/PaymentSheet$Configuration :
public fun (Ljava/lang/String;Lcom/stripe/android/paymentsheet/PaymentSheet$CustomerConfiguration;Lcom/stripe/android/paymentsheet/PaymentSheet$GooglePayConfiguration;Landroid/content/res/ColorStateList;Lcom/stripe/android/paymentsheet/PaymentSheet$BillingDetails;)V
public fun (Ljava/lang/String;Lcom/stripe/android/paymentsheet/PaymentSheet$CustomerConfiguration;Lcom/stripe/android/paymentsheet/PaymentSheet$GooglePayConfiguration;Landroid/content/res/ColorStateList;Lcom/stripe/android/paymentsheet/PaymentSheet$BillingDetails;Z)V
public fun (Ljava/lang/String;Lcom/stripe/android/paymentsheet/PaymentSheet$CustomerConfiguration;Lcom/stripe/android/paymentsheet/PaymentSheet$GooglePayConfiguration;Landroid/content/res/ColorStateList;Lcom/stripe/android/paymentsheet/PaymentSheet$BillingDetails;ZLcom/stripe/android/paymentsheet/PaymentSheet$Appearance;)V
- public fun (Ljava/lang/String;Lcom/stripe/android/paymentsheet/PaymentSheet$CustomerConfiguration;Lcom/stripe/android/paymentsheet/PaymentSheet$GooglePayConfiguration;Landroid/content/res/ColorStateList;Lcom/stripe/android/paymentsheet/PaymentSheet$BillingDetails;ZLcom/stripe/android/paymentsheet/PaymentSheet$Appearance;Ljava/lang/String;)V
- public synthetic fun (Ljava/lang/String;Lcom/stripe/android/paymentsheet/PaymentSheet$CustomerConfiguration;Lcom/stripe/android/paymentsheet/PaymentSheet$GooglePayConfiguration;Landroid/content/res/ColorStateList;Lcom/stripe/android/paymentsheet/PaymentSheet$BillingDetails;ZLcom/stripe/android/paymentsheet/PaymentSheet$Appearance;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public synthetic fun (Ljava/lang/String;Lcom/stripe/android/paymentsheet/PaymentSheet$CustomerConfiguration;Lcom/stripe/android/paymentsheet/PaymentSheet$GooglePayConfiguration;Landroid/content/res/ColorStateList;Lcom/stripe/android/paymentsheet/PaymentSheet$BillingDetails;ZLcom/stripe/android/paymentsheet/PaymentSheet$Appearance;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()Lcom/stripe/android/paymentsheet/PaymentSheet$CustomerConfiguration;
public final fun component3 ()Lcom/stripe/android/paymentsheet/PaymentSheet$GooglePayConfiguration;
@@ -275,9 +274,8 @@ public final class com/stripe/android/paymentsheet/PaymentSheet$Configuration :
public final fun component5 ()Lcom/stripe/android/paymentsheet/PaymentSheet$BillingDetails;
public final fun component6 ()Z
public final fun component7 ()Lcom/stripe/android/paymentsheet/PaymentSheet$Appearance;
- public final fun component8 ()Ljava/lang/String;
- public final fun copy (Ljava/lang/String;Lcom/stripe/android/paymentsheet/PaymentSheet$CustomerConfiguration;Lcom/stripe/android/paymentsheet/PaymentSheet$GooglePayConfiguration;Landroid/content/res/ColorStateList;Lcom/stripe/android/paymentsheet/PaymentSheet$BillingDetails;ZLcom/stripe/android/paymentsheet/PaymentSheet$Appearance;Ljava/lang/String;)Lcom/stripe/android/paymentsheet/PaymentSheet$Configuration;
- public static synthetic fun copy$default (Lcom/stripe/android/paymentsheet/PaymentSheet$Configuration;Ljava/lang/String;Lcom/stripe/android/paymentsheet/PaymentSheet$CustomerConfiguration;Lcom/stripe/android/paymentsheet/PaymentSheet$GooglePayConfiguration;Landroid/content/res/ColorStateList;Lcom/stripe/android/paymentsheet/PaymentSheet$BillingDetails;ZLcom/stripe/android/paymentsheet/PaymentSheet$Appearance;Ljava/lang/String;ILjava/lang/Object;)Lcom/stripe/android/paymentsheet/PaymentSheet$Configuration;
+ public final fun copy (Ljava/lang/String;Lcom/stripe/android/paymentsheet/PaymentSheet$CustomerConfiguration;Lcom/stripe/android/paymentsheet/PaymentSheet$GooglePayConfiguration;Landroid/content/res/ColorStateList;Lcom/stripe/android/paymentsheet/PaymentSheet$BillingDetails;ZLcom/stripe/android/paymentsheet/PaymentSheet$Appearance;)Lcom/stripe/android/paymentsheet/PaymentSheet$Configuration;
+ public static synthetic fun copy$default (Lcom/stripe/android/paymentsheet/PaymentSheet$Configuration;Ljava/lang/String;Lcom/stripe/android/paymentsheet/PaymentSheet$CustomerConfiguration;Lcom/stripe/android/paymentsheet/PaymentSheet$GooglePayConfiguration;Landroid/content/res/ColorStateList;Lcom/stripe/android/paymentsheet/PaymentSheet$BillingDetails;ZLcom/stripe/android/paymentsheet/PaymentSheet$Appearance;ILjava/lang/Object;)Lcom/stripe/android/paymentsheet/PaymentSheet$Configuration;
public fun describeContents ()I
public fun equals (Ljava/lang/Object;)Z
public final fun getAllowsDelayedPaymentMethods ()Z
diff --git a/paymentsheet/src/androidTest/java/com/stripe/android/paymentsheet/addresselement/AutocompleteScreenTest.kt b/paymentsheet/src/androidTest/java/com/stripe/android/paymentsheet/addresselement/AutocompleteScreenTest.kt
index 9e9196f7706..98ca258ef08 100644
--- a/paymentsheet/src/androidTest/java/com/stripe/android/paymentsheet/addresselement/AutocompleteScreenTest.kt
+++ b/paymentsheet/src/androidTest/java/com/stripe/android/paymentsheet/addresselement/AutocompleteScreenTest.kt
@@ -131,7 +131,7 @@ class AutocompleteScreenTest {
) =
composeTestRule.setContent {
DefaultPaymentsTheme {
- AutocompleteTextField(
+ AutocompleteScreenUI(
viewModel = AutocompleteViewModel(
args,
AddressElementNavigator(),
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheet.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheet.kt
index 3cf0e5f75e0..ba8ec99d2f0 100644
--- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheet.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/PaymentSheet.kt
@@ -6,7 +6,6 @@ import android.os.Parcelable
import androidx.activity.ComponentActivity
import androidx.annotation.ColorInt
import androidx.annotation.FontRes
-import androidx.annotation.RestrictTo
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.fragment.app.Fragment
@@ -142,13 +141,7 @@ class PaymentSheet internal constructor(
/**
* Describes the appearance of Payment Sheet.
*/
- val appearance: Appearance = Appearance(),
-
- /**
- * Google Places API key used for autocomplete addresses.
- */
- @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- val googlePlacesApiKey: String? = null
+ val appearance: Appearance = Appearance()
) : Parcelable {
/**
* [Configuration] builder for cleaner object creation from Java.
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AutocompleteScreen.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AutocompleteScreen.kt
index 4853f4cd623..5a6480fc691 100644
--- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AutocompleteScreen.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AutocompleteScreen.kt
@@ -32,7 +32,6 @@ import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp
@@ -60,11 +59,11 @@ internal fun AutocompleteScreen(injector: NonFallbackInjector) {
it.initialize()
}
- AutocompleteTextField(viewModel = viewModel)
+ AutocompleteScreenUI(viewModel = viewModel)
}
@Composable
-internal fun AutocompleteTextField(viewModel: AutocompleteViewModel) {
+internal fun AutocompleteScreenUI(viewModel: AutocompleteViewModel) {
val predictions by viewModel.predictions.collectAsState()
val loading by viewModel.loading.collectAsState(initial = false)
val query = viewModel.textFieldController.fieldValue.collectAsState(initial = "")
@@ -95,8 +94,8 @@ internal fun AutocompleteTextField(viewModel: AutocompleteViewModel) {
)
)
},
- style = TextStyle.Default.copy(
- color = MaterialTheme.paymentsColors.materialColors.primary
+ style = MaterialTheme.typography.body1.copy(
+ color = MaterialTheme.colors.primary
)
) {
viewModel.onEnterAddressManually()
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AutocompleteViewModel.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AutocompleteViewModel.kt
index 46f64d24526..7c5b71b807c 100644
--- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AutocompleteViewModel.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/AutocompleteViewModel.kt
@@ -6,7 +6,6 @@ import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
-import com.stripe.android.model.Address
import com.stripe.android.paymentsheet.R
import com.stripe.android.ui.core.injection.NonFallbackInjectable
import com.stripe.android.paymentsheet.injection.AutoCompleteViewModelSubcomponent
@@ -46,7 +45,7 @@ internal class AutocompleteViewModel @Inject constructor(
get() = _loading
@VisibleForTesting
- val addressResult = MutableStateFlow?>(null)
+ val addressResult = MutableStateFlow?>(null)
val textFieldController = SimpleTextFieldController(
SimpleTextFieldConfig(
@@ -69,9 +68,11 @@ internal class AutocompleteViewModel @Inject constructor(
fun initialize(
clientProvider: () -> PlacesClientProxy? = {
- args.config?.googlePlacesApiKey?.let {
- PlacesClientProxy.create(getApplication(), it)
- }
+ // TODO: Update the PaymentSheet Configuration to include api key
+// args.config?.googlePlacesApiKey?.let {
+// PlacesClientProxy.create(getApplication(), it)
+// }
+ PlacesClientProxy.create(getApplication(), "")
}
) {
client = clientProvider()
@@ -107,8 +108,16 @@ internal class AutocompleteViewModel @Inject constructor(
)?.fold(
onSuccess = {
_loading.value = false
+ val address = it.place.transformGoogleToStripeAddress(getApplication())
addressResult.value = Result.success(
- it.place.transformGoogleToStripeAddress(getApplication())
+ ShippingAddress(
+ city = address.city,
+ country = address.country,
+ line1 = address.line1,
+ line2 = address.line2,
+ postalCode = address.postalCode,
+ state = address.state
+ )
)
setResultAndGoBack()
},
diff --git a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/InputAddressViewModel.kt b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/InputAddressViewModel.kt
index 38c80668659..a38ca2d376c 100644
--- a/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/InputAddressViewModel.kt
+++ b/paymentsheet/src/main/java/com/stripe/android/paymentsheet/addresselement/InputAddressViewModel.kt
@@ -3,7 +3,6 @@ package com.stripe.android.paymentsheet.addresselement
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
-import com.stripe.android.model.Address
import com.stripe.android.ui.core.injection.NonFallbackInjectable
import com.stripe.android.paymentsheet.injection.InputAddressViewModelSubcomponent
import com.stripe.android.ui.core.injection.FormControllerSubcomponent
@@ -25,13 +24,13 @@ internal class InputAddressViewModel @Inject constructor(
init {
viewModelScope.launch {
- navigator.getResultFlow(ShippingAddress.KEY)?.collect {
+ navigator.getResultFlow(ShippingAddress.KEY)?.collect {
val oldShippingAddress = _collectedAddress.value
_collectedAddress.emit(
ShippingAddress(
- name = oldShippingAddress?.name,
- company = oldShippingAddress?.company,
- phoneNumber = oldShippingAddress?.phoneNumber,
+ name = oldShippingAddress?.name ?: it?.name,
+ company = oldShippingAddress?.company ?: it?.company,
+ phoneNumber = oldShippingAddress?.phoneNumber ?: it?.phoneNumber,
city = it?.city,
country = it?.country,
line1 = it?.line1,
diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/addresselement/AutocompleteViewModelTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/addresselement/AutocompleteViewModelTest.kt
index 0fff29ecae9..cf6ad5620b9 100644
--- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/addresselement/AutocompleteViewModelTest.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/addresselement/AutocompleteViewModelTest.kt
@@ -5,7 +5,6 @@ import android.text.SpannableString
import androidx.lifecycle.viewModelScope
import androidx.test.core.app.ApplicationProvider
import com.google.common.truth.Truth.assertThat
-import com.stripe.android.model.Address
import com.stripe.android.ui.core.elements.TextFieldIcon
import com.stripe.android.ui.core.elements.autocomplete.PlacesClientProxy
import com.stripe.android.ui.core.elements.autocomplete.model.AutocompletePrediction
@@ -73,7 +72,7 @@ class AutocompleteViewModelTest {
)
)
val expectedResult = Result.success(
- Address(
+ ShippingAddress(
city = null,
country = null,
line1 = "",
@@ -130,7 +129,7 @@ class AutocompleteViewModelTest {
fun `onEnterAddressManually sets the current address and navigates back`() = runTest(UnconfinedTestDispatcher()) {
val viewModel = createViewModel()
val expectedResult = Result.success(
- Address(
+ ShippingAddress(
city = "city",
country = null,
line1 = "",
diff --git a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/addresselement/InputAddressViewModelTest.kt b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/addresselement/InputAddressViewModelTest.kt
index c24f894a18d..e6949ea2e96 100644
--- a/paymentsheet/src/test/java/com/stripe/android/paymentsheet/addresselement/InputAddressViewModelTest.kt
+++ b/paymentsheet/src/test/java/com/stripe/android/paymentsheet/addresselement/InputAddressViewModelTest.kt
@@ -1,7 +1,6 @@
package com.stripe.android.paymentsheet.addresselement
import com.google.common.truth.Truth.assertThat
-import com.stripe.android.model.Address
import com.stripe.android.ui.core.injection.FormControllerSubcomponent
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@@ -30,8 +29,8 @@ class InputAddressViewModelTest {
@Test
fun `no autocomplete address passed has an empty address to start`() = runTest {
- val flow = MutableStateFlow(null)
- whenever(navigator.getResultFlow(any())).thenReturn(flow)
+ val flow = MutableStateFlow(null)
+ whenever(navigator.getResultFlow(any())).thenReturn(flow)
val viewModel = createViewModel()
assertThat(viewModel.collectedAddress.value).isEqualTo(ShippingAddress())
@@ -39,11 +38,11 @@ class InputAddressViewModelTest {
@Test
fun `autocomplete address passed is collected to start`() = runTest {
- val expectedAddress = Address(city = "Seattle")
- val flow = MutableStateFlow(expectedAddress)
- whenever(navigator.getResultFlow(any())).thenReturn(flow)
+ val expectedAddress = ShippingAddress(name = "skyler", company = "stripe")
+ val flow = MutableStateFlow(expectedAddress)
+ whenever(navigator.getResultFlow(any())).thenReturn(flow)
val viewModel = createViewModel()
- assertThat(viewModel.collectedAddress.value).isEqualTo(ShippingAddress(city = "Seattle"))
+ assertThat(viewModel.collectedAddress.value).isEqualTo(expectedAddress)
}
}