Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support showing institution picker first #10397

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions financial-connections/api/financial-connections.api
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,14 @@ public final class com/stripe/android/financialconnections/model/GetFinancialCon
public synthetic fun newArray (I)[Ljava/lang/Object;
}

public final class com/stripe/android/financialconnections/model/IDConsentContentPane$Creator : android/os/Parcelable$Creator {
public fun <init> ()V
public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/financialconnections/model/IDConsentContentPane;
public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object;
public final fun newArray (I)[Lcom/stripe/android/financialconnections/model/IDConsentContentPane;
public synthetic fun newArray (I)[Ljava/lang/Object;
}

public final class com/stripe/android/financialconnections/model/Image$Creator : android/os/Parcelable$Creator {
public fun <init> ()V
public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/financialconnections/model/Image;
Expand Down Expand Up @@ -821,6 +829,7 @@ public final class com/stripe/android/financialconnections/navigation/Composable
public final fun getLambda-20$financial_connections_release ()Lkotlin/jvm/functions/Function3;
public final fun getLambda-21$financial_connections_release ()Lkotlin/jvm/functions/Function3;
public final fun getLambda-22$financial_connections_release ()Lkotlin/jvm/functions/Function3;
public final fun getLambda-23$financial_connections_release ()Lkotlin/jvm/functions/Function3;
public final fun getLambda-3$financial_connections_release ()Lkotlin/jvm/functions/Function3;
public final fun getLambda-4$financial_connections_release ()Lkotlin/jvm/functions/Function3;
public final fun getLambda-5$financial_connections_release ()Lkotlin/jvm/functions/Function3;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.stripe.android.financialconnections.features.networkingsavetolinkveri
import com.stripe.android.financialconnections.features.notice.NoticeSheetViewModel
import com.stripe.android.financialconnections.features.partnerauth.PartnerAuthViewModel
import com.stripe.android.financialconnections.features.reset.ResetViewModel
import com.stripe.android.financialconnections.features.streamlinedconsent.IDConsentContentViewModel
import com.stripe.android.financialconnections.features.success.SuccessViewModel
import com.stripe.android.financialconnections.model.SynchronizeSessionResponse
import com.stripe.android.financialconnections.presentation.FinancialConnectionsSheetNativeState
Expand All @@ -44,6 +45,7 @@ internal interface FinancialConnectionsSheetNativeComponent {
val viewModel: FinancialConnectionsSheetNativeViewModel

val consentViewModelFactory: ConsentViewModel.Factory
val idConsentContentViewModelFactory: IDConsentContentViewModel.Factory
val institutionPickerViewModelFactory: InstitutionPickerViewModel.Factory
val accountPickerViewModelFactory: AccountPickerViewModel.Factory
val manualEntryViewModelFactory: ManualEntryViewModel.Factory
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.stripe.android.financialconnections.features.common

import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
Expand All @@ -10,6 +11,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.painter.ColorPainter
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
Expand Down Expand Up @@ -90,6 +92,13 @@ internal fun ShapedIcon(
)
}
},
loadingContent = {
Image(
modifier = modifier,
contentDescription = contentDescription,
painter = ColorPainter(color = colors.background),
)
},
contentScale = ContentScale.Crop
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ internal class ConsentViewModel @AssistedInject constructor(
hideStripeLogo = state.consent()?.shouldShowMerchantLogos ?: true,
allowBackNavigation = true,
error = state.consent.error,
canCloseWithoutConfirmation = true,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import FinancialConnectionsGenericInfoScreen.Footer
import FinancialConnectionsGenericInfoScreen.Header
import Size
import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
Expand All @@ -22,17 +23,21 @@ import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import com.stripe.android.financialconnections.features.common.IconSize
import com.stripe.android.financialconnections.features.common.ListItem
import com.stripe.android.financialconnections.features.common.ShapedIcon
import com.stripe.android.financialconnections.ui.FinancialConnectionsPreview
import com.stripe.android.financialconnections.ui.LocalImageLoader
import com.stripe.android.financialconnections.ui.TextResource
import com.stripe.android.financialconnections.ui.components.AnnotatedText
import com.stripe.android.financialconnections.ui.components.FinancialConnectionsButton
import com.stripe.android.financialconnections.ui.sdui.BulletUI
import com.stripe.android.financialconnections.ui.sdui.fromHtml
import com.stripe.android.financialconnections.ui.theme.FinancialConnectionsTheme.colors
import com.stripe.android.financialconnections.ui.theme.FinancialConnectionsTheme.typography
Expand Down Expand Up @@ -133,6 +138,23 @@ internal fun GenericBody(
modifier = Modifier.padding(horizontal = 24.dp),
)
}
is Body.Entry.Bullets -> {
val bullets = remember(entry.bullets) {
entry.bullets.map(BulletUI::from)
}

Column(
modifier = Modifier.padding(horizontal = 24.dp),
) {
bullets.forEach { bullet ->
ListItem(
bullet = bullet,
onClickableTextClick = onClickableTextClick
)
Spacer(modifier = Modifier.size(24.dp))
}
}
}
else -> {
Log.e("GenericBody", "Unsupported entry type: $entry")
}
Expand All @@ -148,16 +170,29 @@ internal fun GenericHeader(
onClickableTextClick: (String) -> Unit,
modifier: Modifier = Modifier,
) {
val isBrandIcon: Boolean =
remember(payload.icon?.default) { payload.icon?.default?.contains("BrandIcon") == true }
val isBrandIcon: Boolean = remember(payload.icon?.default) {
payload.icon?.default?.contains("BrandIcon") == true
}

Column(
modifier = modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(20.dp),
) {
val iconShape = if (isBrandIcon) RoundedCornerShape(12.dp) else CircleShape

val iconModifier = if (isBrandIcon) {
Modifier
.shadow(8.dp, iconShape)
.clip(iconShape)
.background(color = colors.backgroundSecondary, shape = iconShape)
} else {
Modifier
}

payload.icon?.default?.let { iconUrl ->
ShapedIcon(
modifier = Modifier.align(payload.alignment.toComposeAlignment()),
backgroundShape = if (isBrandIcon) RoundedCornerShape(12.dp) else CircleShape,
modifier = iconModifier.align(payload.alignment.toComposeAlignment()),
backgroundShape = iconShape,
flushed = isBrandIcon,
url = iconUrl,
contentDescription = null,
Expand Down Expand Up @@ -210,7 +245,7 @@ internal fun GenericFooter(
onClickableTextClick = onClickableTextClick,
defaultStyle = typography.labelSmall.copy(
color = colors.textDefault,
textAlign = TextAlign.Start,
textAlign = TextAlign.Center,
Comment on lines -213 to +248
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our footer content is usually centered.

),
)
}
Expand All @@ -234,11 +269,11 @@ internal fun GenericFooter(
payload.belowCta?.let { belowCta ->
AnnotatedText(
modifier = Modifier.fillMaxWidth(),
text = TextResource.Text(fromHtml(belowCta.label)),
text = TextResource.Text(fromHtml(belowCta)),
onClickableTextClick = onClickableTextClick,
defaultStyle = typography.labelSmall.copy(
color = colors.textDefault,
textAlign = TextAlign.Start,
textAlign = TextAlign.Center,
Comment on lines -241 to +276
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here: Our footer content is usually centered.

),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import com.stripe.android.financialconnections.utils.error
import com.stripe.android.financialconnections.utils.isCancellationError
import com.stripe.android.financialconnections.utils.measureTimeMillis
import com.stripe.android.uicore.navigation.NavigationManager
import com.stripe.android.uicore.navigation.PopUpToBehavior
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
Expand Down Expand Up @@ -112,6 +113,7 @@ internal class InstitutionPickerViewModel @AssistedInject constructor(
allowBackNavigation = canNavigateBack,
allowElevation = false,
error = state.payload.error,
canCloseWithoutConfirmation = state.referrer == null,
)
}

Expand Down Expand Up @@ -221,7 +223,10 @@ internal class InstitutionPickerViewModel @AssistedInject constructor(
// This implies that we have shown the institution picker first and haven't shown the consent
// pane yet. Mark the institution as selected and let the backend guide us to the consent pane.
val response = selectInstitution.invoke(institution)
navigationManager.tryNavigateTo(response.manifest.nextPane.destination(referrer = PANE))
navigationManager.tryNavigateTo(
route = response.manifest.nextPane.destination(referrer = PANE),
popUpTo = PopUpToBehavior.Current(inclusive = true),
)
}
}.execute { async ->
copy(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ internal class ManualEntrySuccessViewModel @AssistedInject constructor(
pane = Pane.MANUAL_ENTRY_SUCCESS,
allowBackNavigation = false,
error = state.payload.error,
canCloseWithoutConfirmation = true,
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.stripe.android.financialconnections.features.streamlinedconsent

import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import com.stripe.android.financialconnections.features.common.FullScreenGenericLoading
import com.stripe.android.financialconnections.features.common.UnclassifiedErrorContent
import com.stripe.android.financialconnections.features.generic.GenericScreen
import com.stripe.android.financialconnections.presentation.Async.Fail
import com.stripe.android.financialconnections.presentation.Async.Loading
import com.stripe.android.financialconnections.presentation.Async.Success
import com.stripe.android.financialconnections.presentation.Async.Uninitialized
import com.stripe.android.financialconnections.presentation.paneViewModel
import com.stripe.android.financialconnections.presentation.parentViewModel
import com.stripe.android.uicore.utils.collectAsState

@Composable
internal fun IDConsentContentScreen() {
val viewModel: IDConsentContentViewModel = paneViewModel(IDConsentContentViewModel::factory)
val parentViewModel = parentViewModel()

val state by viewModel.stateFlow.collectAsState()

IDConsentContent(
state = state,
onPrimaryButtonClick = viewModel::onContinueClick,
onSecondaryButtonClick = {
// There is no secondary button
},
onClickableTextClick = viewModel::onClickableTextClick,
onCloseFromErrorClick = parentViewModel::onCloseFromErrorClick,
)
}

@Composable
private fun IDConsentContent(
state: IDConsentContentState,
onPrimaryButtonClick: () -> Unit,
onSecondaryButtonClick: () -> Unit,
onClickableTextClick: (String) -> Unit,
onCloseFromErrorClick: (Throwable) -> Unit,
) {
when (val result = state.payload) {
is Uninitialized,
is Loading -> {
FullScreenGenericLoading()
}
is Success -> {
GenericScreen(
state = result().genericScreenState,
onPrimaryButtonClick = onPrimaryButtonClick,
onSecondaryButtonClick = onSecondaryButtonClick,
onClickableTextClick = onClickableTextClick,
)
}
is Fail -> {
UnclassifiedErrorContent(
onCtaClick = { onCloseFromErrorClick(result.error) },
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.stripe.android.financialconnections.features.streamlinedconsent

import com.stripe.android.financialconnections.features.generic.GenericScreenState
import com.stripe.android.financialconnections.model.FinancialConnectionsSessionManifest
import com.stripe.android.financialconnections.model.IDConsentContentPane
import com.stripe.android.financialconnections.presentation.Async

internal data class IDConsentContentState(
val payload: Async<Payload> = Async.Uninitialized,
val acceptConsent: Async<FinancialConnectionsSessionManifest> = Async.Uninitialized,
val viewEffect: ViewEffect? = null,
) {

data class Payload(
val idConsentContentPane: IDConsentContentPane,
) {

val genericScreenState: GenericScreenState
get() = GenericScreenState(idConsentContentPane.screen, inModal = false)
}

sealed interface ViewEffect {
data class OpenUrl(
val url: String,
val id: Long,
) : ViewEffect
}
}
Loading
Loading