Skip to content

Commit 03a3f67

Browse files
committed
Support “institution picker first” in IBP
1 parent 6669770 commit 03a3f67

File tree

29 files changed

+366
-35
lines changed

29 files changed

+366
-35
lines changed

financial-connections/api/financial-connections.api

+9
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,14 @@ public final class com/stripe/android/financialconnections/model/GetFinancialCon
659659
public synthetic fun newArray (I)[Ljava/lang/Object;
660660
}
661661

662+
public final class com/stripe/android/financialconnections/model/IDConsentContentPane$Creator : android/os/Parcelable$Creator {
663+
public fun <init> ()V
664+
public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/financialconnections/model/IDConsentContentPane;
665+
public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object;
666+
public final fun newArray (I)[Lcom/stripe/android/financialconnections/model/IDConsentContentPane;
667+
public synthetic fun newArray (I)[Ljava/lang/Object;
668+
}
669+
662670
public final class com/stripe/android/financialconnections/model/Image$Creator : android/os/Parcelable$Creator {
663671
public fun <init> ()V
664672
public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/financialconnections/model/Image;
@@ -821,6 +829,7 @@ public final class com/stripe/android/financialconnections/navigation/Composable
821829
public final fun getLambda-20$financial_connections_release ()Lkotlin/jvm/functions/Function3;
822830
public final fun getLambda-21$financial_connections_release ()Lkotlin/jvm/functions/Function3;
823831
public final fun getLambda-22$financial_connections_release ()Lkotlin/jvm/functions/Function3;
832+
public final fun getLambda-23$financial_connections_release ()Lkotlin/jvm/functions/Function3;
824833
public final fun getLambda-3$financial_connections_release ()Lkotlin/jvm/functions/Function3;
825834
public final fun getLambda-4$financial_connections_release ()Lkotlin/jvm/functions/Function3;
826835
public final fun getLambda-5$financial_connections_release ()Lkotlin/jvm/functions/Function3;

financial-connections/src/main/java/com/stripe/android/financialconnections/di/FinancialConnectionsSheetNativeComponent.kt

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import com.stripe.android.financialconnections.features.networkingsavetolinkveri
2121
import com.stripe.android.financialconnections.features.notice.NoticeSheetViewModel
2222
import com.stripe.android.financialconnections.features.partnerauth.PartnerAuthViewModel
2323
import com.stripe.android.financialconnections.features.reset.ResetViewModel
24+
import com.stripe.android.financialconnections.features.streamlinedconsent.IDConsentContentViewModel
2425
import com.stripe.android.financialconnections.features.success.SuccessViewModel
2526
import com.stripe.android.financialconnections.model.SynchronizeSessionResponse
2627
import com.stripe.android.financialconnections.presentation.FinancialConnectionsSheetNativeState
@@ -44,6 +45,7 @@ internal interface FinancialConnectionsSheetNativeComponent {
4445
val viewModel: FinancialConnectionsSheetNativeViewModel
4546

4647
val consentViewModelFactory: ConsentViewModel.Factory
48+
val idConsentContentViewModelFactory: IDConsentContentViewModel.Factory
4749
val institutionPickerViewModelFactory: InstitutionPickerViewModel.Factory
4850
val accountPickerViewModelFactory: AccountPickerViewModel.Factory
4951
val manualEntryViewModelFactory: ManualEntryViewModel.Factory

financial-connections/src/main/java/com/stripe/android/financialconnections/features/common/ShapedIcon.kt

+9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.stripe.android.financialconnections.features.common
22

3+
import androidx.compose.foundation.Image
34
import androidx.compose.foundation.background
45
import androidx.compose.foundation.layout.Box
56
import androidx.compose.foundation.layout.size
@@ -10,6 +11,7 @@ import androidx.compose.ui.Alignment
1011
import androidx.compose.ui.Modifier
1112
import androidx.compose.ui.draw.clip
1213
import androidx.compose.ui.graphics.Shape
14+
import androidx.compose.ui.graphics.painter.ColorPainter
1315
import androidx.compose.ui.graphics.painter.Painter
1416
import androidx.compose.ui.layout.ContentScale
1517
import androidx.compose.ui.res.painterResource
@@ -90,6 +92,13 @@ internal fun ShapedIcon(
9092
)
9193
}
9294
},
95+
loadingContent = {
96+
Image(
97+
modifier = modifier,
98+
contentDescription = contentDescription,
99+
painter = ColorPainter(color = colors.background),
100+
)
101+
},
93102
contentScale = ContentScale.Crop
94103
)
95104
}

