Skip to content

Commit ecc3599

Browse files
committed
Support “institution picker first” in IBP
1 parent f6e68c9 commit ecc3599

21 files changed

+358
-36
lines changed

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

+42-7
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
}
@@ -234,11 +269,11 @@ internal fun GenericFooter(
234269
payload.belowCta?.let { belowCta ->
235270
AnnotatedText(
236271
modifier = Modifier.fillMaxWidth(),
237-
text = TextResource.Text(fromHtml(belowCta.label)),
272+
text = TextResource.Text(fromHtml(belowCta)),
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

+2-1
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,14 @@ internal class InstitutionPickerViewModel @AssistedInject constructor(
105105
override fun updateTopAppBar(state: InstitutionPickerState): TopAppBarStateUpdate {
106106
// We don't allow users to return to the signup pane, as this might result
107107
// in them accidentally creating multiple accounts.
108-
val canNavigateBack = state.referrer != Pane.LINK_LOGIN
108+
val canNavigateBack = state.referrer != null && state.referrer != Pane.LINK_LOGIN
109109

110110
return TopAppBarStateUpdate(
111111
pane = PANE,
112112
allowBackNavigation = canNavigateBack,
113113
allowElevation = false,
114114
error = state.payload.error,
115+
canCloseWithoutConfirmation = state.referrer == null,
115116
)
116117
}
117118

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,63 @@
1+
@file:JvmName("IDConsentContentStateKt")
2+
3+
package com.stripe.android.financialconnections.features.streamlinedconsent
4+
5+
import androidx.compose.runtime.Composable
6+
import androidx.compose.runtime.getValue
7+
import com.stripe.android.financialconnections.features.common.FullScreenGenericLoading
8+
import com.stripe.android.financialconnections.features.common.UnclassifiedErrorContent
9+
import com.stripe.android.financialconnections.features.generic.GenericScreen
10+
import com.stripe.android.financialconnections.presentation.Async.Fail
11+
import com.stripe.android.financialconnections.presentation.Async.Loading
12+
import com.stripe.android.financialconnections.presentation.Async.Success
13+
import com.stripe.android.financialconnections.presentation.Async.Uninitialized
14+
import com.stripe.android.financialconnections.presentation.paneViewModel
15+
import com.stripe.android.financialconnections.presentation.parentViewModel
16+
import com.stripe.android.uicore.utils.collectAsState
17+
18+
@Composable
19+
fun StreamlinedConsent() {
20+
val viewModel: IDConsentContentViewModel = paneViewModel(IDConsentContentViewModel::factory)
21+
val parentViewModel = parentViewModel()
22+
23+
val state by viewModel.stateFlow.collectAsState()
24+
25+
StreamlinedConsentContent(
26+
state = state,
27+
onPrimaryButtonClick = viewModel::onContinueClick,
28+
onSecondaryButtonClick = {
29+
// There is no secondary button
30+
},
31+
onClickableTextClick = viewModel::onClickableTextClick,
32+
onCloseFromErrorClick = parentViewModel::onCloseFromErrorClick,
33+
)
34+
}
35+
36+
@Composable
37+
private fun StreamlinedConsentContent(
38+
state: IDConsentContentState,
39+
onPrimaryButtonClick: () -> Unit,
40+
onSecondaryButtonClick: () -> Unit,
41+
onClickableTextClick: (String) -> Unit,
42+
onCloseFromErrorClick: (Throwable) -> Unit,
43+
) {
44+
when (val result = state.payload) {
45+
is Uninitialized,
46+
is Loading -> {
47+
FullScreenGenericLoading()
48+
}
49+
is Success -> {
50+
GenericScreen(
51+
state = result().genericScreenState,
52+
onPrimaryButtonClick = onPrimaryButtonClick,
53+
onSecondaryButtonClick = onSecondaryButtonClick,
54+
onClickableTextClick = onClickableTextClick,
55+
)
56+
}
57+
is Fail -> {
58+
UnclassifiedErrorContent(
59+
onCtaClick = { onCloseFromErrorClick(result.error) },
60+
)
61+
}
62+
}
63+
}
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)