financial-connections/src/main/java/com/stripe/android/financialconnections/features/consent/ConsentViewModel.kt

+1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ internal class ConsentViewModel @AssistedInject constructor(
8383
hideStripeLogo = state.consent()?.shouldShowMerchantLogos ?: true,
8484
allowBackNavigation = true,
8585
error = state.consent.error,
86+
canCloseWithoutConfirmation = true,
8687
)
8788
}
8889

financial-connections/src/main/java/com/stripe/android/financialconnections/features/generic/GenericScreen.kt

+41-6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import FinancialConnectionsGenericInfoScreen.Footer
77
import FinancialConnectionsGenericInfoScreen.Header
88
import Size
99
import android.util.Log
10+
import androidx.compose.foundation.background
1011
import androidx.compose.foundation.layout.Arrangement
1112
import androidx.compose.foundation.layout.Box
1213
import androidx.compose.foundation.layout.Column
@@ -22,17 +23,21 @@ import androidx.compose.material.Text
2223
import androidx.compose.runtime.Composable
2324
import androidx.compose.runtime.remember
2425
import androidx.compose.ui.Modifier
26+
import androidx.compose.ui.draw.clip
27+
import androidx.compose.ui.draw.shadow
2528
import androidx.compose.ui.text.style.TextAlign
2629
import androidx.compose.ui.tooling.preview.Preview
2730
import androidx.compose.ui.tooling.preview.PreviewParameter
2831
import androidx.compose.ui.unit.dp
2932
import com.stripe.android.financialconnections.features.common.IconSize
33+
import com.stripe.android.financialconnections.features.common.ListItem
3034
import com.stripe.android.financialconnections.features.common.ShapedIcon
3135
import com.stripe.android.financialconnections.ui.FinancialConnectionsPreview
3236
import com.stripe.android.financialconnections.ui.LocalImageLoader
3337
import com.stripe.android.financialconnections.ui.TextResource
3438
import com.stripe.android.financialconnections.ui.components.AnnotatedText
3539
import com.stripe.android.financialconnections.ui.components.FinancialConnectionsButton
40+
import com.stripe.android.financialconnections.ui.sdui.BulletUI
3641
import com.stripe.android.financialconnections.ui.sdui.fromHtml
3742
import com.stripe.android.financialconnections.ui.theme.FinancialConnectionsTheme.colors
3843
import com.stripe.android.financialconnections.ui.theme.FinancialConnectionsTheme.typography
@@ -133,6 +138,23 @@ internal fun GenericBody(
133138
modifier = Modifier.padding(horizontal = 24.dp),
134139
)
135140
}
141+
is Body.Entry.Bullets -> {
142+
val bullets = remember(entry.bullets) {
143+
entry.bullets.map(BulletUI::from)
144+
}
145+
146+
Column(
147+
modifier = Modifier.padding(horizontal = 24.dp),
148+
) {
149+
bullets.forEach { bullet ->
150+
ListItem(
151+
bullet = bullet,
152+
onClickableTextClick = onClickableTextClick
153+
)
154+
Spacer(modifier = Modifier.size(24.dp))
155+
}
156+
}
157+
}
136158
else -> {
137159
Log.e("GenericBody", "Unsupported entry type: $entry")
138160
}
@@ -148,16 +170,29 @@ internal fun GenericHeader(
148170
onClickableTextClick: (String) -> Unit,
149171
modifier: Modifier = Modifier,
150172
) {
151-
val isBrandIcon: Boolean =
152-
remember(payload.icon?.default) { payload.icon?.default?.contains("BrandIcon") == true }
173+
val isBrandIcon: Boolean = remember(payload.icon?.default) {
174+
payload.icon?.default?.contains("BrandIcon") == true
175+
}
176+
153177
Column(
154178
modifier = modifier.fillMaxWidth(),
155179
verticalArrangement = Arrangement.spacedBy(20.dp),
156180
) {
181+
val iconShape = if (isBrandIcon) RoundedCornerShape(12.dp) else CircleShape
182+
183+
val iconModifier = if (isBrandIcon) {
184+
Modifier
185+
.shadow(8.dp, iconShape)
186+
.clip(iconShape)
187+
.background(color = colors.backgroundSecondary, shape = iconShape)
188+
} else {
189+
Modifier
190+
}
191+
157192
payload.icon?.default?.let { iconUrl ->
158193
ShapedIcon(
159-
modifier = Modifier.align(payload.alignment.toComposeAlignment()),
160-
backgroundShape = if (isBrandIcon) RoundedCornerShape(12.dp) else CircleShape,
194+
modifier = iconModifier.align(payload.alignment.toComposeAlignment()),
195+
backgroundShape = iconShape,
161196
flushed = isBrandIcon,
162197
url = iconUrl,
163198
contentDescription = null,
@@ -210,7 +245,7 @@ internal fun GenericFooter(
210245
onClickableTextClick = onClickableTextClick,
211246
defaultStyle = typography.labelSmall.copy(
212247
color = colors.textDefault,
213-
textAlign = TextAlign.Start,
248+
textAlign = TextAlign.Center,
214249
),
215250
)
216251
}
@@ -238,7 +273,7 @@ internal fun GenericFooter(
238273
onClickableTextClick = onClickableTextClick,
239274
defaultStyle = typography.labelSmall.copy(
240275
color = colors.textDefault,
241-
textAlign = TextAlign.Start,
276+
textAlign = TextAlign.Center,
242277
),
243278
)
244279
}

financial-connections/src/main/java/com/stripe/android/financialconnections/features/institutionpicker/InstitutionPickerViewModel.kt

+7-2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import com.stripe.android.financialconnections.utils.error
4646
import com.stripe.android.financialconnections.utils.isCancellationError
4747
import com.stripe.android.financialconnections.utils.measureTimeMillis
4848
import com.stripe.android.uicore.navigation.NavigationManager
49+
import com.stripe.android.uicore.navigation.PopUpToBehavior
4950
import dagger.assisted.Assisted
5051
import dagger.assisted.AssistedFactory
5152
import dagger.assisted.AssistedInject
@@ -105,13 +106,14 @@ internal class InstitutionPickerViewModel @AssistedInject constructor(
105106
override fun updateTopAppBar(state: InstitutionPickerState): TopAppBarStateUpdate {
106107
// We don't allow users to return to the signup pane, as this might result
107108
// in them accidentally creating multiple accounts.
108-
val canNavigateBack = state.referrer != Pane.LINK_LOGIN
109+
val canNavigateBack = state.referrer != null && state.referrer != Pane.LINK_LOGIN
109110

110111
return TopAppBarStateUpdate(
111112
pane = PANE,
112113
allowBackNavigation = canNavigateBack,
113114
allowElevation = false,
114115
error = state.payload.error,
116+
canCloseWithoutConfirmation = state.referrer == null,
115117
)
116118
}
117119

@@ -221,7 +223,10 @@ internal class InstitutionPickerViewModel @AssistedInject constructor(
221223
// This implies that we have shown the institution picker first and haven't shown the consent
222224
// pane yet. Mark the institution as selected and let the backend guide us to the consent pane.
223225
val response = selectInstitution.invoke(institution)
224-
navigationManager.tryNavigateTo(response.manifest.nextPane.destination(referrer = PANE))
226+
navigationManager.tryNavigateTo(
227+
route = response.manifest.nextPane.destination(referrer = PANE),
228+
popUpTo = PopUpToBehavior.Current(inclusive = true),
229+
)
225230
}
226231
}.execute { async ->
227232
copy(

financial-connections/src/main/java/com/stripe/android/financialconnections/features/manualentrysuccess/ManualEntrySuccessViewModel.kt

+1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ internal class ManualEntrySuccessViewModel @AssistedInject constructor(
6161
pane = Pane.MANUAL_ENTRY_SUCCESS,
6262
allowBackNavigation = false,
6363
error = state.payload.error,
64+
canCloseWithoutConfirmation = true,
6465
)
6566
}
6667

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.stripe.android.financialconnections.features.streamlinedconsent
2+
3+
import androidx.compose.runtime.Composable
4+
import androidx.compose.runtime.getValue
5+
import com.stripe.android.financialconnections.features.common.FullScreenGenericLoading
6+
import com.stripe.android.financialconnections.features.common.UnclassifiedErrorContent
7+
import com.stripe.android.financialconnections.features.generic.GenericScreen
8+
import com.stripe.android.financialconnections.presentation.Async.Fail
9+
import com.stripe.android.financialconnections.presentation.Async.Loading
10+
import com.stripe.android.financialconnections.presentation.Async.Success
11+
import com.stripe.android.financialconnections.presentation.Async.Uninitialized
12+
import com.stripe.android.financialconnections.presentation.paneViewModel
13+
import com.stripe.android.financialconnections.presentation.parentViewModel
14+
import com.stripe.android.uicore.utils.collectAsState
15+
16+
@Composable
17+
internal fun IDConsentContentScreen() {
18+
val viewModel: IDConsentContentViewModel = paneViewModel(IDConsentContentViewModel::factory)
19+
val parentViewModel = parentViewModel()
20+
21+
val state by viewModel.stateFlow.collectAsState()
22+
23+
IDConsentContent(
24+
state = state,
25+
onPrimaryButtonClick = viewModel::onContinueClick,
26+
onSecondaryButtonClick = {
27+
// There is no secondary button
28+
},
29+
onClickableTextClick = viewModel::onClickableTextClick,
30+
onCloseFromErrorClick = parentViewModel::onCloseFromErrorClick,
31+
)
32+
}
33+
34+
@Composable
35+
private fun IDConsentContent(
36+
state: IDConsentContentState,
37+
onPrimaryButtonClick: () -> Unit,
38+
onSecondaryButtonClick: () -> Unit,
39+
onClickableTextClick: (String) -> Unit,
40+
onCloseFromErrorClick: (Throwable) -> Unit,
41+
) {
42+
when (val result = state.payload) {
43+
is Uninitialized,
44+
is Loading -> {
45+
FullScreenGenericLoading()
46+
}
47+
is Success -> {
48+
GenericScreen(
49+
state = result().genericScreenState,
50+
onPrimaryButtonClick = onPrimaryButtonClick,
51+
onSecondaryButtonClick = onSecondaryButtonClick,
52+
onClickableTextClick = onClickableTextClick,
53+
)
54+
}
55+
is Fail -> {
56+
UnclassifiedErrorContent(
57+
onCtaClick = { onCloseFromErrorClick(result.error) },
58+
)
59+
}
60+
}
61+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.stripe.android.financialconnections.features.streamlinedconsent
2+
3+
import com.stripe.android.financialconnections.features.generic.GenericScreenState
4+
import com.stripe.android.financialconnections.model.FinancialConnectionsSessionManifest
5+
import com.stripe.android.financialconnections.model.IDConsentContentPane
6+
import com.stripe.android.financialconnections.presentation.Async
7+
8+
internal data class IDConsentContentState(
9+
val payload: Async<Payload> = Async.Uninitialized,
10+
val acceptConsent: Async<FinancialConnectionsSessionManifest> = Async.Uninitialized,
11+
val viewEffect: ViewEffect? = null,
12+
) {
13+
14+
data class Payload(
15+
val idConsentContentPane: IDConsentContentPane,
16+
) {
17+
18+
val genericScreenState: GenericScreenState
19+
get() = GenericScreenState(idConsentContentPane.screen, inModal = false)
20+
}
21+
22+
sealed interface ViewEffect {
23+
data class OpenUrl(
24+
val url: String,
25+
val id: Long,
26+
) : ViewEffect
27+
}
28+
}

0 commit comments

Comments
 (0)